An operator reporting "nothing happens for two or three seconds after I click" was impossible to answer from opslog.log, because the first line applog writes already sits inside startup(). Everything spent loading the binary and creating the WebView2 environment happened before the log begins. In one of the reported logs, startup() to window-visible is 251 ms end to end - so whatever the wait is, it is not OpsLog initialising. It just could not be proven, only asserted. processStart is stamped on the first instruction of main. Two lines now bracket the launch: how long was spent before startup() ran at all, and click-to-window, which is the only number that matches what the operator experiences.
156 lines
5.4 KiB
Go
156 lines
5.4 KiB
Go
package main
|
|
|
|
import (
|
|
"embed"
|
|
"os"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/wailsapp/wails/v2"
|
|
"github.com/wailsapp/wails/v2/pkg/options"
|
|
"github.com/wailsapp/wails/v2/pkg/options/assetserver"
|
|
)
|
|
|
|
//go:embed all:frontend/dist
|
|
var assets embed.FS
|
|
|
|
// profileArg extracts a profile name from the command line. Accepts
|
|
// "--profile NAME", "--profile=NAME", "-profile NAME", "-p NAME" so a desktop
|
|
// shortcut can launch OpsLog straight into a given profile (e.g. F4BPO / TM2Q).
|
|
func profileArg(args []string) string {
|
|
for i := 0; i < len(args); i++ {
|
|
a := args[i]
|
|
switch {
|
|
case a == "--profile" || a == "-profile" || a == "-p":
|
|
if i+1 < len(args) {
|
|
return strings.TrimSpace(args[i+1])
|
|
}
|
|
case strings.HasPrefix(a, "--profile="):
|
|
return strings.TrimSpace(strings.TrimPrefix(a, "--profile="))
|
|
case strings.HasPrefix(a, "-profile="):
|
|
return strings.TrimSpace(strings.TrimPrefix(a, "-profile="))
|
|
case strings.HasPrefix(a, "-p="):
|
|
return strings.TrimSpace(strings.TrimPrefix(a, "-p="))
|
|
}
|
|
}
|
|
return ""
|
|
}
|
|
|
|
// hasFlag reports whether flag is present in args.
|
|
func hasFlag(args []string, flag string) bool {
|
|
for _, a := range args {
|
|
if a == flag {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// acquireInstance grabs the single-instance mutex. On a normal launch it's a plain
|
|
// try (fail → another OpsLog is running, so exit). On a --post-update relaunch the
|
|
// previous instance may still be shutting down and holding the mutex, so retry for
|
|
// a few seconds until it frees.
|
|
func acquireInstance(postUpdate bool) bool {
|
|
if acquireSingleInstance() {
|
|
return true
|
|
}
|
|
if !postUpdate {
|
|
return false
|
|
}
|
|
deadline := time.Now().Add(20 * time.Second)
|
|
for time.Now().Before(deadline) {
|
|
time.Sleep(300 * time.Millisecond)
|
|
if acquireSingleInstance() {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// processStart is stamped on the very first instruction of main, before the
|
|
// single-instance guard and before anything else runs.
|
|
//
|
|
// It exists to split a slow launch in two, because the log alone could not: the
|
|
// first line applog writes already sits inside startup(), so everything spent
|
|
// loading the binary and creating the WebView2 environment happened before the
|
|
// log begins and was invisible. An operator reporting "nothing happens for three
|
|
// seconds" was impossible to answer from a file whose first timestamp is the
|
|
// moment the app was already running.
|
|
var processStart = time.Now()
|
|
|
|
func main() {
|
|
// Single-instance guard: if OpsLog is already running, focus that window and
|
|
// exit instead of spawning a duplicate. A second process would open its own
|
|
// CAT (FlexRadio) connection and Ultrabeam follow loop, and the two would
|
|
// fight over the rig/antenna frequency — the cause of "the antenna re-tunes on
|
|
// its own" when a windowless zombie instance was left running.
|
|
// A --post-update relaunch (from the auto-updater) may start while the previous
|
|
// instance is still exiting and holding the single-instance mutex — wait for it
|
|
// to free instead of bailing out. Then clear the old exe it left behind.
|
|
postUpdate := hasFlag(os.Args[1:], "--post-update")
|
|
if !acquireInstance(postUpdate) {
|
|
return
|
|
}
|
|
if postUpdate {
|
|
cleanupOldUpdateBinary()
|
|
}
|
|
|
|
// Create an instance of the app structure
|
|
app := NewApp()
|
|
app.startupProfile = profileArg(os.Args[1:])
|
|
|
|
// Restore the window's SIZE and maximised state at CREATION, from the geometry
|
|
// saved on last close. Doing it here (not after startup) is what makes the
|
|
// window open already at the right size instead of maximising then snapping
|
|
// smaller. Position can't be set through options, so it is applied while the
|
|
// window is still hidden (domReady) — invisible, so no jump. First run, or a
|
|
// window closed maximised, keeps the historical maximised default.
|
|
width, height := 1400, 900
|
|
startState := options.Maximised
|
|
if dataDir, err := userDataDir(); err == nil {
|
|
if ws, ok := readWindowState(dataDir); ok && !ws.Maximised &&
|
|
ws.Width >= 1100 && ws.Height >= 700 && ws.Width <= 8000 && ws.Height <= 6000 {
|
|
width, height = ws.Width, ws.Height
|
|
startState = options.Normal
|
|
}
|
|
}
|
|
|
|
// Create application with options
|
|
err := wails.Run(&options.App{
|
|
Title: "OpsLog",
|
|
Width: width,
|
|
Height: height,
|
|
MinWidth: 1100,
|
|
MinHeight: 700,
|
|
WindowStartState: startState,
|
|
// No OS title bar: it was a dead 32-pixel band above a window that already
|
|
// has its own title strip. The app header takes over — it carries the drag
|
|
// region, the double-click-to-maximise, and the minimise/maximise/close
|
|
// buttons. Windows still draws the resize borders and honours Aero Snap,
|
|
// which is why the frame is dropped rather than the whole chrome.
|
|
Frameless: true,
|
|
// Start hidden and reveal only once the saved position has been applied and
|
|
// the DOM has painted (OnDomReady → domReady) — so the window appears
|
|
// already at its final size and position, with no post-launch jump.
|
|
StartHidden: true,
|
|
AssetServer: &assetserver.Options{
|
|
Assets: assets,
|
|
},
|
|
// The colour the window is painted before the WebView draws. Matches the
|
|
// graphite theme's background (#16181d), which is the default on a fresh
|
|
// install — otherwise every launch flashes white first.
|
|
BackgroundColour: &options.RGBA{R: 0x16, G: 0x18, B: 0x1d, A: 1},
|
|
OnStartup: app.startup,
|
|
OnDomReady: app.domReady,
|
|
OnBeforeClose: app.beforeClose,
|
|
OnShutdown: app.shutdown,
|
|
Bind: []interface{}{
|
|
app,
|
|
},
|
|
})
|
|
|
|
if err != nil {
|
|
println("Error:", err.Error())
|
|
}
|
|
}
|