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

11 KiB

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:

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:

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 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 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 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 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.