Files
navi/internal/singleinstance/singleinstance_windows.go
T
M4andCursor 57d719f02f
ci / test (macos-latest) (push) Waiting to run
ci / test (ubuntu-latest) (push) Waiting to run
ci / test (windows-latest) (push) Waiting to run
Release 3.8.2.11: idle rev-cache, port-safe Connect, single-instance tray UX.
Faster unchanged polls; orphan-core cleanup on listen ports; clearer busy-port errors; cores only from binDir; incremental server list; CSP without Google Fonts; single-instance activates existing window; close-to-tray without kill-others or balloon.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-01 17:27:47 +03:00

69 lines
1.5 KiB
Go

//go:build windows
package singleinstance
import (
"fmt"
"unsafe"
"golang.org/x/sys/windows"
)
var (
user32 = windows.NewLazySystemDLL("user32.dll")
procFindWindow = user32.NewProc("FindWindowW")
procShowWindow = user32.NewProc("ShowWindow")
procSetForeground = user32.NewProc("SetForegroundWindow")
procIsIconic = user32.NewProc("IsIconic")
)
const (
swShow = 5
swRestore = 9
)
// Lock acquires a per-user mutex. If another Navis holds it, activates that
// window (title "Navis") and returns ErrAlreadyRunning.
func Lock(appName string) (func(), error) {
if appName == "" {
appName = "Navis"
}
name, err := windows.UTF16PtrFromString("Local\\" + appName + "SingleInstance")
if err != nil {
return nil, err
}
handle, err := windows.CreateMutex(nil, false, name)
if err == windows.ERROR_ALREADY_EXISTS {
if handle != 0 {
_ = windows.CloseHandle(handle)
}
activateWindow(appName)
return nil, ErrAlreadyRunning
}
if err != nil {
return nil, fmt.Errorf("singleinstance: %w", err)
}
return func() {
_ = windows.ReleaseMutex(handle)
_ = windows.CloseHandle(handle)
}, nil
}
func activateWindow(title string) {
t, err := windows.UTF16PtrFromString(title)
if err != nil {
return
}
hwnd, _, _ := procFindWindow.Call(0, uintptr(unsafe.Pointer(t)))
if hwnd == 0 {
return
}
iconic, _, _ := procIsIconic.Call(hwnd)
if iconic != 0 {
procShowWindow.Call(hwnd, swRestore)
} else {
procShowWindow.Call(hwnd, swShow)
}
procSetForeground.Call(hwnd)
}