//go:build windows package main import ( "os" "sync" "testing" "time" "unsafe" ) func requireNativeGUI(t *testing.T) { t.Helper() if os.Getenv("AP_NATIVE_GUI") != "1" { t.Skip("opt-in native GUI test; no process scanner is run") } } func TestNativeTrayOneExitItemAndGracefulCommand(t *testing.T) { requireNativeGUI(t) requested := make(chan struct{}) restored := make(chan struct{}) close(restored) var once sync.Once tr, err := newTray(func() { once.Do(func() { close(requested) }) }, restored) if err != nil { t.Fatal(err) } defer tr.Close() h := tr.hwnd.Load() count, _, _ := u32.NewProc("GetMenuItemCount").Call(tr.menu) if count != 1 { t.Fatalf("want exactly one menu item, got %d", count) } var label [32]uint16 item := menuItemInfo{Size: uint32(unsafe.Sizeof(menuItemInfo{})), Mask: 0x40 | 0x2 | 0x80, Text: &label[0], TextLen: uint32(len(label))} ok, _, e := u32.NewProc("GetMenuItemInfoW").Call(tr.menu, 0, 1, uintptr(unsafe.Pointer(&item))) if ok == 0 { t.Fatal(e) } if item.ID != menuExitID || item.Bitmap == 0 || string(utf16Runes(label[:])) != "Выход" { t.Fatal("missing Exit label, command, or cross bitmap") } ok, _, e = pPostMessage.Call(h, 0x111, menuExitID, 0) if ok == 0 { t.Fatal(e) } select { case <-requested: case <-time.After(3 * time.Second): t.Fatal("Exit command did not request graceful shutdown") } if err = tr.Close(); err != nil { t.Fatal(err) } valid, _, _ := u32.NewProc("IsWindow").Call(h) if valid != 0 { t.Fatal("tray window leaked") } } func utf16Runes(in []uint16) []rune { out := []rune{} for _, v := range in { if v == 0 { break } out = append(out, rune(v)) } return out }