This document describes the Linux gamepad activity subsystem used by the runtime monitor.
It is implementation guidance, not a roadmap. The goal is to make controller, wheel, and similar HID input count as user activity without adding user-facing modes or device-specific configuration.
The subsystem watches readable Linux input devices and reports controller input as auxiliary user activity to the session runner. The shared native-session runtime starts it independently of the selected desktop provider. GNOME and native Wayland both use that runtime, but neither the gamepad source nor its lifecycle belongs to a desktop environment.
The subsystem does not decide whether to blank or restore the TV. It only reports activity. The session runner and screen policy remain responsible for screen behavior.
- The shared native-session runtime in
session/runner.rsstarts a background gamepad activity thread alongside the selected desktop provider. session/gamepad/devices.rsscans/dev/input/event*, opens each event node, checks whether its capabilities look gamepad-like, records vendor/product IDs, and maps related/dev/hidraw*paths through sysfs.session/gamepad/reader.rsopens generic evdev readers for devices that passed discovery. It emits raw button and axis observations.session/gamepad/adapters/asks registered device adapters whether they support each discovered device. Matching adapters may add supplemental readers for device surfaces that are not adequately represented through evdev.session/gamepad/registry.rsstores per-device activity state.session/gamepad/activity.rsevaluates raw observations. Button activity is immediate. Axis activity is compared against a moving baseline so arbitrary resting positions, such as a wheel left turned, do not continuously count as input.- The runner receives throttled
AuxiliaryInputactivity observations, preserves that source on the resulting runtime event, and resets the same inactivity deadline used by desktop activity.
| Path | Responsibility |
|---|---|
session/gamepad/mod.rs |
Public subsystem entrypoints, source setup, refresh, polling, diagnostics |
session/gamepad/devices.rs |
/dev/input/event* discovery, capability classification, metadata, hidraw lookup |
session/gamepad/device_events.rs |
Linux uevent listener for input and hidraw add/remove/change notifications |
session/gamepad/reader.rs |
Generic evdev event reader and raw event mapping |
session/gamepad/activity.rs |
Pure activity policy for buttons and axes |
session/gamepad/registry.rs |
Per-device state registry that applies activity policy |
session/gamepad/adapters/ |
Device-specific supplemental activity adapters |
session/gamepad/hidraw.rs |
Reusable nonblocking raw HID report reader for adapters |
The monitor performs an initial discovery scan when the gamepad thread starts. After that it refreshes the watched device set from three triggers:
- relevant Linux uevents for input and hidraw device add, remove, or change
- short retry refreshes after transient discovery or open failures
- periodic reconciliation scans in case a uevent was missed
Refreshes are intentionally full rescans. Device events only say that something changed; discovery owns the authoritative view of currently useful devices and adapter readers.
State cleanup follows these rules:
- devices confidently absent from a successful discovery scan are removed
- adapter readers not returned by a successful discovery scan are removed
- devices that fail inspection are retained, because the old open file descriptor may still be valid while permissions or udev state settle
- a failed input-directory scan does not prune existing readers
- polling read errors remove the failing reader immediately
This keeps hotplug responsive while avoiding needless loss of already-open readers during transient permission or metadata failures.
Adapters live under session/gamepad/adapters/ and implement
GamepadActivityAdapter.
An adapter decides whether it supports a discovered GamepadDevice and returns
zero or more ActivityReaderSpecs. A reader spec has a stable
ActivityReaderKey and opens an ActivityReader. Adapter readers map device
data into normal button or axis observations. Those
observations pass through the shared movement and noise policy before they can
count as activity. Receiving an opaque report is not itself proof of user
activity because devices may emit unsolicited status reports while untouched.
Adapters should stay narrow. They should not own refresh scheduling, retries, registry cleanup, idle policy, screen behavior, or user configuration.
The Logitech G923 adapter is the current example. It matches the wheel by vendor/product ID and attaches raw HID readers for wheel and pedal reports that may not appear through the generic evdev path.
Use this shape for a new device adapter:
- Add a file under
crates/lg-buddy/src/session/gamepad/adapters/. - Match devices narrowly, preferably by vendor/product ID. If a whole device family is supported, document the reason in the adapter tests or comments.
- Reuse
RawHidReportReaderto read reports from a hidraw surface. - Add a custom reader that extracts meaningful controls as
RawGamepadEventvalues; ignore sequence, timestamp, status, and other non-input fields. - Register the adapter in
adapters/mod.rs. - Add tests for support matching and reader specs. If the adapter parses reports, add parser tests with captured representative payloads.
Pull requests that add device support should include the tested device name, vendor/product IDs, which input surfaces were checked, what was missing from the generic evdev path, and how the behavior was validated.
Default tests should cover the behavior without requiring real hardware:
- capability classification and event-node filtering
- sysfs hidraw mapping and device metadata propagation
- uevent filtering and refresh scheduling
- adapter support matching and reader spec keys
- registry cleanup and retention rules
- button, axis, baseline, and cooldown policy
- backend-neutral native-session integration of gamepad activity into inactivity observations
Use the hardware smoke test when changing behavior that depends on real input devices:
LG_BUDDY_GAMEPAD_SMOKE_SECS=20 cargo test -p lg-buddy --lib \
session::gamepad::tests::hardware_smoke_reports_real_gamepad_activity \
-- --ignored --nocaptureRun the smoke test from a desktop session with read access to the connected controllers. Move buttons, sticks, wheels, and pedals during the capture window and confirm activity is reported for each surface the change is meant to cover.