Skip to content

Commit f484db0

Browse files
committed
wayland/shortcuts-inhibit: add shortcuts inhibitor
1 parent 1d94144 commit f484db0

File tree

6 files changed

+330
-0
lines changed

6 files changed

+330
-0
lines changed

src/wayland/CMakeLists.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,9 @@ list(APPEND WAYLAND_MODULES Quickshell.Wayland._IdleInhibitor)
120120
add_subdirectory(idle_notify)
121121
list(APPEND WAYLAND_MODULES Quickshell.Wayland._IdleNotify)
122122

123+
add_subdirectory(shortcuts_inhibit)
124+
list(APPEND WAYLAND_MODULES Quickshell.Wayland._ShortcutsInhibitor)
125+
123126
# widgets for qmenu
124127
target_link_libraries(quickshell-wayland PRIVATE
125128
Qt::Quick Qt::Widgets Qt::WaylandClient Qt::WaylandClientPrivate
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
qt_add_library(quickshell-wayland-shortcuts-inhibit STATIC
2+
proto.cpp
3+
inhibitor.cpp
4+
)
5+
6+
qt_add_qml_module(quickshell-wayland-shortcuts-inhibit
7+
URI Quickshell.Wayland._ShortcutsInhibitor
8+
VERSION 0.1
9+
DEPENDENCIES QtQuick
10+
)
11+
12+
install_qml_module(quickshell-wayland-shortcuts-inhibit)
13+
14+
qs_add_module_deps_light(quickshell-wayland-shortcuts-inhibit Quickshell)
15+
16+
wl_proto(wlp-shortcuts-inhibit keyboard-shortcuts-inhibit-unstable-v1 "${WAYLAND_PROTOCOLS}/unstable/keyboard-shortcuts-inhibit")
17+
18+
target_link_libraries(quickshell-wayland-shortcuts-inhibit PRIVATE
19+
Qt::Quick Qt::WaylandClient Qt::WaylandClientPrivate wayland-client
20+
wlp-shortcuts-inhibit
21+
)
22+
23+
qs_module_pch(quickshell-wayland-shortcuts-inhibit SET large)
24+
25+
target_link_libraries(quickshell PRIVATE quickshell-wayland-shortcuts-inhibitplugin)
Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
#include "inhibitor.hpp"
2+
3+
#include <private/qwaylandwindow_p.h>
4+
#include <qlogging.h>
5+
#include <qobject.h>
6+
#include <qtmetamacros.h>
7+
8+
#include "../../window/proxywindow.hpp"
9+
#include "../../window/windowinterface.hpp"
10+
#include "proto.hpp"
11+
12+
namespace qs::wayland::shortcuts_inhibit {
13+
using QtWaylandClient::QWaylandWindow;
14+
15+
ShortcutsInhibitor::ShortcutsInhibitor() {
16+
this->bBoundWindow.setBinding([this] {
17+
return this->bEnabled ? this->bWindowObject.value() : nullptr;
18+
});
19+
}
20+
21+
ShortcutsInhibitor::~ShortcutsInhibitor() { delete this->inhibitor; }
22+
23+
QObject* ShortcutsInhibitor::window() const { return this->bWindowObject; }
24+
25+
void ShortcutsInhibitor::setWindow(QObject* window) {
26+
if (window == this->bWindowObject) return;
27+
28+
auto* proxyWindow = qobject_cast<ProxyWindowBase*>(window);
29+
30+
if (proxyWindow == nullptr) {
31+
if (auto* iface = qobject_cast<WindowInterface*>(window)) {
32+
proxyWindow = iface->proxyWindow();
33+
}
34+
}
35+
36+
this->bWindowObject = proxyWindow ? window : nullptr;
37+
}
38+
39+
void ShortcutsInhibitor::boundWindowChanged() {
40+
auto* window = this->bBoundWindow.value();
41+
auto* proxyWindow = qobject_cast<ProxyWindowBase*>(window);
42+
43+
if (proxyWindow == nullptr) {
44+
if (auto* iface = qobject_cast<WindowInterface*>(window)) {
45+
proxyWindow = iface->proxyWindow();
46+
}
47+
}
48+
49+
if (proxyWindow == this->proxyWindow) return;
50+
51+
if (this->mWaylandWindow) {
52+
QObject::disconnect(this->mWaylandWindow, nullptr, this, nullptr);
53+
this->mWaylandWindow = nullptr;
54+
this->onWaylandSurfaceDestroyed();
55+
}
56+
57+
if (this->proxyWindow) {
58+
QObject::disconnect(this->proxyWindow, nullptr, this, nullptr);
59+
this->proxyWindow = nullptr;
60+
}
61+
62+
if (proxyWindow) {
63+
this->proxyWindow = proxyWindow;
64+
65+
QObject::connect(
66+
proxyWindow,
67+
&QObject::destroyed,
68+
this,
69+
&ShortcutsInhibitor::onWindowDestroyed
70+
);
71+
72+
QObject::connect(
73+
proxyWindow,
74+
&ProxyWindowBase::backerVisibilityChanged,
75+
this,
76+
&ShortcutsInhibitor::onWindowVisibilityChanged
77+
);
78+
79+
this->onWindowVisibilityChanged();
80+
}
81+
82+
emit this->windowChanged();
83+
}
84+
85+
void ShortcutsInhibitor::onWindowDestroyed() {
86+
this->proxyWindow = nullptr;
87+
this->onWaylandSurfaceDestroyed();
88+
this->bWindowObject = nullptr;
89+
}
90+
91+
void ShortcutsInhibitor::onWindowVisibilityChanged() {
92+
if (!this->proxyWindow->isVisibleDirect()) return;
93+
94+
auto* window = this->proxyWindow->backingWindow();
95+
if (!window->handle()) window->create();
96+
97+
auto* waylandWindow = dynamic_cast<QWaylandWindow*>(window->handle());
98+
if (waylandWindow == this->mWaylandWindow) return;
99+
this->mWaylandWindow = waylandWindow;
100+
101+
QObject::connect(
102+
waylandWindow,
103+
&QObject::destroyed,
104+
this,
105+
&ShortcutsInhibitor::onWaylandWindowDestroyed
106+
);
107+
108+
QObject::connect(
109+
waylandWindow,
110+
&QWaylandWindow::surfaceCreated,
111+
this,
112+
&ShortcutsInhibitor::onWaylandSurfaceCreated
113+
);
114+
115+
QObject::connect(
116+
waylandWindow,
117+
&QWaylandWindow::surfaceDestroyed,
118+
this,
119+
&ShortcutsInhibitor::onWaylandSurfaceDestroyed
120+
);
121+
122+
if (waylandWindow->surface()) this->onWaylandSurfaceCreated();
123+
}
124+
125+
void ShortcutsInhibitor::onWaylandWindowDestroyed() { this->mWaylandWindow = nullptr; }
126+
127+
void ShortcutsInhibitor::onWaylandSurfaceCreated() {
128+
auto* manager = impl::ShortcutsInhibitManager::instance();
129+
130+
if (!manager) {
131+
qWarning() << "Cannot enable shortcuts inhibitor as keyboard-shortcuts-inhibit-unstable-v1 is "
132+
"not supported by "
133+
"the current compositor.";
134+
return;
135+
}
136+
137+
delete this->inhibitor;
138+
this->inhibitor = manager->createShortcutsInhibitor(this->mWaylandWindow);
139+
}
140+
141+
void ShortcutsInhibitor::onWaylandSurfaceDestroyed() {
142+
delete this->inhibitor;
143+
this->inhibitor = nullptr;
144+
}
145+
146+
} // namespace qs::wayland::shortcuts_inhibit
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
#pragma once
2+
3+
#include <qobject.h>
4+
#include <qproperty.h>
5+
#include <qqmlintegration.h>
6+
#include <qtclasshelpermacros.h>
7+
#include <qtmetamacros.h>
8+
9+
#include "../../window/proxywindow.hpp"
10+
#include "proto.hpp"
11+
12+
namespace qs::wayland::shortcuts_inhibit {
13+
14+
///! Prevents compositor keyboard shortcuts from being triggered
15+
/// A shortcuts inhibitor prevents the compositor from processing its own keyboard shortcuts
16+
/// for the focused surface. This allows applications to receive key events for shortcuts
17+
/// that would normally be handled by the compositor.
18+
///
19+
/// The inhibitor only takes effect when the associated window is focused and the inhibitor
20+
/// is enabled. The compositor may choose to ignore inhibitor requests based on its policy.
21+
///
22+
/// > [!NOTE] Using a shortcuts inhibitor requires the compositor support the [keyboard-shortcuts-inhibit-unstable-v1] protocol.
23+
///
24+
/// [keyboard-shortcuts-inhibit-unstable-v1]: https://wayland.app/protocols/keyboard-shortcuts-inhibit-unstable-v1
25+
class ShortcutsInhibitor: public QObject {
26+
Q_OBJECT;
27+
QML_ELEMENT;
28+
// clang-format off
29+
/// If the shortcuts inhibitor should be enabled. Defaults to false.
30+
Q_PROPERTY(bool enabled READ default WRITE default NOTIFY enabledChanged BINDABLE bindableEnabled);
31+
/// The window to associate the shortcuts inhibitor with.
32+
///
33+
/// Must be set to a non null value to enable the inhibitor.
34+
Q_PROPERTY(QObject* window READ window WRITE setWindow NOTIFY windowChanged);
35+
// clang-format on
36+
37+
public:
38+
ShortcutsInhibitor();
39+
~ShortcutsInhibitor() override;
40+
Q_DISABLE_COPY_MOVE(ShortcutsInhibitor);
41+
42+
[[nodiscard]] QObject* window() const;
43+
void setWindow(QObject* window);
44+
45+
[[nodiscard]] QBindable<bool> bindableEnabled() { return &this->bEnabled; }
46+
47+
signals:
48+
void enabledChanged();
49+
void windowChanged();
50+
51+
private slots:
52+
void onWindowDestroyed();
53+
void onWindowVisibilityChanged();
54+
void onWaylandWindowDestroyed();
55+
void onWaylandSurfaceCreated();
56+
void onWaylandSurfaceDestroyed();
57+
58+
private:
59+
void boundWindowChanged();
60+
ProxyWindowBase* proxyWindow = nullptr;
61+
QtWaylandClient::QWaylandWindow* mWaylandWindow = nullptr;
62+
63+
impl::ShortcutsInhibitor* inhibitor = nullptr;
64+
65+
// clang-format off
66+
Q_OBJECT_BINDABLE_PROPERTY(ShortcutsInhibitor, bool, bEnabled, &ShortcutsInhibitor::enabledChanged);
67+
Q_OBJECT_BINDABLE_PROPERTY(ShortcutsInhibitor, QObject*, bWindowObject, &ShortcutsInhibitor::windowChanged);
68+
Q_OBJECT_BINDABLE_PROPERTY(ShortcutsInhibitor, QObject*, bBoundWindow, &ShortcutsInhibitor::boundWindowChanged);
69+
// clang-format on
70+
};
71+
72+
} // namespace qs::wayland::shortcuts_inhibit
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
#include "proto.hpp"
2+
3+
#include <private/qwaylanddisplay_p.h>
4+
#include <private/qwaylandinputdevice_p.h>
5+
#include <private/qwaylandintegration_p.h>
6+
#include <private/qwaylandwindow_p.h>
7+
#include <qlogging.h>
8+
#include <qloggingcategory.h>
9+
#include <qwaylandclientextension.h>
10+
11+
#include "../../core/logcat.hpp"
12+
13+
namespace qs::wayland::shortcuts_inhibit::impl {
14+
15+
namespace {
16+
QS_LOGGING_CATEGORY(logShortcutsInhibit, "quickshell.wayland.shortcuts_inhibit", QtWarningMsg);
17+
}
18+
19+
ShortcutsInhibitManager::ShortcutsInhibitManager(): QWaylandClientExtensionTemplate(1) {
20+
this->initialize();
21+
}
22+
23+
ShortcutsInhibitManager* ShortcutsInhibitManager::instance() {
24+
static auto* instance = new ShortcutsInhibitManager(); // NOLINT
25+
return instance->isInitialized() ? instance : nullptr;
26+
}
27+
28+
ShortcutsInhibitor*
29+
ShortcutsInhibitManager::createShortcutsInhibitor(QtWaylandClient::QWaylandWindow* surface) {
30+
auto* display = QtWaylandClient::QWaylandIntegration::instance()->display();
31+
auto* inputDevice = display->lastInputDevice();
32+
if (inputDevice == nullptr) inputDevice = display->defaultInputDevice();
33+
34+
if (inputDevice == nullptr) {
35+
qCCritical(logShortcutsInhibit) << "Could not create shortcuts inhibitor: No seat.";
36+
return nullptr;
37+
}
38+
39+
auto* inhibitor =
40+
new ShortcutsInhibitor(this->inhibit_shortcuts(surface->surface(), inputDevice->object()));
41+
qCDebug(logShortcutsInhibit) << "Created inhibitor" << inhibitor;
42+
return inhibitor;
43+
}
44+
45+
ShortcutsInhibitor::~ShortcutsInhibitor() {
46+
qCDebug(logShortcutsInhibit) << "Destroyed inhibitor" << this;
47+
if (this->isInitialized()) this->destroy();
48+
}
49+
50+
} // namespace qs::wayland::shortcuts_inhibit::impl
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
#pragma once
2+
3+
#include <private/qwaylandwindow_p.h>
4+
#include <qtclasshelpermacros.h>
5+
#include <qwayland-keyboard-shortcuts-inhibit-unstable-v1.h>
6+
#include <qwaylandclientextension.h>
7+
8+
#include "wayland-keyboard-shortcuts-inhibit-unstable-v1-client-protocol.h"
9+
10+
namespace qs::wayland::shortcuts_inhibit::impl {
11+
12+
class ShortcutsInhibitor;
13+
14+
class ShortcutsInhibitManager
15+
: public QWaylandClientExtensionTemplate<ShortcutsInhibitManager>
16+
, public QtWayland::zwp_keyboard_shortcuts_inhibit_manager_v1 {
17+
public:
18+
explicit ShortcutsInhibitManager();
19+
20+
ShortcutsInhibitor* createShortcutsInhibitor(QtWaylandClient::QWaylandWindow* surface);
21+
22+
static ShortcutsInhibitManager* instance();
23+
};
24+
25+
class ShortcutsInhibitor: public QtWayland::zwp_keyboard_shortcuts_inhibitor_v1 {
26+
public:
27+
explicit ShortcutsInhibitor(::zwp_keyboard_shortcuts_inhibitor_v1* inhibitor)
28+
: QtWayland::zwp_keyboard_shortcuts_inhibitor_v1(inhibitor) {}
29+
30+
~ShortcutsInhibitor() override;
31+
Q_DISABLE_COPY_MOVE(ShortcutsInhibitor);
32+
};
33+
34+
} // namespace qs::wayland::shortcuts_inhibit::impl

0 commit comments

Comments
 (0)