mirror of
https://github.com/ddeverill/SteamlessController.git
synced 2026-10-21 22:22:16 -05:00
Cycle the controller through the helper even when already elevated
RestartControllerDevices took a shortcut on an elevated run: rather than firing the helper, it cycled every interface itself, in a loop, on the UI thread. Each interface disables, waits a second for the removal to propagate, and enables, so a four-interface receiver spent about five seconds inside that loop with no message pump running. Nothing was pumping, so every WM_DEVICECHANGE the cycle produced sat in the queue until it finished. The first interface was back — and taken by Steam — seconds before we drained those messages and claimed. A reporter's log in #79 shows it exactly: RELEASE at 15:49:26.049, then five seconds of no log lines at all, then four CONNECTs inside 22ms as the queue emptied, then the claim refused. Eleven of that session's fourteen cycles ran this path and lost; the three that went through the helper are the ones that behaved. It also explains the app appearing to hang mid-cycle, reported as the tray menu opening as a white rectangle: the UI thread was inside the cycle. The helper is a separate process, so the cycle runs while this one keeps pumping and the arrival drives the claim as soon as it lands. Measured on the same elevated configuration: helper starts 56ms after the request, whole cycle-to-claim 1.27s, message loop live throughout. Dropping the shortcut also removes a fork that had already drifted — the in-process path wrote nothing to cycle.log, so an elevated run left no record of a cycle having happened at all, which is why the helper's holder scan never appeared in that reporter's logs. The three EnsureCycleTaskRegistered call sites no longer skip registration when elevated. They assumed an elevated run never needed the task; it does now. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
c3f58de856
commit
5e220d4724
1 file changed
+22
-35
+22
-35
@@ -242,8 +242,10 @@ LRESULT TrayApp::HandleMessage(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) {
|
||||
// Taking the device back from a running Steam needs the
|
||||
// elevated helper. Settle the one-time UAC prompt on this
|
||||
// click instead of letting it ambush the first cycle.
|
||||
if (!IsProcessElevated())
|
||||
EnsureCycleTaskRegistered();
|
||||
// Registered even when already elevated: every cycle goes
|
||||
// through the helper now, so an elevated run needs the task
|
||||
// just as much.
|
||||
EnsureCycleTaskRegistered();
|
||||
m_wantControl = true;
|
||||
m_acquireRetries = 0;
|
||||
TryAcquireController();
|
||||
@@ -260,16 +262,14 @@ LRESULT TrayApp::HandleMessage(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) {
|
||||
break;
|
||||
case IDM_MODE_GAME:
|
||||
// Make sure the elevated helper task exists (one-time UAC when
|
||||
// the installer didn't register it) before the mode needs it.
|
||||
if (!IsProcessElevated())
|
||||
EnsureCycleTaskRegistered();
|
||||
// the installer did not register it) before the mode needs it.
|
||||
EnsureCycleTaskRegistered();
|
||||
SetAutoMode(AutoMode::OffOnlyInGame);
|
||||
break;
|
||||
case IDM_MODE_PROFILE:
|
||||
// Same reason as the mode above: this one can be asked to take the
|
||||
// controller while Steam is running it, and that needs the helper.
|
||||
if (!IsProcessElevated())
|
||||
EnsureCycleTaskRegistered();
|
||||
EnsureCycleTaskRegistered();
|
||||
SetAutoMode(AutoMode::OffUnlessProfile);
|
||||
// With nothing to match, this mode holds the controller for
|
||||
// nothing at all. Said once, on the click, because a controller
|
||||
@@ -1045,34 +1045,21 @@ bool TrayApp::RestartControllerDevices() {
|
||||
m_inFlightLogged = false;
|
||||
m_lastCycleTick = GetTickCount64();
|
||||
|
||||
// On the rare elevated run, cycle directly. This works on every transport
|
||||
// including Bluetooth: the devnode being restarted is the HID child, not
|
||||
// the pairing (which lives up at the BTHLEDEVICE layer), and it
|
||||
// re-enumerates as fast as a USB replug.
|
||||
if (IsProcessElevated()) {
|
||||
bool anyRestarted = false;
|
||||
DeviceRestart::CycleResult summary;
|
||||
for (const auto& path : SteamController::EnumerateAll()) {
|
||||
DeviceRestart::CycleResult r;
|
||||
if (DeviceRestart::RestartInterfaceDevice(path, r))
|
||||
anyRestarted = true;
|
||||
const bool better = r.kind > summary.kind
|
||||
|| (r.kind == summary.kind
|
||||
&& summary.vetoType == PNP_VetoTypeUnknown
|
||||
&& r.vetoType != PNP_VetoTypeUnknown);
|
||||
if (better) summary = r;
|
||||
}
|
||||
// Same status file the helper writes, so the verdict logic below does
|
||||
// not care which path performed the cycle.
|
||||
summary.ticks = GetTickCount64();
|
||||
DeviceRestart::WriteCycleStatus(summary);
|
||||
// Synchronous: the cycle has already finished and the device is back,
|
||||
// so there is nothing left to hold the claim off for.
|
||||
m_cycleInFlight = false;
|
||||
return anyRestarted;
|
||||
}
|
||||
|
||||
// Normal path: fire the elevated helper via its scheduled task. Starting
|
||||
// Always through the helper, elevated or not. Cycling in this process is
|
||||
// possible when elevated, but it is synchronous: four interfaces at a
|
||||
// second apiece means five seconds with no message pump, so every
|
||||
// WM_DEVICECHANGE the cycle produces sits in the queue until it finishes.
|
||||
// The first interface is back and taken by Steam long before we drain
|
||||
// those messages and claim, which is how an elevated run loses a race the
|
||||
// helper path wins (#79) — and it is the tray menu failing to paint that
|
||||
// users report as the app hanging mid-cycle.
|
||||
//
|
||||
// The helper is a separate process, so the cycle runs while this one keeps
|
||||
// pumping and the arrival drives the claim the moment it lands. Keeping one
|
||||
// path also keeps the two from drifting: the shortcut had diverged into
|
||||
// different timing and different logging (it wrote no cycle.log at all).
|
||||
//
|
||||
// Fire the elevated helper via its scheduled task. Starting
|
||||
// a task the user authored needs no elevation, so no UAC prompt here.
|
||||
std::wstring run = L"schtasks.exe /Run /TN \"";
|
||||
run += CYCLE_TASK_NAME;
|
||||
|
||||
Reference in new issue
Block a user