209 lines
11 KiB
Markdown
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.
|