use crate::screenshot::take_area_screenshot; use tauri::App; use tauri::AppHandle; use tauri::Emitter; 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; /// Constants for Tauri store configuration const HAYSTACK_TAURI_STORE: &str = "haystack_tauri_store"; // Name of the persistent store const HAYSTACK_GLOBAL_SHORTCUT: &str = "haystack_global_shortcut"; // Key for storing the toggle window shortcut const HAYSTACK_SCREENSHOT_SHORTCUT: &str = "haystack_screenshot_shortcut"; // Key for storing the screenshot shortcut /// Platform-specific default shortcuts #[cfg(target_os = "macos")] const DEFAULT_SHORTCUT: &str = "command+shift+k"; // macOS uses Command key #[cfg(target_os = "macos")] const DEFAULT_SCREENSHOT_SHORTCUT: &str = "command+shift+p"; // macOS screenshot shortcut #[cfg(any(target_os = "windows", target_os = "linux"))] const DEFAULT_SHORTCUT: &str = "ctrl+shift+k"; // Windows/Linux use Ctrl key #[cfg(any(target_os = "windows", target_os = "linux"))] const DEFAULT_SCREENSHOT_SHORTCUT: &str = "ctrl+shift+p"; // Windows/Linux screenshot shortcut /// Initializes the global shortcut during application startup. /// This function: /// 1. Checks if a shortcut is already stored /// 2. Uses the stored shortcut if it exists /// 3. Falls back to the platform-specific default if no shortcut is stored /// 4. Registers the shortcut with the system pub fn enable_shortcut(app: &App) { // Get or create the persistent store let store = app .store(HAYSTACK_TAURI_STORE) .expect("Creating the store should not fail"); // Initialize toggle window shortcut let toggle_shortcut = 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 ), }; stored_shortcut_str .parse::() .expect("Stored shortcut string should be valid") } else { store.set( HAYSTACK_GLOBAL_SHORTCUT, JsonValue::String(DEFAULT_SHORTCUT.to_string()), ); DEFAULT_SHORTCUT .parse::() .expect("Default shortcut should be valid") }; // Initialize screenshot shortcut let screenshot_shortcut = if let Some(stored_shortcut) = store.get(HAYSTACK_SCREENSHOT_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 ), }; stored_shortcut_str .parse::() .expect("Stored shortcut string should be valid") } else { store.set( HAYSTACK_SCREENSHOT_SHORTCUT, JsonValue::String(DEFAULT_SCREENSHOT_SHORTCUT.to_string()), ); DEFAULT_SCREENSHOT_SHORTCUT .parse::() .expect("Default screenshot shortcut should be valid") }; // Register both shortcuts register_shortcut_upon_start(app, toggle_shortcut, screenshot_shortcut); } /// Returns the currently configured shortcut as a string. /// This is exposed as a Tauri command for the frontend to query. #[tauri::command] pub fn get_current_shortcut(app: AppHandle) -> Result { Ok(get_shortcut_from_store(&app)) } /// Unregisters the current global shortcut from the system. /// This is exposed as a Tauri command for the frontend to trigger. #[tauri::command] pub fn unregister_shortcut(app: AppHandle) { let shortcut_str = get_shortcut_from_store(&app); let shortcut = shortcut_str .parse::() .expect("Stored shortcut string should be valid"); app.global_shortcut() .unregister(shortcut) .expect("Failed to unregister shortcut") } /// Changes the global shortcut to a new key combination. /// This function: /// 1. Validates the new shortcut /// 2. Stores it in the persistent store /// 3. Registers it with the system #[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(()) } /// Handles the window visibility toggle logic when the shortcut is pressed. /// This function: /// 1. Hides the window if it's visible /// 2. Shows and focuses the window if it's hidden /// 3. Emits a 'focus-search' event when showing the window fn handle_window_visibility(app: &AppHandle, window: &tauri::WebviewWindow) { if window.is_visible().unwrap() { window.hide().unwrap(); } else { window.show().unwrap(); window.set_focus().unwrap(); app.emit("focus-search", ()).unwrap(); } } /// Registers a new shortcut with the system. /// This is used when changing shortcuts during runtime. /// The function: /// 1. Gets the main window /// 2. Sets up the shortcut handler /// 3. Registers the shortcut with the system fn register_shortcut(app: &AppHandle, shortcut: Shortcut) { let main_window = app.get_webview_window("main").unwrap(); app.global_shortcut() .on_shortcut(shortcut, move |app, scut, event| { if scut == &shortcut { if let ShortcutState::Pressed = event.state() { handle_window_visibility(app, &main_window); } } }) .map_err(|err| format!("Failed to register new shortcut '{}'", err)) .unwrap(); } /// Registers a shortcut during application startup. /// This is similar to register_shortcut but handles the initial plugin setup. /// The function: /// 1. Gets the main window /// 2. Sets up the global shortcut plugin /// 3. Registers the shortcut with the system fn register_shortcut_upon_start( app: &App, toggle_shortcut: Shortcut, screenshot_shortcut: Shortcut, ) { let window = app .get_webview_window("main") .expect("webview to be defined"); app.handle() .plugin( tauri_plugin_global_shortcut::Builder::new() .with_handler(move |app, scut, event| { if scut == &toggle_shortcut { if let ShortcutState::Pressed = event.state() { handle_window_visibility(app, &window); } } else if scut == &screenshot_shortcut { if let ShortcutState::Pressed = event.state() { // TODO: Implement screenshot functionality println!("Screenshot shortcut pressed"); } if let Err(e) = take_area_screenshot(app) { println!("Failed to take screenshot: {}", e); } } }) .build(), ) .unwrap(); app.global_shortcut().register(toggle_shortcut).unwrap(); app.global_shortcut().register(screenshot_shortcut).unwrap(); } /// Retrieves the currently stored shortcut from the persistent store. /// This is a helper function used by other functions to access the stored shortcut. fn get_shortcut_from_store(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 ), } } #[tauri::command] pub fn get_current_screenshot_shortcut(app: AppHandle) -> Result { Ok(get_screenshot_shortcut_from_store(&app)) } #[tauri::command] pub fn unregister_screenshot_shortcut(app: AppHandle) { let shortcut_str = get_screenshot_shortcut_from_store(&app); let shortcut = shortcut_str .parse::() .expect("Stored shortcut string should be valid"); app.global_shortcut() .unregister(shortcut) .expect("Failed to unregister screenshot shortcut") } #[tauri::command] pub fn change_screenshot_shortcut( app: AppHandle, _window: tauri::Window, key: String, ) -> Result<(), String> { let shortcut = match key.parse::() { Ok(shortcut) => shortcut, Err(_) => return Err(format!("Invalid screenshot shortcut {}", key)), }; let store = app .get_store(HAYSTACK_TAURI_STORE) .expect("Store should already be loaded or created"); store.set(HAYSTACK_SCREENSHOT_SHORTCUT, JsonValue::String(key)); register_screenshot_shortcut(&app, shortcut); Ok(()) } fn register_screenshot_shortcut(app: &AppHandle, shortcut: Shortcut) { app.global_shortcut() .on_shortcut(shortcut, move |app, scut, event| { if scut == &shortcut { if let ShortcutState::Pressed = event.state() { if let Err(e) = take_area_screenshot(app) { println!("Failed to take screenshot: {}", e); } } } }) .map_err(|err| format!("Failed to register new screenshot shortcut '{}'", err)) .unwrap(); } fn get_screenshot_shortcut_from_store(app: &AppHandle) -> String { let store = app .get_store(HAYSTACK_TAURI_STORE) .expect("Store should already be loaded or created"); match store .get(HAYSTACK_SCREENSHOT_SHORTCUT) .expect("Screenshot shortcut should already be stored") { JsonValue::String(str) => str, unexpected_type => panic!( "Haystack shortcuts should be stored as strings, found type: {} ", unexpected_type ), } }