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>
69 lines
1.5 KiB
Go
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)
|
|
}
|