//! Einstieg: Argument-Dispatch auf die drei Prozessrollen eines Binaries — //! Haupt-App (Tray), Terminal-Prozess (`--terminal `) und //! MCP-stdio-Server (`--mcp-panel`). Fachlogik liegt in domain/, OS-Aufrufe //! in platform/, Tauri-Verdrahtung in app.rs/commands.rs. mod app; mod commands; mod domain; mod mcp; mod platform; mod terminal; /// Panic-Tracer: erzwingt bei jedem Panic einen vollständigen Backtrace /// (unabhängig von RUST_BACKTRACE), gibt ihn klar abgegrenzt auf stderr aus und /// schreibt ihn zusätzlich in eine kopierbare Datei (Pfad pro Prozess, damit /// Haupt- und Terminal-Prozesse sich nicht überschreiben). fn install_panic_tracer() { std::panic::set_hook(Box::new(|info| { let bt = std::backtrace::Backtrace::force_capture(); let dump = format!( "\n========== ai-control PANIC (pid {}) ==========\n{info}\n\n{bt}\n========== END ==========\n", std::process::id() ); eprintln!("{dump}"); let path = std::env::temp_dir().join(format!("ai-control-panic-{}.log", std::process::id())); if std::fs::write(&path, &dump).is_ok() { eprintln!("Backtrace kopierbar in: {}", path.display()); } })); } #[cfg_attr(mobile, tauri::mobile_entry_point)] pub fn run() { install_panic_tracer(); let mut args = std::env::args().skip(1); let first = args.next(); // `app --mcp-panel`: reiner MCP-stdio-Server (write_panel), kein Tauri/GTK. // Von claude als Tool-Server gestartet. if first.as_deref() == Some("--mcp-panel") { mcp::run_mcp_panel(); return; } // AppImage ist installationsfrei und kann die GNOME-Popup-Extension nicht // ausrollen/aktivieren. Unter GNOME daher auf das deb-/rpm-Paket verweisen und // beenden. KDE/XFCE zeigen das Tray ohne Extension — dort läuft die AppImage. #[cfg(target_os = "linux")] if std::env::var_os("APPIMAGE").is_some() && platform::is_gnome() { rfd::MessageDialog::new() .set_level(rfd::MessageLevel::Warning) .set_title("ai-control") .set_description( "Die AppImage-Fassung wird unter GNOME nicht unterstützt: Das Tray-Icon \ braucht eine GNOME-Shell-Extension, die nur das deb-/rpm-Paket \ installiert. Bitte das deb- bzw. rpm-Paket verwenden.", ) .set_buttons(rfd::MessageButtons::Ok) .show(); std::process::exit(1); } // generate_context! darf pro Crate nur einmal expandieren (_EMBED_INFO_PLIST). let context = tauri::generate_context!(); match first.as_deref() { // `app --terminal `: eigener Prozess pro Terminal-Fenster, // damit jedes Terminal ein eigenes Dock-Icon bekommt. Some("--terminal") => { let project = args.next().expect("--terminal braucht einen Projektnamen"); // Eigene Wayland-app_id pro Terminal-Prozess -> eigener Dock-Eintrag // (muss vor dem GTK-Init stehen; No-op außerhalb Linux). platform::set_app_id(&format!("aicontrol-{project}")); let icon = domain::project::project_config(&project) .expect("Projekt-Config nicht lesbar") .terminal .icon .map(|i| { domain::project::resolve_icon_path(&domain::paths::Paths::real(), &project, &i) .expect("Projekt nicht registriert") .to_string_lossy() .into_owned() }); app::terminal_builder(project) .build(context) .expect("error while building tauri application") // Das Dock-Icon erst nach dem App-Start setzen: in setup() gesetzt // überschreibt macOS es beim Anlegen des Dock-Tiles wieder. .run(move |app, event| { if let tauri::RunEvent::Ready = event { if let Some(icon) = icon.as_deref() { platform::set_dock_icon(icon); } platform::activate_self(app); } }); } _ => { // Feste Wayland-app_id fürs Hauptfenster (vor GTK-Init) — GNOME ordnet // dem offenen Fenster über ai-control.desktop das App-Icon zu. platform::set_app_id("ai-control"); app::main_builder() .run(context) .expect("error while running tauri application"); } } }