Files
SteamlessController/CMakeLists.txt
T
Dylan DeverillandClaude Opus 5 e4f3b51d33 Hold the virtual pad type for as long as its game is running (#92)
Alt-tabbing out of a game switched the profile back to the default
immediately, and a profile that changes platform tears the virtual pad
down and builds it again. That is the Windows device connect/disconnect
sound the report describes, twice per round trip — and worse than the
noise, a game polling XInput sees its controller unplugged and replugged
while it is still running.

The delay that should have covered this never ran. RELEASE_GRACE_MS
guards handing the physical controller back, and in Manual mode
WantControlNow keeps answering yes when the foreground changes, so the
profile was pushed with nothing in the way. Exposing that delay as a
setting, which was the plan, would have made a number configurable that
this path never consults.

The unit being switched was also wrong. A platform is only meaningful to
a game: desktop bindings are keyboard and mouse going out through
SendInput, which never touches the virtual pad, so nothing on the desktop
cares what type it is. Bindings are the opposite — they describe what the
user is doing right now, and when the desktop is in front the desktop's
bindings are the ones wanted.

So the two are split. Bindings still follow the foreground, exactly as
before. The pad type follows whichever profiled game is running: claimed
when a game comes to the front, kept across alt-tabs, and released when
the process exits — the one moment the rebuild costs nothing, because
the game that needed it has gone.

Liveness is a wait on a process handle, falling back to re-opening by pid
where anti-cheat refuses one, since being denied access to a process is
proof it exists. Polled every two seconds rather than watched: nothing is
looking at the pad in the moment a game exits.

Deliberately does nothing for AutoMode::OffUnlessProfile, which releases
the physical controller the moment a profiled game is not in front and
destroys the pad outright — leaving no pad to hold a type for. That mode
exists to be restrictive and stays that way.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-06 23:33:38 -07:00

219 lines
7.4 KiB
CMake

cmake_minimum_required(VERSION 3.20)
project(SteamlessController CXX)
include(FetchContent)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# Shared HID + Steam Controller library code
add_library(SteamController STATIC
src/hid/HidDevice.cpp
src/steam/SteamController.cpp
)
target_include_directories(SteamController PUBLIC src)
target_link_libraries(SteamController PUBLIC hid setupapi)
if (MSVC)
target_compile_options(SteamController PRIVATE /W4 /WX /utf-8)
endif()
# Diagnostic probe tool (console)
add_executable(SteamProbe
src/main.cpp
)
target_link_libraries(SteamProbe PRIVATE SteamController)
if (MSVC)
target_compile_options(SteamProbe PRIVATE /W4 /WX /utf-8)
set_target_properties(SteamProbe PROPERTIES LINK_FLAGS_DEBUG "/SUBSYSTEM:CONSOLE")
endif()
# ViGEmClient (built from source, static)
add_library(ViGEmClient STATIC
third_party/ViGEmClient-b66d02d/src/ViGEmClient.cpp
)
target_include_directories(ViGEmClient PUBLIC
third_party/ViGEmClient-b66d02d/include
)
target_link_libraries(ViGEmClient PUBLIC setupapi)
if (MSVC)
target_compile_options(ViGEmClient PRIVATE /W0 /utf-8 /wd4828)
endif()
# RawGameController diagnostic probe (WinRT)
add_executable(RawControllerProbe
src/probe/RawControllerProbe.cpp
)
target_link_libraries(RawControllerProbe PRIVATE windowsapp)
if (MSVC)
target_compile_options(RawControllerProbe PRIVATE /W4 /utf-8)
endif()
# Game-library diagnostic probe — dumps every source the picker draws on
add_executable(GameLibraryProbe
src/probe/GameLibraryProbe.cpp
src/app/GameLibrary.cpp
src/app/LauncherGames.cpp
src/app/ProcessIdentity.cpp
)
target_include_directories(GameLibraryProbe PRIVATE src)
target_link_libraries(GameLibraryProbe PRIVATE ole32 shell32 shlwapi version windowsapp)
if (MSVC)
target_compile_options(GameLibraryProbe PRIVATE /W4 /WX /utf-8)
set_target_properties(GameLibraryProbe PROPERTIES LINK_FLAGS_DEBUG "/SUBSYSTEM:CONSOLE")
endif()
# Foreground-watcher diagnostic probe — prints what the per-game profile
# matcher would see as the user switches applications
add_executable(ForegroundProbe
src/probe/ForegroundProbe.cpp
src/app/ForegroundWatcher.cpp
src/app/ProcessIdentity.cpp
)
target_include_directories(ForegroundProbe PRIVATE src)
if (MSVC)
target_compile_options(ForegroundProbe PRIVATE /W4 /WX /utf-8)
set_target_properties(ForegroundProbe PROPERTIES LINK_FLAGS_DEBUG "/SUBSYSTEM:CONSOLE")
endif()
# Steam manifest diagnostic probe — dumps the app id to install directory map
# that per-game profiles for Steam games are matched through
add_executable(SteamAppProbe
src/probe/SteamAppProbe.cpp
src/app/SteamAppLocator.cpp
)
target_include_directories(SteamAppProbe PRIVATE src)
if (MSVC)
target_compile_options(SteamAppProbe PRIVATE /W4 /WX /utf-8)
set_target_properties(SteamAppProbe PROPERTIES LINK_FLAGS_DEBUG "/SUBSYSTEM:CONSOLE")
endif()
# Binding encoding checks — the packed and wire forms both have to keep
# decoding values written by earlier builds
add_executable(BindingCodecProbe
src/probe/BindingCodecProbe.cpp
)
target_include_directories(BindingCodecProbe PRIVATE src)
if (MSVC)
target_compile_options(BindingCodecProbe PRIVATE /W4 /WX /utf-8)
set_target_properties(BindingCodecProbe PROPERTIES LINK_FLAGS_DEBUG "/SUBSYSTEM:CONSOLE")
endif()
# Virtual gamepad bus probe — dumps every bus the ViGEm client can see, which
# is the one question a "vigem_connect failed" report cannot answer on its own
add_executable(ViGEmBusProbe
src/probe/ViGEmBusProbe.cpp
src/app/ViGEmBusInfo.cpp
)
target_include_directories(ViGEmBusProbe PRIVATE src)
target_link_libraries(ViGEmBusProbe PRIVATE ViGEmClient)
if (MSVC)
target_compile_options(ViGEmBusProbe PRIVATE /W4 /WX /utf-8 /wd4828)
set_target_properties(ViGEmBusProbe PROPERTIES LINK_FLAGS_DEBUG "/SUBSYSTEM:CONSOLE")
endif()
# Elevated on-demand helper — restarts the controller device node so the tray
# app (which never runs elevated) can take the device from a running Steam.
add_executable(SteamlessDeviceCycle WIN32
src/helper/DeviceCycleHelper.cpp
src/app/DeviceRestart.cpp
src/app/HandleFinder.cpp
)
target_link_libraries(SteamlessDeviceCycle PRIVATE SteamController cfgmgr32)
if (MSVC)
target_compile_options(SteamlessDeviceCycle PRIVATE /W4 /WX /utf-8)
endif()
# Directional pad checks — the sector maths wraps at 0/360, holds a sector
# against hysteresis, and latches a press's zone, none of which fail loudly
add_executable(TrackpadDirectionProbe
src/probe/TrackpadDirectionProbe.cpp
src/app/TrackpadInput.cpp
src/app/InputInjection.cpp
src/app/EventLog.cpp
)
target_include_directories(TrackpadDirectionProbe PRIVATE src)
target_link_libraries(TrackpadDirectionProbe PRIVATE SteamController)
if (MSVC)
target_compile_options(TrackpadDirectionProbe PRIVATE /W4 /WX /utf-8)
set_target_properties(TrackpadDirectionProbe PROPERTIES LINK_FLAGS_DEBUG "/SUBSYSTEM:CONSOLE")
endif()
# TrackpadZoneProbe — measures where a thumb lands when it clicks a pad, so the
# Directional Pad ring boundary is calibrated rather than guessed.
add_executable(TrackpadZoneProbe
src/probe/TrackpadZoneProbe.cpp
)
target_link_libraries(TrackpadZoneProbe PRIVATE SteamController)
if (MSVC)
target_compile_options(TrackpadZoneProbe PRIVATE /W4 /WX /utf-8)
set_target_properties(TrackpadZoneProbe PROPERTIES LINK_FLAGS_DEBUG "/SUBSYSTEM:CONSOLE")
endif()
# HapticTester — local-only interactive haptic tuning tool (source is gitignored)
if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/src/probe/HapticTester.cpp")
add_executable(HapticTester src/probe/HapticTester.cpp)
target_link_libraries(HapticTester PRIVATE SteamController)
if (MSVC)
target_compile_options(HapticTester PRIVATE /W4 /utf-8)
set_target_properties(HapticTester PROPERTIES LINK_FLAGS_DEBUG "/SUBSYSTEM:CONSOLE")
endif()
endif()
# WebView2 — downloaded from NuGet (.nupkg is a zip; FetchContent extracts it)
FetchContent_Declare(WebView2
URL https://www.nuget.org/api/v2/package/Microsoft.Web.WebView2/1.0.2849.39
DOWNLOAD_EXTRACT_TIMESTAMP ON
)
FetchContent_MakeAvailable(WebView2)
# Map CMake architecture to the NuGet lib subdirectory name
if(CMAKE_SIZEOF_VOID_P EQUAL 8)
set(WV2_ARCH "x64")
else()
set(WV2_ARCH "x86")
endif()
# System tray app (no console window)
add_executable(SteamlessController WIN32
src/app/main.cpp
src/app/TrayApp.cpp
src/app/ControllerManager.cpp
src/app/VirtualController.cpp
src/app/TrackpadInput.cpp
src/app/KeyInput.cpp
src/app/TouchKeyboard.cpp
src/app/InputInjection.cpp
src/app/SteamWatcher.cpp
src/app/DeviceRestart.cpp
src/app/EventLog.cpp
src/app/ForegroundWatcher.cpp
src/app/GameLibrary.cpp
src/app/GameLiveness.cpp
src/app/GameProfiles.cpp
src/app/HandleFinder.cpp
src/app/LauncherGames.cpp
src/app/ProcessIdentity.cpp
src/app/RemapWindow.cpp
src/app/SteamAppLocator.cpp
src/app/ViGEmBusInfo.cpp
resources/app.rc
)
target_include_directories(SteamlessController PRIVATE
resources
${webview2_SOURCE_DIR}/build/native/include
)
target_link_libraries(SteamlessController PRIVATE
SteamController
ViGEmClient
${webview2_SOURCE_DIR}/build/native/${WV2_ARCH}/WebView2LoaderStatic.lib
version
ole32
shell32
shlwapi
cfgmgr32
windowsapp
)
if (MSVC)
target_compile_options(SteamlessController PRIVATE /W4 /WX /utf-8 /wd4828)
endif()