From 174fbba14c10cafe64010df36d2fc6f2d4375bab Mon Sep 17 00:00:00 2001 From: wenjie Date: Tue, 17 Mar 2026 19:43:44 +0800 Subject: [PATCH] refactor(backend): add darwin no-cgo tray fallback (#1689) --- web/backend/app_runtime.go | 46 +++++++++++++++++++++++++ web/backend/main.go | 9 ++--- web/backend/systray.go | 48 +++------------------------ web/backend/tray_stub_darwin_nocgo.go | 32 ++++++++++++++++++ 4 files changed, 86 insertions(+), 49 deletions(-) create mode 100644 web/backend/app_runtime.go create mode 100644 web/backend/tray_stub_darwin_nocgo.go diff --git a/web/backend/app_runtime.go b/web/backend/app_runtime.go new file mode 100644 index 000000000..cf54e18a1 --- /dev/null +++ b/web/backend/app_runtime.go @@ -0,0 +1,46 @@ +package main + +import ( + "context" + "fmt" + "time" + + "github.com/sipeed/picoclaw/pkg/logger" + "github.com/sipeed/picoclaw/web/backend/utils" +) + +const ( + browserDelay = 500 * time.Millisecond + shutdownTimeout = 15 * time.Second +) + +func shutdownApp() { + fmt.Println(T(Exiting)) + + if apiHandler != nil { + apiHandler.Shutdown() + } + + if server != nil { + server.SetKeepAlivesEnabled(false) + + ctx, cancel := context.WithTimeout(context.Background(), shutdownTimeout) + defer cancel() + if err := server.Shutdown(ctx); err != nil { + if err == context.DeadlineExceeded { + logger.Infof("Server shutdown timeout after %v, forcing close", shutdownTimeout) + } else { + logger.Errorf("Server shutdown error: %v", err) + } + } else { + logger.Infof("Server shutdown completed successfully") + } + } +} + +func openBrowser() error { + if serverAddr == "" { + return fmt.Errorf("server address not set") + } + return utils.OpenBrowser(serverAddr) +} diff --git a/web/backend/main.go b/web/backend/main.go index f2fe3de97..ec4e2832d 100644 --- a/web/backend/main.go +++ b/web/backend/main.go @@ -22,8 +22,6 @@ import ( "strconv" "time" - "fyne.io/systray" - "github.com/sipeed/picoclaw/pkg/config" "github.com/sipeed/picoclaw/web/backend/api" "github.com/sipeed/picoclaw/web/backend/launcherconfig" @@ -168,10 +166,10 @@ func main() { } fmt.Println() - // Set server address for systray + // Share the local URL with the launcher runtime. serverAddr = fmt.Sprintf("http://localhost:%s", effectivePort) - // Auto-open browser will be handled by systray onReady + // Auto-open browser will be handled by the launcher runtime. // Auto-start gateway after backend starts listening. go func() { @@ -188,6 +186,5 @@ func main() { } }() - // Start system tray - systray.Run(onReady, onExit) + runTray() } diff --git a/web/backend/systray.go b/web/backend/systray.go index 1ff98c71b..902cc65e0 100644 --- a/web/backend/systray.go +++ b/web/backend/systray.go @@ -1,10 +1,10 @@ +//go:build !darwin || cgo + package main import ( - "context" _ "embed" "fmt" - "time" "fyne.io/systray" @@ -12,10 +12,9 @@ import ( "github.com/sipeed/picoclaw/web/backend/utils" ) -const ( - browserDelay = 500 * time.Millisecond - shutdownTimeout = 15 * time.Second -) +func runTray() { + systray.Run(onReady, shutdownApp) +} // onReady is called when the system tray is ready func onReady() { @@ -90,43 +89,6 @@ func onReady() { } } -// onExit is called when the system tray is exiting -func onExit() { - fmt.Println(T(Exiting)) - - // First, shutdown API handler - if apiHandler != nil { - apiHandler.Shutdown() - } - - if server != nil { - // Disable keep-alive to allow graceful shutdown - server.SetKeepAlivesEnabled(false) - - ctx, cancel := context.WithTimeout(context.Background(), shutdownTimeout) - defer cancel() - if err := server.Shutdown(ctx); err != nil { - // Context deadline exceeded is expected if there are active connections - // This is not necessarily an error, so log it at info level - if err == context.DeadlineExceeded { - logger.Infof("Server shutdown timeout after %v, forcing close", shutdownTimeout) - } else { - logger.Errorf("Server shutdown error: %v", err) - } - } else { - logger.Infof("Server shutdown completed successfully") - } - } -} - -// openBrowser opens the PicoClaw web console in the default browser -func openBrowser() error { - if serverAddr == "" { - return fmt.Errorf("server address not set") - } - return utils.OpenBrowser(serverAddr) -} - // getIcon returns the system tray icon func getIcon() []byte { return iconData diff --git a/web/backend/tray_stub_darwin_nocgo.go b/web/backend/tray_stub_darwin_nocgo.go new file mode 100644 index 000000000..c54aaac1b --- /dev/null +++ b/web/backend/tray_stub_darwin_nocgo.go @@ -0,0 +1,32 @@ +//go:build darwin && !cgo + +package main + +import ( + "context" + "os" + "os/signal" + "syscall" + "time" + + "github.com/sipeed/picoclaw/pkg/logger" +) + +func runTray() { + logger.Infof("System tray is unavailable in darwin builds without cgo; running without tray") + + if !*noBrowser { + go func() { + time.Sleep(browserDelay) + if err := openBrowser(); err != nil { + logger.Errorf("Warning: Failed to auto-open browser: %v", err) + } + }() + } + + ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM) + defer stop() + + <-ctx.Done() + shutdownApp() +}