Compare commits

...

9 commits

22 changed files with 1040 additions and 27 deletions

View file

@ -134,8 +134,10 @@ void ProxyWindowBase::setScreen(QuickshellScreenInfo* screen) {
QObject::connect(qscreen, &QObject::destroyed, this, &ProxyWindowBase::onScreenDestroyed);
}
if (this->window == nullptr) this->mScreen = qscreen;
else this->window->setScreen(qscreen);
if (this->window == nullptr) {
this->mScreen = qscreen;
emit this->screenChanged();
} else this->window->setScreen(qscreen);
}
void ProxyWindowBase::onScreenDestroyed() { this->mScreen = nullptr; }

View file

@ -22,7 +22,7 @@ message(STATUS "Found wayland-scanner at ${waylandscanner}")
execute_process(
COMMAND pkg-config --variable=pkgdatadir wayland-protocols
OUTPUT_VARIABLE WAYLAND_PROTOCOLS_DIR
OUTPUT_VARIABLE WAYLAND_PROTOCOLS
OUTPUT_STRIP_TRAILING_WHITESPACE
)
@ -46,6 +46,7 @@ endfunction()
qt_add_library(quickshell-wayland STATIC
wlr_layershell.cpp
session_lock.cpp
)
qt_add_qml_module(quickshell-wayland URI Quickshell.Wayland)
@ -54,6 +55,7 @@ qt_add_qml_module(quickshell-wayland URI Quickshell.Wayland)
add_library(quickshell-wayland-init OBJECT init.cpp)
add_subdirectory(wlr_layershell)
add_subdirectory(session_lock)
target_link_libraries(quickshell-wayland PRIVATE ${QT_DEPS})
target_link_libraries(quickshell-wayland-init PRIVATE ${QT_DEPS})

View file

@ -3,5 +3,6 @@ description = "Wayland specific Quickshell types"
headers = [
"wlr_layershell/window.hpp",
"wlr_layershell.hpp",
"session_lock.hpp",
]
-----

View file

@ -0,0 +1,282 @@
#include "session_lock.hpp"
#include <qcolor.h>
#include <qcoreapplication.h>
#include <qguiapplication.h>
#include <qlogging.h>
#include <qobject.h>
#include <qqmlcomponent.h>
#include <qqmlengine.h>
#include <qqmllist.h>
#include <qquickitem.h>
#include <qquickwindow.h>
#include <qscreen.h>
#include <qtmetamacros.h>
#include <qtypes.h>
#include "../core/qmlscreen.hpp"
#include "../core/reload.hpp"
#include "session_lock/session_lock.hpp"
void SessionLock::onReload(QObject* oldInstance) {
auto* old = qobject_cast<SessionLock*>(oldInstance);
if (old != nullptr) {
QObject::disconnect(old->manager, nullptr, old, nullptr);
this->manager = old->manager;
this->manager->setParent(this);
} else {
this->manager = new SessionLockManager(this);
}
// clang-format off
QObject::connect(this->manager, &SessionLockManager::locked, this, &SessionLock::secureStateChanged);
QObject::connect(this->manager, &SessionLockManager::unlocked, this, &SessionLock::secureStateChanged);
QObject::connect(this->manager, &SessionLockManager::unlocked, this, &SessionLock::unlock);
auto* app = QCoreApplication::instance();
auto* guiApp = qobject_cast<QGuiApplication*>(app);
if (guiApp != nullptr) {
QObject::connect(guiApp, &QGuiApplication::primaryScreenChanged, this, &SessionLock::onScreensChanged);
QObject::connect(guiApp, &QGuiApplication::screenAdded, this, &SessionLock::onScreensChanged);
QObject::connect(guiApp, &QGuiApplication::screenRemoved, this, &SessionLock::onScreensChanged);
}
// clang-format on
if (this->lockTarget) {
if (!this->manager->lock()) this->lockTarget = false;
this->updateSurfaces(old);
} else {
this->setLocked(false);
}
}
void SessionLock::updateSurfaces(SessionLock* old) {
if (this->manager->isLocked()) {
auto screens = QGuiApplication::screens();
auto map = this->surfaces.toStdMap();
for (auto& [screen, surface]: map) {
if (!screens.contains(screen)) {
this->surfaces.remove(screen);
surface->deleteLater();
}
}
if (this->mSurfaceComponent == nullptr) {
qWarning() << "SessionLock.surface is null. Aborting lock.";
this->unlock();
return;
}
for (auto* screen: screens) {
if (!this->surfaces.contains(screen)) {
auto* instanceObj = this->mSurfaceComponent->create(QQmlEngine::contextForObject(this));
auto* instance = qobject_cast<SessionLockSurface*>(instanceObj);
if (instance == nullptr) {
qWarning() << "SessionLock.surface does not create a SessionLockSurface. Aborting lock.";
this->unlock();
return;
}
instance->setParent(this);
instance->setScreen(screen);
auto* oldInstance = old == nullptr ? nullptr : old->surfaces.value(screen, nullptr);
instance->onReload(oldInstance);
this->surfaces[screen] = instance;
}
for (auto* surface: this->surfaces.values()) {
surface->show();
}
}
}
}
void SessionLock::unlock() {
if (this->isLocked()) {
this->lockTarget = false;
this->manager->unlock();
for (auto* surface: this->surfaces) {
surface->deleteLater();
}
this->surfaces.clear();
emit this->lockStateChanged();
}
}
void SessionLock::onScreensChanged() { this->updateSurfaces(); }
bool SessionLock::isLocked() const {
return this->manager == nullptr ? this->lockTarget : this->manager->isLocked();
}
bool SessionLock::isSecure() const {
return this->manager != nullptr && SessionLockManager::isSecure();
}
void SessionLock::setLocked(bool locked) {
if (this->isLocked() == locked) return;
this->lockTarget = locked;
if (this->manager == nullptr) {
emit this->lockStateChanged();
return;
}
if (locked) {
if (!this->manager->lock()) this->lockTarget = false;
this->updateSurfaces();
if (this->lockTarget) emit this->lockStateChanged();
} else {
this->unlock(); // emits lockStateChanged
}
}
QQmlComponent* SessionLock::surfaceComponent() const { return this->mSurfaceComponent; }
void SessionLock::setSurfaceComponent(QQmlComponent* surfaceComponent) {
if (this->mSurfaceComponent != nullptr) this->mSurfaceComponent->deleteLater();
if (surfaceComponent != nullptr) surfaceComponent->setParent(this);
this->mSurfaceComponent = surfaceComponent;
emit this->surfaceComponentChanged();
}
SessionLockSurface::SessionLockSurface(QObject* parent)
: Reloadable(parent)
, mContentItem(new QQuickItem())
, ext(new LockWindowExtension(this)) {
this->mContentItem->setParent(this);
// clang-format off
QObject::connect(this, &SessionLockSurface::widthChanged, this, &SessionLockSurface::onWidthChanged);
QObject::connect(this, &SessionLockSurface::heightChanged, this, &SessionLockSurface::onHeightChanged);
// clang-format on
}
SessionLockSurface::~SessionLockSurface() {
if (this->window != nullptr) {
this->window->deleteLater();
}
}
void SessionLockSurface::onReload(QObject* oldInstance) {
if (auto* old = qobject_cast<SessionLockSurface*>(oldInstance)) {
this->window = old->disownWindow();
}
if (this->window == nullptr) {
this->window = new QQuickWindow();
}
this->mContentItem->setParentItem(this->window->contentItem());
this->mContentItem->setWidth(this->width());
this->mContentItem->setHeight(this->height());
if (this->mScreen != nullptr) this->window->setScreen(this->mScreen);
this->window->setColor(this->mColor);
// clang-format off
QObject::connect(this->window, &QWindow::visibilityChanged, this, &SessionLockSurface::visibleChanged);
QObject::connect(this->window, &QWindow::widthChanged, this, &SessionLockSurface::widthChanged);
QObject::connect(this->window, &QWindow::heightChanged, this, &SessionLockSurface::heightChanged);
QObject::connect(this->window, &QWindow::screenChanged, this, &SessionLockSurface::screenChanged);
QObject::connect(this->window, &QQuickWindow::colorChanged, this, &SessionLockSurface::colorChanged);
// clang-format on
if (auto* parent = qobject_cast<SessionLock*>(this->parent())) {
if (!this->ext->attach(this->window, parent->manager)) {
qWarning(
) << "Failed to attach LockWindowExtension to window. Surface will not behave correctly.";
}
} else {
qWarning(
) << "SessionLockSurface parent is not a SessionLock. Surface will not behave correctly.";
}
}
QQuickWindow* SessionLockSurface::disownWindow() {
QObject::disconnect(this->window, nullptr, this, nullptr);
this->mContentItem->setParentItem(nullptr);
auto* window = this->window;
this->window = nullptr;
return window;
}
void SessionLockSurface::show() { this->ext->setVisible(); }
QQuickItem* SessionLockSurface::contentItem() const { return this->mContentItem; }
bool SessionLockSurface::isVisible() const { return this->window->isVisible(); }
qint32 SessionLockSurface::width() const {
if (this->window == nullptr) return 0;
else return this->window->width();
}
qint32 SessionLockSurface::height() const {
if (this->window == nullptr) return 0;
else return this->window->height();
}
QuickshellScreenInfo* SessionLockSurface::screen() const {
QScreen* qscreen = nullptr;
if (this->window == nullptr) {
if (this->mScreen != nullptr) qscreen = this->mScreen;
} else {
qscreen = this->window->screen();
}
return new QuickshellScreenInfo(
const_cast<SessionLockSurface*>(this), // NOLINT
qscreen
);
}
void SessionLockSurface::setScreen(QScreen* qscreen) {
if (this->mScreen != nullptr) {
QObject::disconnect(this->mScreen, nullptr, this, nullptr);
}
if (qscreen != nullptr) {
QObject::connect(qscreen, &QObject::destroyed, this, &SessionLockSurface::onScreenDestroyed);
}
if (this->window == nullptr) {
this->mScreen = qscreen;
emit this->screenChanged();
} else this->window->setScreen(qscreen);
}
void SessionLockSurface::onScreenDestroyed() { this->mScreen = nullptr; }
QColor SessionLockSurface::color() const {
if (this->window == nullptr) return this->mColor;
else return this->window->color();
}
void SessionLockSurface::setColor(QColor color) {
if (this->window == nullptr) {
this->mColor = color;
emit this->colorChanged();
} else this->window->setColor(color);
}
QQmlListProperty<QObject> SessionLockSurface::data() {
return this->mContentItem->property("data").value<QQmlListProperty<QObject>>();
}
void SessionLockSurface::onWidthChanged() { this->mContentItem->setWidth(this->width()); }
void SessionLockSurface::onHeightChanged() { this->mContentItem->setHeight(this->height()); }

View file

@ -0,0 +1,191 @@
#pragma once
#include <qcolor.h>
#include <qcontainerfwd.h>
#include <qguiapplication.h>
#include <qmap.h>
#include <qnamespace.h>
#include <qobject.h>
#include <qqmlcomponent.h>
#include <qqmlintegration.h>
#include <qquickitem.h>
#include <qquickwindow.h>
#include <qscreen.h>
#include <qtclasshelpermacros.h>
#include <qtmetamacros.h>
#include <qtypes.h>
#include "../core/qmlscreen.hpp"
#include "../core/reload.hpp"
#include "session_lock/session_lock.hpp"
class SessionLockSurface;
///! Wayland session locker.
/// Wayland session lock implemented using the [ext_session_lock_v1] protocol.
///
/// SessionLock will create an instance of its `surface` component for every screen when
/// `locked` is set to true. The `surface` component must create a [SessionLockSurface]
/// which will be displayed on each screen.
///
/// The below example will create a session lock that disappears when the button is clicked.
/// ```qml
/// SessionLock {
/// id: lock
///
/// SessionLockSurface {
/// Button {
/// text: "unlock me"
/// onClicked: lock.locked = false
/// }
/// }
/// }
///
/// // ...
/// lock.locked = true
/// ```
///
/// > [!WARNING] If the SessionLock is destroyed or quickshell exits without setting `locked`
/// > to false, conformant compositors will leave the screen locked and painted with a solid
/// > color.
/// >
/// > This is what makes the session lock secure. The lock dying will not expose your session,
/// > but it will render it inoperable.
///
/// [ext_session_lock_v1]: https://wayland.app/protocols/ext-session-lock-v1
/// [SessionLockSurface]: ../sessionlocksurface
class SessionLock: public Reloadable {
Q_OBJECT;
// clang-format off
/// Controls the lock state.
///
/// > [!WARNING] Only one SessionLock may be locked at a time. Attempting to enable a lock while
/// > another lock is enabled will do nothing.
Q_PROPERTY(bool locked READ isLocked WRITE setLocked NOTIFY lockStateChanged);
/// The compositor lock state.
///
/// This is set to true once the compositor has confirmed all screens are covered with locks.
Q_PROPERTY(bool secure READ isSecure NOTIFY secureStateChanged);
/// The surface that will be created for each screen. Must create a [SessionLockSurface].
///
/// [SessionLockSurface]: ../sessionlocksurface
Q_PROPERTY(QQmlComponent* surface READ surfaceComponent WRITE setSurfaceComponent NOTIFY surfaceComponentChanged);
// clang-format on
QML_ELEMENT;
Q_CLASSINFO("DefaultProperty", "surface");
public:
explicit SessionLock(QObject* parent = nullptr): Reloadable(parent) {}
void onReload(QObject* oldInstance) override;
[[nodiscard]] bool isLocked() const;
void setLocked(bool locked);
[[nodiscard]] bool isSecure() const;
[[nodiscard]] QQmlComponent* surfaceComponent() const;
void setSurfaceComponent(QQmlComponent* surfaceComponent);
signals:
void lockStateChanged();
void secureStateChanged();
void surfaceComponentChanged();
private slots:
void unlock();
void onScreensChanged();
private:
void updateSurfaces(SessionLock* old = nullptr);
SessionLockManager* manager = nullptr;
QQmlComponent* mSurfaceComponent = nullptr;
QMap<QScreen*, SessionLockSurface*> surfaces;
bool lockTarget = false;
friend class SessionLockSurface;
};
///! Surface to display with a `SessionLock`.
/// Surface displayed by a [SessionLock] when it is locked.
///
/// [SessionLock]: ../sessionlock
class SessionLockSurface: public Reloadable {
Q_OBJECT;
// clang-format off
Q_PROPERTY(QQuickItem* contentItem READ contentItem);
/// If the surface has been made visible.
///
/// Note: SessionLockSurfaces will never become invisible, they will only be destroyed.
Q_PROPERTY(bool visible READ isVisible NOTIFY visibleChanged);
Q_PROPERTY(qint32 width READ width NOTIFY widthChanged);
Q_PROPERTY(qint32 height READ height NOTIFY heightChanged);
/// The screen that the surface is displayed on.
Q_PROPERTY(QuickshellScreenInfo* screen READ screen NOTIFY screenChanged);
/// The background color of the window. Defaults to white.
///
/// > [!WARNING] This seems to behave weirdly when using transparent colors on some systems.
/// > Using a colored content item over a transparent window is the recommended way to work around this:
/// > ```qml
/// > ProxyWindow {
/// > Rectangle {
/// > anchors.fill: parent
/// > color: "#20ffffff"
/// >
/// > // your content here
/// > }
/// > }
/// > ```
/// > ... but you probably shouldn't make a transparent lock,
/// > and most compositors will ignore an attempt to do so.
Q_PROPERTY(QColor color READ color WRITE setColor NOTIFY colorChanged);
Q_PROPERTY(QQmlListProperty<QObject> data READ data);
// clang-format on
QML_ELEMENT;
Q_CLASSINFO("DefaultProperty", "data");
public:
explicit SessionLockSurface(QObject* parent = nullptr);
~SessionLockSurface() override;
Q_DISABLE_COPY_MOVE(SessionLockSurface);
void onReload(QObject* oldInstance) override;
QQuickWindow* disownWindow();
void show();
[[nodiscard]] QQuickItem* contentItem() const;
[[nodiscard]] bool isVisible() const;
[[nodiscard]] qint32 width() const;
[[nodiscard]] qint32 height() const;
[[nodiscard]] QuickshellScreenInfo* screen() const;
void setScreen(QScreen* qscreen);
[[nodiscard]] QColor color() const;
void setColor(QColor color);
[[nodiscard]] QQmlListProperty<QObject> data();
signals:
void visibleChanged();
void widthChanged();
void heightChanged();
void screenChanged();
void colorChanged();
private slots:
void onScreenDestroyed();
void onWidthChanged();
void onHeightChanged();
private:
QQuickWindow* window = nullptr;
QQuickItem* mContentItem;
QScreen* mScreen = nullptr;
QColor mColor = Qt::white;
LockWindowExtension* ext;
};

View file

@ -0,0 +1,12 @@
qt_add_library(quickshell-wayland-sessionlock STATIC
manager.cpp
surface.cpp
lock.cpp
shell_integration.cpp
session_lock.cpp
)
wl_proto(quickshell-wayland-sessionlock ext-session-lock-v1 "${WAYLAND_PROTOCOLS}/staging/ext-session-lock/ext-session-lock-v1.xml")
target_link_libraries(quickshell-wayland-sessionlock PRIVATE ${QT_DEPS} wayland-client)
target_link_libraries(quickshell-wayland PRIVATE quickshell-wayland-sessionlock)

View file

@ -0,0 +1,48 @@
#include "lock.hpp"
#include <qtmetamacros.h>
#include <wayland-ext-session-lock-v1-client-protocol.h>
#include "manager.hpp"
QSWaylandSessionLock::QSWaylandSessionLock(
QSWaylandSessionLockManager* manager,
::ext_session_lock_v1* lock
)
: manager(manager) {
this->init(lock); // if isInitialized is false that means we already unlocked.
}
QSWaylandSessionLock::~QSWaylandSessionLock() {
if (this->isInitialized()) {
// This will intentionally lock the session if the lock is destroyed without calling unlock.
this->destroy();
}
}
void QSWaylandSessionLock::unlock() {
if (this->isInitialized()) {
if (this->finished) this->destroy();
else this->unlock_and_destroy();
this->secure = false;
this->manager->active = nullptr;
emit this->unlocked();
}
}
bool QSWaylandSessionLock::active() const { return this->isInitialized(); }
bool QSWaylandSessionLock::hasCompositorLock() const { return this->secure; }
void QSWaylandSessionLock::ext_session_lock_v1_locked() {
this->secure = true;
emit this->compositorLocked();
}
void QSWaylandSessionLock::ext_session_lock_v1_finished() {
this->secure = false;
this->finished = true;
this->unlock();
}

View file

@ -0,0 +1,39 @@
#pragma once
#include <qobject.h>
#include <qtclasshelpermacros.h>
#include <qwayland-ext-session-lock-v1.h>
class QSWaylandSessionLockManager;
class QSWaylandSessionLock
: public QObject
, public QtWayland::ext_session_lock_v1 {
Q_OBJECT;
public:
QSWaylandSessionLock(QSWaylandSessionLockManager* manager, ::ext_session_lock_v1* lock);
~QSWaylandSessionLock() override;
Q_DISABLE_COPY_MOVE(QSWaylandSessionLock);
void unlock();
// Returns true if the lock has not finished.
[[nodiscard]] bool active() const;
// Returns true if the compositor considers the session to be locked.
[[nodiscard]] bool hasCompositorLock() const;
signals:
void compositorLocked();
void unlocked();
private:
void ext_session_lock_v1_locked() override;
void ext_session_lock_v1_finished() override;
QSWaylandSessionLockManager* manager; // static and not dealloc'd
// true when the compositor determines the session is locked
bool secure = false;
bool finished = false;
};

View file

@ -0,0 +1,23 @@
#include "manager.hpp"
#include <qwaylandclientextension.h>
#include "lock.hpp"
QSWaylandSessionLockManager::QSWaylandSessionLockManager()
: QWaylandClientExtensionTemplate<QSWaylandSessionLockManager>(1) {
this->initialize();
}
QSWaylandSessionLockManager::~QSWaylandSessionLockManager() { this->destroy(); }
QSWaylandSessionLock* QSWaylandSessionLockManager::acquireLock() {
if (this->isLocked()) return nullptr;
this->active = new QSWaylandSessionLock(this, this->lock());
return this->active;
}
bool QSWaylandSessionLockManager::isLocked() const { return this->active != nullptr; }
bool QSWaylandSessionLockManager::isSecure() const {
return this->isLocked() && this->active->hasCompositorLock();
}

View file

@ -0,0 +1,28 @@
#pragma once
#include <qtclasshelpermacros.h>
#include <qwayland-ext-session-lock-v1.h>
#include <qwaylandclientextension.h>
#include "lock.hpp"
class QSWaylandSessionLockManager
: public QWaylandClientExtensionTemplate<QSWaylandSessionLockManager>
, public QtWayland::ext_session_lock_manager_v1 {
public:
QSWaylandSessionLockManager();
~QSWaylandSessionLockManager() override;
Q_DISABLE_COPY_MOVE(QSWaylandSessionLockManager);
// Create a new session lock if there is no currently active lock, otherwise null.
QSWaylandSessionLock* acquireLock();
[[nodiscard]] bool isLocked() const;
[[nodiscard]] bool isSecure() const;
static bool sessionLocked();
private:
QSWaylandSessionLock* active = nullptr;
friend class QSWaylandSessionLock;
};

View file

@ -0,0 +1,114 @@
#include "session_lock.hpp"
#include <private/qwaylanddisplay_p.h>
#include <qlogging.h>
#include <qobject.h>
#include <qwindow.h>
#include "lock.hpp"
#include "manager.hpp"
#include "shell_integration.hpp"
#include "surface.hpp"
namespace {
QSWaylandSessionLockManager* manager() {
static QSWaylandSessionLockManager* manager = nullptr; // NOLINT
if (manager == nullptr) {
manager = new QSWaylandSessionLockManager();
}
return manager;
}
} // namespace
bool SessionLockManager::lock() {
if (this->isLocked() || SessionLockManager::sessionLocked()) return false;
this->mLock = manager()->acquireLock();
this->mLock->setParent(this);
// clang-format off
QObject::connect(this->mLock, &QSWaylandSessionLock::compositorLocked, this, &SessionLockManager::locked);
QObject::connect(this->mLock, &QSWaylandSessionLock::unlocked, this, &SessionLockManager::unlocked);
// clang-format on
return true;
}
bool SessionLockManager::unlock() {
if (!this->isLocked()) return false;
this->mLock->unlock();
auto* lock = this->mLock;
this->mLock = nullptr;
delete lock;
return true;
}
bool SessionLockManager::isLocked() const { return this->mLock != nullptr; }
bool SessionLockManager::sessionLocked() { return manager()->isLocked(); }
bool SessionLockManager::isSecure() { return manager()->isSecure(); }
LockWindowExtension::~LockWindowExtension() {
if (this->surface != nullptr) {
this->surface->setExtension(nullptr);
}
}
LockWindowExtension* LockWindowExtension::get(QWindow* window) {
auto v = window->property("sessionlock_ext");
if (v.canConvert<LockWindowExtension*>()) {
return v.value<LockWindowExtension*>();
} else {
return nullptr;
}
}
bool LockWindowExtension::attach(QWindow* window, SessionLockManager* manager) {
if (this->surface != nullptr) throw "Cannot change the attached window of a LockWindowExtension";
auto* current = LockWindowExtension::get(window);
QtWaylandClient::QWaylandWindow* waylandWindow = nullptr;
if (current != nullptr) {
current->surface->setExtension(this);
} else {
// Qt appears to be resetting the window's screen on creation on some systems. This works around it.
auto* screen = window->screen();
window->create();
window->setScreen(screen);
waylandWindow = dynamic_cast<QtWaylandClient::QWaylandWindow*>(window->handle());
if (waylandWindow == nullptr) {
qWarning() << window << "is not a wayland window. Cannot create lock surface.";
return false;
}
static QSWaylandSessionLockIntegration* lockIntegration = nullptr; // NOLINT
if (lockIntegration == nullptr) {
lockIntegration = new QSWaylandSessionLockIntegration();
if (!lockIntegration->initialize(waylandWindow->display())) {
delete lockIntegration;
lockIntegration = nullptr;
qWarning() << "Failed to initialize lockscreen integration";
}
}
waylandWindow->setShellIntegration(lockIntegration);
}
this->setParent(window);
window->setProperty("sessionlock_ext", QVariant::fromValue(this));
this->lock = manager->mLock;
if (waylandWindow != nullptr) {
this->surface = new QSWaylandSessionLockSurface(waylandWindow);
if (this->immediatelyVisible) this->surface->setVisible();
}
return true;
}
void LockWindowExtension::setVisible() {
if (this->surface == nullptr) this->immediatelyVisible = true;
else this->surface->setVisible();
}

View file

@ -0,0 +1,93 @@
#pragma once
#include <qobject.h>
#include <qtclasshelpermacros.h>
#include <qtmetamacros.h>
#include <qwindow.h>
class QSWaylandSessionLock;
class QSWaylandSessionLockSurface;
class QSWaylandSessionLockIntegration;
class SessionLockManager: public QObject {
Q_OBJECT;
public:
explicit SessionLockManager(QObject* parent = nullptr): QObject(parent) {}
// Returns true if a lock was acquired.
// If true is returned the caller must watch the global screen list and create/destroy
// windows with an attached LockWindowExtension to match it.
bool lock();
// Returns true if the session was locked and is now unlocked.
bool unlock();
[[nodiscard]] bool isLocked() const;
static bool sessionLocked();
static bool isSecure();
signals:
// This signal is sent once the compositor considers the session to be fully locked.
// This corrosponds to the ext_session_lock_v1::locked event.
void locked();
// This signal is sent once the compositor considers the session to be unlocked.
// This corrosponds to the ext_session_lock_v1::finished event.
//
// The session lock will end in one of three cases.
// 1. unlock() is called.
// 2. The SessionLockManager is destroyed.
// 3. The compositor forcibly unlocks the session.
//
// After receiving this event the caller should destroy all of its lock surfaces.
void unlocked();
private slots:
//void onUnlocked();
private:
QSWaylandSessionLock* mLock = nullptr;
friend class LockWindowExtension;
};
class LockWindowExtension: public QObject {
Q_OBJECT;
public:
explicit LockWindowExtension(QObject* parent = nullptr): QObject(parent) {}
~LockWindowExtension() override;
Q_DISABLE_COPY_MOVE(LockWindowExtension);
// Attach this lock extension to the given window.
// The extension is reparented to the window and replaces any existing lock extension.
// Returns false if the window cannot be used.
bool attach(QWindow* window, SessionLockManager* manager);
// This must be called in place of QWindow::setVisible. Calling QWindow::setVisible will result in a crash.
// To make a window invisible, destroy it as it cannot be recovered.
void setVisible();
[[nodiscard]] bool isLocked() const;
static LockWindowExtension* get(QWindow* window);
signals:
// This signal is sent once the compositor considers the session to be fully locked.
// See SessionLockManager::locked for details.
void locked();
// After receiving this signal the window is no longer in use by the compositor
// and should be destroyed. See SessionLockManager::unlocked for details.
void unlocked();
private:
QSWaylandSessionLockSurface* surface = nullptr;
QSWaylandSessionLock* lock = nullptr;
bool immediatelyVisible = false;
friend class QSWaylandSessionLockSurface;
friend class QSWaylandSessionLockIntegration;
};

View file

@ -0,0 +1,20 @@
#include "shell_integration.hpp"
#include <private/qwaylandshellsurface_p.h>
#include <private/qwaylandwindow_p.h>
#include <qlogging.h>
#include "session_lock.hpp"
#include "surface.hpp"
QtWaylandClient::QWaylandShellSurface*
QSWaylandSessionLockIntegration::createShellSurface(QtWaylandClient::QWaylandWindow* window) {
auto* lock = LockWindowExtension::get(window->window());
if (lock == nullptr || lock->surface == nullptr || !lock->surface->isExposed()) {
qFatal() << "Visibility canary failed. A window with a LockWindowExtension MUST be set to "
"visible via LockWindowExtension::setVisible";
throw nullptr;
}
return lock->surface;
}

View file

@ -0,0 +1,13 @@
#pragma once
#include <private/qwaylanddisplay_p.h>
#include <private/qwaylandshellintegration_p.h>
#include <private/qwaylandshellsurface_p.h>
#include <private/qwaylandwindow_p.h>
class QSWaylandSessionLockIntegration: public QtWaylandClient::QWaylandShellIntegration {
public:
bool initialize(QtWaylandClient::QWaylandDisplay* /* display */) override { return true; }
QtWaylandClient::QWaylandShellSurface* createShellSurface(QtWaylandClient::QWaylandWindow* window
) override;
};

View file

@ -0,0 +1,115 @@
#include "surface.hpp"
#include <private/qwaylandscreen_p.h>
#include <private/qwaylandshellsurface_p.h>
#include <private/qwaylandshmbackingstore_p.h>
#include <private/qwaylandsurface_p.h>
#include <private/qwaylandwindow_p.h>
#include <qlogging.h>
#include <qobject.h>
#include <qtypes.h>
#include "lock.hpp"
#include "session_lock.hpp"
QSWaylandSessionLockSurface::QSWaylandSessionLockSurface(QtWaylandClient::QWaylandWindow* window)
: QtWaylandClient::QWaylandShellSurface(window) {
auto* qwindow = window->window();
this->setExtension(LockWindowExtension::get(qwindow));
if (this->ext == nullptr) {
qFatal() << "QSWaylandSessionLockSurface created with null LockWindowExtension";
throw nullptr;
}
if (this->ext->lock == nullptr) {
qFatal() << "QSWaylandSessionLock for QSWaylandSessionLockSurface died";
throw nullptr;
}
wl_output* output = nullptr; // NOLINT (include)
auto* waylandScreen = dynamic_cast<QtWaylandClient::QWaylandScreen*>(qwindow->screen()->handle());
if (waylandScreen != nullptr) {
output = waylandScreen->output();
} else {
qFatal() << "Session lock screen does not corrospond to a real screen. Force closing window";
throw nullptr;
}
this->init(this->ext->lock->get_lock_surface(window->waylandSurface()->object(), output));
}
QSWaylandSessionLockSurface::~QSWaylandSessionLockSurface() {
if (this->ext != nullptr) this->ext->surface = nullptr;
this->destroy();
}
bool QSWaylandSessionLockSurface::isExposed() const { return this->configured; }
void QSWaylandSessionLockSurface::applyConfigure() {
this->window()->resizeFromApplyConfigure(this->size);
}
bool QSWaylandSessionLockSurface::handleExpose(const QRegion& region) {
if (this->initBuf != nullptr) {
// at this point qt's next commit to the surface will have a new buffer, and we can safely delete this one.
delete this->initBuf;
this->initBuf = nullptr;
}
return this->QtWaylandClient::QWaylandShellSurface::handleExpose(region);
}
void QSWaylandSessionLockSurface::setExtension(LockWindowExtension* ext) {
if (ext == nullptr) {
if (this->window() != nullptr) this->window()->window()->close();
} else {
if (this->ext != nullptr) {
this->ext->surface = nullptr;
}
this->ext = ext;
this->ext->surface = this;
}
}
void QSWaylandSessionLockSurface::setVisible() {
if (this->configured && !this->visible) this->initVisible();
this->visible = true;
}
void QSWaylandSessionLockSurface::ext_session_lock_surface_v1_configure(
quint32 serial,
quint32 width,
quint32 height
) {
this->ack_configure(serial);
this->size = QSize(static_cast<qint32>(width), static_cast<qint32>(height));
if (!this->configured) {
this->configured = true;
this->window()->resizeFromApplyConfigure(this->size);
this->window()->handleExpose(QRect(QPoint(), this->size));
if (this->visible) this->initVisible();
} else {
// applyConfigureWhenPossible runs too late and causes a protocol error on reconfigure.
this->window()->resizeFromApplyConfigure(this->size);
}
}
void QSWaylandSessionLockSurface::initVisible() {
this->visible = true;
// qt always commits a null buffer in QWaylandWindow::initWindow.
// We attach a dummy buffer to satisfy ext_session_lock_v1.
this->initBuf = new QtWaylandClient::QWaylandShmBuffer(
this->window()->display(),
this->size,
QImage::Format_ARGB32
);
this->window()->waylandSurface()->attach(this->initBuf->buffer(), 0, 0);
this->window()->window()->setVisible(true);
}

View file

@ -0,0 +1,39 @@
#pragma once
#include <private/qwaylandshellsurface_p.h>
#include <private/qwaylandshmbackingstore_p.h>
#include <private/qwaylandwindow_p.h>
#include <qregion.h>
#include <qtclasshelpermacros.h>
#include <qtypes.h>
#include <qwayland-ext-session-lock-v1.h>
#include "session_lock.hpp"
class QSWaylandSessionLockSurface
: public QtWaylandClient::QWaylandShellSurface
, public QtWayland::ext_session_lock_surface_v1 {
public:
QSWaylandSessionLockSurface(QtWaylandClient::QWaylandWindow* window);
~QSWaylandSessionLockSurface() override;
Q_DISABLE_COPY_MOVE(QSWaylandSessionLockSurface);
[[nodiscard]] bool isExposed() const override;
void applyConfigure() override;
bool handleExpose(const QRegion& region) override;
void setExtension(LockWindowExtension* ext);
void setVisible();
private:
void
ext_session_lock_surface_v1_configure(quint32 serial, quint32 width, quint32 height) override;
void initVisible();
LockWindowExtension* ext = nullptr;
QSize size;
bool configured = false;
bool visible = false;
QtWaylandClient::QWaylandShmBuffer* initBuf = nullptr;
};

View file

@ -5,14 +5,13 @@
#include <private/qwaylandwindow_p.h>
#include "surface.hpp"
#include "wayland-wlr-layer-shell-unstable-v1-client-protocol.h"
QSWaylandLayerShellIntegration::QSWaylandLayerShellIntegration()
: QtWaylandClient::QWaylandShellIntegrationTemplate<QSWaylandLayerShellIntegration>(4) {}
QSWaylandLayerShellIntegration::~QSWaylandLayerShellIntegration() {
if (this->object() != nullptr) {
zwlr_layer_shell_v1_destroy(this->object());
if (this->isInitialized()) {
this->destroy();
}
}

View file

@ -2,7 +2,7 @@
#include <private/qwaylandshellintegration_p.h>
#include <private/qwaylandshellsurface_p.h>
#include <qtwaylandclientexports.h>
#include <qtclasshelpermacros.h>
#include <qwayland-wlr-layer-shell-unstable-v1.h>
class QSWaylandLayerShellIntegration
@ -11,10 +11,7 @@ class QSWaylandLayerShellIntegration
public:
QSWaylandLayerShellIntegration();
~QSWaylandLayerShellIntegration() override;
QSWaylandLayerShellIntegration(QSWaylandLayerShellIntegration&&) = delete;
QSWaylandLayerShellIntegration(const QSWaylandLayerShellIntegration&) = delete;
void operator=(QSWaylandLayerShellIntegration&&) = delete;
void operator=(const QSWaylandLayerShellIntegration&) = delete;
Q_DISABLE_COPY_MOVE(QSWaylandLayerShellIntegration);
QtWaylandClient::QWaylandShellSurface* createShellSurface(QtWaylandClient::QWaylandWindow* window
) override;

View file

@ -23,12 +23,11 @@
[[nodiscard]] QSize constrainedSize(const Anchors& anchors, const QSize& size) noexcept;
// clang-format on
// clang-format off
QSWaylandLayerSurface::QSWaylandLayerSurface(
QSWaylandLayerShellIntegration* shell,
QtWaylandClient::QWaylandWindow* window
): QtWaylandClient::QWaylandShellSurface(window) {
// clang-format on
)
: QtWaylandClient::QWaylandShellSurface(window) {
auto* qwindow = window->window();
this->ext = LayershellWindowExtension::get(qwindow);
@ -37,7 +36,7 @@ QSWaylandLayerSurface::QSWaylandLayerSurface(
throw "QSWaylandLayerSurface created with null LayershellWindowExtension";
}
wl_output* output = nullptr; // NOLINT (import)
wl_output* output = nullptr; // NOLINT (include)
if (this->ext->useWindowScreen) {
auto* waylandScreen =
dynamic_cast<QtWaylandClient::QWaylandScreen*>(qwindow->screen()->handle());
@ -45,8 +44,8 @@ QSWaylandLayerSurface::QSWaylandLayerSurface(
if (waylandScreen != nullptr) {
output = waylandScreen->output();
} else {
qWarning() << "Layershell screen is set but does not corrospond to a real screen. Letting "
"the compositor pick.";
qWarning(
) << "Layershell screen does not corrospond to a real screen. Letting the compositor pick.";
}
}

View file

@ -2,6 +2,7 @@
#include <private/qwaylandshellsurface_p.h>
#include <private/qwaylandwindow_p.h>
#include <qtclasshelpermacros.h>
#include <qtwaylandclientexports.h>
#include <qtypes.h>
#include <qwayland-wlr-layer-shell-unstable-v1.h>
@ -20,10 +21,7 @@ public:
);
~QSWaylandLayerSurface() override;
QSWaylandLayerSurface(QSWaylandLayerSurface&&) = delete;
QSWaylandLayerSurface(const QSWaylandLayerSurface&) = delete;
void operator=(QSWaylandLayerSurface&&) = delete;
void operator=(const QSWaylandLayerSurface&) = delete;
Q_DISABLE_COPY_MOVE(QSWaylandLayerSurface);
[[nodiscard]] bool isExposed() const override;
void applyConfigure() override;

View file

@ -54,7 +54,10 @@ bool LayershellWindowExtension::attach(QWindow* window) {
}
if (!hasSurface) {
// Qt appears to be resetting the window's screen on creation on some systems. This works around it.
auto* screen = window->screen();
window->create();
window->setScreen(screen);
auto* waylandWindow = dynamic_cast<QtWaylandClient::QWaylandWindow*>(window->handle());
if (waylandWindow == nullptr) {

View file

@ -56,17 +56,12 @@ class LayershellWindowExtension: public QObject {
public:
LayershellWindowExtension(QObject* parent = nullptr): QObject(parent) {}
~LayershellWindowExtension() override = default;
LayershellWindowExtension(LayershellWindowExtension&&) = delete;
LayershellWindowExtension(const LayershellWindowExtension&) = delete;
void operator=(LayershellWindowExtension&&) = delete;
void operator=(const LayershellWindowExtension&) = delete;
// returns the layershell extension if attached, otherwise nullptr
static LayershellWindowExtension* get(QWindow* window);
// Attach this layershell extension to the given window.
// The extension is reparented to the window and replaces any existing extensions.
// The extension is reparented to the window and replaces any existing layershell extension.
// Returns false if the window cannot be used.
bool attach(QWindow* window);