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.
This commit is contained in:
Binary file not shown.
@ -1,43 +1,45 @@
|
|||||||
{
|
{
|
||||||
"name": "haystack",
|
"name": "haystack",
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"description": "Screenshots that organize themselves",
|
"description": "Screenshots that organize themselves",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "vite",
|
"start": "vite",
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
"build": "vite build",
|
"build": "vite build",
|
||||||
"serve": "vite preview",
|
"serve": "vite preview",
|
||||||
"tauri": "tauri",
|
"tauri": "tauri",
|
||||||
"lint": "bunx @biomejs/biome lint .",
|
"lint": "bunx @biomejs/biome lint .",
|
||||||
"format": "bunx @biomejs/biome format . --write"
|
"format": "bunx @biomejs/biome format . --write"
|
||||||
},
|
},
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@kobalte/core": "^0.13.9",
|
"@kobalte/core": "^0.13.9",
|
||||||
"@kobalte/tailwindcss": "^0.9.0",
|
"@kobalte/tailwindcss": "^0.9.0",
|
||||||
"@solidjs/router": "^0.15.3",
|
"@solidjs/router": "^0.15.3",
|
||||||
"@tabler/icons-solidjs": "^3.30.0",
|
"@tabler/icons-solidjs": "^3.30.0",
|
||||||
"@tauri-apps/api": "^2",
|
"@tauri-apps/api": "^2",
|
||||||
"@tauri-apps/plugin-dialog": "~2",
|
"@tauri-apps/plugin-dialog": "~2",
|
||||||
"@tauri-apps/plugin-opener": "^2",
|
"@tauri-apps/plugin-global-shortcut": "~2",
|
||||||
"clsx": "^2.1.1",
|
"@tauri-apps/plugin-opener": "^2",
|
||||||
"fuse.js": "^7.1.0",
|
"@tauri-apps/plugin-store": "~2",
|
||||||
"jwt-decode": "^4.0.0",
|
"clsx": "^2.1.1",
|
||||||
"solid-js": "^1.9.3",
|
"fuse.js": "^7.1.0",
|
||||||
"solid-motionone": "^1.0.3",
|
"jwt-decode": "^4.0.0",
|
||||||
"tailwind-scrollbar-hide": "^2.0.0",
|
"solid-js": "^1.9.3",
|
||||||
"valibot": "^1.0.0-rc.2"
|
"solid-motionone": "^1.0.3",
|
||||||
},
|
"tailwind-scrollbar-hide": "^2.0.0",
|
||||||
"devDependencies": {
|
"valibot": "^1.0.0-rc.2"
|
||||||
"@biomejs/biome": "^1.9.4",
|
},
|
||||||
"@tauri-apps/cli": "^2",
|
"devDependencies": {
|
||||||
"autoprefixer": "^10.4.20",
|
"@biomejs/biome": "^1.9.4",
|
||||||
"postcss": "^8.5.3",
|
"@tauri-apps/cli": "^2",
|
||||||
"postcss-cli": "^11.0.0",
|
"autoprefixer": "^10.4.20",
|
||||||
"tailwindcss": "3.4.0",
|
"postcss": "^8.5.3",
|
||||||
"typescript": "~5.6.2",
|
"postcss-cli": "^11.0.0",
|
||||||
"vite": "^6.0.3",
|
"tailwindcss": "3.4.0",
|
||||||
"vite-plugin-solid": "^2.11.0"
|
"typescript": "~5.6.2",
|
||||||
}
|
"vite": "^6.0.3",
|
||||||
|
"vite-plugin-solid": "^2.11.0"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
50
frontend/src-tauri/Cargo.lock
generated
50
frontend/src-tauri/Cargo.lock
generated
@ -14,7 +14,9 @@ dependencies = [
|
|||||||
"tauri",
|
"tauri",
|
||||||
"tauri-build",
|
"tauri-build",
|
||||||
"tauri-plugin-dialog",
|
"tauri-plugin-dialog",
|
||||||
|
"tauri-plugin-global-shortcut",
|
||||||
"tauri-plugin-opener",
|
"tauri-plugin-opener",
|
||||||
|
"tauri-plugin-store",
|
||||||
"tokio",
|
"tokio",
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -1459,6 +1461,23 @@ version = "0.3.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2"
|
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]]
|
[[package]]
|
||||||
name = "gobject-sys"
|
name = "gobject-sys"
|
||||||
version = "0.18.0"
|
version = "0.18.0"
|
||||||
@ -4123,6 +4142,21 @@ dependencies = [
|
|||||||
"uuid",
|
"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]]
|
[[package]]
|
||||||
name = "tauri-plugin-opener"
|
name = "tauri-plugin-opener"
|
||||||
version = "2.2.5"
|
version = "2.2.5"
|
||||||
@ -4145,6 +4179,22 @@ dependencies = [
|
|||||||
"zbus",
|
"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]]
|
[[package]]
|
||||||
name = "tauri-runtime"
|
name = "tauri-runtime"
|
||||||
version = "2.3.0"
|
version = "2.3.0"
|
||||||
|
@ -26,6 +26,10 @@ tauri-plugin-dialog = "2"
|
|||||||
notify = "6.1.1"
|
notify = "6.1.1"
|
||||||
base64 = "0.21.7"
|
base64 = "0.21.7"
|
||||||
tokio = { version = "1.36.0", features = ["full"] }
|
tokio = { version = "1.36.0", features = ["full"] }
|
||||||
|
tauri-plugin-store = "2"
|
||||||
|
|
||||||
[target."cfg(target_os = \"macos\")".dependencies]
|
[target."cfg(target_os = \"macos\")".dependencies]
|
||||||
cocoa = "0.26"
|
cocoa = "0.26"
|
||||||
|
|
||||||
|
[target."cfg(not(any(target_os = \"android\", target_os = \"ios\")))".dependencies]
|
||||||
|
tauri-plugin-global-shortcut = "2"
|
||||||
|
@ -7,6 +7,10 @@
|
|||||||
"core:default",
|
"core:default",
|
||||||
"opener:default",
|
"opener:default",
|
||||||
"dialog: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"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
mod commands;
|
mod commands;
|
||||||
|
mod shortcut;
|
||||||
mod state;
|
mod state;
|
||||||
mod utils;
|
mod utils;
|
||||||
mod window;
|
mod window;
|
||||||
@ -11,12 +12,20 @@ pub fn run() {
|
|||||||
let watcher_state = new_shared_watcher_state();
|
let watcher_state = new_shared_watcher_state();
|
||||||
|
|
||||||
tauri::Builder::default()
|
tauri::Builder::default()
|
||||||
|
.plugin(tauri_plugin_store::Builder::new().build())
|
||||||
.plugin(tauri_plugin_dialog::init())
|
.plugin(tauri_plugin_dialog::init())
|
||||||
.plugin(tauri_plugin_opener::init())
|
.plugin(tauri_plugin_opener::init())
|
||||||
.manage(watcher_state)
|
.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(|app| {
|
||||||
setup_window(app)?;
|
setup_window(app)?;
|
||||||
|
shortcut::enable_shortcut(app);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
})
|
})
|
||||||
.run(tauri::generate_context!())
|
.run(tauri::generate_context!())
|
||||||
|
167
frontend/src-tauri/src/shortcut.rs
Normal file
167
frontend/src-tauri/src/shortcut.rs
Normal file
@ -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::<Shortcut>()
|
||||||
|
.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::<Shortcut>()
|
||||||
|
.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<R: Runtime>(app: AppHandle<R>) -> Result<String, String> {
|
||||||
|
let shortcut = _get_shortcut(&app);
|
||||||
|
Ok(shortcut)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Unregister the current shortcut in Tauri
|
||||||
|
#[tauri::command]
|
||||||
|
pub fn unregister_shortcut<R: Runtime>(app: AppHandle<R>) {
|
||||||
|
let shortcut_str = _get_shortcut(&app);
|
||||||
|
let shortcut = shortcut_str
|
||||||
|
.parse::<Shortcut>()
|
||||||
|
.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<R: Runtime>(
|
||||||
|
app: AppHandle<R>,
|
||||||
|
_window: tauri::Window<R>,
|
||||||
|
key: String,
|
||||||
|
) -> Result<(), String> {
|
||||||
|
println!("Key: {}", key);
|
||||||
|
let shortcut = match key.parse::<Shortcut>() {
|
||||||
|
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<R: Runtime>(app: &AppHandle<R>, 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<R: Runtime>(app: &AppHandle<R>) -> 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
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user