Dylan DeverillandClaude Opus 5 93ff4a0f52 Resolve directional pad directions in TrackpadInput
Phase 2 of the v1.20 trackpad work. Nothing dispatches these yet — the
directions are resolved and handed out, and Phase 3 wires them to the
virtual pad and to SendInput.

TrackpadMouse is renamed TrackpadInput. The name stopped being true once
the class resolves d-pad directions as well as driving the mouse; the
registry value L"TrackpadMouse" keeps its name, being the retired toggle
this has nothing to do with.

Directions come from the click rather than from touch, so a resting thumb
presses nothing, and the pad is split by where the click lands: inside
kPadRingRadius the press is the pad's click binding, outside it is the
direction under the thumb. The zone is decided once on the rising edge
and never revisited — sliding from ring to centre mid-press would
otherwise release a direction and fire the click, which is one physical
press meaning two things and the collision the split exists to stop.
Within a ring press the direction does keep following the thumb, so a
roll from up to right registers as both in turn.

Eight-way presses two directions on a diagonal; four-way rounds to the
nearer cardinal. A sector is held until the thumb clears its boundary by
8 degrees, because directions are edge-dispatched and a thumb parked on a
boundary would otherwise emit a stream of key down/up pairs rather than
merely flickering.

TrackpadDirectionProbe drives all of this through synthesized state
reports and checks 50 cases: sector bearings including the wrap through
0/360, both diagonal modes, the ring boundary either side, the zone
latching in both directions, release clearing state, hysteresis both
holding and letting go, every other mode resolving nothing, and a short
report being ignored. Feeding whole reports rather than calling the
resolver also covers the byte offsets, which is where a left-pad reader
ends up reading right-pad coordinates.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-06 21:26:09 -07:00
2026-05-10 19:28:03 -07:00

SteamlessController

A lightweight Windows system tray app that lets you use a Steam Controller as a standard gamepad — without Steam running.

image

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 (or PlayStation controller) via ViGEmBus, making it compatible with any game that supports the Xbox or PlayStation controller.

Features

  • Supports all connection modes! Puck, wired, and Bluetooth!
  • Runs alongside Steam! No need to close Steam to toggle modes or force you to use your PC in a weird way.
  • Brand new extensive remapping controls for back buttons and trackpads. Save custom mappings and load them per game!
  • Enable/Disable manually, or use new auto modes to turn on / off when it makes sense.
  • Start on launch, suppress notifications, make this utility your own.
image image

Requirements

To run

  • Windows 10 or later (64-bit)
  • ViGEmBus driver installed
  • Steam Controller — wired USB (PID 0x1302), wireless dongle (PID 0x1304), or Bluetooth LE (PID 0x1303, 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).
GameLibraryProbe Console diagnostic — lists every application the per-game profile picker can find, with the source each came from and a per-source count. Ask for its output when someone reports that a game is missing from the picker. Given a directory, it instead runs only the Epic manifest reader against it.

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.

Per-game profiles

A profile can be attached to one game instead of applying everywhere. The game is chosen from a picker that draws on several sources at once: both Start Menu and both Desktop shortcut folders, the installed MSIX/Store package list, Steam's own manifests, and the records kept by the Epic Games Store, GOG Galaxy, Ubisoft Connect and Battle.net. Anything none of those know about — a portable build, an emulator, an itch.io download — can be added by hand, either by picking it out of the applications currently running or by browsing to its .exe.

Whichever profile applies is decided by what is in the foreground, not by what is running: a launcher that stays resident long after the game exits would otherwise keep claiming the controller. See src/app/ForegroundWatcher.h for why, and src/app/GameLibrary.h for the four shapes a game's saved identity can take.

Third-party

  • ViGEmClient — MIT License, built from source as a static library
S
Description
Support for the 2026 Steam Controller in non-Steam games
Readme MIT
1.3 MiB
0 Stars 1 Watchers 0 Forks
2026-09-23 01:17:41 -05:00
Languages
C++ 98%
CMake 1%
Inno Setup 0.9%