From f09cc137a3742b5ae4f8ca65d5bb2d2a7bc44b6a Mon Sep 17 00:00:00 2001 From: Dmytro Kondakov Date: Sun, 13 Apr 2025 16:40:04 +0200 Subject: [PATCH] feat: add global shortcut functionality and update dependencies - Introduced global shortcut management in the Tauri application, allowing users to set, change, and unregister shortcuts. - Added new dependencies for global shortcut functionality in Cargo.toml and updated package.json. - Enhanced the default capabilities to include global shortcut permissions. - Refactored the main application logic to integrate the new shortcut features. --- frontend/bun.lockb | Bin 118512 -> 119374 bytes frontend/package.json | 84 +++++----- frontend/src-tauri/Cargo.lock | 50 ++++++ frontend/src-tauri/Cargo.toml | 4 + frontend/src-tauri/capabilities/default.json | 6 +- frontend/src-tauri/src/lib.rs | 11 +- frontend/src-tauri/src/shortcut.rs | 167 +++++++++++++++++++ 7 files changed, 279 insertions(+), 43 deletions(-) create mode 100644 frontend/src-tauri/src/shortcut.rs diff --git a/frontend/bun.lockb b/frontend/bun.lockb index 3e7fff770f996de721d726592f7206885747c900..170eb8b07d13c5156190448e66b83b1bbfd82743 100755 GIT binary patch delta 19777 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 UpAxj(ddX)DvtRu~A?x(N0Pfb!l>h($ delta 19284 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 UZ_H;sMCN>!xTWU;R=v}I0SZ|IF#rGn diff --git a/frontend/package.json b/frontend/package.json index ff969af..dc290f7 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,43 +1,45 @@ { - "name": "haystack", - "version": "0.1.0", - "description": "Screenshots that organize themselves", - "type": "module", - "scripts": { - "start": "vite", - "dev": "vite", - "build": "vite build", - "serve": "vite preview", - "tauri": "tauri", - "lint": "bunx @biomejs/biome lint .", - "format": "bunx @biomejs/biome format . --write" - }, - "license": "MIT", - "dependencies": { - "@kobalte/core": "^0.13.9", - "@kobalte/tailwindcss": "^0.9.0", - "@solidjs/router": "^0.15.3", - "@tabler/icons-solidjs": "^3.30.0", - "@tauri-apps/api": "^2", - "@tauri-apps/plugin-dialog": "~2", - "@tauri-apps/plugin-opener": "^2", - "clsx": "^2.1.1", - "fuse.js": "^7.1.0", - "jwt-decode": "^4.0.0", - "solid-js": "^1.9.3", - "solid-motionone": "^1.0.3", - "tailwind-scrollbar-hide": "^2.0.0", - "valibot": "^1.0.0-rc.2" - }, - "devDependencies": { - "@biomejs/biome": "^1.9.4", - "@tauri-apps/cli": "^2", - "autoprefixer": "^10.4.20", - "postcss": "^8.5.3", - "postcss-cli": "^11.0.0", - "tailwindcss": "3.4.0", - "typescript": "~5.6.2", - "vite": "^6.0.3", - "vite-plugin-solid": "^2.11.0" - } + "name": "haystack", + "version": "0.1.0", + "description": "Screenshots that organize themselves", + "type": "module", + "scripts": { + "start": "vite", + "dev": "vite", + "build": "vite build", + "serve": "vite preview", + "tauri": "tauri", + "lint": "bunx @biomejs/biome lint .", + "format": "bunx @biomejs/biome format . --write" + }, + "license": "MIT", + "dependencies": { + "@kobalte/core": "^0.13.9", + "@kobalte/tailwindcss": "^0.9.0", + "@solidjs/router": "^0.15.3", + "@tabler/icons-solidjs": "^3.30.0", + "@tauri-apps/api": "^2", + "@tauri-apps/plugin-dialog": "~2", + "@tauri-apps/plugin-global-shortcut": "~2", + "@tauri-apps/plugin-opener": "^2", + "@tauri-apps/plugin-store": "~2", + "clsx": "^2.1.1", + "fuse.js": "^7.1.0", + "jwt-decode": "^4.0.0", + "solid-js": "^1.9.3", + "solid-motionone": "^1.0.3", + "tailwind-scrollbar-hide": "^2.0.0", + "valibot": "^1.0.0-rc.2" + }, + "devDependencies": { + "@biomejs/biome": "^1.9.4", + "@tauri-apps/cli": "^2", + "autoprefixer": "^10.4.20", + "postcss": "^8.5.3", + "postcss-cli": "^11.0.0", + "tailwindcss": "3.4.0", + "typescript": "~5.6.2", + "vite": "^6.0.3", + "vite-plugin-solid": "^2.11.0" + } } diff --git a/frontend/src-tauri/Cargo.lock b/frontend/src-tauri/Cargo.lock index 9a599d4..887d2cb 100644 --- a/frontend/src-tauri/Cargo.lock +++ b/frontend/src-tauri/Cargo.lock @@ -14,7 +14,9 @@ dependencies = [ "tauri", "tauri-build", "tauri-plugin-dialog", + "tauri-plugin-global-shortcut", "tauri-plugin-opener", + "tauri-plugin-store", "tokio", ] @@ -1459,6 +1461,23 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" +[[package]] +name = "global-hotkey" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41fbb3a4e56c901ee66c190fdb3fa08344e6d09593cc6c61f8eb9add7144b271" +dependencies = [ + "crossbeam-channel", + "keyboard-types", + "objc2 0.6.0", + "objc2-app-kit 0.3.0", + "once_cell", + "serde", + "thiserror 2.0.11", + "windows-sys 0.59.0", + "x11-dl", +] + [[package]] name = "gobject-sys" version = "0.18.0" @@ -4123,6 +4142,21 @@ dependencies = [ "uuid", ] +[[package]] +name = "tauri-plugin-global-shortcut" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00f646a09511e8d283267dcdaa08c2ef27c4116bf271d9114849d9ca215606c3" +dependencies = [ + "global-hotkey", + "log", + "serde", + "serde_json", + "tauri", + "tauri-plugin", + "thiserror 2.0.11", +] + [[package]] name = "tauri-plugin-opener" version = "2.2.5" @@ -4145,6 +4179,22 @@ dependencies = [ "zbus", ] +[[package]] +name = "tauri-plugin-store" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c0c08fae6995909f5e9a0da6038273b750221319f2c0f3b526d6de1cde21505" +dependencies = [ + "dunce", + "serde", + "serde_json", + "tauri", + "tauri-plugin", + "thiserror 2.0.11", + "tokio", + "tracing", +] + [[package]] name = "tauri-runtime" version = "2.3.0" diff --git a/frontend/src-tauri/Cargo.toml b/frontend/src-tauri/Cargo.toml index df37364..7bf2860 100644 --- a/frontend/src-tauri/Cargo.toml +++ b/frontend/src-tauri/Cargo.toml @@ -26,6 +26,10 @@ tauri-plugin-dialog = "2" notify = "6.1.1" base64 = "0.21.7" tokio = { version = "1.36.0", features = ["full"] } +tauri-plugin-store = "2" [target."cfg(target_os = \"macos\")".dependencies] cocoa = "0.26" + +[target."cfg(not(any(target_os = \"android\", target_os = \"ios\")))".dependencies] +tauri-plugin-global-shortcut = "2" diff --git a/frontend/src-tauri/capabilities/default.json b/frontend/src-tauri/capabilities/default.json index d21ade3..e7874eb 100644 --- a/frontend/src-tauri/capabilities/default.json +++ b/frontend/src-tauri/capabilities/default.json @@ -7,6 +7,10 @@ "core:default", "opener:default", "dialog:default", - "core:window:allow-start-dragging" + "core:window:allow-start-dragging", + "global-shortcut:allow-is-registered", + "global-shortcut:allow-register", + "global-shortcut:allow-unregister", + "global-shortcut:allow-unregister-all" ] } diff --git a/frontend/src-tauri/src/lib.rs b/frontend/src-tauri/src/lib.rs index 964adee..8fd4e3c 100644 --- a/frontend/src-tauri/src/lib.rs +++ b/frontend/src-tauri/src/lib.rs @@ -1,4 +1,5 @@ mod commands; +mod shortcut; mod state; mod utils; mod window; @@ -11,12 +12,20 @@ pub fn run() { let watcher_state = new_shared_watcher_state(); tauri::Builder::default() + .plugin(tauri_plugin_store::Builder::new().build()) .plugin(tauri_plugin_dialog::init()) .plugin(tauri_plugin_opener::init()) .manage(watcher_state) - .invoke_handler(tauri::generate_handler![commands::handle_selected_folder]) + .invoke_handler(tauri::generate_handler![ + commands::handle_selected_folder, + shortcut::change_shortcut, + shortcut::unregister_shortcut, + shortcut::get_current_shortcut, + ]) .setup(|app| { setup_window(app)?; + shortcut::enable_shortcut(app); + Ok(()) }) .run(tauri::generate_context!()) diff --git a/frontend/src-tauri/src/shortcut.rs b/frontend/src-tauri/src/shortcut.rs new file mode 100644 index 0000000..1032f91 --- /dev/null +++ b/frontend/src-tauri/src/shortcut.rs @@ -0,0 +1,167 @@ +use tauri::App; +use tauri::AppHandle; +use tauri::Manager; +use tauri::Runtime; +use tauri_plugin_global_shortcut::GlobalShortcutExt; +use tauri_plugin_global_shortcut::Shortcut; +use tauri_plugin_global_shortcut::ShortcutState; +use tauri_plugin_store::JsonValue; +use tauri_plugin_store::StoreExt; + +/// Name of the Tauri storage +const HAYSTACK_TAURI_STORE: &str = "haystack_tauri_store"; + +/// Key for storing global shortcuts +const HAYSTACK_GLOBAL_SHORTCUT: &str = "haystack_global_shortcut"; + +/// Default shortcut for macOS +#[cfg(target_os = "macos")] +const DEFAULT_SHORTCUT: &str = "command+shift+k"; + +/// Default shortcut for Windows and Linux +#[cfg(any(target_os = "windows", target_os = "linux"))] +const DEFAULT_SHORTCUT: &str = "ctrl+shift+k"; + +/// Set shortcut during application startup +pub fn enable_shortcut(app: &App) { + let store = app + .store(HAYSTACK_TAURI_STORE) + .expect("Creating the store should not fail"); + + // Use stored shortcut if it exists + if let Some(stored_shortcut) = store.get(HAYSTACK_GLOBAL_SHORTCUT) { + let stored_shortcut_str = match stored_shortcut { + JsonValue::String(str) => str, + unexpected_type => panic!( + "Haystack shortcuts should be stored as strings, found type: {} ", + unexpected_type + ), + }; + let stored_shortcut = stored_shortcut_str + .parse::() + .expect("Stored shortcut string should be valid"); + _register_shortcut_upon_start(app, stored_shortcut); // Register stored shortcut + } else { + // Use default shortcut if none is stored + store.set( + HAYSTACK_GLOBAL_SHORTCUT, + JsonValue::String(DEFAULT_SHORTCUT.to_string()), + ); + let default_shortcut = DEFAULT_SHORTCUT + .parse::() + .expect("Default shortcut should be valid"); + _register_shortcut_upon_start(app, default_shortcut); // Register default shortcut + } +} + +/// Get the current stored shortcut as a string +#[tauri::command] +pub fn get_current_shortcut(app: AppHandle) -> Result { + let shortcut = _get_shortcut(&app); + Ok(shortcut) +} + +/// Unregister the current shortcut in Tauri +#[tauri::command] +pub fn unregister_shortcut(app: AppHandle) { + let shortcut_str = _get_shortcut(&app); + let shortcut = shortcut_str + .parse::() + .expect("Stored shortcut string should be valid"); + + // Unregister the shortcut + app.global_shortcut() + .unregister(shortcut) + .expect("Failed to unregister shortcut") +} + +/// Change the global shortcut +#[tauri::command] +pub fn change_shortcut( + app: AppHandle, + _window: tauri::Window, + key: String, +) -> Result<(), String> { + println!("Key: {}", key); + let shortcut = match key.parse::() { + Ok(shortcut) => shortcut, + Err(_) => return Err(format!("Invalid shortcut {}", key)), + }; + + // Store the new shortcut + let store = app + .get_store(HAYSTACK_TAURI_STORE) + .expect("Store should already be loaded or created"); + store.set(HAYSTACK_GLOBAL_SHORTCUT, JsonValue::String(key)); + + // Register the new shortcut + _register_shortcut(&app, shortcut); + + Ok(()) +} + +/// Helper function to register a shortcut, primarily for updating shortcuts +fn _register_shortcut(app: &AppHandle, shortcut: Shortcut) { + let main_window = app.get_webview_window("main").unwrap(); + // Register global shortcut and define its behavior + app.global_shortcut() + .on_shortcut(shortcut, move |_app, scut, event| { + if scut == &shortcut { + if let ShortcutState::Pressed = event.state() { + // Toggle window visibility + if main_window.is_visible().unwrap() { + main_window.hide().unwrap(); // Hide window + } else { + main_window.show().unwrap(); // Show window + main_window.set_focus().unwrap(); // Focus window + } + } + } + }) + .map_err(|err| format!("Failed to register new shortcut '{}'", err)) + .unwrap(); +} + +/// Helper function to register shortcuts during application startup +fn _register_shortcut_upon_start(app: &App, shortcut: Shortcut) { + let window = app.get_webview_window("main").unwrap(); + // Initialize global shortcut and set its handler + app.handle() + .plugin( + tauri_plugin_global_shortcut::Builder::new() + .with_handler(move |_app, scut, event| { + if scut == &shortcut { + if let ShortcutState::Pressed = event.state() { + // Toggle window visibility + if window.is_visible().unwrap() { + window.hide().unwrap(); // Hide window + } else { + window.show().unwrap(); // Show window + window.set_focus().unwrap(); // Focus window + } + } + } + }) + .build(), + ) + .unwrap(); + app.global_shortcut().register(shortcut).unwrap(); // Register global shortcut +} + +/// Retrieve the stored global shortcut as a string +pub fn _get_shortcut(app: &AppHandle) -> String { + let store = app + .get_store(HAYSTACK_TAURI_STORE) + .expect("Store should already be loaded or created"); + + match store + .get(HAYSTACK_GLOBAL_SHORTCUT) + .expect("Shortcut should already be stored") + { + JsonValue::String(str) => str, + unexpected_type => panic!( + "Haystack shortcuts should be stored as strings, found type: {} ", + unexpected_type + ), + } +}