Skip to content

Commit d03cb48

Browse files
committed
fix(desktop): cache autostart state and respect Windows light theme
- Cache autostart state at startup instead of querying OS on every refresh cycle, eliminating repeated registry calls on Windows that stall the UI thread - Update cached autostart state only when toggle succeeds - Replace hardcoded light glyphs on Windows with dynamic detection via AppsUseLightTheme registry key; use light glyphs on dark menus and dark glyphs on light menus - Add parsing and tests for Windows registry theme detection; gracefully default to dark menus if registry query fails
1 parent 4d2619c commit d03cb48

6 files changed

Lines changed: 71 additions & 17 deletions

File tree

.textile-monorepo-source

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
ea6d78e6aa4826a87833c3792b3036d7bfc31748
1+
6819314f4b44a974b3920dd880f20448bc13ca97

.textile-stitch-release-version

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
0.1.146
1+
0.1.147

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.146"
3+
version = "0.1.147"
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"

src/bin/stitch-desktop/main.rs

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -251,11 +251,15 @@ fn run() -> Result<()> {
251251
#[cfg(not(target_os = "macos"))]
252252
let hide_dock_row = false;
253253

254+
// Cache OS login-item state. RefreshStatus runs every few seconds; on Windows
255+
// is_enabled() shells out to `reg query`, which stalls the UI thread.
256+
let mut autostart_enabled = autostart::is_enabled();
257+
254258
let html = if awaiting_signup {
255259
control_ui::signup_html(legacy_password_reset)
256260
} else {
257261
control_ui::html(
258-
autostart::is_enabled(),
262+
autostart_enabled,
259263
prefs.hide_dock_icon,
260264
panel_running,
261265
hide_dock_row,
@@ -313,7 +317,7 @@ fn run() -> Result<()> {
313317
&supervisor,
314318
!quiet_launch,
315319
webview.as_ref(),
316-
autostart::is_enabled(),
320+
autostart_enabled,
317321
prefs.hide_dock_icon,
318322
update_version.as_deref(),
319323
);
@@ -339,7 +343,7 @@ fn run() -> Result<()> {
339343
&update_item,
340344
&menu_icons,
341345
webview.as_ref(),
342-
autostart::is_enabled(),
346+
autostart_enabled,
343347
prefs.hide_dock_icon,
344348
&supervisor,
345349
update_version.as_deref(),
@@ -361,7 +365,7 @@ fn run() -> Result<()> {
361365
Ok(()) => {
362366
awaiting_signup = false;
363367
let control_html = control_ui::html(
364-
autostart::is_enabled(),
368+
autostart_enabled,
365369
prefs.hide_dock_icon,
366370
false,
367371
hide_dock_row,
@@ -380,7 +384,7 @@ fn run() -> Result<()> {
380384
&supervisor,
381385
true,
382386
webview.as_ref(),
383-
autostart::is_enabled(),
387+
autostart_enabled,
384388
prefs.hide_dock_icon,
385389
update_version.as_deref(),
386390
);
@@ -403,6 +407,7 @@ fn run() -> Result<()> {
403407
elwt,
404408
control_flow,
405409
webview.as_ref(),
410+
&mut autostart_enabled,
406411
update_version.as_deref(),
407412
);
408413
}
@@ -420,7 +425,7 @@ fn run() -> Result<()> {
420425
if !awaiting_signup {
421426
sync_control_ui(
422427
webview.as_ref(),
423-
autostart::is_enabled(),
428+
autostart_enabled,
424429
prefs.hide_dock_icon,
425430
&supervisor,
426431
update_version.as_deref(),
@@ -439,7 +444,7 @@ fn run() -> Result<()> {
439444
if !awaiting_signup {
440445
sync_control_ui(
441446
webview.as_ref(),
442-
autostart::is_enabled(),
447+
autostart_enabled,
443448
prefs.hide_dock_icon,
444449
&supervisor,
445450
update_version.as_deref(),
@@ -593,6 +598,7 @@ fn handle_ipc(
593598
elwt: &tao::event_loop::EventLoopWindowTarget<UserEvent>,
594599
control_flow: &mut ControlFlow,
595600
webview: Option<&wry::WebView>,
601+
autostart_enabled: &mut bool,
596602
update_version: Option<&str>,
597603
) {
598604
match msg {
@@ -614,8 +620,9 @@ fn handle_ipc(
614620
}
615621
other if other.starts_with("toggle_autostart:") => {
616622
let enabled = other.ends_with(":1");
617-
if let Err(e) = autostart::set_enabled(enabled) {
618-
eprintln!("start at login failed: {e}");
623+
match autostart::set_enabled(enabled) {
624+
Ok(()) => *autostart_enabled = enabled,
625+
Err(e) => eprintln!("start at login failed: {e}"),
619626
}
620627
}
621628
#[cfg(target_os = "macos")]
@@ -635,7 +642,7 @@ fn handle_ipc(
635642
}
636643
sync_control_ui(
637644
webview,
638-
autostart::is_enabled(),
645+
*autostart_enabled,
639646
prefs.hide_dock_icon,
640647
supervisor,
641648
update_version,

src/bin/stitch-desktop/menu_icons.rs

Lines changed: 50 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -164,8 +164,7 @@ fn action_bitmap(icons: &MenuIcons, kind: ActionKind) -> Icon {
164164
}
165165
}
166166

167-
/// Ink for monochrome glyphs. Prefer light glyphs on Windows (Win11 tray menus
168-
/// are usually dark) and when a dark GTK theme is advertised.
167+
/// Ink for monochrome glyphs. Light glyphs on dark menus; dark glyphs on light.
169168
#[cfg(not(target_os = "macos"))]
170169
fn menu_ink() -> (u8, u8, u8) {
171170
if prefer_light_glyphs() {
@@ -179,7 +178,9 @@ fn menu_ink() -> (u8, u8, u8) {
179178
fn prefer_light_glyphs() -> bool {
180179
#[cfg(target_os = "windows")]
181180
{
182-
true
181+
// AppsUseLightTheme=1 → light app chrome / menus → dark ink.
182+
// Missing key or query failure: Win11 tray menus are usually dark.
183+
!windows_apps_use_light_theme().unwrap_or(false)
183184
}
184185
#[cfg(not(target_os = "windows"))]
185186
{
@@ -189,6 +190,52 @@ fn prefer_light_glyphs() -> bool {
189190
}
190191
}
191192

193+
/// `HKCU\...\Personalize\AppsUseLightTheme` — 1 light, 0 dark.
194+
#[cfg(target_os = "windows")]
195+
fn windows_apps_use_light_theme() -> Option<bool> {
196+
use std::process::Command;
197+
let output = Command::new("reg")
198+
.args([
199+
"query",
200+
r"HKCU\Software\Microsoft\Windows\CurrentVersion\Themes\Personalize",
201+
"/v",
202+
"AppsUseLightTheme",
203+
])
204+
.output()
205+
.ok()?;
206+
if !output.status.success() {
207+
return None;
208+
}
209+
parse_apps_use_light_theme_reg(&String::from_utf8_lossy(&output.stdout))
210+
}
211+
212+
/// Parse `reg query … /v AppsUseLightTheme` stdout.
213+
#[cfg(any(test, target_os = "windows"))]
214+
fn parse_apps_use_light_theme_reg(stdout: &str) -> Option<bool> {
215+
// Typical line: ` AppsUseLightTheme REG_DWORD 0x1`
216+
let value = stdout
217+
.lines()
218+
.find(|line| line.contains("AppsUseLightTheme"))?
219+
.split_whitespace()
220+
.last()?;
221+
let n = u32::from_str_radix(value.trim_start_matches("0x"), 16).ok()?;
222+
Some(n != 0)
223+
}
224+
225+
#[cfg(test)]
226+
mod theme_ink_tests {
227+
use super::parse_apps_use_light_theme_reg;
228+
229+
#[test]
230+
fn parses_light_and_dark_reg_output() {
231+
let light = "\nHKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize\n AppsUseLightTheme REG_DWORD 0x1\n\n";
232+
let dark = "\nHKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize\n AppsUseLightTheme REG_DWORD 0x0\n\n";
233+
assert_eq!(parse_apps_use_light_theme_reg(light), Some(true));
234+
assert_eq!(parse_apps_use_light_theme_reg(dark), Some(false));
235+
assert_eq!(parse_apps_use_light_theme_reg("nope"), None);
236+
}
237+
}
238+
192239
// --- Windows / Linux bitmap fallbacks (32×32, anti-aliased) ---
193240

194241
#[cfg(not(target_os = "macos"))]

0 commit comments

Comments
 (0)