5.0 KiB
5.0 KiB
Thrustmaster Sol-R 1 -> Space Engineers input mapping (Linux/Arch/Hyprland)
Goal
Fix "throttle behaves like a trigger" bug and build a full custom input scheme for the Sol-R1 (grip + base) in Space Engineers, on Arch/Hyprland, without Windows/T.A.R.G.E.T.
Stack decided on
- joyful (AUR:
yay -S joyful) - Go-based Linux joystick remapper, creates a virtual gamepad device from physical Sol-R inputs via YAML rules in~/.config/joyful/. Chosen over input-remapper because it natively supports split-axis and segmented-axis-to-button mappings, which input-remapper doesn't (see upstream issue #900). - switch_sync.py (custom, in this repo/outputs) - a separate stateful daemon for switches that need to reconcile physical position against actual in-game state (bypasses joyful, which can't do this since it has no game-state feedback).
- Steam launch options wrapper script - starts/stops joyful and
switch_sync around the actual game session via
%command%, instead of a Hyprland-IPC window-watcher (Proton WM_CLASS is unreliable for this). NOTE: Steam launch options are per-machine (stored inuserdata/<id>/config/localconfig.vdf, not synced) - the wrapper script path should be identical across machines (keep it in dotfiles), but the launch-options string itself must be re-pasted into Steam on each machine.
Diagnostic tools (already written, in outputs/)
find_codes.py- run this first on every physical control to get real evdev BTN_/ABS_ codes. Nothing below has real codes yet - they're allTODO:placeholders until this is run.switch_sync.py- screen-reads HUD state viagrimregion capture + color sampling, compares against physical switch position (evdev), and taps a virtual key only when they disagree. Has--sample X,Yfor pixel-color calibration and--dump-example-config.
Current mapping plan
| Physical control | SE function | Mechanism |
|---|---|---|
| Throttle (base) | Move Forward/Backward | joyful axis passthrough (was original bug: SE read raw HID axis like a trigger) |
| Main stick (grip) X/Y | View/look/rotate | joyful axis passthrough |
| Ministick (grip) | Strafe horizontal/vertical | joyful axis passthrough |
| Dual-position trigger, stage 1/2 | Primary/secondary click (use/fire, alt-fire) | joyful button passthrough |
| 2 orange buttons (top of stick) | Dampeners on/off (Z) + Relative dampers (Ctrl+Z) | joyful, KEY_Z / [KEY_LEFTCTRL, KEY_Z] key emulation (not gamepad buttons - Relative Dampers isn't confirmed as a standalone bindable action, it's a modifier on the toggle key) |
| Rotary knob 1 (base) | Switch toolbar (next/prev, default ,/.) |
joyful, detent pulses -> KEY_COMMA/KEY_DOT - ASSUMES knob reports as BTN pulses, not an ABS axis; confirm with find_codes.py |
| Rotary knob 2 (base) | Hotbar slot select | joyful, same pulse assumption as above; if it's an ABS axis instead, needs joyful's segmented axis-to-button feature |
| 4-button group (base, left side) | Jetpack / Helmet / Flashlight / Parking toggles | joyful button passthrough to virtual gamepad buttons, bound in SE's own control menu |
| 4 tilt switches (base, top), 2 buttons each | Same 4 toggles as above, but as REAL on/off switches | switch_sync.py - NOT joyful, because this needs game-state feedback to avoid desync |
Known caveats / open items
- SE's native joystick support only got real per-binding customization in update 1.209; before that (and possibly still, depending on version) it treated joysticks through an Xbox-controller-shaped abstraction. Check Options -> Controls -> Controllers on the current version before assuming full flexibility.
- Dampeners are a stateless toggle (Z) + contextual modifier (Ctrl+Z, only works if dampers are off AND you're looking at a moving target in range). No true 3-state action exists in SE.
switch_sync.py's HUD detection is pixel-color based and fragile: breaks on HUD detail mode changes (Tab), resolution/UI scale changes, or camera view changes. Needs recalibration after any of those.- joyful's exact per-rule-type YAML field names (beyond the top-level
devices/modes/rules structure and evdev keycode naming) were not fully
verified against
examples/ruletypes.ymlin the actual installed version - check that file before trusting rule blocks verbatim. - None of the joyful config blocks have real BTN_/ABS_ codes yet - all placeholders pending find_codes.py output on the actual hardware.
Next steps
- Run find_codes.py against every physical control, replace all TODOs.
- Confirm rotary knobs are pulse (BTN) vs continuous (ABS) - determines which joyful rule type to use.
- Verify SE's actual keybinds for helmet/flashlight/parking in Options before finalizing switch_sync.py's toggle_key values.
- Calibrate switch_sync.py's HUD regions/colors for jetpack, helmet, light, parking indicators.
- Write the Steam launch-options wrapper script, add to dotfiles repo.
- Repeat launch-options paste on each machine (desktop, Framework 12, ThinkPad T440p) - not synced by Steam.