From 293da5fe9763675b77460a7c99973951d6eacdcf Mon Sep 17 00:00:00 2001 From: ant-black <1016930479@qq.com> Date: Sun, 28 Jun 2026 18:51:46 +0800 Subject: [PATCH] fix: restore existing window on repeated launch --- single_instance_activate_windows.go | 87 +++++++++++++++++++++++++---- 1 file changed, 76 insertions(+), 11 deletions(-) diff --git a/single_instance_activate_windows.go b/single_instance_activate_windows.go index f94b00b0..255bcc2d 100644 --- a/single_instance_activate_windows.go +++ b/single_instance_activate_windows.go @@ -16,8 +16,13 @@ const ( var ( user32Activate = windows.NewLazySystemDLL("user32.dll") procAllowSetForegroundWindow = user32Activate.NewProc("AllowSetForegroundWindow") + procAttachThreadInput = user32Activate.NewProc("AttachThreadInput") procEnumWindows = user32Activate.NewProc("EnumWindows") + procGetForegroundWindow = user32Activate.NewProc("GetForegroundWindow") + procGetWindow = user32Activate.NewProc("GetWindow") + procGetWindowTextLengthW = user32Activate.NewProc("GetWindowTextLengthW") procGetWindowThreadProcessID = user32Activate.NewProc("GetWindowThreadProcessId") + procIsIconic = user32Activate.NewProc("IsIconic") procIsWindowVisible = user32Activate.NewProc("IsWindowVisible") procShowWindow = user32Activate.NewProc("ShowWindow") procSetForegroundWindow = user32Activate.NewProc("SetForegroundWindow") @@ -25,6 +30,7 @@ var ( ) const ( + gwOwner = 4 hwndTopmost = ^uintptr(0) hwndNotopmost = ^uintptr(1) swpNoMove = 0x0002 @@ -44,8 +50,13 @@ func activateExistingSingleInstanceWindow(pid int) { } grantExistingSingleInstanceForeground(pid) if hwnd := findTopLevelWindowByPID(uint32(pid)); hwnd != 0 { - procShowWindow.Call(hwnd, swShow) procShowWindow.Call(hwnd, swRestore) + procShowWindow.Call(hwnd, swShow) + if ok, _, _ := procSetForegroundWindow.Call(hwnd); ok == 0 { + attachForegroundThreadInput(hwnd, func() { + procSetForegroundWindow.Call(hwnd) + }) + } if ok, _, _ := procSetForegroundWindow.Call(hwnd); ok == 0 { procSetWindowPos.Call(hwnd, hwndTopmost, 0, 0, 0, 0, swpNoMove|swpNoSize|swpShowWindow) procSetWindowPos.Call(hwnd, hwndNotopmost, 0, 0, 0, 0, swpNoMove|swpNoSize|swpShowWindow) @@ -54,23 +65,77 @@ func activateExistingSingleInstanceWindow(pid int) { } } +func attachForegroundThreadInput(hwnd uintptr, fn func()) { + foreground, _, _ := procGetForegroundWindow.Call() + if foreground == 0 || hwnd == 0 { + fn() + return + } + + var foregroundPID uint32 + foregroundThread, _, _ := procGetWindowThreadProcessID.Call(foreground, uintptr(unsafe.Pointer(&foregroundPID))) + var targetPID uint32 + targetThread, _, _ := procGetWindowThreadProcessID.Call(hwnd, uintptr(unsafe.Pointer(&targetPID))) + if foregroundThread == 0 || targetThread == 0 || foregroundThread == targetThread { + fn() + return + } + + procAttachThreadInput.Call(foregroundThread, targetThread, 1) + defer procAttachThreadInput.Call(foregroundThread, targetThread, 0) + fn() +} + func findTopLevelWindowByPID(pid uint32) uintptr { - var matched uintptr + type candidateWindow struct { + hwnd uintptr + visible bool + titled bool + owned bool + minimized bool + } + var candidates []candidateWindow callback := windows.NewCallback(func(hwnd uintptr, lparam uintptr) uintptr { var windowPID uint32 procGetWindowThreadProcessID.Call(hwnd, uintptr(unsafe.Pointer(&windowPID))) if windowPID == pid { - if visible, _, _ := procIsWindowVisible.Call(hwnd); visible == 0 && matched != 0 { - return 1 - } - matched = hwnd - if visible, _, _ := procIsWindowVisible.Call(hwnd); visible != 0 { - return 0 - } - return 1 + visible, _, _ := procIsWindowVisible.Call(hwnd) + titleLength, _, _ := procGetWindowTextLengthW.Call(hwnd) + owner, _, _ := procGetWindow.Call(hwnd, gwOwner) + minimized, _, _ := procIsIconic.Call(hwnd) + candidates = append(candidates, candidateWindow{ + hwnd: hwnd, + visible: visible != 0, + titled: titleLength > 0, + owned: owner != 0, + minimized: minimized != 0, + }) } return 1 }) procEnumWindows.Call(callback, 0) - return matched + if len(candidates) == 0 { + return 0 + } + for _, candidate := range candidates { + if candidate.visible && candidate.titled && !candidate.owned { + return candidate.hwnd + } + } + for _, candidate := range candidates { + if candidate.titled && !candidate.owned { + return candidate.hwnd + } + } + for _, candidate := range candidates { + if candidate.visible && !candidate.owned { + return candidate.hwnd + } + } + for _, candidate := range candidates { + if candidate.minimized && !candidate.owned { + return candidate.hwnd + } + } + return candidates[0].hwnd }