Skip to content

fix(xwayland): sync keyboard modifiers state to XWayland - #1426

Open
deepin-wm wants to merge 1 commit into
linuxdeepin:masterfrom
deepin-wm:agent/developer/1806fa699580
Open

deepin-wm wants to merge 1 commit into
linuxdeepin:masterfrom
deepin-wm:agent/developer/1806fa699580

Conversation

@deepin-wm

@deepin-wm deepin-wm commented Sep 21, 2026 •

Copy link
Copy Markdown
Contributor

确认结果

此前键盘修饰键状态没有主动同步给 XWayland:

  • wl_keyboard.modifiers 事件只会发给当前持有键盘焦点的客户端(wlroots wlr_seat_keyboard_send_modifiers 只发给 keyboard_state.focused_client);
  • 而 XWayland 侧(xserver xwayland-input.c 的 keyboard_handle_modifiers)会无条件把收到的 wl_keyboard.modifiers 应用到自己的 XKB 状态——即使它没有键盘焦点。

因此当键盘焦点在某个 Wayland 客户端时(最常见场景),XWayland 收不到修饰键变化,其内部 XKB 状态停留在旧值,X11 侧查询接口(如 XkbGetState、Qt QGuiApplication::keyboardModifiers)读到的就是过期状态。例如:在 Wayland 窗口里按 Caps Lock/Num Lock,切到 XWayland 应用后状态查询接口不会反映新的锁定状态。

修复内容

waylib/src/server/protocols/wxwayland.cpp:

  • 监听 seat 键盘(wlr_keyboard.events.modifiers)修饰键变化;
  • 当 XWayland 自身不持有键盘焦点时,主动把当前 depressed/latched/locked/group 通过 wl_keyboard.modifiers 推送给 XWayland 服务器客户端;
  • 处理边界情况:键盘设备切换(如输入法虚拟键盘接管)、键盘设备销毁、setSeat 先于 XWayland 启动的时序(ready 后补发一次);
  • setSeat 修正为可传入空 seat;seat 切换时重新绑定 keyboardChanged 连接。

验证

远程构建机器全量编译通过(0 error):cmake --preset=default && cmake --build build -j60,libwaylibserver.so.0.10.0 与 build/src/treeland 均构建成功。

对应 Multica issue: WM-501

Summary by Sourcery

Keep XWayland's keyboard modifier state synchronized with the active Wayland seat.

Bug Fixes:

  • Synchronize keyboard modifier state to XWayland when it does not hold keyboard focus, keeping X11 modifier and lock-state queries current.

Enhancements:

  • Handle seat changes, keyboard replacement or destruction, and XWayland startup ordering when maintaining modifier synchronization.

@deepin-ci-robot

Copy link
Copy Markdown

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: deepin-wm

The full list of commands accepted by this bot can be found here.

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@sourcery-ai

sourcery-ai Bot commented Sep 21, 2026

Copy link
Copy Markdown

Reviewer's Guide

Updates WXWayland to proactively mirror the active seat keyboard’s modifier state to XWayland whenever another Wayland client has focus, while handling keyboard replacement, destruction, nullable seats, and XWayland startup ordering.

Sequence diagram for syncing keyboard modifiers to XWayland

sequenceDiagram
    participant Keyboard as Seat keyboard
    participant WXWayland as WXWayland
    participant XWayland as XWayland server
    participant Client as Focused Wayland client

    Keyboard->>WXWayland: modifiers event
    WXWayland->>WXWayland: syncModifiersToXWayland()
    alt XWayland does not hold keyboard focus
        WXWayland->>XWayland: wl_keyboard_send_modifiers(depressed, latched, locked, group)
        XWayland->>XWayland: Update XKB modifier state
    else XWayland holds keyboard focus
        WXWayland->>WXWayland: Skip explicit synchronization
    end
Loading

State diagram for WXWayland keyboard tracking

stateDiagram-v2
    [*] --> NoKeyboard
    NoKeyboard --> WatchingKeyboard: setSeat(seat) / watchSeatKeyboard()
    WatchingKeyboard --> WatchingKeyboard: keyboardChanged / reattach listeners and syncModifiersToXWayland()
    WatchingKeyboard --> WatchingKeyboard: modifiers / syncModifiersToXWayland()
    WatchingKeyboard --> NoKeyboard: keyboard destroy
    NoKeyboard --> WatchingKeyboard: replacement keyboard available
    WatchingKeyboard --> WaitingForXWayland: setSeat before XWayland ready
    WaitingForXWayland --> WatchingKeyboard: ready / syncModifiersToXWayland()
Loading

File-Level Changes

Change Details Files
Explicitly synchronize the seat’s current keyboard modifier state to XWayland when XWayland is not the keyboard-focused client.
  • Read depressed, latched, locked, and group state from the active wlroots keyboard.
  • Send wl_keyboard.modifiers to all XWayland keyboard resources using a seat-client serial.
  • Skip redundant delivery when XWayland already owns keyboard focus.
waylib/src/server/protocols/wxwayland.cpp
Track keyboard and seat lifecycle changes so modifier synchronization remains valid across device replacement and startup ordering.
  • Attach listeners to keyboard modifier and device-destroy events.
  • Reattach listeners when the seat keyboard changes and handle absent keyboards or seats.
  • Perform an initial synchronization after XWayland becomes ready.
waylib/src/server/protocols/wxwayland.cpp
Make seat assignment nullable and integrate it with synchronization setup.
  • Pass a null seat through to wlr_xwayland_set_seat.
  • Refresh keyboard listeners and modifier state whenever setSeat is called.
waylib/src/server/protocols/wxwayland.cpp

Assessment against linked issues

Issue Objective Addressed Explanation
#501 Ensure the SSD title bar displays the correct state-dependent maximize/restore icon when a Qt application window is maximized. ❌ The PR only synchronizes keyboard modifier state from the Wayland seat to XWayland. It contains no changes to SSD title-bar rendering or maximize/restore state handling.
#501 Display the window title information correctly in the SSD title bar for Qt applications running under Wayland. ❌ The changes are limited to XWayland keyboard modifier synchronization and do not modify window title retrieval, propagation, or title-bar display.

Possibly linked issues


Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 1 issue

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path="waylib/src/server/protocols/wxwayland.cpp" line_range="489-498" />
<code_context>
+
+    // The seat keyboard can be replaced (e.g. by the input method's virtual
+    // keyboard) or destroyed at any time, so re-attach on changes.
+    if (!seatKeyboardChangedConnection) {
+        if (auto *seatObject = WSeat::fromHandle(seat)) {
+            seatKeyboardChangedConnection = QObject::connect(seatObject,
+                                                             &WSeat::keyboardChanged,
+                                                             q, [this] {
+                watchSeatKeyboard();
+                syncModifiersToXWayland();
+            });
+        }
+    }
+
</code_context>
<issue_to_address>
**issue (bug_risk):** The seat keyboard-change connection is created only when `seatKeyboardChangedConnection` is empty and is never disconnected or replaced when `setSeat()` switches from one seat to another. After the first seat has been watched, keyboard replacement on the new seat (including virtual-keyboard switching) is not observed, so the XWayland modifier listener is not reattached and its state becomes stale.

**Triggers:** When `setSeat()` is called with a different non-null seat after a seat has already been configured.

**Suggested fix:** Disconnect the existing connection whenever the seat changes, then connect `keyboardChanged` on the new seat and store the connection associated with that seat.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨

Comment thread waylib/src/server/protocols/wxwayland.cpp Outdated
@zccrs
zccrs requested review from zccrs and zzxyb and a lite review from Copilot September 21, 2026 02:49

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

Three moderate findings remain unresolved, along with an issue-reference discrepancy.

Get a fresh assessment by requesting another Copilot review.

Review effort: Lite
Findings: 1 Medium severity

Open (1)
What changed in this PR

Synchronizes compositor keyboard modifier state to XWayland when it lacks keyboard focus.

Changes:

  • Sends modifier updates to XWayland.
  • Handles keyboard replacement, destruction, seat reassignment, and startup ordering.
  • Allows clearing the associated seat.
File Review summary
waylib/​src/​server/​protocols/​wxwayland.cpp Adds modifier synchronization and lifecycle handling. Outstanding findings: three moderate issues (votes: 3, 1, 1) involving stale seat/keyboard listeners and inert resources, plus one nit (vote: 1) regarding the unrelated Fixes #501 reference.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +513 to +519
W_D(WXWayland);

if (auto handle = this->handle())
wlr_xwayland_set_seat(handle, seat->handle());
wlr_xwayland_set_seat(handle, seat ? seat->handle() : nullptr);

d->watchSeatKeyboard();
d->syncModifiersToXWayland();

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed: the connection is now disconnected and reset on every watchSeatKeyboard() call (i.e. on every setSeat() change) before being rebound to the current seat. Current head: 45e4d6a (rebased onto master).

Comment thread waylib/src/server/protocols/wxwayland.cpp Outdated
@zzxyb

zzxyb commented Sep 21, 2026

Copy link
Copy Markdown
Collaborator

在enter到xwayland窗口后,将已经存在的修饰键发送过去不行吗?不增减新的键盘监控链路

@zccrs

zccrs commented Sep 21, 2026

Copy link
Copy Markdown
Member

在enter到xwayland窗口后,将已经存在的修饰键发送过去不行吗?不增减新的键盘监控链路

这个链路就是为了在没有在操作xwayland窗口时也允许xwayland程序查询修饰键的,为了支持x的全局查询修饰键的接口。

@deepin-wm
deepin-wm force-pushed the agent/developer/1806fa699580 branch from a68aae4 to 250eb52 Compare September 21, 2026 05:29
@deepin-wm

Copy link
Copy Markdown
Contributor Author

Thanks for the reviews. All three findings are addressed in 250eb52 (forced update):

  • Seat switch stale connection (Sourcery + Copilot): watchSeatKeyboard() now disconnects and resets seatKeyboardChangedConnection on every call before rebinding it to the current seat, so setSeat() switching to another seat (or clearing it) correctly re-attaches the keyboardChanged listener.
  • Lambda comments (zccrs): the modifiers and keyboard-destroy listeners now use member function pointers (&WXWaylandPrivate::syncModifiersToXWayland / &WXWaylandPrivate::on_keyboard_device_destroy) instead of lambdas.
  • PR body: removed the incorrect Fixes #501 reference (it pointed at an unrelated GitHub issue); the corresponding task is tracked as Multica WM-501.

Rebuilt on the build machine: incremental build passes with 0 errors.

1. Listen to the seat keyboard modifiers event and proactively push
   wl_keyboard.modifiers to the XWayland server client when XWayland
   itself does not hold the keyboard focus.
2. Handle keyboard device switch/destroy and the timing where the seat
   is set before the XWayland server starts (re-sync on ready).
3. Allow setSeat to accept a null seat.

X11 APIs that query keyboard state (e.g. XkbGetState, Qt
QGuiApplication::keyboardModifiers) now reflect the current modifier
state even when the keyboard focus is on a Wayland client.

Log: XWayland 应用通过 X11 接口查询到的修饰键状态(Caps/Num Lock 等)不再过期。

Influence:
1. Run an XWayland app, press Caps Lock/Num Lock while focus is on a
   Wayland window, then check the state via X11 query APIs.
2. Switch the input method virtual keyboard on/off and verify modifier
   state stays correct.
3. Verify no regression in normal keyboard focus flow on XWayland apps.

fix(xwayland): 同步键盘修饰键状态到 XWayland

1. 监听 seat 键盘修饰键事件,在 XWayland 自身不持有键盘焦点时
   主动向其推送 wl_keyboard.modifiers。
2. 处理键盘设备切换/销毁、seat 先于 XWayland 启动的时序。
3. setSeat 支持传入空 seat。

Log: XWayland 应用通过 X11 接口查询到的修饰键状态不再过期。

Influence:
1. 在 Wayland 窗口上按 Caps/Num Lock,切换到 XWayland 应用后用
   X11 接口验证锁定状态。
2. 开关输入法虚拟键盘,验证修饰键状态保持正确。
3. 验证 XWayland 应用正常键盘焦点流程无回归。
@deepin-wm
deepin-wm force-pushed the agent/developer/1806fa699580 branch from 250eb52 to 45e4d6a Compare September 30, 2026 03:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants