horizon-wheel-tui: Second USB HID Device VID/PID Missing from Generated Profile, All Bindings Collapsed to Primary Device
When building a multi-device profile in horizon-wheel-tui, the TUI correctly enumerates a second USB HID device (Neo X Hub) during the binding step, but the serialized profile output omits that…
At a Glance
When building a multi-device profile in horizon-wheel-tui, the TUI correctly enumerates a second USB HID device (Neo X Hub) during the binding step, but the serialized profile output omits that device's VID/PID entirely.
Summary
When building a multi-device profile in horizon-wheel-tui, the TUI correctly enumerates a second USB HID device (Neo X Hub) during the binding step, but the serialized profile output omits that device's VID/PID entirely. As a result, every axis and button binding in the written profile is attributed to the primary wheelbase (Device 1), causing all secondary-device inputs to be silently ignored in-game despite the UI displaying the correct button icons.
Root-Cause Analysis
Confirmed Evidence
- The TUI's device-enumeration and bindings UI correctly identifies both devices; the second device appears on the bindings page with correct button mapping.
- The written profile does not contain the second device's VID/PID.
- All axes and buttons in the output file reference the first device (Simagic Evo Sport wheelbase) regardless of which device they were bound to interactively.
- The issue reproduces regardless of the role assigned to the second device (hub vs. shifter).
- In-game inputs from the second device produce no registered events, while the UI icons correctly reflect the intended bindings — confirming the disconnect is in profile serialization, not in TUI state.
Reasonable Inference
The bug most likely resides in the profile serialization path, not the device discovery path. During the interactive session, bindings are stored in an in-memory structure that correctly tracks the source device. When the profile is written to disk, the code appears to either:
- Fail to persist the device-index-to-VID/PID mapping for any device beyond index 0, causing all binding records to fall back to the primary device's identifiers, or
- Overwrite the device reference field with a hardcoded or default value (Device 1) during serialization of each binding entry.
Contributing Factor — Software-Passthrough Layer
The Neo X Hub is connected through SimHub / Control Mapper, which presents the physical hub as a virtual or remapped HID device to the OS. The VID/PID the OS exposes for this virtual device may differ from the hub's native identifiers. If horizon-wheel-tui captures the VID/PID at detection time but does not re-resolve or correctly store it when writing the profile, the passthrough layer introduces an additional opportunity for the identifier to be lost or mismatched.
Alternative Cause
It is also possible that the TUI correctly serializes a device index (e.g., device: 1) into the profile, but the game or the profile loader resolves that index against a system HID device list at runtime — and because the virtual SimHub/Control Mapper device is not enumerated at the same index during game startup, the loader silently re-maps everything to the device at index 0.
What Would Confirm the Diagnosis
- Inspecting the raw output profile file (likely JSON, YAML, or TOML) to determine whether a second device block is present but missing VID/PID, or is entirely absent.
- Checking whether removing SimHub/Control Mapper and connecting the Neo X Hub directly changes the VID/PID written to the profile.
- Reviewing the serialization function in the
horizon-wheel-tuisource responsible for writing device identifiers per binding.
Resolution Steps
- Inspect the generated profile file directly to determine the exact failure mode before attempting any workaround.
- Check whether the second device block exists in the output file. Look for VID/PID fields, device index fields, or device name fields in the binding entries for the second device.
- Connect the Neo X Hub directly via USB, bypassing SimHub/Control Mapper entirely, then recreate the profile. This isolates whether the software passthrough layer is altering the VID/PID that the TUI captures and serializes.
- If the direct-connect profile serializes correctly, the issue is the virtual VID/PID presented by SimHub/Control Mapper. In that case:
- Check SimHub/Control Mapper settings for options to preserve the original device VID/PID rather than presenting a synthetic one.
- Use the VID/PID of the virtual device (captured via the OS device manager or a HID tool) and, if the profile format supports it, manually correct the serialized profile to reference the correct identifier.
- If the profile is still malformed with a direct connection, the bug is in the serializer. As a temporary workaround, manually edit the output profile file to add the correct VID/PID for Device 2 in each affected binding entry.
- Report the exact profile file content (with any sensitive identifiers redacted) upstream so the maintainer can patch the serialization logic.
CLI Commands
Identify VID/PID of all connected HID devices on Linux:
lsusb
# or for detailed HID info:
ls /sys/bus/usb/devices/*/idVendor | xargs -I{} sh -c 'echo {} && cat {}'On Windows (PowerShell), enumerate HID devices and their hardware IDs:
Get-PnpDevice -Class HIDClass | Select-Object FriendlyName, InstanceId | Sort-Object FriendlyNameOn macOS, list USB devices with VID/PID:
system_profiler SPUSBDataType | grep -A 10 "Neo\|SimHub\|<DEVICE_NAME>"Inspect the generated profile for device identifier fields (adjust filename and field names to match the actual format):
# If JSON
cat <PROFILE_FILE>.json | python3 -m json.tool | grep -i -E "vid|pid|device_id|device"
# If YAML
grep -i -E "vid|pid|device" <PROFILE_FILE>.yamlVerification
After applying a fix or workaround:
- Open the generated profile file and confirm two distinct device entries are present, each with the correct VID/PID for its physical (or virtual) device.
- Load the profile in-game. Press buttons on the Neo X Hub and verify inputs register correctly in the game's input overlay or button test screen.
- On Linux, use
evtestorjstestto confirm OS-level input from the second device while the profile is active:
# List available input devices
ls /dev/input/by-id/
# Monitor events from the second device
evtest /dev/input/by-id/<SECOND_DEVICE_EVENT_NODE>- If the profile reverts to broken behavior after any TUI re-save, the serialization bug is confirmed and the fix must come from upstream.
Rollback indicator: If in-game inputs remain unregistered after edits, diff the profile file against a known-good state to confirm the changes persisted correctly.
Prevention
- Profile validation step: Add a post-serialization check to the TUI that reads the written profile back and asserts that every device referenced in the in-memory binding map has a corresponding VID/PID entry in the output file. Fail loudly if a device entry is missing.
- Device identity logging: During profile write, log the VID/PID being serialized per device so discrepancies are visible without requiring manual file inspection.
- Integration test: Add a test case covering multi-device profiles that asserts the serialized output contains one device block per detected device with non-null/non-default identifiers.
- Document passthrough-layer behavior: Note in the project README that devices connected through software HID bridges (SimHub, Control Mapper, vJoy, etc.) may present different VID/PIDs than their native hardware; users should verify the virtual VID/PID using OS tools before creating profiles.
- Explicit device-role enforcement at serialization: Ensure that the role assignment (hub, shifter, wheelbase) does not reset or overwrite the device reference field in binding records during profile generation.