Skip to content

Rework virtual_keyboard, implement zwp_virtual_keyboard_v1 and zwlr_virtual_pointer_v1 - #2159

Open
pgaskin wants to merge 2 commits into
Smithay:masterfrom
pgaskin:virtual-input
Open

pgaskin wants to merge 2 commits into
Smithay:masterfrom
pgaskin:virtual-input

Conversation

@pgaskin

@pgaskin pgaskin commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

Description

Keyboard

Previously, virtual keyboards would bypass the compositor's event handling, directly writing events to wl_keyboards. This results in compositor bindings, keyboard grabs, shortcut inhibition, and focus not being handled correctly.

This commit makes virtual keyboards work the same way as real input devices, similar to how the libei backend works.

The device is only created once a keymap is uploaded from the client since all keycodes are interpreted using it.

The compositor is now responsible for switching the keymap when handling events from the virtual device. Previously, it was changed silently by smithay behind the compositor's back.

Since KeymapChanged and Modifiers don't have a counterpart in InputEvent, they are sent as InputEvent::Special.

This also fixes a bug where the client could cause the server to panic by specifying an invalid seat in create_virtual_keyboard.

The new XkbContext::set_modifier_mask function allows the modifier state to be set from a raw xkb mask, and is necessary since KeyboardHandle::set_modifier_state is lossy due to serialize_back. It handles leds_state the same way as set_layout.

The keyboard behaviour differs slightly from wlroots in that modifiers are shared. On wlroots, the per-keyboard modifiers are more a result of how input works than an explicit design choice for virtual keyboards. The zwp_virtual_keyboard_v1 protocol only states that it should "emulate the behaviour of a physical keyboard", which is what this does.

Also see #1903 and niri-wm/niri#403.

Pointer

The new virtual pointer implementation works similarly.

There are some protocol deviations for wlroots compatibility:

  • Button state is treated as a C-style boolean rather than an enum.
  • Missing axis_source defaults to Wheel.
  • Accumulated axis values are cleared on axis_stop.

The absolute positions from the client are normalized to 0.0..=1.0 based on the requested extents.

AI usage

I used AI to help figure out a design consistent with the rest of the codebase, and to compare the implementation to wlroots for compatibility. I also used it to generate the Dispatch boilerplate.

The actual logic and all prose (comments, commit messages, this PR) was hand-written by me.

Testing

I previously implemented wl-uinput-proxy as a workaround, and learned a LOT about wayland and xkb while doing so (this was my first ever wayland-related project, one of my first rust projects, but not my first virtual input or remote-desktop related project).

I have been using this (along with RealVNC via xwlrvnc, and wayvnc) daily since mid-June, and am pretty confident in the correctness and completeness.

I have also tested this with wtype and a few one-off scripts.

See niri-wm/niri#4548 for the niri side.

Checklist


closes #2036 (vibe-coded incomplete virtual-pointer impl)
closes #1903 (modifies virtual_keyboard to do a similar keymap-swapping trick, but less cleanly)
closes #1597 (same idea as the set_modifier_mask thing I added)
fixes #1937
see niri-wm/niri#403
see niri-wm/niri#3595

@pgaskin

pgaskin commented Sep 8, 2026

Copy link
Copy Markdown
Contributor Author

cc @Bot-wxt1221 @itaylor @YaLTeR

pgaskin added a commit to pgaskin/niri that referenced this pull request Sep 8, 2026
Fixes a bunch of virtual input bugs, most notable keybind handling and
virtual keyboard keymaps.

needs Smithay/smithay#2159
closes niri-wm#3309
fixes niri-wm#3394
fixes niri-wm#403
closes niri-wm#2246
fixes niri-wm#3595
fixes niri-wm#3274
pgaskin added a commit to pgaskin/niri that referenced this pull request Sep 8, 2026
Fixes a bunch of virtual input bugs, most notably keybind handling and
virtual keyboard keymaps.

needs Smithay/smithay#2159
closes niri-wm#3309
fixes niri-wm#3394
fixes niri-wm#403
closes niri-wm#2246
fixes niri-wm#3595
fixes niri-wm#3274
Comment thread anvil/src/input_handler.rs
Comment thread anvil/src/input_handler.rs
Comment thread src/wayland/virtual_pointer/input.rs
Comment thread anvil/src/input_handler.rs
@pgaskin
pgaskin requested a review from YaLTeR September 8, 2026 12:18
Comment thread src/wayland/virtual_pointer/mod.rs Outdated
Comment thread src/wayland/virtual_keyboard/mod.rs
@pgaskin
pgaskin requested a review from YaLTeR September 8, 2026 14:18
Comment thread src/wayland/virtual_keyboard/mod.rs Outdated
Comment thread src/wayland/virtual_keyboard/mod.rs Outdated
@pgaskin

pgaskin commented Sep 8, 2026

Copy link
Copy Markdown
Contributor Author

Made a few changes:

  • Added support for the no_keymap format (which uses the existing seat keymap).
  • Added logging for keymap compilation errors.
  • Fixed some copy-paste errors in doc comments between pointer and keyboard impls.
  • Made the pointer always send wheel steps, even if 0 (to match the trait). Edit: reverted this; I realized why I didn't do this to begin when I originally implemented it (it breaks stuff which based on wheel events if the client doesn't send discrete values).
  • Squashed the fixups.

pgaskin added a commit to pgaskin/niri that referenced this pull request Sep 8, 2026
Fixes a bunch of virtual input bugs, most notably keybind handling and
virtual keyboard keymaps.

needs Smithay/smithay#2159
closes niri-wm#3309
fixes niri-wm#3394
fixes niri-wm#403
closes niri-wm#2246
fixes niri-wm#3595
fixes niri-wm#3274
pgaskin added a commit to pgaskin/niri that referenced this pull request Sep 8, 2026
Fixes a bunch of virtual input bugs, most notably keybind handling and
virtual keyboard keymaps.

needs Smithay/smithay#2159
closes niri-wm#3309
fixes niri-wm#3394
fixes niri-wm#403
closes niri-wm#2246
fixes niri-wm#3595
fixes niri-wm#3274
pgaskin added a commit to pgaskin/niri that referenced this pull request Sep 8, 2026
Fixes a bunch of virtual input bugs, most notably keybind handling and
virtual keyboard keymaps.

needs Smithay/smithay#2159
closes niri-wm#3309
fixes niri-wm#3394
fixes niri-wm#403
closes niri-wm#2246
fixes niri-wm#3595
fixes niri-wm#3274
@pgaskin

pgaskin commented Sep 8, 2026

Copy link
Copy Markdown
Contributor Author

I did some more testing and review of niri-wm/niri#4548 against wlroots. The differences are:

  • Keyboard
    • XKB state is per-seat rather than per-keyboard. This is wlroots-specific. The protocol states that it should "emulate the behaviour of a physical keyboard", so arguable, both are correct. Regardless, I think it makes the most sense for modifiers to be shared across all keyboards on a seat.
    • Virtual keyboards added with the keymap format no_keymap still have modifiers (this is basically the same thing as above).
    • Invalid keymap formats (or keymap mapping errors) are ignored (and logged), resulting in a no_keymap when the client attempts to send a key. In wlroots, it emits a invalid_keymap_format protocol error when parsing the keymap (but this can't be done with smithay's bindings).
  • Pointer
    • An axis event is emitted per-frame rather than per-axis. This isn't actually different though, since wlroots will merge it itself, but smithay doesn't.
    • No frame event is emitted if there aren't any axis ones pending (basically the same as above).
    • The absolute position is clamped.

And the wlroots quirks I implemented on purpose here for compatibility:

  • Keyboard
    • The no_keymap format results in the seat keymap being used.
  • Pointer
    • Button state is interpreted as a C boolean rather than an enum.
    • Default axis source is wheel if none is sent with an event.
    • Absolute motion with a zero extent (i.e., would result in a divide by zero) is ignored silently.
    • High-resolution scroll wheel axis events emitting one logical wheel step for every delta of 120 (i.e. WLR_POINTER_AXIS_DISCRETE_STEP).

pgaskin added a commit to pgaskin/niri that referenced this pull request Sep 8, 2026
Fixes a bunch of virtual input bugs, most notably keybind handling and
virtual keyboard keymaps.

needs Smithay/smithay#2159
closes niri-wm#3309
fixes niri-wm#3394
fixes niri-wm#403
closes niri-wm#2246
fixes niri-wm#3595
fixes niri-wm#3274
pgaskin added a commit to pgaskin/niri that referenced this pull request Sep 8, 2026
Fixes a bunch of virtual input bugs, most notably keybind handling and
virtual keyboard keymaps.

needs Smithay/smithay#2159
closes niri-wm#3309
fixes niri-wm#3394
fixes niri-wm#403
closes niri-wm#2246
fixes niri-wm#3595
fixes niri-wm#3274
@pgaskin

pgaskin commented Sep 9, 2026

Copy link
Copy Markdown
Contributor Author

I need to test this a bit more to ensure the recent tweaks and fixes I did didn't introduce any regressions compared to the build I've been using for the last few months.

I'll post another comment here once I do.

pgaskin added a commit to pgaskin/niri that referenced this pull request Sep 9, 2026
Fixes a bunch of virtual input bugs, most notably keybind handling and
virtual keyboard keymaps.

needs Smithay/smithay#2159
closes niri-wm#3309
fixes niri-wm#3394
fixes niri-wm#403
closes niri-wm#2246
fixes niri-wm#3595
fixes niri-wm#3274
@pgaskin

pgaskin commented Sep 10, 2026

Copy link
Copy Markdown
Contributor Author

I think I'm happy with this now, and it should be ready to fully review/merge (as long as no issues are found with niri-wm/niri#4548).

I tested this on niri with various edge cases including:

  • Scrolling with different combinations of axis values.
  • Sharing modifiers with real input device.
  • Pressing and releasing keys at the same time as a real keyboard.
  • Pressing and releasing keys at the same time as another keyboard.
  • Releasing keys/buttons when a virtual keyboard/pointer is removed.
  • Rotated and scaled outputs, including as part of mixed layouts.
    • I fixed a bug with this (see absolute_position_is_logical).
  • Multiple virtual keyboards with the same and different layouts
    • I fixed a few bugs around keymap switching here (mostly for the no_keymap case).
    • I did some optimizations to reduce unnecessary layout switches (see configured_keymap).
  • Modifiers on both the physical and real keyboards.
    • I fixed a bug where clients would forget modifiers in some cases due to keymap changes (see restore_keyboard_state).
  • Virtual keymaps with different shifted layouts.

I've also been using this (along with niri-wm/niri#4554) for multiple hours today with RealVNC on xwlrvnc (also with x11vnc on xwlrvnc), and I've also tested it with wayvnc.

@pgaskin
pgaskin requested a review from YaLTeR September 10, 2026 02:46

@YaLTeR YaLTeR left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This and the niri change look ok to me; someone else please take a look

pgaskin added a commit to pgaskin/niri that referenced this pull request Sep 11, 2026
Fixes a bunch of virtual input bugs, most notably keybind handling and
virtual keyboard keymaps.

needs Smithay/smithay#2159
closes niri-wm#3309
fixes niri-wm#3394
fixes niri-wm#403
closes niri-wm#2246
fixes niri-wm#3595
fixes niri-wm#3274
pgaskin added a commit to pgaskin/niri that referenced this pull request Sep 12, 2026
Fixes a bunch of virtual input bugs, most notably keybind handling and
virtual keyboard keymaps.

needs Smithay/smithay#2159
closes niri-wm#3309
fixes niri-wm#3394
fixes niri-wm#403
closes niri-wm#2246
fixes niri-wm#3595
fixes niri-wm#3274
Comment thread src/wayland/virtual_pointer/mod.rs Outdated
…nt zwp_virtual_keyboard_v1

Previously, virtual keyboards would bypass the compositor's event
handling, directly writing events to wl_keyboards. This results in
compositor bindings, keyboard grabs, shortcut inhibition, and focus
not being handled correctly.

This commit makes virtual keyboards work the same way as real input
devices, similar to how the libei backend works.

The device is only created once a keymap is uploaded from the client
since all keycodes are interpreted using it.

The compositor is now responsible for switching the keymap when
handling events from the virtual device. Previously, it was changed
silently by smithay behind the compositor's back.

Since KeymapChanged and Modifiers don't have a counterpart in
InputEvent, they are sent as InputEvent::Special.

This also fixes a bug where the client could cause the server to panic
by specifying an invalid seat in create_virtual_keyboard.

The new XkbContext::set_modifier_mask function allows the modifier
state to be set from a raw xkb mask, and is necessary since
KeyboardHandle::set_modifier_state is lossy due to serialize_back. It
handles leds_state the same way as set_layout.

Also see Smithay#1903.
Similar to the virtual_keyboard zwp_virtual_keyboard_v1 implementation.

There are some protocol deviations for wlroots compatibility:
- Button state is treated as a C-style boolean rather than an enum.
- Missing axis_source defaults to Wheel.
- Accumulated axis values are cleared on axis_stop.

The absolute positions from the client are normalized to 0.0..=1.0
based on the requested extents.
@codecov-commenter

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 1.19522% with 496 lines in your changes missing coverage. Please review.
✅ Project coverage is 17.15%. Comparing base (28c8e0f) to head (e78f406).
⚠️ Report is 8 commits behind head on master.

Files with missing lines Patch % Lines
src/wayland/virtual_pointer/mod.rs 0.00% 183 Missing ⚠️
src/wayland/virtual_keyboard/mod.rs 4.37% 131 Missing ⚠️
src/wayland/virtual_pointer/input.rs 0.00% 104 Missing ⚠️
src/wayland/virtual_keyboard/input.rs 0.00% 42 Missing ⚠️
src/input/keyboard/mod.rs 0.00% 20 Missing ⚠️
anvil/src/input_handler.rs 0.00% 16 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master    #2159      +/-   ##
==========================================
- Coverage   17.39%   17.15%   -0.25%     
==========================================
  Files         187      189       +2     
  Lines       31162    30888     -274     
==========================================
- Hits         5420     5298     -122     
+ Misses      25742    25590     -152     
Flag Coverage Δ
wlcs-buffer 14.93% <1.19%> (-0.15%) ⬇️
wlcs-core 14.52% <1.19%> (-0.15%) ⬇️
wlcs-output 6.53% <1.19%> (-0.01%) ⬇️
wlcs-pointer-input 16.14% <1.19%> (-0.23%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

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.

New keyboard impl

3 participants