You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
feat: XI2.1 smooth scrolling via xf86-input-neko scroll valuators (#196)
# Checklist
- [ ] Requires
[kernel/neko#hiro/xi2-scroll](https://github.com/kernel/neko/tree/hiro/xi2-scroll)
to be merged and the `ghcr.io/kernel/neko/base:xi2-scroll` image to be
published
- [x] A description of the changes proposed in the pull request.
- [ ] @mentions of the person or team responsible for reviewing proposed
changes.
## Summary
Replace the XTest button-event scroll path with XInput2.1 smooth scroll
valuators on the `xf86-input-neko` Xorg driver. This fixes the
fundamental scrolling problem: XTest could only fire discrete "notch"
events (~100px per click in Chromium), making it impossible to achieve
smooth trackpad scrolling. With XI2.1, we get **pixel-precise 1:1
mapping** between client trackpad deltas and browser scroll pixels.
### Problem
The old XTest approach (`xf86PostButtonEvent` with buttons 4/5) had a
fixed scroll quantum — each button press scrolled ~100px in Chromium
with no sub-notch precision. This meant:
- `pixelsPerNotch` too low → fires many button clicks → scrolls too fast
- `pixelsPerNotch` too high → needs lots of delta before firing → feels
jumpy/unresponsive
- No middle ground exists because each click always scrolls the same
fixed amount
### Solution
Use XInput2.1 scroll valuators which provide continuous, sub-pixel
scroll precision:
**xf86-input-neko driver (`neko.c`):**
- Add vertical/horizontal scroll valuator axes (3, 4) with
`SetScrollValuator`
- Handle `NEKO_SCROLL` (0x80) messages via `xf86PostMotionEventM`
instead of button events
- Change device type from `XI_TOUCHSCREEN` to `XI_MOUSE` so Chromium
respects scroll valuators
**Client (`video.vue` / `base.ts`):**
- Remove `PIXELS_PER_TICK` quantization and 100ms throttle
- Send raw pixel deltas batched per `requestAnimationFrame`
- Use document-level wheel listener with `passive: false` for reliable
`preventDefault()`
- Extend binary wheel message to include `controlKey` byte (length=5)
**Config:**
- Enable xinput driver in `neko.yaml` with socket path
- Use neko base image with XI2 scroll support
### Test results
Programmatic tests confirm exact 1:1 pixel mapping:
| Client deltaY | Actual scrollY | Per-event |
|---|---|---|
| 3 (tiny trackpad) | 3px | 3.0px |
| 10 (small) | 10px | 10.0px |
| 50 (medium) | 50px | 50.0px |
| 200 (fast swipe) | 200px | 200.0px |
## Depends on
- **neko fork**:
[`kernel/neko@hiro/xi2-scroll`](kernel/neko@master...hiro/xi2-scroll)
— server-side changes to send raw pixel deltas to xinput driver
Made with [Cursor](https://cursor.com)
<!-- CURSOR_SUMMARY -->
---
> [!NOTE]
> **Medium Risk**
> Medium risk because it changes the end-to-end input/scroll pipeline
(client wheel handling, binary protocol, and Xorg input driver), which
could regress scrolling or pointer/touch behavior across
browsers/devices.
>
> **Overview**
> Switches remote scrolling from discrete XTest-style “ticks” to XI2.1
scroll valuators for *smooth, pixel-precise* trackpad/mouse wheel input.
>
> The web client now captures wheel events at the document level
(non-passive, capture) and sends scaled raw deltas (clamped to `int16`)
plus a `controlKey` flag in an extended `wheel` data message; the old
quantization/throttle logic and default scroll inversion are removed.
>
> The `xf86-input-neko` Xorg driver is extended with vertical/horizontal
scroll valuator axes and a `NEKO_SCROLL` message path that posts motion
events for scroll, and the image/config is updated to use the newer
`neko/base` and enable the input socket in `neko.yaml`.
>
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
ca24892. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
0 commit comments