Skip to content

Commit 4f578c8

Browse files
committed
fix(cli): create macOS tray icon after event loop init
- Move tray icon creation to StartCause::Init event handler on macOS to ensure the event loop is running before TrayIcon::new, fixing missing menu-bar icon for LSUIElement apps - Set ActivationPolicy::Accessory before event loop runs to prevent Dock icon appearance - Keep tray alive even if panel startup fails, allowing user to retry or quit - Defer panel startup to init event to match tray icon lifecycle - Update install documentation to clarify menu-bar icon location and absence of Dock window on macOS - Bump version to 0.1.134
1 parent f3cea9f commit 4f578c8

6 files changed

Lines changed: 64 additions & 29 deletions

File tree

.textile-monorepo-source

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
e79e08b5a7cbf446fb15f9fb08dfa72fb86749df
1+
27f445cfdc792bc60ad88ca2df9eed36b86a597a

.textile-stitch-release-version

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
0.1.133
1+
0.1.134

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "stitch-bot"
3-
version = "0.1.133"
3+
version = "0.1.134"
44
edition = "2021"
55
description = "Stitch — Textile filler-network operator bot; market-makes the filler order book with signed UniswapX limit orders."
66
license = "AGPL-3.0-or-later"

docs/install-desktop.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@ you. No Docker and no terminal. For an always-on server, see
55
[install-panel.md](install-panel.md).
66

77
The desktop app (`stitch-desktop`) is a light menu-bar (macOS) or system-tray
8-
(Windows / Linux) controller. On first launch it:
8+
(Windows / Linux) controller. There is **no Dock window** on macOS — look for a
9+
teal Stitch icon in the menu bar (top right; may be under the `»` overflow). On
10+
first launch it:
911

1012
1. Creates a data directory (`~/Library/Application Support/Stitch` on macOS,
1113
`%APPDATA%\Stitch` on Windows, `~/.local/share/stitch` on Linux).

src/bin/stitch-desktop/main.rs

Lines changed: 57 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ use anyhow::{Context, Result};
2222
use tao::event::{Event, StartCause};
2323
use tao::event_loop::{ControlFlow, EventLoopBuilder};
2424
use tray_icon::menu::{CheckMenuItem, Menu, MenuEvent, MenuItem, PredefinedMenuItem};
25-
use tray_icon::{Icon, TrayIconBuilder};
25+
use tray_icon::{Icon, TrayIcon, TrayIconBuilder};
2626

2727
use 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

Comments
 (0)