// Claude Desktop — Tauri Backend // Hauptmodul für die Rust-Seite der App use std::sync::{Arc, Mutex}; use tauri::{ Emitter, Manager, menu::{Menu, MenuItem}, tray::{MouseButton, MouseButtonState, TrayIconBuilder, TrayIconEvent}, }; #[cfg(target_os = "linux")] use webkit2gtk::WebViewExt; mod audit; mod claude; mod clipboard; mod commands; mod context; mod db; mod guard; mod hooks; mod ide; mod knowledge; mod memory; mod programs; mod session; mod teaching; mod update; mod voice; mod chat_window; /// Initialisiert die App #[cfg_attr(mobile, tauri::mobile_entry_point)] pub fn run() { tauri::Builder::default() .plugin(tauri_plugin_shell::init()) .plugin(tauri_plugin_dialog::init()) .plugin(tauri_plugin_global_shortcut::Builder::new().build()) .manage(Arc::new(Mutex::new(claude::ClaudeState::default()))) .manage(guard::GuardState::new(Mutex::new(guard::GuardRails::new()))) .manage::(Arc::new(Mutex::new(hooks::HookManager::default()))) .manage::(Arc::new(Mutex::new(clipboard::ClipboardWatcher::default()))) .manage::(Arc::new(Mutex::new(ide::IdeConnector::default()))) // Phase 2.0: MySQL Pool als Managed State — wird einmal erstellt, von allen Knowledge-Commands geteilt .manage::(knowledge::create_managed_pool()) .invoke_handler(tauri::generate_handler![ // Claude SDK claude::send_message, claude::stop_all_agents, claude::get_agent_status, claude::set_model, claude::get_available_models, claude::get_current_model, claude::set_agent_mode, claude::get_agent_mode, claude::init_sticky_context, // Gedächtnis-System memory::load_memory, memory::get_sticky_memory_entries, memory::save_memory_entry, memory::delete_memory_entry, memory::list_memory_entries, memory::get_autoload_memory, memory::save_pattern, memory::detect_issue, // Audit-Log audit::get_audit_log, audit::add_audit_entry, audit::get_audit_stats, // Guard-Rails guard::check_action, guard::add_permission, guard::remove_permission, guard::get_permissions, guard::get_blocked_patterns, // Datenbank db::init_database, db::get_db_stats, // Settings db::get_setting, db::set_setting, db::get_all_settings, // Sessions session::create_session, session::update_session, session::list_sessions, session::get_session, session::delete_session, session::resume_session, session::get_active_session, session::set_claude_session_id, session::update_session_stats, // Messages db::save_message, db::load_messages, db::clear_messages, db::compact_session, // Monitor-Events db::save_monitor_event, db::load_monitor_events, db::load_monitor_events_by_type, db::clear_all_monitor_events, db::get_monitor_stats, // Phase 2.0: Fehler-Tracking db::track_error, db::set_error_kb_pattern, db::get_error_stats, // Projekte db::list_projects, db::save_project, db::delete_project, db::switch_project, // Wissensbasis (claude-db) knowledge::search_knowledge, knowledge::get_knowledge, knowledge::save_knowledge, knowledge::get_knowledge_categories, knowledge::get_recent_knowledge, knowledge::test_knowledge_connection, knowledge::get_tool_hints, knowledge::format_tool_hints, // Phase 2.0: Proaktive Intelligenz knowledge::extract_message_keywords, knowledge::get_session_hints, knowledge::auto_save_error_pattern, // Phase 3: KB-Cache knowledge::invalidate_kb_cache, // Phase 3.1: Smart Hints v2 — Session-Management knowledge::reset_kb_session, knowledge::get_kb_session_status, // Context-Management context::get_sticky_context, context::set_sticky_context, context::remove_sticky_context, context::get_project_context, context::extract_context_before_compacting, context::log_context_failure, context::get_full_context, context::list_sticky_context, // Voice-Interface voice::transcribe_audio, voice::text_to_speech, voice::check_voice_availability, voice::get_tts_voices, // Hook-System hooks::list_hooks, hooks::set_hook_enabled, hooks::get_hook_executions, hooks::fire_hook, // IDE-Connector (VSCodium) ide::ide_connect, ide::ide_disconnect, ide::ide_status, ide::ide_call, // Programm-Steuerung (D-Bus, Xvfb, Playwright-Info) programs::dbus_call, programs::dbus_list_services, programs::dbus_list_actions, programs::dbus_run_action, programs::capture_screenshot, programs::xvfb_start, programs::xvfb_stop, programs::xvfb_status, programs::xvfb_screenshot, programs::playwright_info, // Schulungsmodus (Phase 15) teaching::presentation_open, teaching::presentation_close, teaching::presentation_send_slide, teaching::presentation_clear, // Chat-Fenster (herauslösen) chat_window::chat_window_open, chat_window::chat_window_close, // Auto-Update update::check_for_update, update::download_update, update::apply_update, update::apply_bundle_update, update::get_current_version, // Clipboard-Watch clipboard::clipboard_watch_start, clipboard::clipboard_watch_stop, clipboard::clipboard_watch_status, // Slash-Command Registry commands::get_slash_commands, ]) .setup(|app| { let handle = app.handle().clone(); println!("🤖 Claude Desktop gestartet"); // Datenbank initialisieren let app_dir = app.path().app_data_dir() .expect("Konnte App-Datenverzeichnis nicht ermitteln"); std::fs::create_dir_all(&app_dir).ok(); let db_path = app_dir.join("claude-desktop.db"); println!("📦 Datenbank: {:?}", db_path); let db = db::Database::open(&db_path) .expect("Datenbank konnte nicht geöffnet werden"); app.manage(Arc::new(Mutex::new(db))); // Guard-Rails: permanente Permissions aus DB laden { let db_state = handle.state::>>(); let guard_state = handle.state::(); let db_lock = db_state.lock().unwrap(); let mut guard_lock = guard_state.lock().unwrap(); if let Ok(perms) = db_lock.load_permissions() { for p in perms { guard_lock.add_permission(p); } println!("🛡️ {} permanente Permissions geladen", guard_lock.get_all_permissions().len()); } } // Gedächtnis-System laden tauri::async_runtime::spawn(async move { println!("🧠 Initialisiere Gedächtnis-System..."); }); // Tray-Icon einrichten let show_item = MenuItem::with_id(app, "show", "Fenster zeigen", true, None::<&str>)?; let hide_item = MenuItem::with_id(app, "hide", "Minimieren", true, None::<&str>)?; let quit_item = MenuItem::with_id(app, "quit", "Beenden", true, None::<&str>)?; let tray_menu = Menu::with_items(app, &[&show_item, &hide_item, &quit_item])?; // App-Icon für Tray verwenden let icon = app.default_window_icon() .cloned() .expect("Kein App-Icon konfiguriert"); let tray_icon = TrayIconBuilder::new() .icon(icon) .menu(&tray_menu) .show_menu_on_left_click(false) .tooltip("Claude Desktop") .on_menu_event(|app, event| { match event.id.as_ref() { "show" => { if let Some(window) = app.get_webview_window("main") { let _ = window.show(); let _ = window.set_focus(); } } "hide" => { if let Some(window) = app.get_webview_window("main") { let _ = window.hide(); } } "quit" => { app.exit(0); } _ => {} } }) .on_tray_icon_event(|tray, event| { // Doppelklick auf Tray-Icon zeigt das Fenster if let TrayIconEvent::Click { button: MouseButton::Left, button_state: MouseButtonState::Up, .. } = event { if let Some(window) = tray.app_handle().get_webview_window("main") { let _ = window.show(); let _ = window.set_focus(); } } }) .build(app)?; // Tray-Icon Handle speichern (optional für späteren Zugriff) app.manage(tray_icon); println!("🔲 Tray-Icon eingerichtet"); // Lock-Datei erstellen (Instanz-Schutz + Update-Safety) update::create_lock_file(); // Global Hotkey: Super+C öffnet Claude-Eingabe von überall { use tauri_plugin_global_shortcut::GlobalShortcutExt; let hotkey_handle = app.handle().clone(); let _ = app.global_shortcut().on_shortcut("Super+C", move |_app, _shortcut, event| { if event.state == tauri_plugin_global_shortcut::ShortcutState::Pressed { if let Some(window) = hotkey_handle.get_webview_window("main") { let _ = window.show(); let _ = window.unminimize(); let _ = window.set_focus(); let _ = hotkey_handle.emit("focus-chat-input", ()); } } }); println!("⌨️ Global Hotkey registriert: Super+C"); } // WebKitGTK: Mikrofon-/Kamera-Permissions automatisch erlauben #[cfg(target_os = "linux")] { if let Some(window) = app.get_webview_window("main") { window.with_webview(|webview| { use webkit2gtk::PermissionRequestExt; let wv: webkit2gtk::WebView = webview.inner().clone(); wv.connect_permission_request(|_wv, request: &webkit2gtk::PermissionRequest| { println!("🎤 WebKit Permission-Request → erlaubt"); request.allow(); true // Signal handled }); println!("🎤 WebKit Permission-Handler registriert"); }).ok(); } } // Phase 3: Bridge sofort beim App-Start starten (kein Cold-Start bei erster Nachricht) let bridge_handle = app.handle().clone(); tauri::async_runtime::spawn(async move { match claude::start_bridge(&bridge_handle) { Ok(()) => println!("🔌 Bridge beim Start initialisiert (warm)"), Err(e) => println!("⚠️ Bridge-Start beim App-Start fehlgeschlagen: {} (wird bei erster Nachricht erneut versucht)", e), } }); Ok(()) }) .build(tauri::generate_context!()) .expect("Fehler beim Erstellen der App") .run(|_app_handle, event| { // Lock-Datei bei App-Beendigung aufräumen if let tauri::RunEvent::Exit = event { update::remove_lock_file(); println!("🔒 Lock-Datei aufgeräumt, App beendet."); } // Bei Fenster-Schließen: Lock entfernen falls App komplett beendet wird if let tauri::RunEvent::WindowEvent { event: tauri::WindowEvent::CloseRequested { .. }, .. } = event { // Lock wird beim tatsächlichen Exit entfernt (RunEvent::Exit oben) } }); }