//go:build windows package win32 import ( "fmt" "syscall" "time" "unsafe" "golang.org/x/sys/windows" ) // AcquireInstanceLock takes a named mutex so a second launch of // winauth-go can detect the first one. Returns a Release closer to be // deferred at program exit. If another instance is already running it // returns alreadyRunning=true and a zero Release; the caller may then // call ActivateOtherInstance(windowTitle) to bring the existing window // forward and exit. func AcquireInstanceLock(name string) (release func(), alreadyRunning bool, err error) { wname, err := syscall.UTF16PtrFromString(name) if err != nil { return nil, false, fmt.Errorf("win32: utf16: %w", err) } h, err := windows.CreateMutex(nil, false, wname) if h == 0 { return nil, false, fmt.Errorf("win32: CreateMutex: %w", err) } // CreateMutex returns the existing handle when the name is taken; in // that case GetLastError reports ERROR_ALREADY_EXISTS. err is set // regardless of whether the handle is valid, so check the error // number rather than err != nil. if errno, ok := err.(syscall.Errno); ok && errno == windows.ERROR_ALREADY_EXISTS { windows.CloseHandle(h) return nil, true, nil } return func() { windows.CloseHandle(h) }, false, nil } // ActivateOtherInstance walks the top-level windows looking for one // whose title matches windowTitle, then restores + foregrounds it. The // match is exact. Used after AcquireInstanceLock reports a duplicate // launch. func ActivateOtherInstance(windowTitle string) error { wtitle, err := syscall.UTF16PtrFromString(windowTitle) if err != nil { return err } hwnd, _ := findWindow(nil, wtitle) if hwnd == 0 { // The mutex says somebody is running, but the window isn't up // yet — give it a brief moment then retry once. time.Sleep(200 * time.Millisecond) hwnd, _ = findWindow(nil, wtitle) } if hwnd == 0 { return fmt.Errorf("win32: could not locate existing window %q", windowTitle) } const ( SW_RESTORE = 9 ) showWindow(hwnd, SW_RESTORE) setForegroundWindow(hwnd) return nil } // ----------------------------------------------------------------------------- // raw syscall stubs — the windows package does not expose these directly. var ( user32 = windows.NewLazySystemDLL("user32.dll") procFindWindowW = user32.NewProc("FindWindowW") procShowWindow = user32.NewProc("ShowWindow") procSetForegroundWindow = user32.NewProc("SetForegroundWindow") ) func findWindow(class, title *uint16) (windows.HWND, error) { r, _, e := procFindWindowW.Call( uintptr(unsafe.Pointer(class)), uintptr(unsafe.Pointer(title)), ) if r == 0 { return 0, e } return windows.HWND(r), nil } func showWindow(hwnd windows.HWND, cmdShow int32) bool { r, _, _ := procShowWindow.Call(uintptr(hwnd), uintptr(cmdShow)) return r != 0 } func setForegroundWindow(hwnd windows.HWND) bool { r, _, _ := procSetForegroundWindow.Call(uintptr(hwnd)) return r != 0 }