Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog/next.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ set shell id.
- Added support for creating Polkit agents.
- Added support for creating wayland idle inhibitors.
- Added support for wayland idle timeouts.
- Added support for inhibiting wayland compositor shortcuts for focused windows.
- Added the ability to override Quickshell.cacheDir with a custom path.

## Other Changes
Expand Down
3 changes: 3 additions & 0 deletions src/wayland/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions src/wayland/module.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,6 @@ headers = [
"screencopy/view.hpp",
"idle_inhibit/inhibitor.hpp",
"idle_notify/monitor.hpp",
"shortcuts_inhibit/inhibitor.hpp",
]
-----
25 changes: 25 additions & 0 deletions src/wayland/shortcuts_inhibit/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -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)
187 changes: 187 additions & 0 deletions src/wayland/shortcuts_inhibit/inhibitor.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
#include "inhibitor.hpp"

#include <private/qwaylandwindow_p.h>
#include <qguiapplication.h>
#include <qlogging.h>
#include <qloggingcategory.h>
#include <qobject.h>
#include <qtmetamacros.h>
#include <qwindow.h>

#include "../../window/proxywindow.hpp"
#include "../../window/windowinterface.hpp"
#include "proto.hpp"

namespace qs::wayland::shortcuts_inhibit {
using QtWaylandClient::QWaylandWindow;

ShortcutInhibitor::ShortcutInhibitor() {
this->bBoundWindow.setBinding([this] {
return this->bEnabled ? this->bWindowObject.value() : nullptr;
});

this->bActive.setBinding([this]() {
auto* inhibitor = this->bInhibitor.value();
if (!inhibitor) return false;
if (!inhibitor->bindableActive().value()) return false;
return this->bWindow.value() == this->bFocusedWindow;
});

QObject::connect(
dynamic_cast<QGuiApplication*>(QGuiApplication::instance()),
&QGuiApplication::focusWindowChanged,
this,
&ShortcutInhibitor::onFocusedWindowChanged
);

this->onFocusedWindowChanged(QGuiApplication::focusWindow());
}

ShortcutInhibitor::~ShortcutInhibitor() {
if (!this->bInhibitor) return;

auto* manager = impl::ShortcutsInhibitManager::instance();
if (!manager) return;

manager->unrefShortcutsInhibitor(this->bInhibitor);
}

void ShortcutInhibitor::onBoundWindowChanged() {
auto* window = this->bBoundWindow.value();
auto* proxyWindow = qobject_cast<ProxyWindowBase*>(window);

if (!proxyWindow) {
if (auto* iface = qobject_cast<WindowInterface*>(window)) {
proxyWindow = iface->proxyWindow();
}
}

if (proxyWindow == this->proxyWindow) return;

if (this->proxyWindow) {
QObject::disconnect(this->proxyWindow, nullptr, this, nullptr);
this->proxyWindow = nullptr;
}

if (this->mWaylandWindow) {
QObject::disconnect(this->mWaylandWindow, nullptr, this, nullptr);
this->mWaylandWindow = nullptr;
this->onWaylandSurfaceDestroyed();
}

if (proxyWindow) {
this->proxyWindow = proxyWindow;

QObject::connect(proxyWindow, &QObject::destroyed, this, &ShortcutInhibitor::onWindowDestroyed);

QObject::connect(
proxyWindow,
&ProxyWindowBase::backerVisibilityChanged,
this,
&ShortcutInhibitor::onWindowVisibilityChanged
);

this->onWindowVisibilityChanged();
}
}

void ShortcutInhibitor::onWindowDestroyed() {
this->proxyWindow = nullptr;
this->onWaylandSurfaceDestroyed();
}

void ShortcutInhibitor::onWindowVisibilityChanged() {
if (!this->proxyWindow->isVisibleDirect()) return;

auto* window = this->proxyWindow->backingWindow();
if (!window->handle()) window->create();

auto* waylandWindow = dynamic_cast<QWaylandWindow*>(window->handle());
if (!waylandWindow) {
qCCritical(impl::logShortcutsInhibit()) << "Window handle is not a QWaylandWindow";
return;
}
if (waylandWindow == this->mWaylandWindow) return;
this->mWaylandWindow = waylandWindow;
this->bWindow = window;

QObject::connect(
waylandWindow,
&QObject::destroyed,
this,
&ShortcutInhibitor::onWaylandWindowDestroyed
);

QObject::connect(
waylandWindow,
&QWaylandWindow::surfaceCreated,
this,
&ShortcutInhibitor::onWaylandSurfaceCreated
);

QObject::connect(
waylandWindow,
&QWaylandWindow::surfaceDestroyed,
this,
&ShortcutInhibitor::onWaylandSurfaceDestroyed
);

if (waylandWindow->surface()) this->onWaylandSurfaceCreated();
}

void ShortcutInhibitor::onWaylandWindowDestroyed() { this->mWaylandWindow = nullptr; }

void ShortcutInhibitor::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;
}

if (this->bInhibitor) {
qFatal("ShortcutsInhibitor: inhibitor already exists when creating surface");
}

this->bInhibitor = manager->createShortcutsInhibitor(this->mWaylandWindow);
}

void ShortcutInhibitor::onWaylandSurfaceDestroyed() {
if (!this->bInhibitor) return;

auto* manager = impl::ShortcutsInhibitManager::instance();
if (!manager) return;

manager->unrefShortcutsInhibitor(this->bInhibitor);
this->bInhibitor = nullptr;
}

void ShortcutInhibitor::onInhibitorChanged() {
auto* inhibitor = this->bInhibitor.value();
if (inhibitor) {
QObject::connect(
inhibitor,
&impl::ShortcutsInhibitor::activeChanged,
this,
&ShortcutInhibitor::onInhibitorActiveChanged
);
}
}

void ShortcutInhibitor::onInhibitorActiveChanged() {
auto* inhibitor = this->bInhibitor.value();
if (inhibitor && !inhibitor->isActive()) {
// Compositor has deactivated the inhibitor, making it invalid.
// Set enabled to false so the user can enable it again to create a new inhibitor.
this->bEnabled = false;
emit this->cancelled();
}
}

void ShortcutInhibitor::onFocusedWindowChanged(QWindow* focusedWindow) {
this->bFocusedWindow = focusedWindow;
}

} // namespace qs::wayland::shortcuts_inhibit
89 changes: 89 additions & 0 deletions src/wayland/shortcuts_inhibit/inhibitor.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
#pragma once

#include <qobject.h>
#include <qproperty.h>
#include <qqmlintegration.h>
#include <qtclasshelpermacros.h>
#include <qtmetamacros.h>
#include <qwindow.h>

#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 ShortcutInhibitor: 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.
/// The inhibitor will only inhibit shortcuts pressed while this window has keyboard focus.
///
/// Must be set to a non null value to enable the inhibitor.
Q_PROPERTY(QObject* window READ default WRITE default NOTIFY windowChanged BINDABLE bindableWindow);
/// Whether the inhibitor is currently active. The inhibitor is only active if @@enabled is true,
/// @@window has keyboard focus, and the compositor grants the inhibit request.
///
/// The compositor may deactivate the inhibitor at any time (for example, if the user requests
/// normal shortcuts to be restored). When deactivated by the compositor, the inhibitor cannot be
/// programmatically reactivated.
Q_PROPERTY(bool active READ default NOTIFY activeChanged BINDABLE bindableActive);
// clang-format on

public:
ShortcutInhibitor();
~ShortcutInhibitor() override;
Q_DISABLE_COPY_MOVE(ShortcutInhibitor);

[[nodiscard]] QBindable<bool> bindableEnabled() { return &this->bEnabled; }
[[nodiscard]] QBindable<QObject*> bindableWindow() { return &this->bWindowObject; }
[[nodiscard]] QBindable<bool> bindableActive() const { return &this->bActive; }

signals:
void enabledChanged();
void windowChanged();
void activeChanged();
/// Sent if the compositor cancels the inhibitor while it is active.
void cancelled();

private slots:
void onWindowDestroyed();
void onWindowVisibilityChanged();
void onWaylandWindowDestroyed();
void onWaylandSurfaceCreated();
void onWaylandSurfaceDestroyed();
void onInhibitorActiveChanged();

private:
void onBoundWindowChanged();
void onInhibitorChanged();
void onFocusedWindowChanged(QWindow* focusedWindow);

ProxyWindowBase* proxyWindow = nullptr;
QtWaylandClient::QWaylandWindow* mWaylandWindow = nullptr;

// clang-format off
Q_OBJECT_BINDABLE_PROPERTY(ShortcutInhibitor, bool, bEnabled, &ShortcutInhibitor::enabledChanged);
Q_OBJECT_BINDABLE_PROPERTY(ShortcutInhibitor, QObject*, bWindowObject, &ShortcutInhibitor::windowChanged);
Q_OBJECT_BINDABLE_PROPERTY(ShortcutInhibitor, QObject*, bBoundWindow, &ShortcutInhibitor::onBoundWindowChanged);
Q_OBJECT_BINDABLE_PROPERTY(ShortcutInhibitor, impl::ShortcutsInhibitor*, bInhibitor, &ShortcutInhibitor::onInhibitorChanged);
Q_OBJECT_BINDABLE_PROPERTY(ShortcutInhibitor, QWindow*, bWindow);
Q_OBJECT_BINDABLE_PROPERTY(ShortcutInhibitor, QWindow*, bFocusedWindow);
Q_OBJECT_BINDABLE_PROPERTY(ShortcutInhibitor, bool, bActive, &ShortcutInhibitor::activeChanged);
// clang-format on
};

} // namespace qs::wayland::shortcuts_inhibit
Loading
Loading