//! QuickSearch binary: `quicksearch ` searches from the terminal; //! without a query it opens the egui desktop app. //! //! On Windows this is the GUI only, built as a window-subsystem app so no //! console flashes behind it. Terminal search there is `quicksearch-cli`, //! which is a console app and so keeps working pipes, exit codes, and a shell //! that waits for it. A query passed here still does something useful: it //! seeds the search box. #![cfg_attr(windows, windows_subsystem = "windows")] mod app; mod backend; #[cfg(not(windows))] mod cli; mod duplicates_tab; mod format; mod help_tab; mod keychain; mod logs_tab; mod manage_tab; mod options; mod platform; mod query_highlight; mod search_tab; mod tracker; mod ui_util; mod unlock; use quicksearch_core::config::Config; /// The window icon, shown in the titlebar, taskbar and alt-tab switcher. /// /// X11 takes these pixels directly via `_NET_WM_ICON`. Wayland ignores them and /// instead looks up the app id in `/usr/share/applications/`, so the id below has /// to match the installed `quicksearch.desktop` for the icon to appear there. fn app_icon() -> egui::IconData { eframe::icon_data::from_png_bytes(include_bytes!("../assets/icons/quicksearch-256.png")) .expect("bundled icon is a valid PNG") } /// Leftover positional arguments, joined — used to seed the search box. /// /// Flags are dropped rather than parsed: eframe and winit take some of their /// own, and a stray `--foo` should not end up in the query. fn seed_query() -> Option { let terms: Vec = std::env::args() .skip(1) .filter(|a| !a.starts_with('-')) .collect(); if terms.is_empty() { None } else { Some(terms.join(" ")) } } fn main() { // Must come first: anything below may print, and printing without a // stdio handle panics rather than failing quietly. #[cfg(windows)] platform::redirect_null_stdio(); #[cfg(not(windows))] if let Some(code) = cli::maybe_run_cli() { std::process::exit(code); } // A broken config file should never keep the window from opening — // surface the error in-app and run on defaults. let (config, config_error) = match Config::load() { Ok(c) => (c, None), Err(e) => (Config::default(), Some(e)), }; let initial_query = seed_query(); // With protection on, try the keychain before the window opens; a // verified key means no prompt at all. Anything else starts locked — // the unlock screen owns password entry, bad-salt reporting, and the // forgot-password escape hatch. No index is touched until unlocked. let start_unlocked = !config.security.password_protected || unlock::try_keychain_unlock(&config); let native_options = eframe::NativeOptions { viewport: egui::ViewportBuilder::default() .with_title("QuickSearch") .with_app_id("quicksearch") .with_icon(app_icon()) .with_inner_size([1000.0, 700.0]) .with_min_inner_size([640.0, 400.0]), ..Default::default() }; let result = eframe::run_native( "QuickSearch", native_options, Box::new(move |cc| { let gate = if start_unlocked { unlock::Gate::running(&cc.egui_ctx, config, config_error, initial_query) .map_err(Box::::from)? } else { unlock::Gate::locked(config, config_error, initial_query) }; Ok(Box::new(gate) as Box) }), ); if let Err(e) = result { eprintln!("failed to start GUI: {}", e); std::process::exit(1); } }