desktop open: Windows window check uses real top-level windows (the tray helper windows and MainWindowHandle also answer for a hidden app)

This commit is contained in:
CRTkafa 2026-10-23 22:48:02 +08:00
parent 8df41a26b0
commit 4dff7d9b2e
2 changed files with 15 additions and 2 deletions

View file

@ -15,8 +15,21 @@ export function appExe(platform: NodeJS.Platform, installedPath: string): string
/** The command that lists processes with their executable and arguments (never through a shell). */
export function listCommand(platform: NodeJS.Platform): string[] {
if (platform === 'win32') {
// Window = a visible, unowned, titled top-level window that is not a tool window (the tray helper windows are
// visible but tool windows / untitled; Process.MainWindowHandle also answers for a hidden window)
const cs = 'using System; using System.Collections.Generic; using System.Runtime.InteropServices; using System.Text; '
+ 'public static class SbWin { delegate bool CB(IntPtr h, IntPtr l); '
+ '[DllImport("user32.dll")] static extern bool EnumWindows(CB c, IntPtr l); '
+ '[DllImport("user32.dll")] static extern uint GetWindowThreadProcessId(IntPtr h, out uint p); '
+ '[DllImport("user32.dll")] static extern bool IsWindowVisible(IntPtr h); '
+ '[DllImport("user32.dll")] static extern IntPtr GetWindow(IntPtr h, uint c); '
+ '[DllImport("user32.dll")] static extern int GetWindowTextLength(IntPtr h); '
+ '[DllImport("user32.dll")] static extern int GetWindowLong(IntPtr h, int i); '
+ 'public static uint[] Visible() { var r = new List<uint>(); EnumWindows((h, l) => { uint p; GetWindowThreadProcessId(h, out p); '
+ 'if (IsWindowVisible(h) && GetWindow(h, 4) == IntPtr.Zero && GetWindowTextLength(h) > 0 && (GetWindowLong(h, -20) & 0x80) == 0) r.Add(p); return true; }, IntPtr.Zero); return r.ToArray(); } }';
return ['powershell', '-NoProfile', '-NonInteractive', '-Command',
"Get-CimInstance Win32_Process -Filter \"Name='storebound-desktop.exe'\" | ForEach-Object { $g = Get-Process -Id $_.ProcessId -ErrorAction SilentlyContinue; [pscustomobject]@{ ProcessId = $_.ProcessId; ExecutablePath = $_.ExecutablePath; CommandLine = $_.CommandLine; Window = [bool]($g -and $g.MainWindowHandle -ne 0) } } | ConvertTo-Json -Compress"];
`Add-Type -TypeDefinition '${cs}'; $v = [SbWin]::Visible(); `
+ "Get-CimInstance Win32_Process -Filter \"Name='storebound-desktop.exe'\" | ForEach-Object { [pscustomobject]@{ ProcessId = $_.ProcessId; ExecutablePath = $_.ExecutablePath; CommandLine = $_.CommandLine; Window = [bool]($v -contains [uint32]$_.ProcessId) } } | ConvertTo-Json -Compress"];
}
return ['ps', '-axww', '-o', 'pid=,args='];
}

View file

@ -92,7 +92,7 @@ describe('desktop open: a hidden tray copy that is already running', () => {
expect(openState('win32', [rows[0]!], true)).toBe('hidden');
expect(openState('win32', rows, false)).toBe('ok');
expect(openState('win32', [], true)).toBe('none');
expect(listCommand('win32').join(' ')).toContain('MainWindowHandle');
expect(listCommand('win32').join(' ')).toContain('IsWindowVisible');
});
test('elsewhere: a visible start, or a hidden copy the new start handed over to', () => {
expect(openState('linux', [hidden], false)).toBe('hidden');