返回 DeepSeek-Reasonix
tray_native_windows_test.go
根目录 / desktop / tray_native_windows_test.go
1 //go:build windows
2
3 package main
4
5 import (
6 "encoding/json"
7 "os"
8 "testing"
9 "time"
10 "unsafe"
11
12 "fyne.io/systray"
13 "golang.org/x/sys/windows"
14
15 "reasonix/desktop/internal/instanceidentity"
16 )
17
18 // Opt-in subprocess acceptance: production signature selection and tray loop,
19 // checked against Explorer, including independently signed or moved fixtures.
20 func TestNativeTrayAcceptanceWorker(t *testing.T) {
21 marker := os.Getenv("REASONIX_TEST_TRAY_MARKER")
22 if marker == "" {
23 t.Skip("native acceptance subprocess only")
24 }
25 ready := make(chan struct{})
26 quit := startDesktopTray(func() {
27 systray.SetIcon(trayIconBytes)
28 systray.SetTooltip("Reasonix isolated acceptance")
29 systray.AddMenuItem("Open", "Acceptance menu")
30 close(ready)
31 }, func() {})
32 defer quit()
33 select {
34 case <-ready:
35 case <-time.After(15 * time.Second):
36 t.Fatal("tray failed to initialize")
37 }
38 exe, err := os.Executable()
39 if err != nil {
40 t.Fatal(err)
41 }
42 signed := verifyTraySignature(exe) == nil
43 guid, err := windows.GUIDFromString(instanceidentity.TrayGUID(singleInstanceID()))
44 if err != nil {
45 t.Fatal(err)
46 }
47 user := windows.NewLazySystemDLL("user32.dll")
48 find := user.NewProc("FindWindowExW")
49 getPID := user.NewProc("GetWindowThreadProcessId")
50 class, _ := windows.UTF16PtrFromString("SystrayClass")
51 var hwnd uintptr
52 for {
53 hwnd, _, _ = find.Call(0, hwnd, uintptr(unsafe.Pointer(class)), 0)
54 if hwnd == 0 {
55 t.Fatal("tray window not found")
56 }
57 var pid uint32
58 getPID.Call(hwnd, uintptr(unsafe.Pointer(&pid)))
59 if pid == uint32(os.Getpid()) {
60 break
61 }
62 }
63 identifier := struct {
64 Size uint32
65 Window uintptr
66 ID uint32
67 GUID windows.GUID
68 }{Window: hwnd, ID: 100}
69 identifier.Size = uint32(unsafe.Sizeof(identifier))
70 if signed {
71 identifier.Window = 0
72 identifier.ID = 0
73 identifier.GUID = guid
74 }
75 var rect struct{ Left, Top, Right, Bottom int32 }
76 query := windows.NewLazySystemDLL("shell32.dll").NewProc("Shell_NotifyIconGetRect")
77 result, _, _ := query.Call(uintptr(unsafe.Pointer(&identifier)), uintptr(unsafe.Pointer(&rect)))
78 if int32(result) < 0 {
79 t.Fatalf("Explorer cannot resolve tray identity: HRESULT %#x (signed=%v)", result, signed)
80 }
81 data, _ := json.Marshal(map[string]any{"pid": os.Getpid(), "signed": signed, "guid": guid.String(), "rect": rect, "version": version})
82 if err := os.WriteFile(marker, data, 0600); err != nil {
83 t.Fatal(err)
84 }
85 deadline := time.Now().Add(3 * time.Minute)
86 for time.Now().Before(deadline) {
87 if _, err := os.Stat(marker + ".stop"); err == nil {
88 return
89 }
90 time.Sleep(50 * time.Millisecond)
91 }
92 t.Fatal("acceptance worker timed out")
93 }
94
94 lines GO