mod actions; mod audio_feedback; pub mod audio_toolkit; mod clipboard; mod commands; mod managers; mod overlay; mod settings; mod shortcut; mod tray; mod utils; use managers::audio::AudioRecordingManager; use managers::model::ModelManager; use managers::transcription::TranscriptionManager; use std::collections::HashMap; use std::sync::{Arc, Mutex}; use tauri::image::Image; use tauri::tray::TrayIconBuilder; use tauri::Emitter; use tauri::{AppHandle, Manager}; use tauri_plugin_autostart::{MacosLauncher, ManagerExt}; #[derive(Default)] struct ShortcutToggleStates { // Map: shortcut_binding_id -> is_active active_toggles: HashMap, } type ManagedToggleState = Mutex; fn show_main_window(app: &AppHandle) { if let Some(main_window) = app.get_webview_window("main") { // First, ensure the window is visible if let Err(e) = main_window.show() { eprintln!("Failed to show window: {}", e); } // Then, bring it to the front and give it focus if let Err(e) = main_window.set_focus() { eprintln!("Failed to focus window: {}", e); } // Optional: On macOS, ensure the app becomes active if it was an accessory #[cfg(target_os = "macos")] { if let Err(e) = app.set_activation_policy(tauri::ActivationPolicy::Regular) { eprintln!("Failed to set activation policy to Regular: {}", e); } } } else { eprintln!("Main window not found"); } } #[tauri::command] fn trigger_update_check(app: AppHandle) -> Result<(), String> { app.emit("check-for-updates", ()) .map_err(|e| e.to_string())?; Ok(()) } #[cfg_attr(mobile, tauri::mobile_entry_point)] pub fn run() { env_logger::init(); tauri::Builder::default() .plugin(tauri_plugin_single_instance::init(|app, _args, _cwd| { show_main_window(app); })) .plugin(tauri_plugin_fs::init()) .plugin(tauri_plugin_process::init()) .plugin(tauri_plugin_updater::Builder::new().build()) .plugin(tauri_plugin_os::init()) .plugin(tauri_plugin_clipboard_manager::init()) .plugin(tauri_plugin_macos_permissions::init()) .plugin(tauri_plugin_opener::init()) .plugin(tauri_plugin_store::Builder::default().build()) .plugin(tauri_plugin_global_shortcut::Builder::new().build()) .plugin(tauri_plugin_autostart::init( MacosLauncher::LaunchAgent, Some(vec![]), )) .manage(Mutex::new(ShortcutToggleStates::default())) .setup(move |app| { // Get the current theme to set the appropriate initial icon let initial_theme = if let Some(main_window) = app.get_webview_window("main") { main_window.theme().unwrap_or(tauri::Theme::Dark) } else { tauri::Theme::Dark }; println!("Initial system theme: {:?}", initial_theme); // Choose the appropriate initial icon based on theme let initial_icon_path = match initial_theme { tauri::Theme::Dark => "resources/tray_idle.png", tauri::Theme::Light => "resources/tray_idle_dark.png", _ => "resources/tray_idle.png", // Default fallback }; let tray = TrayIconBuilder::new() .icon(Image::from_path(app.path().resolve( initial_icon_path, tauri::path::BaseDirectory::Resource, )?)?) .show_menu_on_left_click(true) .icon_as_template(true) .on_menu_event(|app, event| match event.id.as_ref() { "settings" => { show_main_window(app); } "check_updates" => { show_main_window(app); let _ = app.emit("check-for-updates", ()); } "cancel" => { use crate::utils::cancel_current_operation; // Use centralized cancellation that handles all operations cancel_current_operation(app); } "quit" => { app.exit(0); } _ => {} }) .build(app)?; app.manage(tray); // Initialize tray menu with idle state utils::update_tray_menu(&app.handle(), &utils::TrayIconState::Idle); // Get the autostart manager let autostart_manager = app.autolaunch(); // Enable autostart let _ = autostart_manager.enable(); let recording_manager = Arc::new( AudioRecordingManager::new(app).expect("Failed to initialize recording manager"), ); let model_manager = Arc::new(ModelManager::new(&app).expect("Failed to initialize model manager")); let transcription_manager = Arc::new( TranscriptionManager::new(&app, model_manager.clone()) .expect("Failed to initialize transcription manager"), ); // Add managers to Tauri's managed state app.manage(recording_manager.clone()); app.manage(model_manager.clone()); app.manage(transcription_manager.clone()); // Create the recording overlay window (hidden by default) utils::create_recording_overlay(&app.handle()); shortcut::init_shortcuts(app); Ok(()) }) .on_window_event(|window, event| match event { tauri::WindowEvent::CloseRequested { api, .. } => { api.prevent_close(); let _res = window.hide(); #[cfg(target_os = "macos")] { let res = window .app_handle() .set_activation_policy(tauri::ActivationPolicy::Accessory); if let Err(e) = res { println!("Failed to set activation policy: {}", e); } } } tauri::WindowEvent::ThemeChanged(theme) => { println!("Theme changed to: {:?}", theme); // Update tray icon to match new theme, maintaining idle state utils::change_tray_icon(&window.app_handle(), utils::TrayIconState::Idle); } _ => {} }) .invoke_handler(tauri::generate_handler![ shortcut::change_binding, shortcut::reset_binding, shortcut::change_ptt_setting, shortcut::change_audio_feedback_setting, shortcut::change_translate_to_english_setting, shortcut::change_selected_language_setting, shortcut::change_overlay_position_setting, shortcut::change_debug_mode_setting, shortcut::change_word_correction_threshold_setting, shortcut::update_custom_words, shortcut::suspend_binding, shortcut::resume_binding, trigger_update_check, commands::cancel_operation, commands::get_app_dir_path, commands::models::get_available_models, commands::models::get_model_info, commands::models::download_model, commands::models::delete_model, commands::models::cancel_download, commands::models::set_active_model, commands::models::get_current_model, commands::models::get_transcription_model_status, commands::models::is_model_loading, commands::models::has_any_models_available, commands::models::has_any_models_or_downloads, commands::models::get_recommended_first_model, commands::audio::update_microphone_mode, commands::audio::get_microphone_mode, commands::audio::get_available_microphones, commands::audio::set_selected_microphone, commands::audio::get_selected_microphone, commands::audio::get_available_output_devices, commands::audio::set_selected_output_device, commands::audio::get_selected_output_device ]) .run(tauri::generate_context!()) .expect("error while running tauri application"); }