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
7 changes: 6 additions & 1 deletion src/modules/shortcut/shortcutcontroller.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -218,14 +218,19 @@
}
}

bool ShortcutController::matchesShortcut(const QKeyEvent *event) const
{
return m_keyMap.contains(normalizeKeyCombination(event->keyCombination()).toCombined());
}

bool ShortcutController::dispatchKeyEvent(const QKeyEvent *kevent)

Check warning on line 226 in src/modules/shortcut/shortcutcontroller.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

The function 'dispatchKeyEvent' is never used.
{
auto combined = normalizeKeyCombination(kevent->keyCombination()).toCombined();
ShortcutController::KeyFlags keyFlags = (kevent->isAutoRepeat() ? ShortcutController::Repeat : ShortcutController::None)
| (kevent->type() == QEvent::KeyPress ? ShortcutController::KeyPress : ShortcutController::None)
| (kevent->type() == QEvent::KeyRelease ? ShortcutController::KeyRelease : ShortcutController::None);

bool matched = m_keyMap.contains(combined);
bool matched = matchesShortcut(kevent);
if (matched) {
qCInfo(lcTlShortcut).noquote()
<< "shortcut match:" << QKeySequence(kevent->keyCombination()).toString(QKeySequence::PortableText)
Expand Down
2 changes: 2 additions & 0 deletions src/modules/shortcut/shortcutcontroller.h
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,8 @@ class ShortcutController : public QObject

void clear();
bool dispatchKeyEvent(const QKeyEvent *event);

bool matchesShortcut(const QKeyEvent *event) const;
static QKeyCombination normalizeKeyCombination(QKeyCombination combination);
static bool isValidShortcutCombination(QKeyCombination combination);
Qt::KeyboardModifiers modifierForAction(ShortcutAction action) const;
Expand Down
59 changes: 54 additions & 5 deletions src/seat/helper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2787,6 +2787,28 @@ bool Helper::beforeDisposeEvent(WSeat *seat, QWindow *targetWindow, QInputEvent
}
}

if (seat == m_primarySeat && event->type() == QEvent::KeyRelease) {
auto *kevent = static_cast<QKeyEvent *>(event);
const uint32_t evdevKeycode = kevent->nativeVirtualKey();
if (!kevent->isAutoRepeat() && seat->isKeyFiltered(evdevKeycode)) {
seat->removeFilteredKey(evdevKeycode);

// Give a release-bound shortcut, or the standalone Win/Meta action
// (only when it was not part of a combo), a chance to fire before
// the mirrored release is consumed.
const bool isMeta = kevent->key() == Qt::Key_Meta
|| kevent->key() == Qt::Key_Super_L || kevent->key() == Qt::Key_Super_R;
const bool canDispatch = !m_captureSelector
&& m_currentMode != CurrentMode::LockScreen;
auto *seatContainer = m_rootSurfaceContainer->getSeatContainer(seat);
if (canDispatch
&& (!isMeta || (seatContainer && seatContainer->metaKeyPressed()))) {
m_shortcutManager->controller()->dispatchKeyEvent(kevent);
}
return true;
}
Comment on lines +2793 to +2809
}

// Suppress compositor shortcuts when a keyboard shortcuts inhibitor is active
if (m_currentMode == CurrentMode::Normal) {
auto *focusSurface = seat->keyboardFocusSurface();
Expand All @@ -2805,19 +2827,46 @@ bool Helper::beforeDisposeEvent(WSeat *seat, QWindow *targetWindow, QInputEvent
auto kevent = static_cast<QKeyEvent *>(event);
auto *seatContainer = m_rootSurfaceContainer->getSeatContainer(seat);

auto *controller = m_shortcutManager->controller();
const uint32_t evdevKeycode = kevent->nativeVirtualKey();
const bool isAutoRepeat = kevent->isAutoRepeat();

// Meta: consume press as modifier; suppress release when used in combo
if (kevent->key() == Qt::Key_Meta || kevent->key() == Qt::Key_Super_L || kevent->key() == Qt::Key_Super_R) {
// Synthetic auto-repeat must not touch the filtered set; it is
// swallowed like any other modifier event.
if (isAutoRepeat)
return true;

if (kevent->type() == QEvent::KeyPress) {
// Record the consumed modifier so that a focus switch while it
// is still held does not advertise it in wl_keyboard.enter.
seat->addFilteredKey(evdevKeycode);
return true;
}
if (kevent->type() == QEvent::KeyRelease && seatContainer && !seatContainer->metaKeyPressed()) {
return false;
}
}

if (m_shortcutManager->controller()->dispatchKeyEvent(kevent)) {
// KeyRelease. Dispatch the standalone Win/Meta action (e.g. the
// launcher) while no other key was pressed in between. The press
// was consumed and kept out of enter, so the release is swallowed
// as well to avoid an orphan release on the client.
if (seatContainer && seatContainer->metaKeyPressed())
controller->dispatchKeyEvent(kevent);
seat->removeFilteredKey(evdevKeycode);
return true;
}

// Record shortcut-consumed keys *before* dispatching: handling a shortcut
// can synchronously switch keyboard focus and emit wl_keyboard.enter
// (e.g. Alt+Tab), and the key must already be excluded from that enter's
// key set. The matching release is mirrored earlier, before the
// inhibitor/capture early returns, so modifier release order cannot
// strand it in the filtered set.
if (!isAutoRepeat && kevent->type() == QEvent::KeyPress
&& controller->matchesShortcut(kevent))
seat->addFilteredKey(evdevKeycode);

if (controller->dispatchKeyEvent(kevent))
return true;
}

return false;
Expand Down
4 changes: 2 additions & 2 deletions tests/protocols/INDEX.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ Wayland 线上请求与事件;本文档规定发出请求后,测试必须观
| [remote-subsurface-unstable-v1](treeland-remote-subsurface-unstable-v1/README.md) | E / P | token 导出、真实 Remote `WSubsurface` 的父子关系、位置与层级;`parent_rejected` / `invalid_sibling` | 跨 client、循环 parent、映射生命周期与 sibling 间排序 |
| [prelaunch-splash-v2](treeland-prelaunch-splash-v2/README.md) | I / E | splash 请求/关闭信号;生产 splash wrapper 创建、加入 workspace 与销毁 |
| [screensaver-v1](treeland-screensaver-v1/README.md) | E / P | 真实 ext-idle 抑制生命周期 |
| [shortcut-manager-v2](treeland-shortcut-manager-v2/README.md) | E / P | 聚焦窗口捕获与快捷键激活 |
| [shortcut-manager-v2](treeland-shortcut-manager-v2/README.md) | E / P | 聚焦窗口捕获与快捷键激活;被消费快捷键的 enter 过滤与 release 吞掉 |
| [virtual-output-manager-v1](treeland-virtual-output-manager-v1/README.md) | P / E | 虚拟输出资源校验;两个既有输出的镜像/恢复 |
| [wallpaper-color-v1](treeland-wallpaper-color-v1/README.md) | I | 订阅、去重与颜色通知 |
| [wallpaper-manager-unstable-v1](treeland-wallpaper-manager-unstable-v1/README.md) | I / P | 真实输出上的壁纸资源生命周期 |
Expand Down Expand Up @@ -131,7 +131,7 @@ request stub 算作 request 覆盖;生成的 client-protocol 文件本身不
| remote-subsurface-unstable-v1 | 8 / 8 | token;真实 Remote `WSubsurface` 的父子关系、位置、Below 层级;`parent_rejected` / `invalid_sibling` | 跨 client、循环 parent、映射生命周期、sibling 间排序 |
| prelaunch-splash-v2 | 3 / 3 | 创建、关闭;真实 splash wrapper 加入/离开 workspace | splash QML 最终可见性、纹理和像素 |
| screensaver-v1 | 2 / 3 | 真实 ext-idle 被 inhibit/uninhibit 改变 | 显式 `destroy` request、实际锁屏 UI |
| shortcut-manager-v2 | 6 / 9 | `commit_success`、`captured`、`activated`;真实 virtual keyboard 输入链 | swipe、hold、`unbind`、`commit_failure` 的业务分支;物理键盘 |
| shortcut-manager-v2 | 6 / 9 | `commit_success`、`captured`、`activated`;真实 virtual keyboard 输入链;被消费快捷键的 enter 过滤与 release 吞掉 | swipe、hold、`unbind`、`commit_failure` 的业务分支;物理键盘 |
| virtual-output-manager-v1 | 5 / 5 | `outputs/error/virtual_output_list`;生产 copy output 创建与恢复 | 热插拔 successor、跨进程持久化、物理显示器内容一致性 |
| wallpaper-color-v1 | 3 / 3 | `output_color` 订阅与去重 | 真实壁纸分析来源、壁纸应用后的 output 色彩变化 |
| wallpaper-manager-unstable-v1 | 4 / 5 | `failed/changed`;`set_image_source` 与 wallpaper shell/output 关联 | `set_video_source`、实际映射/QML 接入、媒体解码失败、最终 output 像素 |
Expand Down
46 changes: 36 additions & 10 deletions tests/protocols/treeland-shortcut-manager-desktop-v2/setup.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,11 @@
#include "workspace/workspace.h"

#include <wbackend.h>
#include <wseat.h>

Check warning on line 12 in tests/protocols/treeland-shortcut-manager-desktop-v2/setup.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

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

namespace {
SurfaceWrapper *g_wrapper = nullptr;
SurfaceWrapper *g_primary = nullptr;
SurfaceWrapper *g_secondary = nullptr;
shortcut_desktop_state g_state {};

WSeat *primarySeat(Helper *helper)
Expand All @@ -21,10 +22,20 @@
return seats.isEmpty() ? nullptr : seats.constFirst();
}

bool hasKeyboardFocus(Helper *helper)
bool hasKeyboardFocus(Helper *helper, SurfaceWrapper *wrapper)
{
auto *seat = primarySeat(helper);
return seat && g_wrapper && seat->keyboardFocusSurface() == g_wrapper->surface();
return seat && wrapper && seat->keyboardFocusSurface() == wrapper->surface();
}

void refreshWrapperState(Helper *helper)
{
g_state.wrapper_created = g_primary ? 1 : 0;
g_state.wrapper_in_workspace =
(g_primary && helper->workspace()->surfaces().contains(g_primary)) ? 1 : 0;
g_state.secondary_created = g_secondary ? 1 : 0;
g_state.secondary_in_workspace =
(g_secondary && helper->workspace()->surfaces().contains(g_secondary)) ? 1 : 0;
}
}

Expand All @@ -37,21 +48,36 @@
[helper](SurfaceWrapper *wrapper) {
if (wrapper->type() != SurfaceWrapper::Type::XdgToplevel)
return;
g_wrapper = wrapper;
g_state.wrapper_created = 1;
g_state.wrapper_in_workspace = helper->workspace()->surfaces().contains(wrapper) ? 1 : 0;
if (!g_primary)
g_primary = wrapper;
else if (!g_secondary)
g_secondary = wrapper;
refreshWrapperState(helper);
});
}

extern "C" void shortcut_desktop_focus_window(void *data)
{
auto *state = static_cast<shortcut_desktop_state *>(data);
auto *helper = Helper::instance();
if (helper && g_wrapper)
helper->activateSurface(g_wrapper, Qt::ActiveWindowFocusReason);
if (helper && g_primary)
helper->activateSurface(g_primary, Qt::ActiveWindowFocusReason);
*state = g_state;
if (helper) {
state->wrapper_visible = g_primary && g_primary->isVisible() ? 1 : 0;
state->keyboard_focused = hasKeyboardFocus(helper, g_primary) ? 1 : 0;
}
}

extern "C" void shortcut_desktop_focus_secondary(void *data)
{
auto *state = static_cast<shortcut_desktop_state *>(data);
auto *helper = Helper::instance();
if (helper && g_secondary)
helper->activateSurface(g_secondary, Qt::ActiveWindowFocusReason);
*state = g_state;
if (helper) {
state->wrapper_visible = g_wrapper && g_wrapper->isVisible() ? 1 : 0;
state->keyboard_focused = hasKeyboardFocus(helper) ? 1 : 0;
state->secondary_visible = g_secondary && g_secondary->isVisible() ? 1 : 0;
state->secondary_focused = hasKeyboardFocus(helper, g_secondary) ? 1 : 0;
}
}
Loading
Loading