mirror of
https://github.com/ddeverill/SteamlessController.git
synced 2026-10-21 22:22:16 -05:00
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>
51 lines
2.1 KiB
C++
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
|