//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) }