Files
runi-dist/docs/controller-support.md
T

209 lines
11 KiB
Markdown

<!-- Generated from the Runi source repository at v0.4.0. -->
<!-- Edit the original there, not this copy. -->
# Controller support and Steam integration
This document records the controller research behind Runi's input
implementation. Observed facts belong here; future product work belongs in
`roadmap.md`.
## Current architecture
Runi detects the 2026 Steam Controller at startup and on hotplug through
SDL3, with evdev as a legacy fallback. During a direct game session it maps the
SDL state either to an Xbox 360-shaped `uinput` device or to separate virtual
keyboard and mouse devices, then temporarily grabs the controller's desktop
keyboard/mouse event nodes. Launcher navigation is locked while the game owns
the controller. The Xbox path was validated with DiRT Showdown: Wine sees the
virtual device as XInput and the game accepts it.
The direct USB path was validated on real `28de:1302` hardware: launcher
navigation worked, a Direct game received one controller, Steam's desktop
mouse/keyboard output stayed suppressed during the session, and launcher
control returned after the game exited. The wireless puck uses `28de:1304`
through the same bridge and suppression path.
Every Play action asks for one of two modes:
- **Runi Direct — Recommended** never requires Steam. It automatically
selects either SDL3 → `uinput` → Wine XInput or a per-game Keyboard & Mouse
profile. The launch dialog can override that choice for one session.
- **Steam Input** uses a deterministic non-Steam shortcut. The shortcut runs
`runi steam-launch <installation-id>`; that command stays alive until
the game exits so Steam does not fall back to its Desktop layout. The local
bridge is not started in this mode, avoiding duplicate controllers.
Steam is detected in native and Flatpak locations. The most recently used
local account is selected in v1. Runi preserves unknown VDF fields, backs
up `shortcuts.vdf`, writes atomically, tags only its own shortcut, and derives
a stable shortcut AppID from `runi-installation-id`. Steam must be closed
while a shortcut is created, repaired or removed.
Controller troubleshooting is explicit rather than permanently verbose.
Overview → Technology → **Run Controller Diagnostic** launches the Direct path
with temporary Wine HID/XInput tracing, counts SDL3-to-uinput changes, checks
whether the virtual device is observable in the launched process tree, and
stores a per-installation stage report. UMU's launcher output and Proton's
separate `steam-<appid>.log` are correlated because the Wine HID/XInput trace
is written to the latter. Normal launches do not enable these trace channels.
## Keyboard & Mouse profiles
Games without native controller support can use a Runi-owned profile.
The left stick emits WASD, the right trackpad controls the mouse with a
right-stick fallback, and buttons, triggers and paddles emit configured
keyboard or mouse actions. Gyro and haptics are not part of this first profile
version.
Bundled profiles live in `runi/controller_profiles`. User changes are
stored in `~/.config/runi/controller-profiles/<pgid>.toml`; the filename
uses the canonical Runi Game ID, so a profile survives removal and
re-import of an installation. Config → Controller edits the persistent method,
pointer tuning and bindings. Reset removes only the user override and reveals
the bundled profile again.
The Thing That Happened is the first curated Keyboard & Mouse profile. Its
mapping is based on the game's own key-binding screen: triggers become aim and
shoot, face buttons cover jump/crouch/use/reload, shoulders cycle weapons,
D-pad selects or holsters weapons, rear buttons cover grenade/fire mode and
flashlight/night vision, and Quick Access opens the map. The Guide button
remains reserved.
## Steam Input is not XInput
Some games ask Valve's native action API for named digital and analog actions
instead of polling XInput. Technology Scan detects embedded interface names
such as `SteamInput006` and recommends Steam mode, but Direct remains available
with a warning.
Digimon Story Time Stranger is the reference compatibility case. Its
executable requests `SteamInput006`. The Runi virtual Xbox reaches Wine
correctly, and the Wine trace confirms `VID_045E&PID_028E`, a `WINEXINPUT`
device and an XInput controller opened at index 0. Finding action-set files is
configuration evidence, not proof that the provider consumed input.
The inspected installation initially contained a 7.3 MB `steam_api64.dll`
from `gbe_fork`, built on 27 November 2025, plus action manifests named
`InGameControls.txt` and `MenuControls.txt`. Updating the provider to the
verified Windows x64 VS2022 regular build from `gbe_fork 2026-05-30` did not by
itself fix input. A temporary debug build produced `STEAM_LOG_*.log`, which
showed that the provider repeatedly found one connected controller, while the
game requested these current named actions:
```text
Square Cross Circle Triangle L1 R1 L2 R2 Select Start L3 R3
Up Right Down Left L_Analog_controls R_Analog_controls
```
The bundled manifests used different action names. Nearly every lookup
therefore returned handle `0`; only `Select` matched by accident and was
incorrectly bound to `A`. The game made no `GetActionSetHandle` or
`ActivateActionSet` calls, so the provider retained its lexicographically first
action set. The validated fix was to add
`steam_settings/controller/00_Default.txt` so the compatible set becomes the
default:
```ini
Square=X
Cross=A
Circle=B
Triangle=Y
L1=LBUMPER
R1=RBUMPER
L2=DLTRIGGER
R2=DRTRIGGER
Select=BACK
Start=START
L3=LSTICK
R3=RSTICK
Up=DUP
Right=DRIGHT
Down=DDOWN
Left=DLEFT
L_Analog_controls=LJOY=joystick_move
R_Analog_controls=RJOY=joystick_move
```
The regular provider build was restored after tracing. Direct mode was then
validated end-to-end without Steam:
Steam Controller → SDL3 → uinput Xbox 360 → Wine XInput → `gbe_fork` named
actions → game. This is a game/build-specific compatibility record, not a
generic mapping to install into unrelated games.
Runi models this as a **local Steam API provider**, not as a special case
for one project. Detection is capability-based and conservative: an ordinary
`steam_api64.dll` is not enough because official games ship it too. Embedded
provider markers, the adjacent configuration layout, exported interfaces and
controller action sets are normalized by provider adapters. Goldberg original,
Goldberg-derived forks, RUNE-style configuration and unknown local providers
all produce the same capability report; the launcher never branches on a
release-group name.
The original Goldberg emulator documents a SteamController/SteamInput bridge
that accepts XInput and maps named actions through files in
`steam_settings/controller`. A complete original-style configuration can
therefore prefer Runi's Direct virtual Xbox path. Goldberg-derived forks
may instead expose a supported non-Steam takeover setting in
`configs.main.ini`. When that exact capability is detected, Steam mode may set
`disable_steamoverlaygameid_env_var=1`, preserving
`configs.main.ini.runi-backup`. Unknown providers are reported but never
modified. Runi does not replace or rename Steam API DLLs.
A second Digimon installation supplied a useful independent validation. It
contains the same game executable and original Steam API payload as the GBE
installation, but uses `steam_emu.ini` from RUNE. That configuration declares
`SteamInput006`, an enabled controller, a `controls` action set and explicit
Xbox mappings. Runi therefore reports `steam_input_via_xinput` and prefers
Direct without modifying the provider. The same configuration also exposes
its per-prefix save location under
`users/Public/Documents/Steam/RUNE/1984270`. RUNE is implemented as one
adapter; future layouts can add an adapter while recommendations remain driven
by observed capabilities.
## Projects inspected
- [Steam Controller Desktop Input Toggle](https://github.com/GloriousEggroll/steam-controller-desktop-input)
demonstrates that Steam's internal controller API can persistently select
its empty Desktop action-set configuration for direct USB (`28de:1302`) and
wireless-dongle (`28de:1304`) connections. Runi uses those transport
identities for its session-scoped Direct bridge, but does not integrate the
tool: the inspected repository has no explicit license, is very new, and
requires an unauthenticated local CEF debugging endpoint plus undocumented
Steam UI APIs. Any future integration needs an opt-in security review,
verified restore semantics and crash recovery; Node.js and CEF remote
debugging are not Runi runtime requirements.
- [SteamlessController](https://github.com/ddeverill/SteamlessController)
validates the same architecture on Windows: raw Steam Controller HID,
lizard-mode suppression and a virtual Xbox through ViGEm. Useful future
ideas include its documented 2026 HID reports, heartbeat, reverse rumble,
multiple devices, back buttons, trackpads, gyro and diagnostic probes.
- [SISR](https://github.com/Alia5/SISR) redirects Steam Input configurations to
system-level virtual devices. On Linux it currently requires Steam, CEF
remote debugging, USBIP and a VIIPER system service; it is inspiration, not
a Runi dependency.
- reWASD is a proprietary Windows product and is useful only as UX inspiration
for per-game profiles and remapping.
- [Steam Shortcut Manager](https://github.com/CorporalQuesadilla/Steam-Shortcut-Manager/wiki/Steam-Shortcuts-Documentation)
documents `shortcuts.vdf`, CRC-based IDs and `steam://rungameid` URLs. Its
2018 byte parser is not reused; Runi uses Python's `vdf` package and
preserves modern and unknown fields.
- Heroic and Lutris do not provide their own Steam Input implementation. They
run normally without Steam and optionally create non-Steam shortcuts. Their
reported failure mode—launcher bootstrap exits while the game remains
open—informed Runi's blocking `steam-launch` command.
- `sc-controller` remains design input for profiles, gyro, haptics and a
virtual keyboard, not a runtime dependency.
## Known limitations in v1
- Only the Steam Controller is a supported physical controller.
- Xbox Direct exposes the standard Xbox control set. Keyboard & Mouse profiles
can use rear buttons and the right trackpad; gyro, rumble and per-mode layers
are not yet forwarded.
- Steam mode needs a locally installed Steam client and a signed-in user.
- Account selection is automatic when several local accounts exist.
- A non-Steam shortcut cannot make every replaced or incompatible local Steam
API provider communicate with the real Steam client. Known behavior is
selected from reported capabilities; unknown providers remain advisory.
- No Steam API DLL, driver, SISR, VIIPER or reWASD component is distributed.