@@ -22,7 +22,7 @@ use anyhow::{Context, Result};
2222use tao:: event:: { Event , StartCause } ;
2323use tao:: event_loop:: { ControlFlow , EventLoopBuilder } ;
2424use tray_icon:: menu:: { CheckMenuItem , Menu , MenuEvent , MenuItem , PredefinedMenuItem } ;
25- use tray_icon:: { Icon , TrayIconBuilder } ;
25+ use tray_icon:: { Icon , TrayIcon , TrayIconBuilder } ;
2626
2727use crate :: supervise:: PanelSupervisor ;
2828
@@ -74,24 +74,19 @@ fn run() -> Result<()> {
7474 }
7575 let password = password:: ensure_panel_password ( & paths) ?;
7676
77- let supervisor = Arc :: new ( Mutex :: new ( PanelSupervisor :: new ( paths. clone ( ) ) ?) ) ;
77+ let supervisor = Arc :: new ( Mutex :: new ( PanelSupervisor :: new ( paths) ?) ) ;
78+
79+ // `mut` is required on macOS for set_activation_policy before run.
80+ #[ allow( unused_mut) ]
81+ let mut event_loop = EventLoopBuilder :: < UserEvent > :: with_user_event ( ) . build ( ) ;
82+ // Menu-bar agent: match Info.plist LSUIElement so we never take a Dock slot.
83+ #[ cfg( target_os = "macos" ) ]
7884 {
79- let mut s = supervisor. lock ( ) . unwrap ( ) ;
80- s. start ( ) . context ( "starting the local Stitch panel" ) ?;
81- }
82- // Interactive launches open the browser; login autostart stays in the tray.
83- // Bots that were `wanted_up` when the panel last stopped come back via the
84- // process runtime's persisted state — no extra work here.
85- if !quiet_launch {
86- std:: thread:: spawn ( || {
87- std:: thread:: sleep ( std:: time:: Duration :: from_millis ( 800 ) ) ;
88- let _ = open_url ( PANEL_URL ) ;
89- } ) ;
85+ use tao:: platform:: macos:: { ActivationPolicy , EventLoopExtMacOS } ;
86+ event_loop. set_activation_policy ( ActivationPolicy :: Accessory ) ;
9087 }
9188
92- let event_loop = EventLoopBuilder :: < UserEvent > :: with_user_event ( ) . build ( ) ;
9389 let proxy = event_loop. create_proxy ( ) ;
94-
9590 MenuEvent :: set_event_handler ( Some ( move |event : MenuEvent | {
9691 let _ = proxy. send_event ( UserEvent :: Menu ( event. id ) ) ;
9792 } ) ) ;
@@ -124,20 +119,57 @@ fn run() -> Result<()> {
124119 menu. append ( & PredefinedMenuItem :: separator ( ) ) ?;
125120 menu. append ( & quit_item) ?;
126121
127- let icon = tray_icon_from_embedded ( ) . unwrap_or_else ( fallback_icon) ;
128- let mut _tray = TrayIconBuilder :: new ( )
129- . with_menu ( Box :: new ( menu) )
130- . with_tooltip ( "Stitch" )
131- . with_icon ( icon)
132- . build ( )
133- . context ( "creating the menu bar / tray icon" ) ?;
134-
135122 let password = Arc :: new ( password) ;
123+ // tray-icon requires the macOS event loop to be running before TrayIcon::new.
124+ // Creating it here (before run) leaves LSUIElement apps with no visible icon.
125+ let mut tray: Option < TrayIcon > = None ;
126+ let mut started_panel = false ;
136127
137128 event_loop. run ( move |event, _, control_flow| {
138129 * control_flow = ControlFlow :: Wait ;
139130 match event {
140- Event :: NewEvents ( StartCause :: Init ) => { }
131+ Event :: NewEvents ( StartCause :: Init ) => {
132+ if tray. is_none ( ) {
133+ let icon = tray_icon_from_embedded ( ) . unwrap_or_else ( fallback_icon) ;
134+ match TrayIconBuilder :: new ( )
135+ . with_menu ( Box :: new ( menu. clone ( ) ) )
136+ . with_tooltip ( "Stitch" )
137+ . with_icon ( icon)
138+ . build ( )
139+ {
140+ Ok ( t) => tray = Some ( t) ,
141+ Err ( e) => {
142+ eprintln ! ( "stitch-desktop: creating menu bar icon failed: {e:#}" ) ;
143+ * control_flow = ControlFlow :: Exit ;
144+ return ;
145+ }
146+ }
147+ }
148+ if !started_panel {
149+ started_panel = true ;
150+ let start_result = {
151+ let mut s = supervisor. lock ( ) . unwrap ( ) ;
152+ s. start ( ) . context ( "starting the local Stitch panel" )
153+ } ;
154+ match start_result {
155+ Ok ( ( ) ) => {
156+ // Interactive launches open the browser; login
157+ // autostart stays in the tray.
158+ if !quiet_launch {
159+ std:: thread:: spawn ( || {
160+ std:: thread:: sleep ( std:: time:: Duration :: from_millis ( 800 ) ) ;
161+ let _ = open_url ( PANEL_URL ) ;
162+ } ) ;
163+ }
164+ }
165+ Err ( e) => {
166+ // Keep the tray alive so the user can Quit / retry Start.
167+ // Finder launches have no terminal for eprintln alone.
168+ eprintln ! ( "stitch-desktop: {e:#}" ) ;
169+ }
170+ }
171+ }
172+ }
141173 Event :: UserEvent ( UserEvent :: Menu ( id) ) => {
142174 if id == open_id {
143175 let _ = open_url ( PANEL_URL ) ;
@@ -177,6 +209,7 @@ fn run() -> Result<()> {
177209 if let Ok ( mut s) = supervisor. lock ( ) {
178210 let _ = s. stop ( ) ;
179211 }
212+ tray. take ( ) ;
180213 }
181214 _ => { }
182215 }
0 commit comments