Download apps/desktop/src-tauri/src/lib.rs from AUXteam/Midday: direct link, hf CLI and curl.
- Browser
- Download file 28.8 kB
-
https://huggingface.co/spaces/AUXteam/Midday/resolve/main/apps/desktop/src-tauri/src/lib.rs
- Command line
-
hf download hf://spaces/AUXteam/Midday/apps/desktop/src-tauri/src/lib.rs
-
curl -L -o lib.rs https://huggingface.co/spaces/AUXteam/Midday/resolve/main/apps/desktop/src-tauri/src/lib.rs
28.8 kB
| use serde_json; | |
| use std::env; | |
| use std::sync::{Arc, Mutex}; | |
| use tauri::{ | |
| Emitter, Listener, Manager, PhysicalPosition, Position, TitleBarStyle, WebviewUrl, | |
| WebviewWindowBuilder, | |
| }; | |
| use tauri_plugin_deep_link::DeepLinkExt; | |
| use tauri_plugin_updater; | |
| use tauri_plugin_dialog; | |
| use tauri_plugin_process; | |
| use tauri::menu::{Menu, MenuItem}; | |
| use tauri::image::Image; | |
| use tauri::tray::{MouseButton, MouseButtonState, TrayIconBuilder, TrayIconEvent}; | |
| use image; | |
| // Global state for search window availability | |
| type SearchWindowState = Arc<Mutex<bool>>; | |
| fn show_window(window: tauri::Window) -> Result<(), String> { | |
| // Always target the main window specifically, not the calling window | |
| let app_handle = window.app_handle(); | |
| let main_window = match app_handle.get_webview_window("main") { | |
| Some(window) => window, | |
| None => { | |
| return Err("Main window not found".to_string()); | |
| } | |
| }; | |
| main_window | |
| .show() | |
| .map_err(|e| format!("Failed to show window: {}", e))?; | |
| main_window | |
| .set_focus() | |
| .map_err(|e| format!("Failed to set focus: {}", e))?; | |
| Ok(()) | |
| } | |
| /// Prompt the user to download and install an update. | |
| /// Shared by both manual and silent update flows. | |
| async fn prompt_and_install_update(app: &tauri::AppHandle, update: tauri_plugin_updater::Update) { | |
| use tauri_plugin_dialog::{DialogExt, MessageDialogKind, MessageDialogButtons}; | |
| let answer = app.dialog() | |
| .message(format!("A new version {} is available. Would you like to update now?", update.version)) | |
| .title("Update Available") | |
| .kind(MessageDialogKind::Info) | |
| .buttons(MessageDialogButtons::OkCancel) | |
| .blocking_show(); | |
| if answer { | |
| let _ = update.download_and_install( | |
| |_chunk_length, _content_length| {}, | |
| || { | |
| println!("Update download finished"); | |
| } | |
| ).await; | |
| } | |
| } | |
| /// Silent update check β only shows a dialog when an update is available. | |
| /// Used on startup and by the periodic background timer. | |
| async fn silent_update_check(app: tauri::AppHandle) { | |
| use tauri_plugin_updater::UpdaterExt; | |
| if let Ok(updater) = app.updater() { | |
| match updater.check().await { | |
| Ok(Some(update)) => { | |
| println!("Update available: {}", update.version); | |
| prompt_and_install_update(&app, update).await; | |
| } | |
| Ok(None) => { | |
| println!("No updates available"); | |
| } | |
| Err(e) => { | |
| println!("Silent update check failed: {}", e); | |
| } | |
| } | |
| } | |
| } | |
| /// Manual update check (triggered from tray menu). | |
| /// Shows dialogs for all outcomes: update available, up-to-date, and errors. | |
| async fn check_for_updates(app: tauri::AppHandle) -> Result<(), String> { | |
| use tauri_plugin_dialog::{DialogExt, MessageDialogKind, MessageDialogButtons}; | |
| use tauri_plugin_updater::UpdaterExt; | |
| { | |
| if let Ok(updater) = app.updater() { | |
| match updater.check().await { | |
| Ok(Some(update)) => { | |
| prompt_and_install_update(&app, update).await; | |
| } | |
| Ok(None) => { | |
| let version = app.package_info().version.to_string(); | |
| app.dialog() | |
| .message(format!("Midday\nversion {}\n\nYou're up to date!", version)) | |
| .title("No Updates Available") | |
| .kind(MessageDialogKind::Info) | |
| .buttons(MessageDialogButtons::Ok) | |
| .blocking_show(); | |
| } | |
| Err(e) => { | |
| app.dialog() | |
| .message(format!("Failed to check for updates: {}", e)) | |
| .title("Update Check Failed") | |
| .kind(MessageDialogKind::Error) | |
| .buttons(MessageDialogButtons::Ok) | |
| .blocking_show(); | |
| } | |
| } | |
| } else { | |
| app.dialog() | |
| .message("Update checking is not available in this build.") | |
| .title("Updates Not Available") | |
| .kind(MessageDialogKind::Warning) | |
| .buttons(MessageDialogButtons::Ok) | |
| .blocking_show(); | |
| } | |
| } | |
| { | |
| app.dialog() | |
| .message("Updates are managed through your app store.") | |
| .title("Check App Store") | |
| .kind(MessageDialogKind::Info) | |
| .buttons(MessageDialogButtons::Ok) | |
| .blocking_show(); | |
| } | |
| Ok(()) | |
| } | |
| fn toggle_search_window( | |
| app: &tauri::AppHandle, | |
| search_state: &SearchWindowState, | |
| ) -> Result<(), Box<dyn std::error::Error>> { | |
| println!("π === TOGGLE_SEARCH_WINDOW CALLED ==="); | |
| let is_search_enabled = { | |
| let guard = search_state.lock().unwrap(); | |
| let value = *guard; | |
| println!("π Current search window state from lock: {}", value); | |
| value | |
| }; | |
| if !is_search_enabled { | |
| println!("β Search window disabled, showing main window instead"); | |
| // Search is disabled, show main window | |
| if let Some(main_window) = app.get_webview_window("main") { | |
| main_window.show()?; | |
| main_window.set_focus()?; | |
| } | |
| return Ok(()); | |
| } | |
| println!("β Search window enabled, proceeding with search toggle"); | |
| // Search is enabled, proceed with search toggle | |
| let search_window_label = "search"; | |
| println!("π Looking for existing search window..."); | |
| if let Some(window) = app.get_webview_window(search_window_label) { | |
| println!("π Found existing search window"); | |
| if window.is_visible()? { | |
| println!("π Search window is visible, hiding it"); | |
| // Emit close event to search window | |
| let _ = window.emit("search-window-open", false); | |
| window.hide()?; | |
| } else { | |
| println!("π Search window is hidden, showing it"); | |
| // Set always on top when showing | |
| window.set_always_on_top(true)?; | |
| position_window_on_current_monitor(app, &window)?; | |
| window.show()?; | |
| window.set_focus()?; // Focus the window so it can detect focus loss | |
| // Emit open event to search window | |
| let _ = window.emit("search-window-open", true); | |
| } | |
| } else { | |
| println!("π Search window doesn't exist, creating it now..."); | |
| // Create search window on-demand | |
| let app_url = get_app_url(); | |
| let app_clone = app.clone(); | |
| // Use blocking approach for shortcut/tray handlers to ensure window is created | |
| tauri::async_runtime::block_on(async move { | |
| if let Ok(_) = create_preloaded_search_window(&app_clone, &app_url).await { | |
| println!("β Search window created successfully via block_on"); | |
| // After creation, show it immediately | |
| if let Some(window) = app_clone.get_webview_window("search") { | |
| let _ = window.set_always_on_top(true); | |
| let _ = position_window_on_current_monitor(&app_clone, &window); | |
| let _ = window.show(); | |
| let _ = window.set_focus(); | |
| let _ = window.emit("search-window-open", true); | |
| println!("β Search window shown successfully"); | |
| } else { | |
| println!("β Search window not found after creation"); | |
| } | |
| } else { | |
| println!("β Failed to create search window"); | |
| } | |
| }); | |
| } | |
| Ok(()) | |
| } | |
| fn position_window_on_current_monitor( | |
| app: &tauri::AppHandle, | |
| window: &tauri::WebviewWindow, | |
| ) -> Result<(), Box<dyn std::error::Error>> { | |
| // Get cursor position to determine current monitor | |
| if let Ok(cursor_position) = app.cursor_position() { | |
| // Get all monitors | |
| if let Ok(monitors) = app.available_monitors() { | |
| // Find which monitor contains the cursor | |
| let current_monitor = monitors.iter().find(|monitor| { | |
| let pos = monitor.position(); | |
| let size = monitor.size(); | |
| cursor_position.x >= pos.x as f64 | |
| && cursor_position.x < (pos.x + size.width as i32) as f64 | |
| && cursor_position.y >= pos.y as f64 | |
| && cursor_position.y < (pos.y + size.height as i32) as f64 | |
| }); | |
| if let Some(monitor) = current_monitor { | |
| let monitor_size = monitor.size(); | |
| let monitor_position = monitor.position(); | |
| // Get the actual window size to ensure accurate centering | |
| let window_size = window.outer_size().unwrap_or(tauri::PhysicalSize { | |
| width: 720, | |
| height: 450, | |
| }); | |
| // Calculate center position on the monitor with slight offset for system UI | |
| let center_x = monitor_position.x + (monitor_size.width as i32 / 2) | |
| - (window_size.width as i32 / 2); | |
| let center_y = monitor_position.y + (monitor_size.height as i32 / 2) | |
| - (window_size.height as i32 / 2); | |
| // Adjust for macOS menu bar (typically 25-30px) and other system UI | |
| let center_y = center_y + 15; // Slight downward adjustment for menu bar | |
| window.set_position(Position::Physical(PhysicalPosition { | |
| x: center_x, | |
| y: center_y, | |
| }))?; | |
| return Ok(()); | |
| } | |
| } | |
| } | |
| // Fallback to default center if monitor detection fails | |
| window.center()?; | |
| Ok(()) | |
| } | |
| async fn create_preloaded_search_window( | |
| app: &tauri::AppHandle, | |
| app_url: &str, | |
| ) -> Result<(), Box<dyn std::error::Error + Send + Sync>> { | |
| let search_window_label = "search"; | |
| let search_url = format!("{}/desktop/search", app_url); | |
| let mut search_builder = WebviewWindowBuilder::new( | |
| app, | |
| search_window_label, | |
| WebviewUrl::External(tauri::Url::parse(&search_url)?), | |
| ) | |
| .title("Midday Search") | |
| .inner_size(720.0, 450.0) | |
| .min_inner_size(720.0, 450.0) | |
| .resizable(false) | |
| .user_agent("Mozilla/5.0 (compatible; Midday Desktop App)") | |
| .transparent(true) | |
| .decorations(false) | |
| .visible(false) // Start hidden for preloading | |
| .on_download(|_window, _event| { | |
| println!("Search window download triggered!"); | |
| // Allow downloads from search window too | |
| true | |
| }); | |
| // Platform-specific styling | |
| search_builder = search_builder | |
| .hidden_title(true) | |
| .title_bar_style(TitleBarStyle::Overlay); | |
| let search_window = search_builder.shadow(false).build()?; | |
| // Position window on primary monitor (will be repositioned when shown) | |
| search_window.center()?; | |
| // Close requests are now handled via auth state changes and direct window management | |
| // Handle window events - comprehensive auto-hide behavior | |
| let window_clone = search_window.clone(); | |
| search_window.on_window_event(move |event| { | |
| match event { | |
| // Main case: window loses focus (click outside anywhere) | |
| tauri::WindowEvent::Focused(false) => { | |
| // Emit close event to search window | |
| let _ = window_clone.emit("search-window-open", false); | |
| // Turn off always on top and hide | |
| let _ = window_clone.set_always_on_top(false); | |
| let _ = window_clone.hide(); | |
| } | |
| // Additional safety: if window somehow becomes invisible but should be hidden | |
| tauri::WindowEvent::Resized(_) | tauri::WindowEvent::Moved(_) => { | |
| // Check if still focused after these events, if not, hide | |
| if let Ok(false) = window_clone.is_focused() { | |
| let _ = window_clone.emit("search-window-open", false); | |
| let _ = window_clone.set_always_on_top(false); | |
| let _ = window_clone.hide(); | |
| } | |
| } | |
| _ => {} | |
| } | |
| }); | |
| Ok(()) | |
| } | |
| fn get_app_url() -> String { | |
| // Try runtime environment variable first, then fall back to compile-time | |
| let env = env::var("MIDDAY_ENV") | |
| .unwrap_or_else(|_| { | |
| option_env!("MIDDAY_ENV") | |
| .unwrap_or("development") | |
| .to_string() | |
| }); | |
| println!("π Environment detected: {}", env); | |
| match env.as_str() { | |
| "development" | "dev" => { | |
| let url = "http://localhost:3001".to_string(); | |
| println!("π Using development URL: {}", url); | |
| url | |
| }, | |
| "staging" => { | |
| let url = "https://beta.midday.ai".to_string(); | |
| println!("π Using staging URL: {}", url); | |
| url | |
| }, | |
| "production" | "prod" => { | |
| let url = "https://app.midday.ai".to_string(); | |
| println!("π Using production URL: {}", url); | |
| url | |
| }, | |
| _ => { | |
| eprintln!("Unknown environment: {}, defaulting to development", env); | |
| let url = "http://localhost:3001".to_string(); | |
| println!("π Using fallback development URL: {}", url); | |
| url | |
| } | |
| } | |
| } | |
| fn is_external_url(url: &str, app_url: &str) -> bool { | |
| // Parse both URLs to compare domains | |
| if let (Ok(target_url), Ok(base_url)) = (tauri::Url::parse(url), tauri::Url::parse(app_url)) { | |
| // Check if schemes are http/https | |
| let is_http_scheme = target_url.scheme() == "http" || target_url.scheme() == "https"; | |
| // Check if it's a different domain/host | |
| let is_different_host = target_url.host() != base_url.host(); | |
| return is_http_scheme && is_different_host; | |
| } | |
| false | |
| } | |
| fn handle_deep_link_event(app_handle: &tauri::AppHandle, urls: Vec<String>) { | |
| for url in &urls { | |
| // Only handle midday schemes (midday://, midday-dev://, midday-staging://) | |
| if let Some(idx) = url.find("://") { | |
| let scheme = &url[..idx]; | |
| if !scheme.contains("midday") { | |
| continue; | |
| } | |
| let path = &url[idx + 3..]; | |
| // Remove any leading slashes | |
| let clean_path = path.trim_start_matches('/'); | |
| // Get the main window and emit navigation event to frontend | |
| if let Some(window) = app_handle.get_webview_window("main") { | |
| // Emit event to frontend with just the path - frontend handles the full URL construction | |
| if let Ok(_) = window.emit("deep-link-navigate", clean_path) { | |
| // Always show the window first, then bring it to front | |
| let _ = window.show(); | |
| let _ = window.set_focus(); | |
| } | |
| } | |
| } | |
| } | |
| } | |
| pub fn run() { | |
| let app_url = get_app_url(); | |
| tauri::Builder::default() | |
| .plugin(tauri_plugin_opener::init()) | |
| .plugin(tauri_plugin_deep_link::init()) | |
| .plugin(tauri_plugin_dialog::init()) | |
| .plugin(tauri_plugin_process::init()) | |
| .plugin(tauri_plugin_upload::init()) | |
| .plugin(tauri_plugin_fs::init()) | |
| .invoke_handler(tauri::generate_handler![show_window, check_for_updates]) | |
| .setup(move |app| { | |
| // Add updater plugin conditionally for desktop | |
| app.handle().plugin(tauri_plugin_updater::Builder::new().build())?; | |
| // Check for updates on startup (after a short delay) and every 4 hours | |
| { | |
| let app_handle_for_updates = app.handle().clone(); | |
| tauri::async_runtime::spawn(async move { | |
| // Wait 5 seconds after startup before first check | |
| tokio::time::sleep(std::time::Duration::from_secs(5)).await; | |
| println!("Running startup update check..."); | |
| silent_update_check(app_handle_for_updates.clone()).await; | |
| // Then check every 4 hours | |
| let mut interval = tokio::time::interval(std::time::Duration::from_secs(4 * 60 * 60)); | |
| interval.tick().await; // skip the immediate first tick | |
| loop { | |
| interval.tick().await; | |
| println!("Running periodic update check..."); | |
| silent_update_check(app_handle_for_updates.clone()).await; | |
| } | |
| }); | |
| } | |
| let app_url_clone = app_url.clone(); | |
| let app_handle = app.handle().clone(); | |
| // Create shared search window state | |
| let search_state: SearchWindowState = Arc::new(Mutex::new(false)); | |
| // Add search state to managed state so commands can access it | |
| app.manage(search_state.clone()); | |
| // Clone app_handle before it gets moved into closures | |
| let app_handle_for_deep_links = app_handle.clone(); | |
| let app_handle_for_navigation = app_handle.clone(); | |
| // Auth state is now accessed via managed state for consistency | |
| // Initialize global shortcuts | |
| { | |
| use tauri_plugin_global_shortcut::{ | |
| Code, GlobalShortcutExt, Modifiers, Shortcut, ShortcutState, | |
| }; | |
| let search_shortcut = | |
| Shortcut::new(Some(Modifiers::SHIFT | Modifiers::ALT), Code::KeyK); | |
| if let Ok(_) = app.handle().plugin( | |
| tauri_plugin_global_shortcut::Builder::new() | |
| .with_handler(move |app_handle, shortcut, event| { | |
| if shortcut == &search_shortcut | |
| && event.state() == ShortcutState::Pressed | |
| { | |
| println!("π Global shortcut triggered - checking search state via managed state"); | |
| // Get search state from managed state (same as commands use) | |
| if let Some(managed_search_state) = app_handle.try_state::<SearchWindowState>() { | |
| let current_search_state = *managed_search_state.lock().unwrap(); | |
| println!("π Shortcut: Search state from managed state: {}", current_search_state); | |
| // Use the same app_handle for both search state and toggle function | |
| let result = toggle_search_window(app_handle, &managed_search_state); | |
| match result { | |
| Ok(_) => println!("π Shortcut: toggle_search_window returned Ok"), | |
| Err(e) => println!("π Shortcut: toggle_search_window returned Err: {}", e) | |
| } | |
| } else { | |
| println!("β Failed to get managed search state for shortcut"); | |
| } | |
| } | |
| }) | |
| .build(), | |
| ) { | |
| let _ = app.global_shortcut().register(search_shortcut); | |
| } | |
| } | |
| // Register deep links at runtime for development (Linux/Windows only). | |
| // macOS does not support runtime registration β the scheme is registered | |
| // via Info.plist when the .app bundle is installed in /Applications. | |
| { | |
| match app_handle.deep_link().register_all() { | |
| Ok(_) => println!("π Deep link schemes registered successfully"), | |
| Err(e) => eprintln!("π Failed to register deep link schemes: {}", e), | |
| } | |
| } | |
| // Log deep link URLs if the app was launched via a deep link | |
| if let Ok(urls) = app_handle.deep_link().get_current() { | |
| println!("π Current deep link URLs on launch: {:?}", urls); | |
| } | |
| // Handle deep link events | |
| app_handle.deep_link().on_open_url(move |event| { | |
| let url_strings: Vec<String> = | |
| event.urls().iter().map(|url| url.to_string()).collect(); | |
| println!("π Deep link received: {:?}", url_strings); | |
| handle_deep_link_event(&app_handle_for_deep_links, url_strings); | |
| }); | |
| let win_builder = WebviewWindowBuilder::new( | |
| app, | |
| "main", | |
| WebviewUrl::External(tauri::Url::parse(&app_url).unwrap()), | |
| ) | |
| .title("Midday") | |
| .inner_size(1450.0, 910.0) | |
| .min_inner_size(1450.0, 910.0) | |
| .user_agent("Mozilla/5.0 (compatible; Midday Desktop App)") | |
| .decorations(false) | |
| .visible(false) | |
| .transparent(true) | |
| .shadow(true) | |
| .hidden_title(true) | |
| .title_bar_style(TitleBarStyle::Overlay) | |
| .disable_drag_drop_handler() | |
| .on_download(|_window, _event| { | |
| println!("Download triggered!"); | |
| // Allow all downloads - they will go to default Downloads folder | |
| true | |
| }) | |
| .on_navigation(move |url| { | |
| let url_str = url.as_str(); | |
| // Check if this is an external URL | |
| if is_external_url(url_str, &app_url_clone) { | |
| // Clone the URL string to avoid lifetime issues | |
| let url_string = url_str.to_string(); | |
| let app_handle_clone = app_handle_for_navigation.clone(); | |
| // Open in system browser using the opener plugin | |
| tauri::async_runtime::spawn(async move { | |
| let _ = tauri_plugin_opener::OpenerExt::opener(&app_handle_clone) | |
| .open_url(url_string, None::<String>); | |
| }); | |
| // Prevent navigation in webview | |
| return false; | |
| } | |
| // Allow internal navigation | |
| true | |
| }); | |
| let window = win_builder.build().unwrap(); | |
| // Listen for search window state events from the frontend | |
| let search_state_for_events = search_state.clone(); | |
| let app_handle_for_events = app_handle.clone(); | |
| window.listen("search-window-enabled", move |event| { | |
| if let Ok(enabled) = serde_json::from_str::<bool>(&event.payload()) { | |
| println!("π Event received: search-window-enabled = {}", enabled); | |
| *search_state_for_events.lock().unwrap() = enabled; | |
| println!("π Search window state updated to {}", enabled); | |
| // If search is disabled, clean up search window to prevent interference | |
| if !enabled { | |
| println!("π Search disabled, cleaning up search window"); | |
| if let Some(search_window) = app_handle_for_events.get_webview_window("search") { | |
| let _ = search_window.close(); | |
| println!("π Search window closed and cleaned up"); | |
| } | |
| } | |
| } | |
| }); | |
| // Listen for search window close requests from the frontend | |
| let app_handle_for_close = app_handle.clone(); | |
| window.listen("search-window-close-requested", move |_event| { | |
| println!("π Event received: search-window-close-requested"); | |
| if let Some(search_window) = app_handle_for_close.get_webview_window("search") { | |
| let _ = search_window.emit("search-window-open", false); | |
| let _ = search_window.set_always_on_top(false); | |
| let _ = search_window.hide(); | |
| println!("π Search window closed via close request"); | |
| } | |
| }); | |
| // Fallback timer to ensure main window shows on first launch | |
| let window_clone = window.clone(); | |
| tauri::async_runtime::spawn(async move { | |
| std::thread::sleep(std::time::Duration::from_secs(2)); | |
| // Check if window is still hidden after 2 seconds | |
| if let Ok(is_visible) = window_clone.is_visible() { | |
| if !is_visible { | |
| let _ = window_clone.show(); | |
| let _ = window_clone.set_focus(); | |
| } | |
| } | |
| }); | |
| // Don't preload search window immediately - create it on first use instead | |
| // This prevents interference with the login flow | |
| // Set the default app menu to restore the Midday menu | |
| let app_menu = Menu::default(app.handle())?; | |
| app.set_menu(app_menu)?; | |
| // Setup simple system tray for search toggle only | |
| // Load custom tray icon | |
| let tray_icon = { | |
| let icon_bytes = include_bytes!("../icons/tray-icon.png"); | |
| let img = image::load_from_memory(icon_bytes).map_err(|e| format!("Failed to load tray icon: {}", e))?; | |
| let rgba = img.to_rgba8(); | |
| let (width, height) = rgba.dimensions(); | |
| Image::new_owned(rgba.into_raw(), width, height) | |
| }; | |
| // Create tray menu | |
| let check_updates_item = MenuItem::with_id(app, "check_updates", "Check for Updates...", true, None::<&str>)?; | |
| let tray_menu = Menu::with_items(app, &[&check_updates_item])?; | |
| let _tray = TrayIconBuilder::new() | |
| .icon(tray_icon) | |
| .menu(&tray_menu) | |
| .show_menu_on_left_click(false) | |
| .on_menu_event(|app, event| { | |
| println!("π§ Tray menu event triggered: {:?}", event.id); | |
| if event.id == "check_updates" { | |
| println!("π§ Calling check_for_updates..."); | |
| let app_handle = app.clone(); | |
| tauri::async_runtime::spawn(async move { | |
| let _ = check_for_updates(app_handle).await; | |
| }); | |
| } | |
| }) | |
| .on_tray_icon_event(move |tray, event| { | |
| match event { | |
| // Handle left clicks to toggle search window (keep existing behavior) | |
| TrayIconEvent::Click { | |
| button: MouseButton::Left, | |
| button_state: MouseButtonState::Up, | |
| .. | |
| } => { | |
| let app_handle = tray.app_handle(); | |
| if let Some(managed_search_state) = app_handle.try_state::<SearchWindowState>() { | |
| let _ = toggle_search_window(app_handle, &managed_search_state); | |
| } | |
| }, | |
| _ => {} | |
| } | |
| }) | |
| .build(app)?; | |
| Ok(()) | |
| }) | |
| .build(tauri::generate_context!()) | |
| .expect("error while building tauri app") | |
| .run(|app_handle, event| match event { | |
| tauri::RunEvent::Reopen { .. } => { | |
| if let Some(main_window) = app_handle.get_webview_window("main") { | |
| let _ = main_window.show(); | |
| let _ = main_window.set_focus(); | |
| } | |
| } | |
| tauri::RunEvent::ExitRequested { api, .. } => { | |
| // Prevent app from quitting to keep global shortcuts working | |
| api.prevent_exit(); | |
| // Hide all windows instead of quitting | |
| if let Some(main_window) = app_handle.get_webview_window("main") { | |
| let _ = main_window.hide(); | |
| } | |
| if let Some(search_window) = app_handle.get_webview_window("search") { | |
| let _ = search_window.hide(); | |
| } | |
| } | |
| _ => {} | |
| }); | |
| } | |