65 lines
2.2 KiB
Go
65 lines
2.2 KiB
Go
//go:build windows
|
|
|
|
package main
|
|
|
|
import (
|
|
"errors"
|
|
"unsafe"
|
|
|
|
"golang.org/x/sys/windows"
|
|
)
|
|
|
|
// singleInstanceName is a per-session named mutex. The Windows kernel releases
|
|
// it automatically when the owning process dies (even on a crash), so a
|
|
// lingering/zombie OpsLog can't permanently block future launches — killing it
|
|
// frees the name at once. Session-local (no "Global\\") = one instance per
|
|
// logged-in desktop, which is what we want.
|
|
const singleInstanceName = "OpsLog-SingleInstance-Mutex"
|
|
|
|
// acquireSingleInstance creates the named mutex. Returns ok=false when another
|
|
// OpsLog already holds it (this instance should exit); on the way out it brings
|
|
// the existing window to the front so a double-click just refocuses OpsLog
|
|
// instead of spawning a duplicate that fights over the CAT / antenna.
|
|
//
|
|
// The mutex handle is deliberately never closed — it must live for the whole
|
|
// process lifetime; the OS reclaims it on exit.
|
|
func acquireSingleInstance() (ok bool) {
|
|
namePtr, err := windows.UTF16PtrFromString(singleInstanceName)
|
|
if err != nil {
|
|
return true // never block launch on an unexpected error
|
|
}
|
|
kernel32 := windows.NewLazySystemDLL("kernel32.dll")
|
|
createMutex := kernel32.NewProc("CreateMutexW")
|
|
// CreateMutexW(lpSecurityAttributes=NULL, bInitialOwner=FALSE, lpName)
|
|
h, _, callErr := createMutex.Call(0, 0, uintptr(unsafe.Pointer(namePtr)))
|
|
if h == 0 {
|
|
return true // couldn't create the mutex → don't block the app
|
|
}
|
|
if errors.Is(callErr, windows.ERROR_ALREADY_EXISTS) {
|
|
focusExistingWindow()
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
// focusExistingWindow finds the running OpsLog window by its title and restores
|
|
// + foregrounds it. Best-effort; failures are silently ignored.
|
|
func focusExistingWindow() {
|
|
user32 := windows.NewLazySystemDLL("user32.dll")
|
|
findWindow := user32.NewProc("FindWindowW")
|
|
setForeground := user32.NewProc("SetForegroundWindow")
|
|
showWindow := user32.NewProc("ShowWindow")
|
|
|
|
title, err := windows.UTF16PtrFromString("OpsLog")
|
|
if err != nil {
|
|
return
|
|
}
|
|
hwnd, _, _ := findWindow.Call(0, uintptr(unsafe.Pointer(title)))
|
|
if hwnd == 0 {
|
|
return
|
|
}
|
|
const swRestore = 9 // SW_RESTORE — un-minimise if needed
|
|
showWindow.Call(hwnd, swRestore)
|
|
setForeground.Call(hwnd)
|
|
}
|