diff --git a/package-lock.json b/package-lock.json index 25b1afe..1067e97 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { - "name": "tauri-app", - "version": "0.1.0", + "name": "gquick", + "version": "0.1.7", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "tauri-app", - "version": "0.1.0", + "name": "gquick", + "version": "0.1.7", "dependencies": { "@tauri-apps/api": "^2", "@tauri-apps/plugin-clipboard-manager": "^2.3.1", diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index ba84be0..cd496c2 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -328,7 +328,7 @@ dependencies = [ "log", "num-rational", "num-traits", - "pastey", + "pastey 0.1.1", "rayon", "thiserror 2.0.18", "v_frame", @@ -2037,6 +2037,7 @@ dependencies = [ "serde_json", "tauri", "tauri-build", + "tauri-nspanel", "tauri-plugin-clipboard-manager", "tauri-plugin-dialog", "tauri-plugin-global-shortcut", @@ -3754,6 +3755,12 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "35fb2e5f958ec131621fdd531e9fc186ed768cbe395337403ae56c17a74c68ec" +[[package]] +name = "pastey" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5a797f0e07bdf071d15742978fc3128ec6c22891c31a3a931513263904c982a" + [[package]] name = "pathdiff" version = "0.2.3" @@ -5695,6 +5702,18 @@ dependencies = [ "tauri-utils", ] +[[package]] +name = "tauri-nspanel" +version = "2.1.0" +source = "git+https://github.com/ahkohd/tauri-nspanel?branch=v2.1#a3122e894383aa068ec5365a42994e3ac94ba1b6" +dependencies = [ + "objc2", + "objc2-app-kit", + "objc2-foundation", + "pastey 0.2.2", + "tauri", +] + [[package]] name = "tauri-plugin" version = "2.5.4" diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 362c533..ba7c5a7 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -46,6 +46,7 @@ tesseract = "0.15" core-graphics = "0.25" objc2 = "0.6" objc2-foundation = "0.3" +tauri-nspanel = { git = "https://github.com/ahkohd/tauri-nspanel", branch = "v2.1" } [target.'cfg(target_os = "windows")'.dependencies] windows-sys = { version = "0.59", features = ["Win32_Foundation", "Win32_UI_WindowsAndMessaging"] } diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 1712334..5e02984 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -12,17 +12,18 @@ use tauri_plugin_global_shortcut::{ Code, GlobalShortcutExt, Modifiers, Shortcut, ShortcutState as GsShortcutState, }; use tauri_plugin_opener::OpenerExt; -#[cfg(target_os = "macos")] -use tesseract::Tesseract; #[cfg(target_os = "macos")] -use objc2::rc::Retained; +use tauri_nspanel::{ + tauri_panel, CollectionBehavior, PanelLevel, StyleMask, + WebviewWindowExt as PanelWindowExt, +}; #[cfg(target_os = "macos")] -use objc2::{class, extern_class, extern_methods, msg_send, sel}; +use tesseract::Tesseract; #[cfg(target_os = "macos")] -use objc2::runtime::NSObject; +use objc2::{class, extern_class, extern_methods, sel}; #[cfg(target_os = "macos")] use objc2_foundation::NSString; @@ -30,6 +31,19 @@ use objc2_foundation::NSString; #[cfg(target_os = "macos")] use std::ffi::c_void; +#[cfg(target_os = "macos")] +tauri_panel! { + panel!(GQuickPanel { + config: { + can_become_key_window: true, + can_become_main_window: false, + is_floating_panel: true, + hides_on_deactivate: false, + works_when_modal: true + } + }) +} + #[cfg(target_os = "macos")] #[link(name = "CoreWLAN", kind = "framework")] extern "C" {} @@ -3177,7 +3191,17 @@ fn restore_previous_focus(app: &tauri::AppHandle) { } fn hide_window(window: &tauri::Window, restore_focus: bool) -> Result<(), String> { - window.hide().map_err(|e| e.to_string())?; + #[cfg(target_os = "macos")] + { + use tauri_nspanel::ManagerExt; + if let Ok(panel) = window.app_handle().get_webview_panel(window.label()) { + panel.hide(); + } + } + #[cfg(not(target_os = "macos"))] + { + window.hide().map_err(|e| e.to_string())?; + } window .emit("window-hidden", ()) .map_err(|e| e.to_string())?; @@ -4693,7 +4717,6 @@ fn show_selector_window(app: &tauri::AppHandle, mode: &str) { match builder.build() { Ok(window) => { - log_capture("selector window built successfully"); if let Err(e) = window.set_focus() { log_capture(&format!("FAILED to focus selector window: {}", e)); } @@ -4928,6 +4951,14 @@ async fn open_image_dialog( } // Show and focus the main window again + #[cfg(target_os = "macos")] + { + use tauri_nspanel::ManagerExt; + if let Ok(panel) = app.get_webview_panel("main") { + panel.show_and_make_key(); + } + } + #[cfg(not(target_os = "macos"))] if let Some(window) = app.get_webview_window("main") { let _ = window.show(); let _ = window.set_focus(); @@ -4974,13 +5005,32 @@ async fn open_image_dialog( fn toggle_window(app: &tauri::AppHandle) { if let Some(window) = app.get_webview_window("main") { + #[cfg(target_os = "macos")] + let is_visible = { + use tauri_nspanel::ManagerExt; + app.get_webview_panel("main") + .map(|p| p.is_visible()) + .unwrap_or(false) + }; + #[cfg(not(target_os = "macos"))] let is_visible = window.is_visible().unwrap_or(false); + if is_visible { if has_running_inline_command(app) { let _ = window.emit("terminal-close-requested", "toggle".to_string()); return; } - let _ = window.hide(); + #[cfg(target_os = "macos")] + { + use tauri_nspanel::ManagerExt; + if let Ok(panel) = app.get_webview_panel("main") { + panel.hide(); + } + } + #[cfg(not(target_os = "macos"))] + { + let _ = window.hide(); + } let _ = window.emit("window-hidden", ()); restore_previous_focus(app); } else { @@ -4991,6 +5041,14 @@ fn toggle_window(app: &tauri::AppHandle) { fn show_main_window(app: &tauri::AppHandle) { if let Some(window) = app.get_webview_window("main") { + #[cfg(target_os = "macos")] + let was_visible = { + use tauri_nspanel::ManagerExt; + app.get_webview_panel("main") + .map(|p| p.is_visible()) + .unwrap_or(false) + }; + #[cfg(not(target_os = "macos"))] let was_visible = window.is_visible().unwrap_or(false); if !was_visible { @@ -5022,8 +5080,20 @@ fn show_main_window(app: &tauri::AppHandle) { } } - let _ = window.show(); - let _ = window.set_focus(); + #[cfg(target_os = "macos")] + { + use tauri_nspanel::ManagerExt; + if let Ok(panel) = app.get_webview_panel("main") { + panel.show_and_make_key(); + } + } + + #[cfg(not(target_os = "macos"))] + { + let _ = window.show(); + let _ = window.set_focus(); + } + if !was_visible { let _ = window.emit("window-shown", ()); } @@ -5040,7 +5110,7 @@ fn open_settings_window(app: &tauri::AppHandle) { #[cfg_attr(mobile, tauri::mobile_entry_point)] pub fn run() { - tauri::Builder::default() + let mut builder = tauri::Builder::default() .plugin(tauri_plugin_opener::init()) .plugin(tauri_plugin_clipboard_manager::init()) .plugin( @@ -5087,11 +5157,41 @@ pub fn run() { ) .plugin(tauri_plugin_dialog::init()) .plugin(tauri_plugin_updater::Builder::new().build()) - .plugin(tauri_plugin_process::init()) + .plugin(tauri_plugin_process::init()); + + #[cfg(target_os = "macos")] + { + builder = builder.plugin(tauri_nspanel::init()); + } + + builder .setup(|app| { // Hide dock icon on macOS, keep only tray icon #[cfg(target_os = "macos")] - app.set_activation_policy(tauri::ActivationPolicy::Accessory); + { + app.set_activation_policy(tauri::ActivationPolicy::Accessory); + + // Convert the main window to an NSPanel so it can overlay fullscreen apps. + let window = app.get_webview_window("main") + .ok_or("Failed to get main window during startup")?; + let panel = window.to_panel::() + .map_err(|e| format!("Failed to convert main window to panel: {}", e))?; + panel.set_level(PanelLevel::ScreenSaver.value()); + panel.set_collection_behavior( + CollectionBehavior::new() + .can_join_all_spaces() + .stationary() + .full_screen_auxiliary() + .ignores_cycle() + .value(), + ); + panel.set_style_mask( + StyleMask::empty() + .borderless() + .nonactivating_panel() + .value(), + ); + } let open_i = MenuItem::with_id(app, "open", "Open GQuick", true, None::<&str>)?; let settings_i = MenuItem::with_id(app, "settings", "Settings", true, None::<&str>)?;