Skip to content

Commit ed036d5

Browse files
bbedwardoutfoxxed
authored andcommitted
wayland/shortcuts-inhibit: add shortcuts inhibitor
1 parent 1ddb355 commit ed036d5

File tree

9 files changed

+523
-0
lines changed

9 files changed

+523
-0
lines changed

changelog/next.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ set shell id.
1414
- Added support for creating Polkit agents.
1515
- Added support for creating wayland idle inhibitors.
1616
- Added support for wayland idle timeouts.
17+
- Added support for inhibiting wayland compositor shortcuts for focused windows.
1718
- Added the ability to override Quickshell.cacheDir with a custom path.
1819

1920
## Other Changes

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

src/wayland/module.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,6 @@ headers = [
77
"screencopy/view.hpp",
88
"idle_inhibit/inhibitor.hpp",
99
"idle_notify/monitor.hpp",
10+
"shortcuts_inhibit/inhibitor.hpp",
1011
]
1112
-----
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: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
#include "inhibitor.hpp"
2+
3+
#include <private/qwaylandwindow_p.h>
4+
#include <qguiapplication.h>
5+
#include <qlogging.h>
6+
#include <qloggingcategory.h>
7+
#include <qobject.h>
8+
#include <qtmetamacros.h>
9+
#include <qwindow.h>
10+
11+
#include "../../window/proxywindow.hpp"
12+
#include "../../window/windowinterface.hpp"
13+
#include "proto.hpp"
14+
15+
namespace qs::wayland::shortcuts_inhibit {
16+
using QtWaylandClient::QWaylandWindow;
17+
18+
ShortcutInhibitor::ShortcutInhibitor() {
19+
this->bBoundWindow.setBinding([this] {
20+
return this->bEnabled ? this->bWindowObject.value() : nullptr;
21+
});
22+
23+
this->bActive.setBinding([this]() {
24+
auto* inhibitor = this->bInhibitor.value();
25+
if (!inhibitor) return false;
26+
if (!inhibitor->bindableActive().value()) return false;
27+
return this->bWindow.value() == this->bFocusedWindow;
28+
});
29+
30+
QObject::connect(
31+
dynamic_cast<QGuiApplication*>(QGuiApplication::instance()),
32+
&QGuiApplication::focusWindowChanged,
33+
this,
34+
&ShortcutInhibitor::onFocusedWindowChanged
35+
);
36+
37+
this->onFocusedWindowChanged(QGuiApplication::focusWindow());
38+
}
39+
40+
ShortcutInhibitor::~ShortcutInhibitor() {
41+
if (!this->bInhibitor) return;
42+
43+
auto* manager = impl::ShortcutsInhibitManager::instance();
44+
if (!manager) return;
45+
46+
manager->unrefShortcutsInhibitor(this->bInhibitor);
47+
}
48+
49+
void ShortcutInhibitor::onBoundWindowChanged() {
50+
auto* window = this->bBoundWindow.value();
51+
auto* proxyWindow = qobject_cast<ProxyWindowBase*>(window);
52+
53+
if (!proxyWindow) {
54+
if (auto* iface = qobject_cast<WindowInterface*>(window)) {
55+
proxyWindow = iface->proxyWindow();
56+
}
57+
}
58+
59+
if (proxyWindow == this->proxyWindow) return;
60+
61+
if (this->proxyWindow) {
62+
QObject::disconnect(this->proxyWindow, nullptr, this, nullptr);
63+
this->proxyWindow = nullptr;
64+
}
65+
66+
if (this->mWaylandWindow) {
67+
QObject::disconnect(this->mWaylandWindow, nullptr, this, nullptr);
68+
this->mWaylandWindow = nullptr;
69+
this->onWaylandSurfaceDestroyed();
70+
}
71+
72+
if (proxyWindow) {
73+
this->proxyWindow = proxyWindow;
74+
75+
QObject::connect(proxyWindow, &QObject::destroyed, this, &ShortcutInhibitor::onWindowDestroyed);
76+
77+
QObject::connect(
78+
proxyWindow,
79+
&ProxyWindowBase::backerVisibilityChanged,
80+
this,
81+
&ShortcutInhibitor::onWindowVisibilityChanged
82+
);
83+
84+
this->onWindowVisibilityChanged();
85+
}
86+
}
87+
88+
void ShortcutInhibitor::onWindowDestroyed() {
89+
this->proxyWindow = nullptr;
90+
this->onWaylandSurfaceDestroyed();
91+
}
92+
93+
void ShortcutInhibitor::onWindowVisibilityChanged() {
94+
if (!this->proxyWindow->isVisibleDirect()) return;
95+
96+
auto* window = this->proxyWindow->backingWindow();
97+
if (!window->handle()) window->create();
98+
99+
auto* waylandWindow = dynamic_cast<QWaylandWindow*>(window->handle());
100+
if (!waylandWindow) {
101+
qCCritical(impl::logShortcutsInhibit()) << "Window handle is not a QWaylandWindow";
102+
return;
103+
}
104+
if (waylandWindow == this->mWaylandWindow) return;
105+
this->mWaylandWindow = waylandWindow;
106+
this->bWindow = window;
107+
108+
QObject::connect(
109+
waylandWindow,
110+
&QObject::destroyed,
111+
this,
112+
&ShortcutInhibitor::onWaylandWindowDestroyed
113+
);
114+
115+
QObject::connect(
116+
waylandWindow,
117+
&QWaylandWindow::surfaceCreated,
118+
this,
119+
&ShortcutInhibitor::onWaylandSurfaceCreated
120+
);
121+
122+
QObject::connect(
123+
waylandWindow,
124+
&QWaylandWindow::surfaceDestroyed,
125+
this,
126+
&ShortcutInhibitor::onWaylandSurfaceDestroyed
127+
);
128+
129+
if (waylandWindow->surface()) this->onWaylandSurfaceCreated();
130+
}
131+
132+
void ShortcutInhibitor::onWaylandWindowDestroyed() { this->mWaylandWindow = nullptr; }
133+
134+
void ShortcutInhibitor::onWaylandSurfaceCreated() {
135+
auto* manager = impl::ShortcutsInhibitManager::instance();
136+
137+
if (!manager) {
138+
qWarning() << "Cannot enable shortcuts inhibitor as keyboard-shortcuts-inhibit-unstable-v1 is "
139+
"not supported by "
140+
"the current compositor.";
141+
return;
142+
}
143+
144+
if (this->bInhibitor) {
145+
qFatal("ShortcutsInhibitor: inhibitor already exists when creating surface");
146+
}
147+
148+
this->bInhibitor = manager->createShortcutsInhibitor(this->mWaylandWindow);
149+
}
150+
151+
void ShortcutInhibitor::onWaylandSurfaceDestroyed() {
152+
if (!this->bInhibitor) return;
153+
154+
auto* manager = impl::ShortcutsInhibitManager::instance();
155+
if (!manager) return;
156+
157+
manager->unrefShortcutsInhibitor(this->bInhibitor);
158+
this->bInhibitor = nullptr;
159+
}
160+
161+
void ShortcutInhibitor::onInhibitorChanged() {
162+
auto* inhibitor = this->bInhibitor.value();
163+
if (inhibitor) {
164+
QObject::connect(
165+
inhibitor,
166+
&impl::ShortcutsInhibitor::activeChanged,
167+
this,
168+
&ShortcutInhibitor::onInhibitorActiveChanged
169+
);
170+
}
171+
}
172+
173+
void ShortcutInhibitor::onInhibitorActiveChanged() {
174+
auto* inhibitor = this->bInhibitor.value();
175+
if (inhibitor && !inhibitor->isActive()) {
176+
// Compositor has deactivated the inhibitor, making it invalid.
177+
// Set enabled to false so the user can enable it again to create a new inhibitor.
178+
this->bEnabled = false;
179+
emit this->cancelled();
180+
}
181+
}
182+
183+
void ShortcutInhibitor::onFocusedWindowChanged(QWindow* focusedWindow) {
184+
this->bFocusedWindow = focusedWindow;
185+
}
186+
187+
} // namespace qs::wayland::shortcuts_inhibit
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
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+
#include <qwindow.h>
9+
10+
#include "../../window/proxywindow.hpp"
11+
#include "proto.hpp"
12+
13+
namespace qs::wayland::shortcuts_inhibit {
14+
15+
///! Prevents compositor keyboard shortcuts from being triggered
16+
/// A shortcuts inhibitor prevents the compositor from processing its own keyboard shortcuts
17+
/// for the focused surface. This allows applications to receive key events for shortcuts
18+
/// that would normally be handled by the compositor.
19+
///
20+
/// The inhibitor only takes effect when the associated window is focused and the inhibitor
21+
/// is enabled. The compositor may choose to ignore inhibitor requests based on its policy.
22+
///
23+
/// > [!NOTE] Using a shortcuts inhibitor requires the compositor support the [keyboard-shortcuts-inhibit-unstable-v1] protocol.
24+
///
25+
/// [keyboard-shortcuts-inhibit-unstable-v1]: https://wayland.app/protocols/keyboard-shortcuts-inhibit-unstable-v1
26+
class ShortcutInhibitor: public QObject {
27+
Q_OBJECT;
28+
QML_ELEMENT;
29+
// clang-format off
30+
/// If the shortcuts inhibitor should be enabled. Defaults to false.
31+
Q_PROPERTY(bool enabled READ default WRITE default NOTIFY enabledChanged BINDABLE bindableEnabled);
32+
/// The window to associate the shortcuts inhibitor with.
33+
/// The inhibitor will only inhibit shortcuts pressed while this window has keyboard focus.
34+
///
35+
/// Must be set to a non null value to enable the inhibitor.
36+
Q_PROPERTY(QObject* window READ default WRITE default NOTIFY windowChanged BINDABLE bindableWindow);
37+
/// Whether the inhibitor is currently active. The inhibitor is only active if @@enabled is true,
38+
/// @@window has keyboard focus, and the compositor grants the inhibit request.
39+
///
40+
/// The compositor may deactivate the inhibitor at any time (for example, if the user requests
41+
/// normal shortcuts to be restored). When deactivated by the compositor, the inhibitor cannot be
42+
/// programmatically reactivated.
43+
Q_PROPERTY(bool active READ default NOTIFY activeChanged BINDABLE bindableActive);
44+
// clang-format on
45+
46+
public:
47+
ShortcutInhibitor();
48+
~ShortcutInhibitor() override;
49+
Q_DISABLE_COPY_MOVE(ShortcutInhibitor);
50+
51+
[[nodiscard]] QBindable<bool> bindableEnabled() { return &this->bEnabled; }
52+
[[nodiscard]] QBindable<QObject*> bindableWindow() { return &this->bWindowObject; }
53+
[[nodiscard]] QBindable<bool> bindableActive() const { return &this->bActive; }
54+
55+
signals:
56+
void enabledChanged();
57+
void windowChanged();
58+
void activeChanged();
59+
/// Sent if the compositor cancels the inhibitor while it is active.
60+
void cancelled();
61+
62+
private slots:
63+
void onWindowDestroyed();
64+
void onWindowVisibilityChanged();
65+
void onWaylandWindowDestroyed();
66+
void onWaylandSurfaceCreated();
67+
void onWaylandSurfaceDestroyed();
68+
void onInhibitorActiveChanged();
69+
70+
private:
71+
void onBoundWindowChanged();
72+
void onInhibitorChanged();
73+
void onFocusedWindowChanged(QWindow* focusedWindow);
74+
75+
ProxyWindowBase* proxyWindow = nullptr;
76+
QtWaylandClient::QWaylandWindow* mWaylandWindow = nullptr;
77+
78+
// clang-format off
79+
Q_OBJECT_BINDABLE_PROPERTY(ShortcutInhibitor, bool, bEnabled, &ShortcutInhibitor::enabledChanged);
80+
Q_OBJECT_BINDABLE_PROPERTY(ShortcutInhibitor, QObject*, bWindowObject, &ShortcutInhibitor::windowChanged);
81+
Q_OBJECT_BINDABLE_PROPERTY(ShortcutInhibitor, QObject*, bBoundWindow, &ShortcutInhibitor::onBoundWindowChanged);
82+
Q_OBJECT_BINDABLE_PROPERTY(ShortcutInhibitor, impl::ShortcutsInhibitor*, bInhibitor, &ShortcutInhibitor::onInhibitorChanged);
83+
Q_OBJECT_BINDABLE_PROPERTY(ShortcutInhibitor, QWindow*, bWindow);
84+
Q_OBJECT_BINDABLE_PROPERTY(ShortcutInhibitor, QWindow*, bFocusedWindow);
85+
Q_OBJECT_BINDABLE_PROPERTY(ShortcutInhibitor, bool, bActive, &ShortcutInhibitor::activeChanged);
86+
// clang-format on
87+
};
88+
89+
} // namespace qs::wayland::shortcuts_inhibit

0 commit comments

Comments
 (0)