//go:build windows package main import ( "fmt" "log" "os" "path/filepath" "sync/atomic" "unsafe" "github.com/crgimenes/glaze" "golang.org/x/sys/windows" ) var ( user32 = windows.NewLazySystemDLL("user32.dll") procGetWindowLongPtr = user32.NewProc("GetWindowLongPtrW") procSetWindowLongPtr = user32.NewProc("SetWindowLongPtrW") procCallWindowProc = user32.NewProc("CallWindowProcW") procShowWindow = user32.NewProc("ShowWindow") procSetForeground = user32.NewProc("SetForegroundWindow") procPostMessage = user32.NewProc("PostMessageW") procIsIconic = user32.NewProc("IsIconic") mainHWND uintptr origWndProc uintptr closeHookCB uintptr allowDestroy atomic.Bool ) const ( // GWLP_WNDPROC as unsigned pointer-sized -4. gwlpWndProc = ^uintptr(3) wmClose = 0x0010 swHide = 0 swShow = 5 swRestore = 9 ) func openExternal(raw string) error { verb, err := windows.UTF16PtrFromString("open") if err != nil { return err } file, err := windows.UTF16PtrFromString(raw) if err != nil { return err } return windows.ShellExecute(0, verb, file, nil, nil, windows.SW_SHOWNORMAL) } func fatalf(format string, args ...any) { msg := fmt.Sprintf(format, args...) log.Println(msg) messageBox(msg) os.Exit(1) } func shutdownSignals() []os.Signal { return []os.Signal{os.Interrupt} } func decorateWindow(w glaze.WebView) { if w == nil { return } hwnd := w.Window() applyWindowIcon(hwnd) installCloseToHide(hwnd) } func installCloseToHide(hwnd unsafe.Pointer) { if hwnd == nil { return } mainHWND = uintptr(hwnd) allowDestroy.Store(false) if closeHookCB == 0 { closeHookCB = windows.NewCallback(closeToHideWndProc) } orig, _, _ := procGetWindowLongPtr.Call(mainHWND, gwlpWndProc) if orig == 0 { return } origWndProc = orig procSetWindowLongPtr.Call(mainHWND, gwlpWndProc, closeHookCB) } func closeToHideWndProc(hwnd uintptr, msg uint32, wParam, lParam uintptr) uintptr { if msg == wmClose && !allowDestroy.Load() { procShowWindow.Call(hwnd, uintptr(swHide)) return 0 } r, _, _ := procCallWindowProc.Call(origWndProc, hwnd, uintptr(msg), wParam, lParam) return r } func showMainWindow() { if mainHWND == 0 { return } iconic, _, _ := procIsIconic.Call(mainHWND) if iconic != 0 { procShowWindow.Call(mainHWND, uintptr(swRestore)) } else { procShowWindow.Call(mainHWND, uintptr(swShow)) } procSetForeground.Call(mainHWND) } func prepareWindowQuit() { allowDestroy.Store(true) } func closeMainWindow() { prepareWindowQuit() if mainHWND == 0 { os.Exit(0) return } procPostMessage.Call(mainHWND, uintptr(wmClose), 0, 0) } func applyWindowIcon(hwnd unsafe.Pointer) { ico := findIconPath() if ico == "" || hwnd == nil { return } pathPtr, err := windows.UTF16PtrFromString(ico) if err != nil { return } loadImage := user32.NewProc("LoadImageW") sendMessage := user32.NewProc("SendMessageW") const ( imageIcon = 1 lrLoadFromFile = 0x0010 lrDefaultSize = 0x0040 wmSetIcon = 0x0080 iconSmall = 0 iconBig = 1 ) h, _, _ := loadImage.Call(0, uintptr(unsafe.Pointer(pathPtr)), imageIcon, 0, 0, lrLoadFromFile|lrDefaultSize) if h == 0 { return } sendMessage.Call(uintptr(hwnd), wmSetIcon, iconSmall, h) sendMessage.Call(uintptr(hwnd), wmSetIcon, iconBig, h) } func findIconPath() string { candidates := []string{} if exe, err := os.Executable(); err == nil { dir := filepath.Dir(exe) candidates = append(candidates, filepath.Join(dir, "assets", "navis.ico"), filepath.Join(dir, "navis.ico"), ) } if cwd, err := os.Getwd(); err == nil { candidates = append(candidates, filepath.Join(cwd, "assets", "navis.ico")) } for _, c := range candidates { if st, err := os.Stat(c); err == nil && !st.IsDir() { return c } } return "" }