Add experimental libinput FFI input plugin (Phase 1) - #362
Open
iberianpig wants to merge 9 commits into
Open
Conversation
Wrap libinput.so/libudev.so directly instead of spawning the libinput CLI: context (path/udev backends), libinput_interface callbacks, gesture event extraction to GestureRecord, and FFI-based device detection. - scale/angle_delta are only queried for PINCH events per the libinput API contract - dlopen happens at require of libinput.rb; callers load it lazily - declare fiddle in Gemfile (removed from default gems in Ruby 3.5) and add minimal Fiddle RBS (no upstream signatures exist) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Create a virtual touchpad via /dev/uinput and verify swipe/pinch/hold gestures end-to-end through real libinput. Lessons encoded in the helper: - UI_ABS_SETUP encodes the struct size (28 = 0x1c) in the ioctl number; a wrong size makes the kernel return E2BIG - event node is resolved via sysfs by device name; kernel reuses event numbers so guessing from /dev/input ordering is unreliable - all slots of a multi-finger update must share one SYN_REPORT frame, otherwise libinput's touch-jump detection discards the motion - BTN_TOOL_FINGER/DOUBLETAP/TRIPLETAP must track the finger count; without them pressure-less touchpads stay in TOUCH_HOVERING and emit nothing - dispatch while injecting (pump_events); libinput schedules tap/hold timers from event timestamps Specs skip gracefully without libinput.so or /dev/uinput access. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Input plugin that reads gestures via the FFI bindings instead of the libinput debug-events subprocess. Uses the udev backend so devices on the seat (including hotplugged ones) are discovered without the CLI, applies tap/dwt config per device on DEVICE_ADDED, and streams GestureRecords through a pipe as length-prefixed Marshal. - disabled by default; Input#enabled? lets config turn inputs on/off and Runner#initialize_plugins skips disabled ones - FFI bindings are required lazily in #start_event_loop: this file is auto-required at boot and an eager require would dlopen libinput.so, breaking fusuma on systems without libinput - #shutdown stops the event thread and releases the context Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Device.fetch_devices uses Libinput::DeviceDetector instead of shelling out to `libinput list-devices` (~200ms) when the FFI input is enabled, removing the libinput-tools dependency from device detection. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Add an integration job that loads the uinput module, relaxes device permissions via a udev rule, and runs the integration specs directly on the runner VM. Docker cannot self-contain this: uinput devices live in the host kernel and libinput classifies devices via the udev daemon's database. Dockerfile.integration reproduces the setup locally by borrowing the host kernel and udev db (see header comment; --device-cgroup-rule is required because the device cgroup rejects open(2) on input nodes with EPERM even as root). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
rbs-trace derives signatures from observed calls, which can be narrower than reality: Base.inherited is a hook (-> void, not the return of Manager.add) and LibinputCommand#which returns nil when the command is not found. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Code review follow-up for the libinput_ffi_input series: - LibinputGestureParser only matched libinput debug-events text lines, so pre-parsed GestureRecords from libinput_ffi_input never reached the gesture buffer; pass them through so the event is re-tagged - declare :source in Parser#config_param_types: every parser supports source switching, but overriding it via config.yml raised NotImplementedError because the key was never declared - close the writer when the FFI event thread dies so the reader sees EOF and shuts fusuma down, matching the CLI input's behavior when its subprocess exits Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Close the remaining feature gaps with the CLI input: - device: config (CLI --device equivalent): on DEVICE_ADDED, gesture devices whose name does not match are muted via libinput_device_config_send_events_set_mode. Matching uses the same regex semantics as libinput_device_filter's keep_device. Non-gesture devices stay enabled so disable-while-typing keeps working. - TouchRecord (TOUCH_DOWN/UP/MOTION/CANCEL/FRAME with mm positions) and PointerRecord (motion/button/scroll) replace the CLI's raw text lines for plugins that consume non-gesture events. touch-events defaults to on (only touchscreens produce them); pointer-events is opt-in because of its frequency. - uinput helper grows a VirtualTouchscreen (INPUT_PROP_DIRECT) and the integration specs cover TOUCH records end-to-end through libinput. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
How to enable the FFI input, all config parameters, four ways to verify it works (specs on uinput, Docker, CI, on a real machine), the record types it emits, and current limitations. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds
libinput_ffi_input, an experimental input plugin that talks tolibinput.sodirectly via Fiddle (FFI) instead of spawning thelibinput debug-eventssubprocess.libinput list-devices(~200ms per call)GestureRecordobjects flow straight into the pipelinedocs/libinput_ffi_input.md)What's included
lib/fusuma/libinput/— context (path/udev), interface callbacks, gesture/touch/pointer event extraction, device detectionlib/fusuma/plugin/inputs/libinput_ffi_input.rb— udev backend, per-device tap/dwt config,device:filtering (regex, like keep_device),enabled?opt-in mechanism for inputsTouchRecord(touchscreens) andPointerRecord(motion/button/scroll, opt-in) for plugins that consume non-gesture eventsintegrationjob running the uinput specs on the runner VM;Dockerfile.integrationfor local reproductiondocs/libinput_ffi_input.md— enabling, all config parameters, 4 ways to verify, limitationsBoot safety: the FFI bindings are required lazily, so fusuma still boots and tests still pass on systems without
libinput.so(verified in a libinput-less container; the build matrix exercises this path).Phased rollout
This PR is Phase 1: experimental, opt-in, zero impact on default behavior (no new gem dependencies;
fiddlestays undeclared while experimental).Later phases (separate PRs):
TouchRecord/PointerRecordfiddledependency for Ruby 3.5+Test plan
bundle exec rake(332 specs + rbs validate + steep) green on Ruby 3.3🤖 Generated with Claude Code