Skip to content
Open
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
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
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)
180 changes: 180 additions & 0 deletions src/wayland/shortcuts_inhibit/inhibitor.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
#include "inhibitor.hpp"

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

#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() {
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; }

void ShortcutsInhibitor::setWindow(QObject* window) {
if (window == this->bWindowObject) return;

auto* proxyWindow = qobject_cast<ProxyWindowBase*>(window);

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

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<ProxyWindowBase*>(window);

if (proxyWindow == nullptr) {
if (auto* iface = qobject_cast<WindowInterface*>(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<QWaylandWindow*>(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;
}

if (this->inhibitor) {
QObject::disconnect(this->inhibitor, nullptr, this, nullptr);
manager->unrefShortcutsInhibitor(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() {
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
81 changes: 81 additions & 0 deletions src/wayland/shortcuts_inhibit/inhibitor.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
#pragma once

#include <qobject.h>
#include <qproperty.h>
#include <qqmlintegration.h>
#include <qtclasshelpermacros.h>
#include <qtmetamacros.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 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);
/// 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.
Q_PROPERTY(bool active READ isActive NOTIFY activeChanged);
// clang-format on

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

[[nodiscard]] QObject* window() const;
void setWindow(QObject* window);

[[nodiscard]] QBindable<bool> bindableEnabled() { return &this->bEnabled; }
[[nodiscard]] bool isActive() const;

signals:
void enabledChanged();
void windowChanged();
void activeChanged();

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
Loading