Files
Behavision/desktop/main.go
Suriyakumarvijayanayagam 50a843ce46 The shop app starts once, and a second launch just shows the window
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
2026-09-18 11:01:19 +05:30

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