The acquire path has cycled a single interface since the receiver work; the release path still took down all four. Only one slot is ever occupied, so the other three were being disabled and re-enabled for nothing — and each one handed Steam another arrival to react to, when the only arrival that matters is on the interface it will actually read the controller from. The cost was hidden until the reopen veto was fixed, because the vetoes dominated: a release cycle ran 15.47s, of which about 13s was two vetoed interfaces. Measured end to end by launching a Steam game in auto mode 2, which is the only thing that exercises this path: 10:33:30.394 AUTO: steam state 2, want=0 10:33:30.394 RELEASE: closing all device handles 10:33:30.459 RELEASE: cycling device so Steam sees an arrival 10:33:31.512 CYCLE: done after 1063 ms 10:33:31.518 CONNECT mi_02, and the rest after it 1.06s against 15.47s, one interface cycled rather than four, no Kernel-PnP 225 events, and the reopens land after the cycle instead of 26ms into it. The handoff itself is confirmed rather than assumed: quitting the game had our next claim refused with "another process holds a write handle", so Steam had genuinely adopted the controller in the interval. We then took it back on one narrowed cycle, 2.2s from the game exiting to game mode being live. Unconditional here, unlike the acquire path, which narrows only its first attempt so that a wrong guess cannot keep costing it across retries. A release has no second attempt to protect, and an empty path already deletes the request file and falls back to cycling everything. LastLivePath is read before ReleaseDevices rather than after. The value survives either way — it is only ever assigned, never cleared — but taking it while the slots are still up has the two paths asking the same question at the same point. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
SteamlessController
A lightweight Windows system tray app that lets you use a Steam Controller as a standard gamepad — without Steam running.
When Steamless Mode is active, the app disables the controller's built-in keyboard/mouse emulation (lizard mode) and exposes it as a virtual Xbox 360 controller via ViGEmBus, making it compatible with any game that supports XInput or the Xbox controller.
Features
- NEW in 1.9 - Bluetooth! Use the controller wired, through the dongle, or paired over Bluetooth LE
- NEW in 1.8 - Automatically enable/disable based on if Steam is running, or a Steam game is running!
- NEW in 1.8 - Advanced debugging and local logging to help diagnose issues
- Rumble Support v2.0
- Support for multiple Steam Controllers! Long live split screen!
- New shiny UI for remapping L4/L5 R4/L5 back buttons!
- Support for Xbox OR PlayStation controllers - with PS Touchpad and Gyro plumbing.
- System tray icon shows connection and mode status
- Steamless Mode — disables lizard mode and exposes controller as Xbox 360 gamepad
- Trackpad Mouse — use the right (or left) trackpad as a mouse cursor
- Back Buttons for Clicking — the upper paddles (L4/R4) left-click by default; rebind any paddle to a gamepad button, right click, or Off
- Use Left Trackpad Instead — mirror all trackpad/back-button functionality to the left side for left-handed users
- Start with Windows — launch automatically at login
- Settings persist across restarts
Requirements
To run
- Windows 10 or later (64-bit)
- ViGEmBus driver installed
- Steam Controller — wired USB (PID
0x1302), wireless dongle (PID0x1304), or Bluetooth LE (PID0x1303, pair via Windows Bluetooth settings) - Steam closed (Steam claims the controller when running)
To build
- Visual Studio 2022 with the Desktop development with C++ workload
- CMake 3.20 or later (included with Visual Studio, or install separately)
- Windows SDK 10.0.22000 or later (installed via Visual Studio Installer)
Building
git clone https://github.com/your-username/SteamlessController.git
cd SteamlessController
cmake -B build
cmake --build build --target SteamlessController
The executable will be at build\Debug\SteamlessController.exe.
For a release build:
cmake -B build/release -G "Visual Studio 18 2025"
cmake --build build/release --config Release --target SteamlessController
If you have Visual Studio 2022, replace
"Visual Studio 18 2025"with"Visual Studio 17 2022".
Building the installer
The installer is compiled from resources\InnoInstallerScript.iss with Inno Setup. It needs two things a fresh clone does not have.
Release builds of all three executables it packages. The command in the previous section builds only the tray app:
cmake --build build/release --config Release --target SteamlessController
cmake --build build/release --config Release --target SteamlessDeviceCycle
cmake --build build/release --config Release --target ViGEmBusProbe
SteamlessDeviceCycle is the elevated helper that restarts the controller's device node. ViGEmBusProbe is how Setup checks whether the driver actually installed, and it stays in the install folder afterwards as a diagnostic.
The ViGEmBus driver installer, which is not in this repository — it is a 6 MB vendored binary, and *.exe is gitignored. Download it from ViGEmBus releases and place it here:
resources\ViGEmBus_1.22.0_x64_x86_arm64.exe
The filename must match #define ViGEmSetup at the top of the script. Inno resolves [Files] sources when it compiles, so a missing or differently named payload fails the build outright rather than producing an installer that silently ships no driver. If you update the ViGEmBus version, change the #define to match the new filename.
CMake Targets
| Target | Description |
|---|---|
SteamlessController |
Main system tray application |
SteamlessDeviceCycle |
Elevated helper that disables and re-enables the controller's device node, run as an on-demand scheduled task. Shipped by the installer. |
ViGEmBusProbe |
Reports whether a ViGEm bus is present by enumerating its interface. Used by Setup to verify the driver installed, and shipped as a diagnostic. |
SteamProbe |
Console diagnostic tool — dumps raw HID report bytes as you interact with the controller. Useful for protocol research. |
RawControllerProbe |
Checks whether Windows.Gaming.Input.RawGameController can enumerate the Steam Controller (requires WinRT). |
How it works
The Steam Controller exposes a vendor HID collection (usage page 0xFF00) that carries all game input in a 54-byte report (ID 0x42) at ~60 Hz. By default the firmware runs in lizard mode, emulating a keyboard and mouse so the controller works without drivers.
SteamlessController sends HID feature reports to disable lizard mode, then reads the raw input reports and translates them into a virtual Xbox 360 controller via ViGEmBus. A background heartbeat re-sends the disable command every 800 ms to keep lizard mode off.
The full input report layout is documented in src/steam/SteamController.h.
Third-party
- ViGEmClient — MIT License, built from source as a static library