docs: user-facing documentation and signing key
This commit is contained in:
@@ -0,0 +1,208 @@
|
||||
<!-- 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.
|
||||
Reference in New Issue
Block a user