From fa6acaf41fe5e2e44e2b9eca3d66f64f939ba1c4 Mon Sep 17 00:00:00 2001 From: Leafd Date: Thu, 9 Oct 2025 10:18:55 -0400 Subject: [PATCH] fix: make windows register deeplinks --- src-tauri/Cargo.lock | 122 +++++++++++++++++--------------------- src-tauri/Cargo.toml | 6 +- src-tauri/src/lib.rs | 80 ++++++++++++++++++------- src-tauri/src/menu.rs | 7 +-- src-tauri/tauri.conf.json | 1 + 5 files changed, 120 insertions(+), 96 deletions(-) diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index a6bcd04..b2bfa61 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -294,12 +294,6 @@ dependencies = [ "serde", ] -[[package]] -name = "block" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" - [[package]] name = "block-buffer" version = "0.10.4" @@ -518,35 +512,6 @@ dependencies = [ "windows-link 0.2.0", ] -[[package]] -name = "cocoa" -version = "0.26.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad36507aeb7e16159dfe68db81ccc27571c3ccd4b76fb2fb72fc59e7a4b1b64c" -dependencies = [ - "bitflags 2.9.4", - "block", - "cocoa-foundation", - "core-foundation 0.10.1", - "core-graphics", - "foreign-types 0.5.0", - "libc", - "objc", -] - -[[package]] -name = "cocoa-foundation" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81411967c50ee9a1fc11365f8c585f863a22a9697c89239c452292c40ba79b0d" -dependencies = [ - "bitflags 2.9.4", - "block", - "core-foundation 0.10.1", - "core-graphics-types", - "objc", -] - [[package]] name = "combine" version = "4.6.7" @@ -854,9 +819,10 @@ version = "0.1.0" dependencies = [ "base64 0.21.7", "chrono", - "cocoa", "discord-rich-presence", - "objc", + "objc2 0.5.2", + "objc2-app-kit 0.2.2", + "objc2-foundation 0.2.2", "once_cell", "open", "rand 0.9.2", @@ -2381,15 +2347,6 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4" -[[package]] -name = "malloc_buf" -version = "0.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb" -dependencies = [ - "libc", -] - [[package]] name = "markup5ever" version = "0.14.1" @@ -2490,7 +2447,7 @@ dependencies = [ "gtk", "keyboard-types", "objc2 0.6.3", - "objc2-app-kit", + "objc2-app-kit 0.3.2", "objc2-core-foundation", "objc2-foundation 0.3.2", "once_cell", @@ -2647,15 +2604,6 @@ dependencies = [ "syn 2.0.106", ] -[[package]] -name = "objc" -version = "0.2.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" -dependencies = [ - "malloc_buf", -] - [[package]] name = "objc-sys" version = "0.3.5" @@ -2682,6 +2630,22 @@ dependencies = [ "objc2-exception-helper", ] +[[package]] +name = "objc2-app-kit" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4e89ad9e3d7d297152b17d39ed92cd50ca8063a89a9fa569046d41568891eff" +dependencies = [ + "bitflags 2.9.4", + "block2 0.5.1", + "libc", + "objc2 0.5.2", + "objc2-core-data 0.2.2", + "objc2-core-image 0.2.2", + "objc2-foundation 0.2.2", + "objc2-quartz-core 0.2.2", +] + [[package]] name = "objc2-app-kit" version = "0.3.2" @@ -2693,10 +2657,10 @@ dependencies = [ "libc", "objc2 0.6.3", "objc2-cloud-kit", - "objc2-core-data", + "objc2-core-data 0.3.2", "objc2-core-foundation", "objc2-core-graphics", - "objc2-core-image", + "objc2-core-image 0.3.2", "objc2-core-text", "objc2-core-video", "objc2-foundation 0.3.2", @@ -2714,6 +2678,18 @@ dependencies = [ "objc2-foundation 0.3.2", ] +[[package]] +name = "objc2-core-data" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "617fbf49e071c178c0b24c080767db52958f716d9eabdf0890523aeae54773ef" +dependencies = [ + "bitflags 2.9.4", + "block2 0.5.1", + "objc2 0.5.2", + "objc2-foundation 0.2.2", +] + [[package]] name = "objc2-core-data" version = "0.3.2" @@ -2749,6 +2725,18 @@ dependencies = [ "objc2-io-surface", ] +[[package]] +name = "objc2-core-image" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55260963a527c99f1819c4f8e3b47fe04f9650694ef348ffd2227e8196d34c80" +dependencies = [ + "block2 0.5.1", + "objc2 0.5.2", + "objc2-foundation 0.2.2", + "objc2-metal", +] + [[package]] name = "objc2-core-image" version = "0.3.2" @@ -2865,7 +2853,7 @@ checksum = "f112d1746737b0da274ef79a23aac283376f335f4095a083a267a082f21db0c0" dependencies = [ "bitflags 2.9.4", "objc2 0.6.3", - "objc2-app-kit", + "objc2-app-kit 0.3.2", "objc2-foundation 0.3.2", ] @@ -2925,7 +2913,7 @@ dependencies = [ "bitflags 2.9.4", "block2 0.6.2", "objc2 0.6.3", - "objc2-app-kit", + "objc2-app-kit 0.3.2", "objc2-core-foundation", "objc2-foundation 0.3.2", "objc2-javascript-core", @@ -4699,7 +4687,7 @@ dependencies = [ "ndk-context", "ndk-sys", "objc2 0.6.3", - "objc2-app-kit", + "objc2-app-kit 0.3.2", "objc2-foundation 0.3.2", "once_cell", "parking_lot", @@ -4765,7 +4753,7 @@ dependencies = [ "mime", "muda", "objc2 0.6.3", - "objc2-app-kit", + "objc2-app-kit 0.3.2", "objc2-foundation 0.3.2", "objc2-ui-kit", "objc2-web-kit", @@ -4903,7 +4891,7 @@ checksum = "786156aa8e89e03d271fbd3fe642207da8e65f3c961baa9e2930f332bf80a1f5" dependencies = [ "dunce", "glob", - "objc2-app-kit", + "objc2-app-kit 0.3.2", "objc2-foundation 0.3.2", "open", "schemars 0.8.22", @@ -4995,7 +4983,7 @@ dependencies = [ "jni", "log", "objc2 0.6.3", - "objc2-app-kit", + "objc2-app-kit 0.3.2", "objc2-foundation 0.3.2", "once_cell", "percent-encoding", @@ -5447,7 +5435,7 @@ dependencies = [ "libappindicator", "muda", "objc2 0.6.3", - "objc2-app-kit", + "objc2-app-kit 0.3.2", "objc2-core-foundation", "objc2-core-graphics", "objc2-foundation 0.3.2", @@ -5974,7 +5962,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9bec5a31f3f9362f2258fd0e9c9dd61a9ca432e7306cc78c444258f0dce9a9c" dependencies = [ "objc2 0.6.3", - "objc2-app-kit", + "objc2-app-kit 0.3.2", "objc2-core-foundation", "objc2-foundation 0.3.2", "raw-window-handle", @@ -6510,7 +6498,7 @@ dependencies = [ "libc", "ndk", "objc2 0.6.3", - "objc2-app-kit", + "objc2-app-kit 0.3.2", "objc2-core-foundation", "objc2-foundation 0.3.2", "objc2-ui-kit", diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index aa828e1..133abe7 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -21,6 +21,7 @@ tauri-build = { version = "2", features = [] } tauri = { version = "2", features = ["tray-icon"] } tauri-plugin-opener = "2" tauri-plugin-deep-link = "2" +tauri-plugin-single-instance = "2" serde = { version = "1", features = ["derive"] } serde_json = "1" open = "5" @@ -41,6 +42,7 @@ once_cell = "1" tauri-plugin-updater = "2" [target."cfg(target_os = \"macos\")".dependencies] -cocoa = "0.26" -objc = "0.2" +objc2 = "0.5" +objc2-app-kit = { version = "0.2", features = ["NSColor"] } +objc2-foundation = "0.2" diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 59b8fdc..ffd08ee 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -61,6 +61,24 @@ fn get_recent_logs() -> Vec { #[cfg_attr(mobile, tauri::mobile_entry_point)] pub fn run() { tauri::Builder::default() + .plugin(tauri_plugin_single_instance::init(|app, args, cwd| { + push_log("info", "backend", format!("Single instance detected. Args: {:?}, CWD: {}", args, cwd)); + + // Show the existing window + if let Some(window) = app.get_webview_window("main") { + let _ = window.show(); + let _ = window.set_focus(); + push_log("info", "backend", "Brought existing window to front".to_string()); + } + + // Process any deep links from the new instance attempt + for arg in args { + if arg.starts_with("hackatime://") { + push_log("info", "backend", format!("Processing deep link from second instance: {}", arg)); + handle_oauth_callback(&app, &arg); + } + } + })) .plugin(tauri_plugin_process::init()) .plugin(tauri_plugin_updater::Builder::new().build()) .plugin(tauri_plugin_opener::init()) @@ -149,33 +167,34 @@ pub fn run() { #[cfg(target_os = "macos")] { - window.set_title_bar_style(TitleBarStyle::Transparent).unwrap(); + use objc2::runtime::AnyObject; + use objc2_app_kit::NSColor; + use objc2::msg_send; + if let Err(e) = window.set_title_bar_style(TitleBarStyle::Transparent) { + push_log("error", "backend", format!("Failed to set title bar style: {}", e)); + } - #[allow(deprecated)] - #[allow(unexpected_cfgs)] - { - use cocoa::appkit::{NSColor, NSWindow}; - use cocoa::base::{id, nil}; - - let ns_window = window.ns_window().unwrap() as id; + // Apply macOS-specific window styling with proper error handling + if let Ok(ns_win) = window.ns_window() { + let ns_window = ns_win as *mut AnyObject; unsafe { - use objc::{msg_send, sel, sel_impl}; - use objc::runtime::NO; + let clear_color = NSColor::clearColor(); + let _: () = msg_send![ns_window, setBackgroundColor: &*clear_color]; + let _: () = msg_send![ns_window, setOpaque: false]; - - let bg_color = NSColor::clearColor(nil); - ns_window.setBackgroundColor_(bg_color); - - ns_window.setOpaque_(NO); - - let content_view: id = msg_send![ns_window, contentView]; + let content_view: *mut AnyObject = msg_send![ns_window, contentView]; let _: () = msg_send![content_view, setWantsLayer: true]; - let layer: id = msg_send![content_view, layer]; + let layer: *mut AnyObject = msg_send![content_view, layer]; let _: () = msg_send![layer, setCornerRadius: 12.0f64]; let _: () = msg_send![layer, setMasksToBounds: true]; + + let _: () = msg_send![layer, setNeedsDisplayOnBoundsChange: true]; } + push_log("info", "backend", "✅ macOS window styling applied".to_string()); + } else { + push_log("error", "backend", "Failed to get NSWindow".to_string()); } } } @@ -224,7 +243,7 @@ pub fn run() { }); - #[cfg(any(target_os = "linux", all(debug_assertions, target_os = "windows")))] + #[cfg(any(target_os = "linux", target_os = "windows"))] { app.deep_link().register_all().unwrap_or_else(|e| { push_log("error", "backend", format!("Failed to register deep links: {}", e)); @@ -236,14 +255,29 @@ pub fn run() { if let Some(window) = app.get_webview_window("main") { - let window_handle = window.clone(); - let _ = window.on_window_event(move |event| { + let app_handle = app.handle().clone(); + window.on_window_event(move |event| { match event { WindowEvent::CloseRequested { api, .. } => { push_log("info", "backend", "🪟 Window close requested - hiding to tray".to_string()); api.prevent_close(); - let _ = window_handle.hide(); - push_log("info", "backend", "✅ Window hidden to tray".to_string()); + + // Use the app handle to get the window and hide it asynchronously + // This prevents potential re-entrancy issues + let app_clone = app_handle.clone(); + std::thread::spawn(move || { + if let Some(win) = app_clone.get_webview_window("main") { + let _ = win.hide(); + push_log("info", "backend", "✅ Window hidden to tray".to_string()); + } + }); + } + WindowEvent::Resized(_) => { + // Handle resize events gracefully - no action needed + // This prevents potential crashes on macOS with transparent windows + } + WindowEvent::Moved(_) => { + // Handle move events gracefully } _ => {} } diff --git a/src-tauri/src/menu.rs b/src-tauri/src/menu.rs index 427fe81..14b45b3 100644 --- a/src-tauri/src/menu.rs +++ b/src-tauri/src/menu.rs @@ -4,13 +4,13 @@ use crate::push_log; pub fn setup_app_menu(app: &AppHandle) -> Result<(), Box> { - let about_quit = MenuItem::with_id(app, "quit", "Quit Hackatime", true, None::<&str>)?; + let quit_item = PredefinedMenuItem::quit(app, Some("Quit Hackatime"))?; let about_menu = Submenu::with_items( app, - "About", + "Hackatime", true, &[ - &about_quit, + &quit_item, ], )?; @@ -62,7 +62,6 @@ pub fn setup_app_menu(app: &AppHandle) -> Result<(), Box> app.on_menu_event(|app, event| { match event.id.as_ref() { - "quit" => app.exit(0), "show" => { if let Some(window) = app.get_webview_window("main") { let _ = window.show(); diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index 61bd200..c4aed69 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -61,6 +61,7 @@ ] } }, + "single-instance": {}, "updater": { "pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IDdERjg4QTFCNTJFMDk0MUQKUldRZGxPQlNHNHI0ZlRkMDN0MGI1MnllY1dUVStZalV3dVdhcTFuREx5SGtBc0txQ2xnTWs3WU4K", "endpoints": [