Make input sidecars compositor-agnostic via /dev/uinput #59

Merged
albert merged 3 commits from u/albert/58/wayland-generic-sidecars into main 2026-05-31 00:56:00 +00:00
Owner

The touch-to-mouse and gamepad bridges emitted synthetic input through
wlroots-only Wayland protocols (zwlr_virtual_pointer_v1,
zwp_virtual_keyboard_v1), so they only worked under Sway and refused to
run on GNOME/Mutter, KWin, etc.

Replace that last hop with a kernel /dev/uinput virtual device, which is
consumed by every compositor (and X11) through libinput. Both bridges
already produce raw evdev codes, so the payload maps almost 1:1 and the
xkb-keymap upload hack is gone — the compositor applies the user's own
layout to raw keycodes.

  • New shepherd-bridge crate: the shared OutputEvent vocabulary, OutputSink
    trait, and UinputSink backend (relative pointer+keyboard for gamepad,
    absolute pointer for touch).
  • Migrate both bridges to UinputSink; delete gamepad-bridge/src/wl.rs and
    drop all wayland-client / wayland-protocols-* dependencies.
  • Add dist/udev/71-shepherd-uinput.rules granting the input group access
    to /dev/uinput, installed by a new shepherd install udev subcommand
    (also run by install all); document in INSTALL.md.
  • Design doc under docs/ai/history.

Verified: cargo fmt, clippy -D warnings, build, and unit tests pass.
Runtime behavior (absolute pointing, scroll feel) still needs validation
on real GNOME/KWin hardware.

Co-Authored-By: Claude Opus 4.8 (1M context) noreply@anthropic.com

Fixes #58

The touch-to-mouse and gamepad bridges emitted synthetic input through wlroots-only Wayland protocols (zwlr_virtual_pointer_v1, zwp_virtual_keyboard_v1), so they only worked under Sway and refused to run on GNOME/Mutter, KWin, etc. Replace that last hop with a kernel /dev/uinput virtual device, which is consumed by every compositor (and X11) through libinput. Both bridges already produce raw evdev codes, so the payload maps almost 1:1 and the xkb-keymap upload hack is gone — the compositor applies the user's own layout to raw keycodes. - New shepherd-bridge crate: the shared OutputEvent vocabulary, OutputSink trait, and UinputSink backend (relative pointer+keyboard for gamepad, absolute pointer for touch). - Migrate both bridges to UinputSink; delete gamepad-bridge/src/wl.rs and drop all wayland-client / wayland-protocols-* dependencies. - Add dist/udev/71-shepherd-uinput.rules granting the input group access to /dev/uinput, installed by a new `shepherd install udev` subcommand (also run by `install all`); document in INSTALL.md. - Design doc under docs/ai/history. Verified: cargo fmt, clippy -D warnings, build, and unit tests pass. Runtime behavior (absolute pointing, scroll feel) still needs validation on real GNOME/KWin hardware. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Fixes #58
Make input sidecars compositor-agnostic via /dev/uinput (#58)
All checks were successful
CI / ShellCheck (pull_request) Successful in 12s
CI / CI image (pull_request) Successful in 11m39s
CI / Rustfmt (pull_request) Successful in 9s
CI / Clippy (pull_request) Successful in 4m8s
CI / Firewall E2E (pull_request) Successful in 4m22s
CI / Android portability (shepherd-media-core) (pull_request) Successful in 43s
CI / Build (pull_request) Successful in 6m45s
CI / Test (pull_request) Successful in 6m43s
CI / E2E (pull_request) Successful in 7m23s
d903918762
The touch-to-mouse and gamepad bridges emitted synthetic input through
wlroots-only Wayland protocols (zwlr_virtual_pointer_v1,
zwp_virtual_keyboard_v1), so they only worked under Sway and refused to
run on GNOME/Mutter, KWin, etc.

Replace that last hop with a kernel /dev/uinput virtual device, which is
consumed by every compositor (and X11) through libinput. Both bridges
already produce raw evdev codes, so the payload maps almost 1:1 and the
xkb-keymap upload hack is gone — the compositor applies the user's own
layout to raw keycodes.

- New shepherd-bridge crate: the shared OutputEvent vocabulary, OutputSink
  trait, and UinputSink backend (relative pointer+keyboard for gamepad,
  absolute pointer for touch).
- Migrate both bridges to UinputSink; delete gamepad-bridge/src/wl.rs and
  drop all wayland-client / wayland-protocols-* dependencies.
- Add dist/udev/71-shepherd-uinput.rules granting the input group access
  to /dev/uinput, installed by a new `shepherd install udev` subcommand
  (also run by `install all`); document in INSTALL.md.
- Design doc under docs/ai/history.

Verified: cargo fmt, clippy -D warnings, build, and unit tests pass.
Runtime behavior (absolute pointing, scroll feel) still needs validation
on real GNOME/KWin hardware.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
albert changed title from Make input sidecars compositor-agnostic via /dev/uinput (#58) to Make input sidecars compositor-agnostic via /dev/uinput 2026-05-30 23:01:30 +00:00
Author
Owner

Manual testing that needs to be done:

  • Gamepad under GNOME
  • Gamepad under shepherd-launcher
  • Touch under GNOME
  • Touch under shepherd-launcher
Manual testing that needs to be done: * [x] Gamepad under GNOME * [x] Gamepad under shepherd-launcher * [ ] Touch under GNOME * [x] Touch under shepherd-launcher
Read D-pad from buttons as well as hat axis (#58)
All checks were successful
CI / ShellCheck (pull_request) Successful in 12s
CI / CI image (pull_request) Successful in 32s
CI / Rustfmt (pull_request) Successful in 7s
CI / Build (pull_request) Successful in 2m0s
CI / Test (pull_request) Successful in 2m12s
CI / E2E (pull_request) Successful in 2m27s
CI / Android portability (shepherd-media-core) (pull_request) Successful in 32s
CI / Clippy (pull_request) Successful in 3m3s
CI / Firewall E2E (pull_request) Successful in 3m49s
60d5ecd976
On controllers whose D-pad is a hat with no native D-pad buttons (e.g. the
Legion Go S, ABS_HAT0X/0Y), gilrs's default axis_dpad_to_button filter
rewrites the hat into Button::DPad{Up,Down,Left,Right} events and drops the
original AxisChanged(DPadX/Y). Both presets read the D-pad only via the
DPadX/DPadY axes, so on those controllers the D-pad did nothing.

Add a dpad() helper that resolves the D-pad from either delivery form (hat
axis or DPad buttons, button wins) and route both presets through it
(productivity -> arrow keys, GPD -> scroll). Pre-existing bug, unrelated to
the uinput migration; surfaced by hardware testing.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Account for output scale in touch bridge absolute mapping (#58)
All checks were successful
CI / ShellCheck (pull_request) Successful in 14s
CI / CI image (pull_request) Successful in 32s
CI / Rustfmt (pull_request) Successful in 10s
CI / Test (pull_request) Successful in 2m13s
CI / Build (pull_request) Successful in 2m19s
CI / E2E (pull_request) Successful in 2m33s
CI / Android portability (shepherd-media-core) (pull_request) Successful in 30s
CI / Clippy (pull_request) Successful in 3m6s
CI / Firewall E2E (pull_request) Successful in 3m50s
b3f5d2745b
The uinput absolute pointer declares a 0..=65535 range that libinput maps
onto the output's physical pixels, but the cursor lives in logical (scaled)
coordinates. Under output scale N a full-surface touch sweep overshot by N,
so only the top-left 1/N of the touchscreen reached the whole screen. (The
old wlroots virtual-pointer protocol took motion_absolute in logical space,
so this only regressed once DPI scaling was configured.)

Divide emitted absolute coordinates by the output scale (a no-op at 1.0, so
unscaled setups are unaffected). shepherd-host-linux queries the live sway
scale via get_outputs() — fractional, e.g. 1.5 — and passes it to the bridge
as --output-scale; UinputSink::new_absolute applies it in rescale_abs, with
clamping for the downscale (scale < 1.0) case.

Sampled once at spawn; a mid-session scale change (e.g. the XWayland
native-resolution workaround) won't be followed until the bridge restarts.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Author
Owner

This change appears to have regressed the touch bridge with HiDPI displays, particularly when the XWayland DPI mitigation is active

This change appears to have regressed the touch bridge with HiDPI displays, particularly when the XWayland DPI mitigation is active
Author
Owner

@albert wrote in #59 (comment):

This change appears to have regressed the touch bridge with HiDPI displays, particularly when the XWayland DPI mitigation is active

This is fixed now

@albert wrote in https://git.armeafamily.com/albert/shepherd-launcher/pulls/59#issuecomment-200: > This change appears to have regressed the touch bridge with HiDPI displays, particularly when the XWayland DPI mitigation is active This is fixed now
albert merged commit 0b51f46b1c into main 2026-05-31 00:56:00 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
albert/shepherd-launcher!59
No description provided.