package tray import ( "log" "os" "os/exec" "path/filepath" "runtime" "sync" ) var ( panelMu sync.Mutex panelProc *os.Process ) // launchPanel opens the Syncthing admin UI. // Tries the embedded panel binary first, falls back to the default browser. func launchPanel(baseURL string) { panelMu.Lock() defer panelMu.Unlock() // Check if panel subprocess is already running if panelProc != nil { if err := panelProc.Signal(os.Signal(nil)); err == nil { log.Println("panel already running") return } panelProc = nil } // Try embedded panel binary panelBin := panelBinaryPath() if panelBin != "" { cmd := exec.Command(panelBin, "-addr", baseURL) cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr if err := cmd.Start(); err == nil { panelProc = cmd.Process log.Printf("panel started (pid %d)", cmd.Process.Pid) go func() { _ = cmd.Wait() panelMu.Lock() panelProc = nil panelMu.Unlock() log.Println("panel exited") }() return } log.Printf("failed to start panel binary: %v", panelBin) } // Fallback: open in default browser log.Printf("opening %s in browser", baseURL) openBrowser(baseURL) } func panelBinaryPath() string { exe, err := os.Executable() if err != nil { return "" } dir := filepath.Dir(exe) name := "syncwarden-panel" if runtime.GOOS == "windows" { name = "syncwarden-panel.exe" } p := filepath.Join(dir, name) if _, err := os.Stat(p); err == nil { return p } return "" } func openBrowser(url string) { var cmd *exec.Cmd switch runtime.GOOS { case "windows": cmd = exec.Command("rundll32", "url.dll,FileProtocolHandler", url) case "darwin": cmd = exec.Command("open", url) default: cmd = exec.Command("xdg-open", url) } if err := cmd.Start(); err != nil { log.Printf("failed to open browser: %v", err) } }