feat(slock): implement ext_session_lock_v1 backend

note: did not run lints or fully test yet
This commit is contained in:
outfoxxed 2024-02-28 04:37:52 -08:00
parent 70c5cf1e16
commit 1fa87b7c5a
Signed by untrusted user: outfoxxed
GPG key ID: 4C88A185FB89301E
12 changed files with 525 additions and 1 deletions

View file

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

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,42 @@
#include "lock.hpp"
#include <private/qwaylandshellintegration_p.h>
#include <qtmetamacros.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() { this->unlock(); }
void QSWaylandSessionLock::unlock() {
if (this->isInitialized()) {
if (this->locked) this->unlock_and_destroy();
else this->destroy();
this->locked = false;
this->manager->active = nullptr;
emit this->unlocked();
}
}
bool QSWaylandSessionLock::active() const { return this->isInitialized(); }
bool QSWaylandSessionLock::hasCompositorLock() const { return this->locked; }
void QSWaylandSessionLock::ext_session_lock_v1_locked() {
this->locked = true;
emit this->compositorLocked();
}
void QSWaylandSessionLock::ext_session_lock_v1_finished() {
this->locked = false;
this->unlock();
}

View file

@ -0,0 +1,38 @@
#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 locked = false;
};

View file

@ -0,0 +1,22 @@
#include "manager.hpp"
#include <qdebug.h>
#include <qlogging.h>
#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; }

View file

@ -0,0 +1,27 @@
#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;
static bool sessionLocked();
private:
QSWaylandSessionLock* active = nullptr;
friend class QSWaylandSessionLock;
};

View file

@ -0,0 +1,107 @@
#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"
static QSWaylandSessionLockManager* manager() {
static QSWaylandSessionLockManager* manager = nullptr;
if (manager == nullptr) {
manager = new QSWaylandSessionLockManager();
}
return manager;
}
bool SessionLockManager::lock() {
if (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;
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(); }
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 {
window->create();
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;
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) immediatelyVisible = true;
else this->surface->setVisible();
}

View file

@ -0,0 +1,92 @@
#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:
SessionLockManager(QObject* parent = nullptr): QObject(parent) {}
Q_DISABLE_COPY_MOVE(SessionLockManager);
// 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();
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:
LockWindowExtension(QObject* parent = nullptr): QObject(parent) {}
~LockWindowExtension() override;
// 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,111 @@
#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 <wayland-client-protocol.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;
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() { 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) {
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) 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) initVisible();
} else {
this->window()->applyConfigureWhenPossible();
}
}
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(),
size,
QImage::Format_ARGB32
);
this->window()->waylandSurface()->attach(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*);
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;
};