From 952f44f84189b3c61b50b743a54ef679ccff846d Mon Sep 17 00:00:00 2001 From: Teknium <127238744+teknium1@users.noreply.github.com> Date: Sun, 9 Aug 2026 01:54:31 -0700 Subject: [PATCH] fix(desktop): focus the update progress window, then hand focus to the relaunched Desktop Two focus polish items from the first fully-working hand-off run (ryanc, 2026-08-09): 1. The progress window came up backgrounded: the script is spawned via `cmd start /min`, and Form.Show() + TopMost keeps it above other windows without ACTIVATING it. Claim activation explicitly (Form.Activate + SetForegroundWindow) right after Show. 2. The relaunched Desktop came up behind whatever the user had focused: a WMI-spawned process starts unfocused and cannot take foreground by itself. Since the hand-off owns foreground while its progress window is up, delegate it: AllowSetForegroundWindow(new pid), poll up to 20s for Electron's MainWindowHandle, then ShowWindow(SW_RESTORE) + SetForegroundWindow. Best-effort at every step -- a focus failure never affects the update result. Sequence on success: progress window foreground during the update -> window closes -> freshly relaunched Hermes.exe takes foreground. Verified live on the incident machine: Add-Type shim compiles under PS 5.1; WMI spawn + AllowSetForegroundWindow + MainWindowHandle poll + ShowWindow all execute against a real spawned window. (In the bg test shell SetForegroundWindow returns False by OS design -- only the current foreground owner may delegate; the real flow's TopMost progress window IS that owner.) PS parse clean, check-windows-footguns clean. --- scripts/desktop-update.ps1 | 48 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/scripts/desktop-update.ps1 b/scripts/desktop-update.ps1 index 3fe538425d7e5..d45484501db33 100644 --- a/scripts/desktop-update.ps1 +++ b/scripts/desktop-update.ps1 @@ -50,6 +50,19 @@ param( ) $ErrorActionPreference = "Continue" +# Foreground helpers: the script is spawned via `cmd start /min`, so its +# WinForms window comes up backgrounded unless we explicitly claim focus -- +# and after the update we must hand focus TO the relaunched Desktop (a +# WMI-spawned process starts unfocused). AllowSetForegroundWindow lets us +# pass our foreground right on to the new Hermes.exe pid. +try { + Add-Type -Namespace HermesHandoff -Name Win32 -MemberDefinition @' +[DllImport("user32.dll")] public static extern bool SetForegroundWindow(System.IntPtr hWnd); +[DllImport("user32.dll")] public static extern bool AllowSetForegroundWindow(int dwProcessId); +[DllImport("user32.dll")] public static extern bool ShowWindow(System.IntPtr hWnd, int nCmdShow); +'@ -ErrorAction Stop + $script:Win32 = $true +} catch { $script:Win32 = $false } # Render UTF-8 glyphs (checkmarks, arrows) correctly in our own console echo # too; the legacy conhost default OEM codepage shows them as mojibake. try { @@ -106,6 +119,13 @@ function Show-ProgressWindow { $form.Controls.Add($bar) $form.Controls.Add($label) $form.Show() + # `cmd start /min` spawned us backgrounded; TopMost keeps the window + # above others but does not take activation. Claim it explicitly so + # the progress window is what the user sees during the update. + try { + $form.Activate() + if ($script:Win32) { [HermesHandoff.Win32]::SetForegroundWindow($form.Handle) | Out-Null } + } catch {} [System.Windows.Forms.Application]::DoEvents() $script:Ui = [pscustomobject]@{ Form = $form; Box = $box } } catch { @@ -172,6 +192,34 @@ function Start-DesktopRelaunch { if ($r -and $r.ReturnValue -eq 0) { Write-HandoffLog "desktop relaunched detached (pid $($r.ProcessId))" $spawned = $true + # Hand our foreground rights to the new Desktop and focus its + # main window once it exists. A WMI-spawned process starts + # unfocused, and Windows only lets the CURRENT foreground + # owner (us, while the progress window is up / just closed) + # delegate that right. Poll briefly for the window: Electron + # takes a couple seconds to create it. + try { + if ($script:Win32) { + [HermesHandoff.Win32]::AllowSetForegroundWindow([int]$r.ProcessId) | Out-Null + $deadline = (Get-Date).AddSeconds(20) + while ((Get-Date) -lt $deadline) { + $hwnd = [System.IntPtr]::Zero + try { + $p = Get-Process -Id $r.ProcessId -ErrorAction Stop + $hwnd = $p.MainWindowHandle + } catch { break } # process died; nothing to focus + if ($hwnd -ne [System.IntPtr]::Zero) { + [HermesHandoff.Win32]::ShowWindow($hwnd, 9) | Out-Null # SW_RESTORE + [HermesHandoff.Win32]::SetForegroundWindow($hwnd) | Out-Null + Write-HandoffLog "focused relaunched desktop window" + break + } + Start-Sleep -Milliseconds 400 + } + } + } catch { + Write-HandoffLog "WARNING: could not focus relaunched desktop: $($_.Exception.Message)" + } } else { Write-HandoffLog "WARNING: WMI relaunch returned $($r.ReturnValue); falling back" }