From 39906a7beae1db39c954804aa693e5b201b92f99 Mon Sep 17 00:00:00 2001 From: bbedward Date: Mon, 29 Sep 2025 18:20:04 -0400 Subject: [PATCH 1/3] wayland/shortcuts-inhibit: add shortcuts inhibitor --- src/wayland/CMakeLists.txt | 3 + src/wayland/shortcuts_inhibit/CMakeLists.txt | 25 ++++ src/wayland/shortcuts_inhibit/inhibitor.cpp | 146 +++++++++++++++++++ src/wayland/shortcuts_inhibit/inhibitor.hpp | 72 +++++++++ src/wayland/shortcuts_inhibit/proto.cpp | 50 +++++++ src/wayland/shortcuts_inhibit/proto.hpp | 34 +++++ 6 files changed, 330 insertions(+) create mode 100644 src/wayland/shortcuts_inhibit/CMakeLists.txt create mode 100644 src/wayland/shortcuts_inhibit/inhibitor.cpp create mode 100644 src/wayland/shortcuts_inhibit/inhibitor.hpp create mode 100644 src/wayland/shortcuts_inhibit/proto.cpp create mode 100644 src/wayland/shortcuts_inhibit/proto.hpp diff --git a/src/wayland/CMakeLists.txt b/src/wayland/CMakeLists.txt index a96fe6bb..ca49c8f7 100644 --- a/src/wayland/CMakeLists.txt +++ b/src/wayland/CMakeLists.txt @@ -120,6 +120,9 @@ list(APPEND WAYLAND_MODULES Quickshell.Wayland._IdleInhibitor) add_subdirectory(idle_notify) list(APPEND WAYLAND_MODULES Quickshell.Wayland._IdleNotify) +add_subdirectory(shortcuts_inhibit) +list(APPEND WAYLAND_MODULES Quickshell.Wayland._ShortcutsInhibitor) + # widgets for qmenu target_link_libraries(quickshell-wayland PRIVATE Qt::Quick Qt::Widgets Qt::WaylandClient Qt::WaylandClientPrivate diff --git a/src/wayland/shortcuts_inhibit/CMakeLists.txt b/src/wayland/shortcuts_inhibit/CMakeLists.txt new file mode 100644 index 00000000..8dedd3d5 --- /dev/null +++ b/src/wayland/shortcuts_inhibit/CMakeLists.txt @@ -0,0 +1,25 @@ +qt_add_library(quickshell-wayland-shortcuts-inhibit STATIC + proto.cpp + inhibitor.cpp +) + +qt_add_qml_module(quickshell-wayland-shortcuts-inhibit + URI Quickshell.Wayland._ShortcutsInhibitor + VERSION 0.1 + DEPENDENCIES QtQuick +) + +install_qml_module(quickshell-wayland-shortcuts-inhibit) + +qs_add_module_deps_light(quickshell-wayland-shortcuts-inhibit Quickshell) + +wl_proto(wlp-shortcuts-inhibit keyboard-shortcuts-inhibit-unstable-v1 "${WAYLAND_PROTOCOLS}/unstable/keyboard-shortcuts-inhibit") + +target_link_libraries(quickshell-wayland-shortcuts-inhibit PRIVATE + Qt::Quick Qt::WaylandClient Qt::WaylandClientPrivate wayland-client + wlp-shortcuts-inhibit +) + +qs_module_pch(quickshell-wayland-shortcuts-inhibit SET large) + +target_link_libraries(quickshell PRIVATE quickshell-wayland-shortcuts-inhibitplugin) \ No newline at end of file diff --git a/src/wayland/shortcuts_inhibit/inhibitor.cpp b/src/wayland/shortcuts_inhibit/inhibitor.cpp new file mode 100644 index 00000000..99ffec1e --- /dev/null +++ b/src/wayland/shortcuts_inhibit/inhibitor.cpp @@ -0,0 +1,146 @@ +#include "inhibitor.hpp" + +#include +#include +#include +#include + +#include "../../window/proxywindow.hpp" +#include "../../window/windowinterface.hpp" +#include "proto.hpp" + +namespace qs::wayland::shortcuts_inhibit { +using QtWaylandClient::QWaylandWindow; + +ShortcutsInhibitor::ShortcutsInhibitor() { + this->bBoundWindow.setBinding([this] { + return this->bEnabled ? this->bWindowObject.value() : nullptr; + }); +} + +ShortcutsInhibitor::~ShortcutsInhibitor() { delete this->inhibitor; } + +QObject* ShortcutsInhibitor::window() const { return this->bWindowObject; } + +void ShortcutsInhibitor::setWindow(QObject* window) { + if (window == this->bWindowObject) return; + + auto* proxyWindow = qobject_cast(window); + + if (proxyWindow == nullptr) { + if (auto* iface = qobject_cast(window)) { + proxyWindow = iface->proxyWindow(); + } + } + + this->bWindowObject = proxyWindow ? window : nullptr; +} + +void ShortcutsInhibitor::boundWindowChanged() { + auto* window = this->bBoundWindow.value(); + auto* proxyWindow = qobject_cast(window); + + if (proxyWindow == nullptr) { + if (auto* iface = qobject_cast(window)) { + proxyWindow = iface->proxyWindow(); + } + } + + if (proxyWindow == this->proxyWindow) return; + + if (this->mWaylandWindow) { + QObject::disconnect(this->mWaylandWindow, nullptr, this, nullptr); + this->mWaylandWindow = nullptr; + this->onWaylandSurfaceDestroyed(); + } + + if (this->proxyWindow) { + QObject::disconnect(this->proxyWindow, nullptr, this, nullptr); + this->proxyWindow = nullptr; + } + + if (proxyWindow) { + this->proxyWindow = proxyWindow; + + QObject::connect( + proxyWindow, + &QObject::destroyed, + this, + &ShortcutsInhibitor::onWindowDestroyed + ); + + QObject::connect( + proxyWindow, + &ProxyWindowBase::backerVisibilityChanged, + this, + &ShortcutsInhibitor::onWindowVisibilityChanged + ); + + this->onWindowVisibilityChanged(); + } + + emit this->windowChanged(); +} + +void ShortcutsInhibitor::onWindowDestroyed() { + this->proxyWindow = nullptr; + this->onWaylandSurfaceDestroyed(); + this->bWindowObject = nullptr; +} + +void ShortcutsInhibitor::onWindowVisibilityChanged() { + if (!this->proxyWindow->isVisibleDirect()) return; + + auto* window = this->proxyWindow->backingWindow(); + if (!window->handle()) window->create(); + + auto* waylandWindow = dynamic_cast(window->handle()); + if (waylandWindow == this->mWaylandWindow) return; + this->mWaylandWindow = waylandWindow; + + QObject::connect( + waylandWindow, + &QObject::destroyed, + this, + &ShortcutsInhibitor::onWaylandWindowDestroyed + ); + + QObject::connect( + waylandWindow, + &QWaylandWindow::surfaceCreated, + this, + &ShortcutsInhibitor::onWaylandSurfaceCreated + ); + + QObject::connect( + waylandWindow, + &QWaylandWindow::surfaceDestroyed, + this, + &ShortcutsInhibitor::onWaylandSurfaceDestroyed + ); + + if (waylandWindow->surface()) this->onWaylandSurfaceCreated(); +} + +void ShortcutsInhibitor::onWaylandWindowDestroyed() { this->mWaylandWindow = nullptr; } + +void ShortcutsInhibitor::onWaylandSurfaceCreated() { + auto* manager = impl::ShortcutsInhibitManager::instance(); + + if (!manager) { + qWarning() << "Cannot enable shortcuts inhibitor as keyboard-shortcuts-inhibit-unstable-v1 is " + "not supported by " + "the current compositor."; + return; + } + + delete this->inhibitor; + this->inhibitor = manager->createShortcutsInhibitor(this->mWaylandWindow); +} + +void ShortcutsInhibitor::onWaylandSurfaceDestroyed() { + delete this->inhibitor; + this->inhibitor = nullptr; +} + +} // namespace qs::wayland::shortcuts_inhibit \ No newline at end of file diff --git a/src/wayland/shortcuts_inhibit/inhibitor.hpp b/src/wayland/shortcuts_inhibit/inhibitor.hpp new file mode 100644 index 00000000..7735eba6 --- /dev/null +++ b/src/wayland/shortcuts_inhibit/inhibitor.hpp @@ -0,0 +1,72 @@ +#pragma once + +#include +#include +#include +#include +#include + +#include "../../window/proxywindow.hpp" +#include "proto.hpp" + +namespace qs::wayland::shortcuts_inhibit { + +///! Prevents compositor keyboard shortcuts from being triggered +/// A shortcuts inhibitor prevents the compositor from processing its own keyboard shortcuts +/// for the focused surface. This allows applications to receive key events for shortcuts +/// that would normally be handled by the compositor. +/// +/// The inhibitor only takes effect when the associated window is focused and the inhibitor +/// is enabled. The compositor may choose to ignore inhibitor requests based on its policy. +/// +/// > [!NOTE] Using a shortcuts inhibitor requires the compositor support the [keyboard-shortcuts-inhibit-unstable-v1] protocol. +/// +/// [keyboard-shortcuts-inhibit-unstable-v1]: https://wayland.app/protocols/keyboard-shortcuts-inhibit-unstable-v1 +class ShortcutsInhibitor: public QObject { + Q_OBJECT; + QML_ELEMENT; + // clang-format off + /// If the shortcuts inhibitor should be enabled. Defaults to false. + Q_PROPERTY(bool enabled READ default WRITE default NOTIFY enabledChanged BINDABLE bindableEnabled); + /// The window to associate the shortcuts inhibitor with. + /// + /// Must be set to a non null value to enable the inhibitor. + Q_PROPERTY(QObject* window READ window WRITE setWindow NOTIFY windowChanged); + // clang-format on + +public: + ShortcutsInhibitor(); + ~ShortcutsInhibitor() override; + Q_DISABLE_COPY_MOVE(ShortcutsInhibitor); + + [[nodiscard]] QObject* window() const; + void setWindow(QObject* window); + + [[nodiscard]] QBindable bindableEnabled() { return &this->bEnabled; } + +signals: + void enabledChanged(); + void windowChanged(); + +private slots: + void onWindowDestroyed(); + void onWindowVisibilityChanged(); + void onWaylandWindowDestroyed(); + void onWaylandSurfaceCreated(); + void onWaylandSurfaceDestroyed(); + +private: + void boundWindowChanged(); + ProxyWindowBase* proxyWindow = nullptr; + QtWaylandClient::QWaylandWindow* mWaylandWindow = nullptr; + + impl::ShortcutsInhibitor* inhibitor = nullptr; + + // clang-format off + Q_OBJECT_BINDABLE_PROPERTY(ShortcutsInhibitor, bool, bEnabled, &ShortcutsInhibitor::enabledChanged); + Q_OBJECT_BINDABLE_PROPERTY(ShortcutsInhibitor, QObject*, bWindowObject, &ShortcutsInhibitor::windowChanged); + Q_OBJECT_BINDABLE_PROPERTY(ShortcutsInhibitor, QObject*, bBoundWindow, &ShortcutsInhibitor::boundWindowChanged); + // clang-format on +}; + +} // namespace qs::wayland::shortcuts_inhibit \ No newline at end of file diff --git a/src/wayland/shortcuts_inhibit/proto.cpp b/src/wayland/shortcuts_inhibit/proto.cpp new file mode 100644 index 00000000..eead95ce --- /dev/null +++ b/src/wayland/shortcuts_inhibit/proto.cpp @@ -0,0 +1,50 @@ +#include "proto.hpp" + +#include +#include +#include +#include +#include +#include +#include + +#include "../../core/logcat.hpp" + +namespace qs::wayland::shortcuts_inhibit::impl { + +namespace { +QS_LOGGING_CATEGORY(logShortcutsInhibit, "quickshell.wayland.shortcuts_inhibit", QtWarningMsg); +} + +ShortcutsInhibitManager::ShortcutsInhibitManager(): QWaylandClientExtensionTemplate(1) { + this->initialize(); +} + +ShortcutsInhibitManager* ShortcutsInhibitManager::instance() { + static auto* instance = new ShortcutsInhibitManager(); // NOLINT + return instance->isInitialized() ? instance : nullptr; +} + +ShortcutsInhibitor* +ShortcutsInhibitManager::createShortcutsInhibitor(QtWaylandClient::QWaylandWindow* surface) { + auto* display = QtWaylandClient::QWaylandIntegration::instance()->display(); + auto* inputDevice = display->lastInputDevice(); + if (inputDevice == nullptr) inputDevice = display->defaultInputDevice(); + + if (inputDevice == nullptr) { + qCCritical(logShortcutsInhibit) << "Could not create shortcuts inhibitor: No seat."; + return nullptr; + } + + auto* inhibitor = + new ShortcutsInhibitor(this->inhibit_shortcuts(surface->surface(), inputDevice->object())); + qCDebug(logShortcutsInhibit) << "Created inhibitor" << inhibitor; + return inhibitor; +} + +ShortcutsInhibitor::~ShortcutsInhibitor() { + qCDebug(logShortcutsInhibit) << "Destroyed inhibitor" << this; + if (this->isInitialized()) this->destroy(); +} + +} // namespace qs::wayland::shortcuts_inhibit::impl \ No newline at end of file diff --git a/src/wayland/shortcuts_inhibit/proto.hpp b/src/wayland/shortcuts_inhibit/proto.hpp new file mode 100644 index 00000000..f6465612 --- /dev/null +++ b/src/wayland/shortcuts_inhibit/proto.hpp @@ -0,0 +1,34 @@ +#pragma once + +#include +#include +#include +#include + +#include "wayland-keyboard-shortcuts-inhibit-unstable-v1-client-protocol.h" + +namespace qs::wayland::shortcuts_inhibit::impl { + +class ShortcutsInhibitor; + +class ShortcutsInhibitManager + : public QWaylandClientExtensionTemplate + , public QtWayland::zwp_keyboard_shortcuts_inhibit_manager_v1 { +public: + explicit ShortcutsInhibitManager(); + + ShortcutsInhibitor* createShortcutsInhibitor(QtWaylandClient::QWaylandWindow* surface); + + static ShortcutsInhibitManager* instance(); +}; + +class ShortcutsInhibitor: public QtWayland::zwp_keyboard_shortcuts_inhibitor_v1 { +public: + explicit ShortcutsInhibitor(::zwp_keyboard_shortcuts_inhibitor_v1* inhibitor) + : QtWayland::zwp_keyboard_shortcuts_inhibitor_v1(inhibitor) {} + + ~ShortcutsInhibitor() override; + Q_DISABLE_COPY_MOVE(ShortcutsInhibitor); +}; + +} // namespace qs::wayland::shortcuts_inhibit::impl \ No newline at end of file From a764cec2f08fb3c82eb9771ce572d6717471b624 Mon Sep 17 00:00:00 2001 From: bbedward Date: Thu, 6 Nov 2025 10:42:06 -0500 Subject: [PATCH 2/3] wayland/shortcuts-inhibit: track active state and prevent duplicate inhibitors --- src/wayland/shortcuts_inhibit/inhibitor.cpp | 28 +++++++++++++++-- src/wayland/shortcuts_inhibit/inhibitor.hpp | 12 ++++++++ src/wayland/shortcuts_inhibit/proto.cpp | 34 +++++++++++++++++++-- src/wayland/shortcuts_inhibit/proto.hpp | 29 ++++++++++++++++-- 4 files changed, 94 insertions(+), 9 deletions(-) diff --git a/src/wayland/shortcuts_inhibit/inhibitor.cpp b/src/wayland/shortcuts_inhibit/inhibitor.cpp index 99ffec1e..42b0e6d3 100644 --- a/src/wayland/shortcuts_inhibit/inhibitor.cpp +++ b/src/wayland/shortcuts_inhibit/inhibitor.cpp @@ -36,6 +36,10 @@ void ShortcutsInhibitor::setWindow(QObject* window) { this->bWindowObject = proxyWindow ? window : nullptr; } +bool ShortcutsInhibitor::isActive() const { + return this->inhibitor ? this->inhibitor->isActive() : false; +} + void ShortcutsInhibitor::boundWindowChanged() { auto* window = this->bBoundWindow.value(); auto* proxyWindow = qobject_cast(window); @@ -134,13 +138,31 @@ void ShortcutsInhibitor::onWaylandSurfaceCreated() { return; } - delete this->inhibitor; + if (this->inhibitor) { + QObject::disconnect(this->inhibitor, nullptr, this, nullptr); + delete this->inhibitor; + this->inhibitor = nullptr; + } this->inhibitor = manager->createShortcutsInhibitor(this->mWaylandWindow); + + if (this->inhibitor) { + QObject::connect( + this->inhibitor, + &impl::ShortcutsInhibitor::activeChanged, + this, + &ShortcutsInhibitor::activeChanged + ); + } } void ShortcutsInhibitor::onWaylandSurfaceDestroyed() { - delete this->inhibitor; - this->inhibitor = nullptr; + if (this->inhibitor) { + QObject::disconnect(this->inhibitor, nullptr, this, nullptr); + auto wasActive = this->inhibitor->isActive(); + delete this->inhibitor; + this->inhibitor = nullptr; + if (wasActive) { emit this->activeChanged(); } + } } } // namespace qs::wayland::shortcuts_inhibit \ No newline at end of file diff --git a/src/wayland/shortcuts_inhibit/inhibitor.hpp b/src/wayland/shortcuts_inhibit/inhibitor.hpp index 7735eba6..bff302ed 100644 --- a/src/wayland/shortcuts_inhibit/inhibitor.hpp +++ b/src/wayland/shortcuts_inhibit/inhibitor.hpp @@ -32,6 +32,16 @@ class ShortcutsInhibitor: public QObject { /// /// Must be set to a non null value to enable the inhibitor. Q_PROPERTY(QObject* window READ window WRITE setWindow NOTIFY windowChanged); + /// Whether the inhibitor is currently active. The inhibitor is active when the compositor + /// has granted the request and shortcuts are being inhibited. + /// + /// The compositor may deactivate the inhibitor if the user requests normal shortcuts to be restored. + /// When inactive, there is no way to programmatically reactivate it - the user must do so through + /// compositor-specific mechanisms. + /// + /// Note that if the surface loses focus or is destroyed, the inhibitor becomes irrelevant and this + /// property will become false, but no inactive event is sent by the compositor in those cases. + Q_PROPERTY(bool active READ isActive NOTIFY activeChanged); // clang-format on public: @@ -43,10 +53,12 @@ class ShortcutsInhibitor: public QObject { void setWindow(QObject* window); [[nodiscard]] QBindable bindableEnabled() { return &this->bEnabled; } + [[nodiscard]] bool isActive() const; signals: void enabledChanged(); void windowChanged(); + void activeChanged(); private slots: void onWindowDestroyed(); diff --git a/src/wayland/shortcuts_inhibit/proto.cpp b/src/wayland/shortcuts_inhibit/proto.cpp index eead95ce..df9c7969 100644 --- a/src/wayland/shortcuts_inhibit/proto.cpp +++ b/src/wayland/shortcuts_inhibit/proto.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #include "../../core/logcat.hpp" @@ -36,15 +37,42 @@ ShortcutsInhibitManager::createShortcutsInhibitor(QtWaylandClient::QWaylandWindo return nullptr; } + auto* wlSurface = surface->surface(); + + if (this->inhibitors.contains(wlSurface)) { + qCWarning(logShortcutsInhibit) + << "An inhibitor already exists for surface, skipping creation." << wlSurface; + return nullptr; + } + auto* inhibitor = - new ShortcutsInhibitor(this->inhibit_shortcuts(surface->surface(), inputDevice->object())); - qCDebug(logShortcutsInhibit) << "Created inhibitor" << inhibitor; + new ShortcutsInhibitor(this->inhibit_shortcuts(wlSurface, inputDevice->object()), wlSurface); + this->inhibitors.insert(wlSurface, inhibitor); + qCDebug(logShortcutsInhibit) << "Created inhibitor" << inhibitor << "for surface" << wlSurface; return inhibitor; } +void ShortcutsInhibitManager::destroyShortcutsInhibitor(ShortcutsInhibitor* inhibitor) { + if (inhibitor) { this->inhibitors.remove(inhibitor->surface()); } +} + ShortcutsInhibitor::~ShortcutsInhibitor() { - qCDebug(logShortcutsInhibit) << "Destroyed inhibitor" << this; + qCDebug(logShortcutsInhibit) << "Destroying inhibitor" << this << "for surface" << this->mSurface; + auto* manager = ShortcutsInhibitManager::instance(); + if (manager) { manager->destroyShortcutsInhibitor(this); } if (this->isInitialized()) this->destroy(); } +void ShortcutsInhibitor::zwp_keyboard_shortcuts_inhibitor_v1_active() { + qCDebug(logShortcutsInhibit) << "Inhibitor became active" << this; + this->mActive = true; + emit this->activeChanged(); +} + +void ShortcutsInhibitor::zwp_keyboard_shortcuts_inhibitor_v1_inactive() { + qCDebug(logShortcutsInhibit) << "Inhibitor became inactive" << this; + this->mActive = false; + emit this->activeChanged(); +} + } // namespace qs::wayland::shortcuts_inhibit::impl \ No newline at end of file diff --git a/src/wayland/shortcuts_inhibit/proto.hpp b/src/wayland/shortcuts_inhibit/proto.hpp index f6465612..106360d5 100644 --- a/src/wayland/shortcuts_inhibit/proto.hpp +++ b/src/wayland/shortcuts_inhibit/proto.hpp @@ -18,17 +18,40 @@ class ShortcutsInhibitManager explicit ShortcutsInhibitManager(); ShortcutsInhibitor* createShortcutsInhibitor(QtWaylandClient::QWaylandWindow* surface); + void destroyShortcutsInhibitor(ShortcutsInhibitor* inhibitor); static ShortcutsInhibitManager* instance(); + +private: + QHash inhibitors; }; -class ShortcutsInhibitor: public QtWayland::zwp_keyboard_shortcuts_inhibitor_v1 { +class ShortcutsInhibitor + : public QObject + , public QtWayland::zwp_keyboard_shortcuts_inhibitor_v1 { + Q_OBJECT; + public: - explicit ShortcutsInhibitor(::zwp_keyboard_shortcuts_inhibitor_v1* inhibitor) - : QtWayland::zwp_keyboard_shortcuts_inhibitor_v1(inhibitor) {} + explicit ShortcutsInhibitor(::zwp_keyboard_shortcuts_inhibitor_v1* inhibitor, wl_surface* surface) + : QtWayland::zwp_keyboard_shortcuts_inhibitor_v1(inhibitor) + , mSurface(surface) {} ~ShortcutsInhibitor() override; Q_DISABLE_COPY_MOVE(ShortcutsInhibitor); + + [[nodiscard]] bool isActive() const { return this->mActive; } + [[nodiscard]] wl_surface* surface() const { return this->mSurface; } + +signals: + void activeChanged(); + +protected: + void zwp_keyboard_shortcuts_inhibitor_v1_active() override; + void zwp_keyboard_shortcuts_inhibitor_v1_inactive() override; + +private: + bool mActive = false; + wl_surface* mSurface; }; } // namespace qs::wayland::shortcuts_inhibit::impl \ No newline at end of file From 9654b19a9e80d3cfc241f73b1ca02b648d60b10c Mon Sep 17 00:00:00 2001 From: bbedward Date: Fri, 7 Nov 2025 09:55:55 -0500 Subject: [PATCH 3/3] wayland/shortcuts-inhibit: merge multiple inhibitors on same surface --- src/wayland/shortcuts_inhibit/inhibitor.cpp | 30 ++++++++++++------ src/wayland/shortcuts_inhibit/inhibitor.hpp | 7 ++--- src/wayland/shortcuts_inhibit/proto.cpp | 34 ++++++++++++++++----- src/wayland/shortcuts_inhibit/proto.hpp | 4 ++- 4 files changed, 53 insertions(+), 22 deletions(-) diff --git a/src/wayland/shortcuts_inhibit/inhibitor.cpp b/src/wayland/shortcuts_inhibit/inhibitor.cpp index 42b0e6d3..d4ba81b8 100644 --- a/src/wayland/shortcuts_inhibit/inhibitor.cpp +++ b/src/wayland/shortcuts_inhibit/inhibitor.cpp @@ -18,7 +18,15 @@ ShortcutsInhibitor::ShortcutsInhibitor() { }); } -ShortcutsInhibitor::~ShortcutsInhibitor() { delete this->inhibitor; } +ShortcutsInhibitor::~ShortcutsInhibitor() { + if (!this->inhibitor) return; + + auto* manager = impl::ShortcutsInhibitManager::instance(); + if (!manager) return; + + QObject::disconnect(this->inhibitor, nullptr, this, nullptr); + manager->unrefShortcutsInhibitor(this->inhibitor); +} QObject* ShortcutsInhibitor::window() const { return this->bWindowObject; } @@ -140,7 +148,7 @@ void ShortcutsInhibitor::onWaylandSurfaceCreated() { if (this->inhibitor) { QObject::disconnect(this->inhibitor, nullptr, this, nullptr); - delete this->inhibitor; + manager->unrefShortcutsInhibitor(this->inhibitor); this->inhibitor = nullptr; } this->inhibitor = manager->createShortcutsInhibitor(this->mWaylandWindow); @@ -156,13 +164,17 @@ void ShortcutsInhibitor::onWaylandSurfaceCreated() { } void ShortcutsInhibitor::onWaylandSurfaceDestroyed() { - if (this->inhibitor) { - QObject::disconnect(this->inhibitor, nullptr, this, nullptr); - auto wasActive = this->inhibitor->isActive(); - delete this->inhibitor; - this->inhibitor = nullptr; - if (wasActive) { emit this->activeChanged(); } - } + if (!this->inhibitor) return; + + auto* manager = impl::ShortcutsInhibitManager::instance(); + if (!manager) return; + + QObject::disconnect(this->inhibitor, nullptr, this, nullptr); + auto wasActive = this->inhibitor->isActive(); + manager->unrefShortcutsInhibitor(this->inhibitor); + this->inhibitor = nullptr; + + if (wasActive) { emit this->activeChanged(); } } } // namespace qs::wayland::shortcuts_inhibit \ No newline at end of file diff --git a/src/wayland/shortcuts_inhibit/inhibitor.hpp b/src/wayland/shortcuts_inhibit/inhibitor.hpp index bff302ed..678f995b 100644 --- a/src/wayland/shortcuts_inhibit/inhibitor.hpp +++ b/src/wayland/shortcuts_inhibit/inhibitor.hpp @@ -32,15 +32,12 @@ class ShortcutsInhibitor: public QObject { /// /// Must be set to a non null value to enable the inhibitor. Q_PROPERTY(QObject* window READ window WRITE setWindow NOTIFY windowChanged); - /// Whether the inhibitor is currently active. The inhibitor is active when the compositor - /// has granted the request and shortcuts are being inhibited. + /// Whether the inhibitor is currently active. The inhibitor is only active when the + /// associated window has keyboard focus and the compositor has granted the request. /// /// The compositor may deactivate the inhibitor if the user requests normal shortcuts to be restored. /// When inactive, there is no way to programmatically reactivate it - the user must do so through /// compositor-specific mechanisms. - /// - /// Note that if the surface loses focus or is destroyed, the inhibitor becomes irrelevant and this - /// property will become false, but no inactive event is sent by the compositor in those cases. Q_PROPERTY(bool active READ isActive NOTIFY activeChanged); // clang-format on diff --git a/src/wayland/shortcuts_inhibit/proto.cpp b/src/wayland/shortcuts_inhibit/proto.cpp index df9c7969..00e185a6 100644 --- a/src/wayland/shortcuts_inhibit/proto.cpp +++ b/src/wayland/shortcuts_inhibit/proto.cpp @@ -40,26 +40,46 @@ ShortcutsInhibitManager::createShortcutsInhibitor(QtWaylandClient::QWaylandWindo auto* wlSurface = surface->surface(); if (this->inhibitors.contains(wlSurface)) { - qCWarning(logShortcutsInhibit) - << "An inhibitor already exists for surface, skipping creation." << wlSurface; - return nullptr; + auto* inhibitor = this->inhibitors.value(wlSurface); + this->refCounts[inhibitor]++; + qCDebug(logShortcutsInhibit) << "Reusing existing inhibitor" << inhibitor << "for surface" + << wlSurface << "- refcount:" << this->refCounts[inhibitor]; + return inhibitor; } auto* inhibitor = new ShortcutsInhibitor(this->inhibit_shortcuts(wlSurface, inputDevice->object()), wlSurface); this->inhibitors.insert(wlSurface, inhibitor); + this->refCounts.insert(inhibitor, 1); qCDebug(logShortcutsInhibit) << "Created inhibitor" << inhibitor << "for surface" << wlSurface; return inhibitor; } -void ShortcutsInhibitManager::destroyShortcutsInhibitor(ShortcutsInhibitor* inhibitor) { - if (inhibitor) { this->inhibitors.remove(inhibitor->surface()); } +void ShortcutsInhibitManager::refShortcutsInhibitor(ShortcutsInhibitor* inhibitor) { + if (inhibitor && this->refCounts.contains(inhibitor)) { + this->refCounts[inhibitor]++; + qCDebug(logShortcutsInhibit) << "Incremented refcount for inhibitor" << inhibitor + << "- refcount:" << this->refCounts[inhibitor]; + } +} + +void ShortcutsInhibitManager::unrefShortcutsInhibitor(ShortcutsInhibitor* inhibitor) { + if (!inhibitor || !this->refCounts.contains(inhibitor)) return; + + this->refCounts[inhibitor]--; + qCDebug(logShortcutsInhibit) << "Decremented refcount for inhibitor" << inhibitor + << "- refcount:" << this->refCounts[inhibitor]; + + if (this->refCounts[inhibitor] <= 0) { + qCDebug(logShortcutsInhibit) << "Refcount reached 0, destroying inhibitor" << inhibitor; + this->inhibitors.remove(inhibitor->surface()); + this->refCounts.remove(inhibitor); + delete inhibitor; + } } ShortcutsInhibitor::~ShortcutsInhibitor() { qCDebug(logShortcutsInhibit) << "Destroying inhibitor" << this << "for surface" << this->mSurface; - auto* manager = ShortcutsInhibitManager::instance(); - if (manager) { manager->destroyShortcutsInhibitor(this); } if (this->isInitialized()) this->destroy(); } diff --git a/src/wayland/shortcuts_inhibit/proto.hpp b/src/wayland/shortcuts_inhibit/proto.hpp index 106360d5..d19b579e 100644 --- a/src/wayland/shortcuts_inhibit/proto.hpp +++ b/src/wayland/shortcuts_inhibit/proto.hpp @@ -18,12 +18,14 @@ class ShortcutsInhibitManager explicit ShortcutsInhibitManager(); ShortcutsInhibitor* createShortcutsInhibitor(QtWaylandClient::QWaylandWindow* surface); - void destroyShortcutsInhibitor(ShortcutsInhibitor* inhibitor); + void refShortcutsInhibitor(ShortcutsInhibitor* inhibitor); + void unrefShortcutsInhibitor(ShortcutsInhibitor* inhibitor); static ShortcutsInhibitManager* instance(); private: QHash inhibitors; + QHash refCounts; }; class ShortcutsInhibitor