返回 DeepSeek-Reasonix
instance_endpoint_windows.go
根目录 / desktop / cmd / update-helper / instance_endpoint_windows.go
1 //go:build windows
2
3 package main
4
5 import (
6 "errors"
7 "fmt"
8 "os"
9 "unsafe"
10
11 "golang.org/x/sys/windows"
12 "reasonix/desktop/internal/instanceidentity"
13 )
14
15 var errEndpointStarting = errors.New("instance mutex exists without an identifiable endpoint")
16
17 var handoffUser32 = windows.NewLazySystemDLL("user32.dll")
18 var findInstanceWindow = handoffUser32.NewProc("FindWindowExW")
19 var instanceWindowPID = handoffUser32.NewProc("GetWindowThreadProcessId")
20
21 // These names match the retired Wails v2 single-instance backend so an
22 // update helper can still find a running pre-Electron app. HWND_MESSAGE
23 // is required: ordinary top-level window enumeration does not include them.
24 func desktopEndpointImage(id string) (string, error) {
25 image, err := instanceidentity.EndpointImage(id)
26 if errors.Is(err, windows.ERROR_PIPE_BUSY) {
27 return "", errEndpointStarting
28 }
29 if err != nil || image != "" {
30 return image, err
31 }
32 name := "wails-app-" + id
33 class, err := windows.UTF16PtrFromString(name + "-sic")
34 if err != nil {
35 return "", err
36 }
37 title, err := windows.UTF16PtrFromString(name + "-siw")
38 if err != nil {
39 return "", err
40 }
41 hwnd, _, _ := findInstanceWindow.Call(^uintptr(2), 0, uintptr(unsafe.Pointer(class)), uintptr(unsafe.Pointer(title)))
42 if hwnd == 0 {
43 mutexName, _ := windows.UTF16PtrFromString(name + "sim")
44 mutex, err := windows.OpenMutex(windows.SYNCHRONIZE, false, mutexName)
45 if errors.Is(err, windows.ERROR_FILE_NOT_FOUND) {
46 return "", nil
47 }
48 if err != nil {
49 return "", err
50 }
51 _ = windows.CloseHandle(mutex)
52 return "", errEndpointStarting
53 }
54 var pid uint32
55 if result, _, err := instanceWindowPID.Call(hwnd, uintptr(unsafe.Pointer(&pid))); result == 0 || pid == 0 {
56 return "", fmt.Errorf("identify instance owner: %w", err)
57 }
58 handle, err := windows.OpenProcess(windows.PROCESS_QUERY_LIMITED_INFORMATION|windows.SYNCHRONIZE, false, pid)
59 if err != nil {
60 return "", err
61 }
62 defer windows.CloseHandle(handle)
63 var owner uint32
64 instanceWindowPID.Call(hwnd, uintptr(unsafe.Pointer(&owner)))
65 if owner != pid {
66 return "", fmt.Errorf("instance owner changed during inspection")
67 }
68 size := uint32(32768)
69 buffer := make([]uint16, size)
70 if err := windows.QueryFullProcessImageName(handle, 0, &buffer[0], &size); err != nil {
71 return "", err
72 }
73 result, err := windows.WaitForSingleObject(handle, 0)
74 if err != nil || result != uint32(windows.WAIT_TIMEOUT) {
75 return "", fmt.Errorf("instance exited during inspection")
76 }
77 return windows.UTF16ToString(buffer[:size]), nil
78 }
79
80 func notifyHandoffBlocked(installed bool) {
81 // Background/silent update execution has no interactive recovery owner.
82 // Its caller records the installed-versus-started result in the update log.
83 if os.Getenv("REASONIX_INTERACTIVE_RECOVERY") != "1" {
84 return
85 }
86 message := "Reasonix could not finish the update restart. Choose Quit in the old Reasonix window, then open Reasonix again. Your running instance was preserved."
87 if installed {
88 message = "The new version is installed, but restart is incomplete. Choose Quit in the old Reasonix window, then open Reasonix again. Your running instance was preserved."
89 }
90 message += "\n\n请在旧 Reasonix 窗口中选择退出,再重新打开 Reasonix。现有实例已保留。"
91 title, _ := windows.UTF16PtrFromString("Reasonix update / 更新")
92 body, _ := windows.UTF16PtrFromString(message)
93 // The helper runs after the desktop has exited; log-only failures are invisible.
94 handoffUser32.NewProc("MessageBoxW").Call(0, uintptr(unsafe.Pointer(body)), uintptr(unsafe.Pointer(title)), 0x00000030|0x00010000)
95 }
96
96 lines GO