Files
OpsLog/singleinstance_windows.go

71 lines
2.6 KiB
Go

//go:build windows && !bindings
// NB the !bindings tag: Wails generates the TypeScript bindings by BUILDING AND
// RUNNING this binary. With the guard active, a normal OpsLog already running on
// the dev machine holds the mutex, the generator's process exits instantly, and
// no bindings are produced. Excluding the guard from that build keeps generation
// working while shipping builds still get it.
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)
}