> For the complete documentation index, see [llms.txt](https://docs.kouch.dev/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.kouch.dev/docs/vpad_plan.md).

# Virtual pads: uinput on Linux, opt-in ViGEm on Windows (2026-09-28)

An addendum (DECK\_UI\_PLAN D5 parity; ARCHITECTURE.md "Virtual pads", Milestones 10–11). It doesn't replace them. `kouch-vpad` is a stub on both OSes; this plan makes it real behind the `VirtualPad` trait.

**Ground rule 3 holds:** Kouch never needs a driver to run. DSU stays the primary output. A virtual pad exists only for a player whose profile asks for one (`players[].vpad`), and only for that game's session.

## Why

* Rumble in every emulator: a virtual pad's force feedback reaches the player's real pad.
* Emulators without a DSU button client (they read only real gamepads) still get Kouch's 16 seats.
* On the Deck, uinput needs no driver: SteamOS's `uaccess` rule lets the seat user open `/dev/uinput`.

## V1 The trait (crate `kouch-vpad`, pure and testable)

* `VirtualPad`:
  * `kind()`;
  * `set_state(&PadState)`, which writes only what changed;
  * `poll_rumble() -> Option<Rumble { large: u8, small: u8 }>`.
* `NullBackend`, `VpadKind::Standard` (the common dual-stick class SDL maps without a mapping file), and `VpadError`:
  * `NoAccess`: no permission for `/dev/uinput`, with the fix in plain words;
  * `NoDriver`: the Windows bus driver is absent;
  * `Io`.
* `open(kind, index) -> Result<Box<dyn VirtualPad>, VpadError>` picks the platform backend. With neither feature built, it returns `NullBackend`.
* One pure mapping, `pad_events(prev, next) -> Vec<Event>`: buttons, sticks (±32767, +y down for evdev), triggers (0..255) and the d-pad as a hat. Both backends share it, and unit tests cover it.

## V2 Linux `UinputPad` (feature `uinput`, evdev 0.13)

* One device per seat, named "Kouch Pad N" (N = the seat), with an id SDL maps as a standard gamepad (ARCHITECTURE.md). No brand names anywhere.
* Keys: south, east, north, west, the shoulders, select, start, mode, the stick clicks. Axes: X/Y/RX/RY (−32768..32767, flat 128), Z/RZ as triggers (0..255), HAT0X/HAT0Y.
* Force feedback:
  * `FF_RUMBLE` with 16 effects;
  * a non-blocking drain of the device's events each tick handles uploads, erases and play/stop;
  * the strongest playing effect becomes `Rumble`, and it expires with the effect's length.
* Without access to `/dev/uinput` it returns `NoAccess` ("add a udev rule …", pointing to docs/RELEASE.md). It never crashes the session.

## V3 The input thread

* `InputCmd::SetVirtualPads(Vec<VpadSeat { slot, kind }>)`: the input thread opens the missing pads and closes the others, so a closed pad disappears from the emulator.
* Each tick it sends the slot's routed `PadState` (the same `SlotOutput` DSU gets; neutral while the Quick Menu freezes routing), but only when it changed.
* Rumble goes through the existing path (`DsuRumble::request(slot, small, large)` → the seat's real pad), so DSU and vpad rumble behave the same.
* Errors come back as one `InputEvent` per open attempt, which the shell turns into a toast. There are no retries every tick.

## V4 Sessions (app)

* At spawn, a profile with any `players[p].vpad` sends `SetVirtualPads` for those seats, before the emulator starts, so it sees them at boot. At session end (quit, crash, force quit) it sends an empty list.
* `validate.rs` warns when a player carries buttons over both DSU and a vpad (double input). That already exists in ARCHITECTURE; it gets checked.
* Settings › Controllers gets a line, "Virtual controllers: ready / not available (why)". On Windows it adds the driver state and a link to its installer. Kouch never installs a driver itself.

## V5 Windows `VigemPad` (feature `vigem`, opt-in)

* `vigem-client` 0.1.4: a standard wired target per seat. Rumble comes from the target's notification.
* If the bus driver is missing, it returns `NoDriver`. The session goes on with DSU only, the toast says why once, and Settings links the installer page with the note that the project is archived (ARCHITECTURE.md risks).
* It's used only when a profile asks, per player. Kouch runs the same without the driver.

## Build

* `kouch-app` enables `kouch-vpad/uinput` on Linux and `kouch-vpad/vigem` on Windows, per target. Both are pure Rust with no build-time system libraries.
* `kouch-lab`:
  * `vpad-16` opens 16 pads and holds them for 10 min, checking SDL sees 16;
  * `rumble-roundtrip` is Early Test 16's (b) and (c) paths.

## Checks

* **Unit:** the event mapping, the open/close diff for `SetVirtualPads`, rumble from FF uploads (a fake device), and the `NoAccess`/`NoDriver` paths.
* **Linux container:** uinput isn't in the container (no `/dev/uinput`), so the device tests skip there with a reason.
* **Live (central + C):**
  * on the Deck, a no-DSU-class test emulator sees the pads, buttons work, and rumble reaches the real pad;
  * on Windows with the driver, the same;
  * on Windows without it, the session runs on DSU with one toast.

## Order and owners

V1–V3 (C), V4 (C, with B reviewing the launch hook), V5 (C), live checks (central + C). Each lands behind the gate as it goes, and the plan's status is below.

| Part                        | Status                                                                                                                                                                                                                                                                             |
| --------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| V1 trait + mapping          | done: `VirtualPad`, `NullBackend`, `open`, `pad_events` + tests                                                                                                                                                                                                                    |
| V2 Linux uinput             | built, and on the Deck 11/11 once the round-trip test waits for udev's access grant: `UinputPad` (keys, axes, hat, `FF_RUMBLE`) and a pure `RumbleTracker`; clippy + tests in the Linux container. The device and rumble round-trip tests need `/dev/uinput`: run them on the Deck |
| V3 input thread             | built: `VpadHub`, `InputCmd::SetVirtualPads`, rumble through `DsuRumble`, `InputEvent::VirtualPadFailed` → one toast per reason                                                                                                                                                    |
| V4 sessions + Settings line | built: session start/stop (`sync_input_for_phase`), Settings › Controllers "Virtual controllers" (`vpad_status`), and B's double-input warning (`ProfileIssue::DoubleInput`, 8986dbd)                                                                                              |
| V5 Windows opt-in           | built: `VigemPad` (the standard target, rumble from the bus notifications), `probe()`; `NoDriver` without the driver. Checked against the installed driver on the owner's PC with the on-demand test (`-- --ignored`)                                                              |


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.kouch.dev/docs/vpad_plan.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
