Files
SteamlessController/src/app/ProcessIdentity.h
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

51 lines
2.1 KiB
C++

#pragma once
#include <Windows.h>
#include <string>
// Who owns a window.
//
// Both fields are filled where they apply: every process has an image path,
// and only a packaged (MSIX/UWP) one has an AUMID. A window we could not
// identify at all — a protected process, or one that closed while being
// asked — reports empty, which callers should read as "unknown", never as
// "the desktop".
struct ForegroundIdentity {
std::wstring exePath;
std::wstring aumid;
// The process this identity was read from, so a caller can go back and
// ask whether it is still running. Zero when nothing was resolved.
//
// Deliberately absent from operator== below: it identifies one run of an
// application, while the rest of this identifies the application. The
// foreground watcher compares identities to suppress repeat notifications,
// and a game relaunched is still the same game to every profile that
// matches it.
DWORD pid = 0;
bool Empty() const { return exePath.empty() && aumid.empty(); }
bool operator==(const ForegroundIdentity& o) const {
return _wcsicmp(exePath.c_str(), o.exePath.c_str()) == 0
&& _wcsicmp(aumid.c_str(), o.aumid.c_str()) == 0;
}
bool operator!=(const ForegroundIdentity& o) const { return !(*this == o); }
};
// Resolving a window to the application behind it, shared by the two things
// that need to do it: ForegroundWatcher, which asks about whatever the user
// switched to, and the remap window's running-app picker, which asks the same
// question of every open window at once. Both have to handle packaged apps
// the same way, so the frame-host unwrapping below lives here rather than in
// either caller.
namespace ProcessIdentity {
// Image path and AUMID for a process id. Empty for a process we cannot open,
// which for an unelevated caller means a protected or elevated one.
ForegroundIdentity ForProcess(DWORD pid);
// The application a top-level window belongs to, with packaged apps resolved
// past their host — see the note on kFrameHostExe in the implementation.
ForegroundIdentity ForWindow(HWND hwnd);
} // namespace ProcessIdentity