The window hides to the tray on close, so the natural next step for a shop assistant is to double-click the shortcut again. That started a second full copy of the app: a second tray icon, a second engine supervisor on the same SQLite WAL and the same port - the start-twice failure the agent package was built to prevent, on the one binary that never had the guard. Seen on a Windows install as a row of tray icons. Wails' SingleInstanceLock now hands the second launch to the first process, which brings its window to the front. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KGcjxF1cNLcuwc3DAPcnfj
92 lines
3.0 KiB
Go
92 lines
3.0 KiB
Go
// Command behavision-desktop is the store-facing application: a tray icon, a
|
|
// window, and the supervisor for the recognition engine.
|
|
//
|
|
// It is one process rather than three because the tray, the window and the
|
|
// supervisor all need the same state, and because a user who quits the tray
|
|
// expects recognition to stop. It is deliberately NOT a Windows service: a
|
|
// service runs in session 0 and cannot draw a tray icon, and spawning a child
|
|
// process needs no elevation while controlling a service does.
|
|
package main
|
|
|
|
import (
|
|
"context"
|
|
"embed"
|
|
"log"
|
|
|
|
"github.com/wailsapp/wails/v2"
|
|
"github.com/wailsapp/wails/v2/pkg/options"
|
|
"github.com/wailsapp/wails/v2/pkg/options/assetserver"
|
|
"github.com/wailsapp/wails/v2/pkg/options/windows"
|
|
"github.com/wailsapp/wails/v2/pkg/runtime"
|
|
)
|
|
|
|
//go:embed all:frontend/dist
|
|
var assets embed.FS
|
|
|
|
func main() {
|
|
app := NewApp()
|
|
tray := newTray(app)
|
|
|
|
// One process per PC, enforced by the OS rather than by hoping.
|
|
//
|
|
// The window hides to the tray on close, so the ordinary next thing a shop
|
|
// assistant does is double-click the desktop shortcut again to get it
|
|
// back. Without this lock that started a SECOND complete copy: a second
|
|
// tray icon, a second engine supervisor on the same SQLite WAL and the
|
|
// same port - the "start twice" failure the agent package exists to
|
|
// prevent, on the one binary that never had the guard. Seen on a Windows
|
|
// install as a row of Behavision icons in the tray. A second launch now
|
|
// only brings the existing window to the front, which is what the person
|
|
// wanted in the first place.
|
|
var ctxRef context.Context
|
|
single := &options.SingleInstanceLock{
|
|
UniqueId: "ai.loyaly.behavision.desktop",
|
|
OnSecondInstanceLaunch: func(options.SecondInstanceData) {
|
|
if ctxRef != nil {
|
|
runtime.Show(ctxRef)
|
|
runtime.WindowUnminimise(ctxRef)
|
|
}
|
|
},
|
|
}
|
|
|
|
err := wails.Run(&options.App{
|
|
SingleInstanceLock: single,
|
|
Title: "Behavision",
|
|
Width: 1280,
|
|
Height: 820,
|
|
// Small enough to still be usable on a cramped shop-counter monitor.
|
|
MinWidth: 1024,
|
|
MinHeight: 640,
|
|
AssetServer: &assetserver.Options{Assets: assets},
|
|
// Closing the window hides it rather than quitting: the engine must
|
|
// keep recognising after a shop assistant clicks the X, and the tray
|
|
// is where they get the window back.
|
|
HideWindowOnClose: true,
|
|
OnStartup: func(ctx context.Context) {
|
|
ctxRef = ctx
|
|
app.startup(ctx)
|
|
tray.start(ctx)
|
|
},
|
|
OnBeforeClose: func(ctx context.Context) bool {
|
|
runtime.Hide(ctx)
|
|
return true // prevent the close
|
|
},
|
|
OnShutdown: func(ctx context.Context) {
|
|
tray.stop()
|
|
app.proxy.stop()
|
|
app.StopEngine()
|
|
},
|
|
Bind: []any{app},
|
|
Windows: &windows.Options{
|
|
WebviewIsTransparent: false,
|
|
WindowIsTranslucent: false,
|
|
// A shop PC is not a developer machine; a stray right-click that
|
|
// opens devtools looks like the software is broken.
|
|
DisableWindowIcon: false,
|
|
},
|
|
})
|
|
if err != nil {
|
|
log.Fatalf("behavision-desktop: %v", err)
|
|
}
|
|
}
|