diff --git a/internal/client/windowstray/tray_windows.go b/internal/client/windowstray/tray_windows.go index f030533..0fc7dc9 100644 --- a/internal/client/windowstray/tray_windows.go +++ b/internal/client/windowstray/tray_windows.go @@ -70,9 +70,11 @@ var ( procGetCursorPos = user32Tray.NewProc("GetCursorPos") procSetForeground = user32Tray.NewProc("SetForegroundWindow") procGetModuleHandle = user32Tray.NewProc("GetModuleHandleW") + procRegisterMessage = user32Tray.NewProc("RegisterWindowMessageW") procShellNotify = shell32Tray.NewProc("Shell_NotifyIconW") trayCallback = syscall.NewCallback(trayWindowProc) trayWindows sync.Map // hwnd -> *trayWindow + taskbarCreatedMessage uint32 ) type trayPoint struct{ X, Y int32 } @@ -212,7 +214,10 @@ func runTrayMessageLoop(output io.Writer, tooltip string, ready chan<- *trayWind window.icon = trayIconData{CbSize: uint32(unsafe.Sizeof(trayIconData{})), HWnd: hwnd, UID: trayIconID, UFlags: trayNIFMessage | trayNIFIcon | trayNIFTip, UCallbackMessage: trayMessage, HIcon: icon} copy(window.icon.Tip[:], winapi.StringToUTF16(tooltip)) trayWindows.Store(hwnd, window) - if result, _, err := procShellNotify.Call(uintptr(trayNIMAdd), uintptr(unsafe.Pointer(&window.icon))); result == 0 { + messageName, _ := winapi.UTF16PtrFromString("TaskbarCreated") + registeredMessage, _, _ := procRegisterMessage.Call(uintptr(unsafe.Pointer(messageName))) + taskbarCreatedMessage = uint32(registeredMessage) + if err := window.addIcon(); err != nil { trayWindows.Delete(hwnd) _, _, _ = procDestroyWindow.Call(hwnd) done <- err @@ -263,10 +268,28 @@ func trayWindowProc(hwnd uintptr, message uint32, wParam, lParam uintptr) uintpt } return 0 } + if window != nil && taskbarCreatedMessage != 0 && message == taskbarCreatedMessage { + // Explorer may restart without destroying the tray window. Re-add the + // icon with the current bounded tooltip so the frontend recovers without + // restarting the service or tray process. + _ = window.addIcon() + return 0 + } result, _, _ := procDefWindowProc.Call(hwnd, uintptr(message), wParam, lParam) return result } +func (window *trayWindow) addIcon() error { + window.icon.UFlags = trayNIFMessage | trayNIFIcon | trayNIFTip + if result, _, err := procShellNotify.Call(uintptr(trayNIMAdd), uintptr(unsafe.Pointer(&window.icon))); result == 0 { + if err == syscall.Errno(0) { + err = syscall.GetLastError() + } + return err + } + return nil +} + func (window *trayWindow) removeIcon() { window.closeOnce.Do(func() { window.icon.UFlags = 0 @@ -400,10 +423,10 @@ func (window *trayWindow) setTooltip(value string) { } func openExactPath(path string) error { - if path == "" || strings.TrimSpace(path) != path || !utf8.ValidString(path) { + if path == "" || strings.TrimSpace(path) != path || !utf8.ValidString(path) || !filepath.IsAbs(path) { return errors.New("invalid path returned by service") } - info, err := os.Stat(path) + info, err := os.Lstat(path) if err != nil || !info.Mode().IsRegular() { return errors.New("service path is not a regular file") } @@ -412,7 +435,7 @@ func openExactPath(path string) error { return err } explorer := filepath.Join(windowsDir, "explorer.exe") - if info, err := os.Stat(explorer); err != nil || !info.Mode().IsRegular() { + if info, err := os.Lstat(explorer); err != nil || !info.Mode().IsRegular() { return errors.New("Windows Explorer is unavailable") } return exec.Command(explorer, path).Start()