Skip to content
Closed
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 src/common/treelandlogging.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ Q_LOGGING_CATEGORY(lcTlInput, "treeland.input")
Q_LOGGING_CATEGORY(lcTlInputManager, "treeland.input.manager")
Q_LOGGING_CATEGORY(lcTlGestures, "treeland.input.gestures")
Q_LOGGING_CATEGORY(lcTlKeyboardNotify, "treeland.input.keyboard.state.notify")
Q_LOGGING_CATEGORY(lcTlKeyboardShortcutsInhibit, "treeland.input.keyboard.shortcuts.inhibit")

// Seat management
Q_LOGGING_CATEGORY(lcTlSeat, "treeland.seat")
Expand Down
1 change: 1 addition & 0 deletions src/common/treelandlogging.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ Q_DECLARE_LOGGING_CATEGORY(lcTlInput)
Q_DECLARE_LOGGING_CATEGORY(lcTlInputManager)
Q_DECLARE_LOGGING_CATEGORY(lcTlGestures)
Q_DECLARE_LOGGING_CATEGORY(lcTlKeyboardNotify)
Q_DECLARE_LOGGING_CATEGORY(lcTlKeyboardShortcutsInhibit)

// Seat management
Q_DECLARE_LOGGING_CATEGORY(lcTlSeat)
Expand Down
1 change: 1 addition & 0 deletions src/modules/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -44,3 +44,4 @@ add_subdirectory(wallpaper)
add_subdirectory(activation)
add_subdirectory(input-manager)
add_subdirectory(keyboard-state-notify)
add_subdirectory(keyboard-shortcuts-inhibit)
9 changes: 9 additions & 0 deletions src/modules/keyboard-shortcuts-inhibit/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
impl_treeland(
NAME
module_keyboard_shortcuts_inhibit
SOURCE
keyboardshortcutsinhibitmanager.h
keyboardshortcutsinhibitmanager.cpp
LINK
Qt6::Core
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,222 @@
// Copyright (C) 2026 UnionTech Software Technology Co., Ltd.
// SPDX-License-Identifier: Apache-2.0 OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only

#include "keyboardshortcutsinhibitmanager.h"
#include "seat/helper.h"

Check warning on line 5 in src/modules/keyboard-shortcuts-inhibit/keyboardshortcutsinhibitmanager.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: "seat/helper.h" not found.
#include "seatsmanager.h"

Check warning on line 6 in src/modules/keyboard-shortcuts-inhibit/keyboardshortcutsinhibitmanager.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: "seatsmanager.h" not found.
#include "common/treelandlogging.h"

Check warning on line 7 in src/modules/keyboard-shortcuts-inhibit/keyboardshortcutsinhibitmanager.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: "common/treelandlogging.h" not found.

#include <wsurface.h>

Check warning on line 9 in src/modules/keyboard-shortcuts-inhibit/keyboardshortcutsinhibitmanager.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: <wsurface.h> not found. Please note: Cppcheck does not need standard library headers to get proper results.
#include <wscoplistener.h>

Check warning on line 10 in src/modules/keyboard-shortcuts-inhibit/keyboardshortcutsinhibitmanager.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: <wscoplistener.h> not found. Please note: Cppcheck does not need standard library headers to get proper results.

extern "C" {
#include <wlr/types/wlr_keyboard_shortcuts_inhibit_v1.h>

Check warning on line 13 in src/modules/keyboard-shortcuts-inhibit/keyboardshortcutsinhibitmanager.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: <wlr/types/wlr_keyboard_shortcuts_inhibit_v1.h> not found. Please note: Cppcheck does not need standard library headers to get proper results.
}

#include <QHash>

Check warning on line 16 in src/modules/keyboard-shortcuts-inhibit/keyboardshortcutsinhibitmanager.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: <QHash> not found. Please note: Cppcheck does not need standard library headers to get proper results.

#include <algorithm>

Check warning on line 18 in src/modules/keyboard-shortcuts-inhibit/keyboardshortcutsinhibitmanager.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: <algorithm> not found. Please note: Cppcheck does not need standard library headers to get proper results.

struct InhibitorEntry {
wlr_keyboard_shortcuts_inhibitor_v1 *inhibitor = nullptr;
WScopedListener destroyListener;
};

class KeyboardShortcutsInhibitManagerV1Private
{
public:
explicit KeyboardShortcutsInhibitManagerV1Private(KeyboardShortcutsInhibitManagerV1 *q)
: q(q) {}

KeyboardShortcutsInhibitManagerV1 *q = nullptr;
WScopedListener newInhibitorListener;
std::vector<InhibitorEntry> inhibitors;
QHash<WSeat *, QMetaObject::Connection> seatFocusConnections;
bool seatConnectionsSetup = false;

wlr_keyboard_shortcuts_inhibit_manager_v1 *handle() const
{
return reinterpret_cast<wlr_keyboard_shortcuts_inhibit_manager_v1 *>(q->handle());
}

void setupSeatConnections();
void onNewInhibitor(wlr_keyboard_shortcuts_inhibitor_v1 *inhibitor);
void onInhibitorDestroy(wlr_keyboard_shortcuts_inhibitor_v1 *inhibitor);
void onSeatFocusChanged(WSeat *seat);
void handleSeatAdded(WSeat *seat);
void handleSeatRemoved(WSeat *seat);
};

void KeyboardShortcutsInhibitManagerV1Private::setupSeatConnections()
{
if (seatConnectionsSetup)
return;
seatConnectionsSetup = true;

auto *helper = Helper::instance();
auto *seatManager = helper->seatManager();
const auto seats = seatManager->seats();
for (auto *seat : seats) {
handleSeatAdded(seat);
}

QObject::connect(seatManager, &SeatsManager::seatAdded, q, [this](WSeat *seat) {
handleSeatAdded(seat);
});
QObject::connect(seatManager, &SeatsManager::seatRemoved, q, [this](WSeat *seat) {
handleSeatRemoved(seat);
});
}

void KeyboardShortcutsInhibitManagerV1Private::handleSeatAdded(WSeat *seat)
{
if (seatFocusConnections.contains(seat))
return;

auto conn = QObject::connect(seat, &WSeat::keyboardFocusSurfaceChanged, q, [this, seat]() {
onSeatFocusChanged(seat);
});
seatFocusConnections.insert(seat, conn);

// Activate inhibitors for surfaces that already have focus on this seat.
onSeatFocusChanged(seat);
}

void KeyboardShortcutsInhibitManagerV1Private::handleSeatRemoved(WSeat *seat)

Check warning on line 85 in src/modules/keyboard-shortcuts-inhibit/keyboardshortcutsinhibitmanager.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Parameter 'seat' can be declared as pointer to const
{
auto it = seatFocusConnections.find(seat);
if (it != seatFocusConnections.end()) {
QObject::disconnect(it.value());
seatFocusConnections.erase(it);
}
}

void KeyboardShortcutsInhibitManagerV1Private::onNewInhibitor(wlr_keyboard_shortcuts_inhibitor_v1 *inhibitor)
{
qCInfo(lcTlKeyboardShortcutsInhibit) << "New inhibitor created for surface"
<< inhibitor->surface << "seat" << inhibitor->seat;

InhibitorEntry entry;
entry.inhibitor = inhibitor;
entry.destroyListener.init(&inhibitor->events.destroy, q, [this, inhibitor](void *) {
onInhibitorDestroy(inhibitor);
});
inhibitors.push_back(std::move(entry));

// If the surface already has keyboard focus on this seat, activate the
// inhibitor immediately (sends 'active' to the client).
auto *wSeat = WSeat::fromHandle(inhibitor->seat);
if (wSeat) {
auto *focusSurface = wSeat->keyboardFocusSurface();
if (focusSurface && focusSurface->handle() == inhibitor->surface) {
wlr_keyboard_shortcuts_inhibitor_v1_activate(inhibitor);
}
}
}

void KeyboardShortcutsInhibitManagerV1Private::onInhibitorDestroy(wlr_keyboard_shortcuts_inhibitor_v1 *inhibitor)
{
qCInfo(lcTlKeyboardShortcutsInhibit) << "Inhibitor destroyed for surface"
<< inhibitor->surface << "seat" << inhibitor->seat;

inhibitors.erase(std::remove_if(inhibitors.begin(), inhibitors.end(),
[inhibitor](const InhibitorEntry &e) {
return e.inhibitor == inhibitor;
}),
inhibitors.end());
}

void KeyboardShortcutsInhibitManagerV1Private::onSeatFocusChanged(WSeat *seat)
{
wlr_seat *wlrSeat = seat->handle();
auto *focusSurface = seat->keyboardFocusSurface();
wlr_surface *wlrSurface = focusSurface ? focusSurface->handle() : nullptr;

for (auto &entry : inhibitors) {
if (entry.inhibitor->seat != wlrSeat)
continue;

if (wlrSurface && entry.inhibitor->surface == wlrSurface) {
// Keyboard focus entered this surface: activate the inhibitor.
// wlr_keyboard_shortcuts_inhibitor_v1_activate() is idempotent
// (only sends 'active' when not already active), so this safely
// re-activates on focus regain.
wlr_keyboard_shortcuts_inhibitor_v1_activate(entry.inhibitor);
} else if (entry.inhibitor->active) {
// Keyboard focus left this surface: restore the compositor's own
// shortcuts. Per the protocol, no 'inactive' event is sent on
// focus leave, so reset 'active' directly (bypassing deactivate())
// to let activate() re-fire on the next focus enter.
entry.inhibitor->active = false;
}
}
}

KeyboardShortcutsInhibitManagerV1::KeyboardShortcutsInhibitManagerV1(QObject *parent)
: QObject(parent)
, d(new KeyboardShortcutsInhibitManagerV1Private(this))
{
}

KeyboardShortcutsInhibitManagerV1::~KeyboardShortcutsInhibitManagerV1() = default;

QByteArrayView KeyboardShortcutsInhibitManagerV1::interfaceName() const
{
return "zwp_keyboard_shortcuts_inhibit_manager_v1";
}

void KeyboardShortcutsInhibitManagerV1::create(WServer *server)
{
m_handle = wlr_keyboard_shortcuts_inhibit_v1_create(server->handle());
if (!m_handle) {
qCWarning(lcTlKeyboardShortcutsInhibit) << "Failed to create keyboard shortcuts inhibit manager";
return;
}

d->newInhibitorListener.init(&d->handle()->events.new_inhibitor, this, [this](void *data) {
auto *inhibitor = static_cast<wlr_keyboard_shortcuts_inhibitor_v1 *>(data);
d->onNewInhibitor(inhibitor);
});

d->setupSeatConnections();
}

void KeyboardShortcutsInhibitManagerV1::destroy([[maybe_unused]] WServer *server)
{
d->newInhibitorListener.disconnect();
for (auto &entry : d->inhibitors)
entry.destroyListener.disconnect();
d->inhibitors.clear();

for (auto &conn : d->seatFocusConnections)

Check warning on line 191 in src/modules/keyboard-shortcuts-inhibit/keyboardshortcutsinhibitmanager.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Variable 'conn' can be declared as reference to const
QObject::disconnect(conn);
d->seatFocusConnections.clear();
d->seatConnectionsSetup = false;

// The wlr manager is reclaimed by display.reset() in WServer::stop();
// null m_handle so handle()/global() return null instead of a dangling
// pointer.
m_handle = nullptr;
}

wl_global *KeyboardShortcutsInhibitManagerV1::global() const
{
return d->handle() ? d->handle()->global : nullptr;
}

bool KeyboardShortcutsInhibitManagerV1::isInhibited(wlr_seat *seat, wlr_surface *surface) const
{
for (const auto &entry : d->inhibitors) {
if (entry.inhibitor->active && entry.inhibitor->seat == seat && entry.inhibitor->surface == surface)
return true;
}
return false;
}

void KeyboardShortcutsInhibitManagerV1::deactivateActiveInhibitor(wlr_seat *seat)
{
for (auto &entry : d->inhibitors) {
if (entry.inhibitor->seat == seat && entry.inhibitor->active)
wlr_keyboard_shortcuts_inhibitor_v1_deactivate(entry.inhibitor);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// Copyright (C) 2026 UnionTech Software Technology Co., Ltd.
// SPDX-License-Identifier: Apache-2.0 OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only

#pragma once

#include <wseat.h>
#include <wserver.h>

#include <wayland-server-core.h>

#include <QObject>

#include <memory>

struct wlr_keyboard_shortcuts_inhibitor_v1;
struct wlr_keyboard_shortcuts_inhibit_manager_v1;
struct wlr_surface;
struct wlr_seat;

WAYLIB_SERVER_USE_NAMESPACE

class KeyboardShortcutsInhibitManagerV1Private;

class KeyboardShortcutsInhibitManagerV1 : public QObject, public WServerInterface
{
Q_OBJECT
public:
explicit KeyboardShortcutsInhibitManagerV1(QObject *parent = nullptr);
~KeyboardShortcutsInhibitManagerV1() override;

QByteArrayView interfaceName() const override;

// Returns true if keyboard shortcuts are currently inhibited for the
// given surface+seat pair (i.e. an active inhibitor exists).
bool isInhibited(wlr_seat *seat, wlr_surface *surface) const;

// Force-deactivate the active inhibitor for the given seat, sending the
// 'inactive' event to the client. Used by the compositor's built-in
// non-inhibitable escape-hatch shortcuts (e.g. Ctrl+Alt+Fn VT switch).
void deactivateActiveInhibitor(wlr_seat *seat);

protected: // WServerInterface
void create(WServer *server) override;
void destroy(WServer *server) override;
wl_global *global() const override;

private:
std::unique_ptr<KeyboardShortcutsInhibitManagerV1Private> d;
};
11 changes: 11 additions & 0 deletions src/seat/helper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
#include "modules/ddm/ddminterfacev1.h"
#include "modules/input-manager/inputmanagerinterfacev1.h"
#include "modules/keyboard-state-notify/keyboardstatenotifymanagerinterfacev1.h"
#include "modules/keyboard-shortcuts-inhibit/keyboardshortcutsinhibitmanager.h"
#include "modules/output-manager/outputmanagement.h"
#include "modules/personalization/personalizationmanagerinterfacev1.h"
#include "modules/resource/treelandremotesource.h"
Expand Down Expand Up @@ -2277,6 +2278,7 @@ void Helper::init(Treeland::Treeland *treeland)
&InputManager::onKeyboardSettingsCreated);

m_keyboardStateNotifyManagerInterfaceV1 = m_server->attach<TreelandKeyboardStateNotifyManagerInterfaceV1>();
m_keyboardShortcutsInhibitManagerV1 = m_server->attach<KeyboardShortcutsInhibitManagerV1>();

#if TREELANDCONFIG_DCONFIG_FILE_VERSION_MINOR > 0
if (m_globalConfig->isInitializeSucceeded()) {
Expand Down Expand Up @@ -2457,6 +2459,8 @@ bool Helper::beforeDisposeEvent(WSeat *seat, QWindow *targetWindow, QInputEvent

qCWarning(lcTlCore) << "Ctrl+Alt+Fn VT shortcut requested" << vtnr;
wlr_session_change_vt(m_backend->session(), vtnr);
if (m_keyboardShortcutsInhibitManagerV1)
m_keyboardShortcutsInhibitManagerV1->deactivateActiveInhibitor(seat->handle());
return true;
}
}
Expand Down Expand Up @@ -2599,6 +2603,13 @@ bool Helper::beforeDisposeEvent(WSeat *seat, QWindow *targetWindow, QInputEvent
}
}

// Suppress compositor shortcuts when a keyboard shortcuts inhibitor is active
if (m_keyboardShortcutsInhibitManagerV1) {
auto *focusSurface = seat->keyboardFocusSurface();
if (focusSurface && m_keyboardShortcutsInhibitManagerV1->isInhibited(seat->handle(), focusSurface->handle()))
return false;
}

// Capture mode: intercept key events before dispatchKeyEvent
if (m_shortcutManager->isCaptureActive() && m_shortcutManager->tryHandleCaptureEvent(seat, event))
return true;
Expand Down
2 changes: 2 additions & 0 deletions src/seat/helper.h
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ class WallpaperColorInterfaceV1;
class WindowManagementInterfaceV1;
class WindowPickerInterface;
class TreelandKeyboardStateNotifyManagerInterfaceV1;
class KeyboardShortcutsInhibitManagerV1;
class WallpaperManager;
class WallpaperItem;
class TreelandInputManagerInterfaceV1;
Expand Down Expand Up @@ -475,6 +476,7 @@ private Q_SLOTS:
TreelandWallpaperManagerInterfaceV1 *m_wallpaperManagerInterfaceV1 = nullptr;
TreelandWallpaperNotifierInterfaceV1 *m_wallpaperNotifierInterfaceV1 = nullptr;
TreelandKeyboardStateNotifyManagerInterfaceV1 *m_keyboardStateNotifyManagerInterfaceV1 = nullptr;
KeyboardShortcutsInhibitManagerV1 *m_keyboardShortcutsInhibitManagerV1 = nullptr;
#ifdef EXT_SESSION_LOCK_V1
WSessionLockManager *m_sessionLockManager = nullptr;
QTimer *m_lockScreenGraceTimer = nullptr;
Expand Down
Loading