From 2d2f62139707911803737fb40f6c23a6d0bf9a48 Mon Sep 17 00:00:00 2001 From: Lennard Kittner Date: Fri, 20 Mar 2026 12:48:48 +0100 Subject: [PATCH] Fix windows theme and quit --- Cargo.lock | 105 +++++++++++++++++++++++++++++++---- Cargo.toml | 3 + src/status_tray_not_linux.rs | 57 +++++++++++++++++++ 3 files changed, 154 insertions(+), 11 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f0d07e3..1cf3eea 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -829,7 +829,7 @@ dependencies = [ "objc2 0.6.3", "objc2-app-kit 0.3.2", "objc2-foundation 0.3.2", - "windows", + "windows 0.61.3", "x11rb", "xkbcommon", "xkeysym", @@ -1341,6 +1341,7 @@ dependencies = [ "shell-escape", "thistermination", "tray-icon", + "windows 0.62.2", "winit", ] @@ -3318,11 +3319,23 @@ version = "0.61.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893" dependencies = [ - "windows-collections", - "windows-core", - "windows-future", + "windows-collections 0.2.0", + "windows-core 0.61.2", + "windows-future 0.2.1", "windows-link 0.1.3", - "windows-numerics", + "windows-numerics 0.2.0", +] + +[[package]] +name = "windows" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "527fadee13e0c05939a6a05d5bd6eec6cd2e3dbd648b9f8e447c6518133d8580" +dependencies = [ + "windows-collections 0.3.2", + "windows-core 0.62.2", + "windows-future 0.3.2", + "windows-numerics 0.3.1", ] [[package]] @@ -3331,7 +3344,16 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8" dependencies = [ - "windows-core", + "windows-core 0.61.2", +] + +[[package]] +name = "windows-collections" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b2d95af1a8a14a3c7367e1ed4fc9c20e0a26e79551b1454d72583c97cc6610" +dependencies = [ + "windows-core 0.62.2", ] [[package]] @@ -3343,8 +3365,21 @@ dependencies = [ "windows-implement", "windows-interface", "windows-link 0.1.3", - "windows-result", - "windows-strings", + "windows-result 0.3.4", + "windows-strings 0.4.2", +] + +[[package]] +name = "windows-core" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link 0.2.1", + "windows-result 0.4.1", + "windows-strings 0.5.1", ] [[package]] @@ -3353,9 +3388,20 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" dependencies = [ - "windows-core", + "windows-core 0.61.2", "windows-link 0.1.3", - "windows-threading", + "windows-threading 0.1.0", +] + +[[package]] +name = "windows-future" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1d6f90251fe18a279739e78025bd6ddc52a7e22f921070ccdc67dde84c605cb" +dependencies = [ + "windows-core 0.62.2", + "windows-link 0.2.1", + "windows-threading 0.2.1", ] [[package]] @@ -3398,10 +3444,20 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" dependencies = [ - "windows-core", + "windows-core 0.61.2", "windows-link 0.1.3", ] +[[package]] +name = "windows-numerics" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e2e40844ac143cdb44aead537bbf727de9b044e107a0f1220392177d15b0f26" +dependencies = [ + "windows-core 0.62.2", + "windows-link 0.2.1", +] + [[package]] name = "windows-result" version = "0.3.4" @@ -3411,6 +3467,15 @@ dependencies = [ "windows-link 0.1.3", ] +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link 0.2.1", +] + [[package]] name = "windows-strings" version = "0.4.2" @@ -3420,6 +3485,15 @@ dependencies = [ "windows-link 0.1.3", ] +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link 0.2.1", +] + [[package]] name = "windows-sys" version = "0.45.0" @@ -3546,6 +3620,15 @@ dependencies = [ "windows-link 0.1.3", ] +[[package]] +name = "windows-threading" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3949bd5b99cafdf1c7ca86b43ca564028dfe27d66958f2470940f73d86d75b37" +dependencies = [ + "windows-link 0.2.1", +] + [[package]] name = "windows_aarch64_gnullvm" version = "0.42.2" diff --git a/Cargo.toml b/Cargo.toml index cdbd9bb..7d65f95 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,3 +21,6 @@ winit = "0.30.13" [target.'cfg(target_os = "windows")'.dependencies] image = "0.25.10" +windows = { version = "0.62.2", features = [ + "Win32_System_LibraryLoader", +] } diff --git a/src/status_tray_not_linux.rs b/src/status_tray_not_linux.rs index d569434..ba2d688 100644 --- a/src/status_tray_not_linux.rs +++ b/src/status_tray_not_linux.rs @@ -34,6 +34,11 @@ pub struct TrayApp { impl ApplicationHandler> for TrayApp { fn new_events(&mut self, _event_loop: &winit::event_loop::ActiveEventLoop, cause: StartCause) { if cause == StartCause::Init { + #[cfg(target_os = "windows")] + unsafe { + enable_dark_context_menus(); + } + #[cfg(target_os = "windows")] { self.tray_icon = Some( @@ -41,6 +46,7 @@ impl ApplicationHandler> for TrayApp { .with_menu(Box::new(Menu::new())) .with_icon(create_tray_icon()) .with_tooltip(NO_COMPATIBLE_DEVICE) + .with_menu_on_left_click(true) .build() .unwrap(), ); @@ -52,6 +58,7 @@ impl ApplicationHandler> for TrayApp { .with_menu(Box::new(Menu::new())) .with_title("🎧") .with_tooltip(NO_COMPATIBLE_DEVICE) + .with_menu_on_left_click(true) .build() .unwrap(), ); @@ -113,6 +120,9 @@ impl TrayApp { return; }; + #[cfg(target_os = "windows")] + let quit_item = MenuItem::new("Quit", true, None); + let menu = Menu::new(); let mut new_callbacks: HashMap> = HashMap::new(); @@ -123,6 +133,14 @@ impl TrayApp { let status_item = MenuItem::new(NO_COMPATIBLE_DEVICE, false, None); menu.append(&status_item).unwrap(); menu.append(&PredefinedMenuItem::separator()).unwrap(); + + #[cfg(target_os = "windows")] + { + menu.append(&quit_item).unwrap(); + new_callbacks.insert(quit_item.id().clone(), Box::new(|| std::process::exit(0))); + } + + #[cfg(target_os = "macos")] menu.append(&PredefinedMenuItem::quit(Some("Quit"))) .unwrap(); @@ -139,6 +157,14 @@ impl TrayApp { let status_item = MenuItem::new(HEADSET_NOT_CONNECTED, false, None); menu.append(&status_item).unwrap(); menu.append(&PredefinedMenuItem::separator()).unwrap(); + + #[cfg(target_os = "windows")] + { + menu.append(&quit_item).unwrap(); + new_callbacks.insert(quit_item.id().clone(), Box::new(|| std::process::exit(0))); + } + + #[cfg(target_os = "macos")] menu.append(&PredefinedMenuItem::quit(Some("Quit"))) .unwrap(); @@ -254,6 +280,14 @@ impl TrayApp { } menu.append(&PredefinedMenuItem::separator()).unwrap(); + + #[cfg(target_os = "windows")] + { + menu.append(&quit_item).unwrap(); + new_callbacks.insert(quit_item.id().clone(), Box::new(|| std::process::exit(0))); + } + + #[cfg(target_os = "macos")] menu.append(&PredefinedMenuItem::quit(Some("Quit"))) .unwrap(); @@ -262,3 +296,26 @@ impl TrayApp { self.current_state = Some(Some(device_properties)); } } + +#[cfg(target_os = "windows")] +/// Dark magic to set dark mode +unsafe fn enable_dark_context_menus() { + use windows::core::PCSTR; + use windows::Win32::System::LibraryLoader::{GetProcAddress, LoadLibraryW}; + + let uxtheme = LoadLibraryW(windows::core::w!("uxtheme.dll")).unwrap(); + + // SetPreferredAppMode is ordinal 135 (undocumented, no name export) + type SetPreferredAppMode = unsafe extern "system" fn(i32) -> i32; + if let Some(func) = GetProcAddress(uxtheme, PCSTR(135 as *const u8)) { + let set_mode: SetPreferredAppMode = std::mem::transmute(func); + set_mode(1); // 1 = AllowDark (follows system theme) + } + + // FlushMenuThemes is ordinal 136 — applies the change immediately + type FlushMenuThemes = unsafe extern "system" fn(); + if let Some(func) = GetProcAddress(uxtheme, PCSTR(136 as *const u8)) { + let flush: FlushMenuThemes = std::mem::transmute(func); + flush(); + } +}