//! DarkRoom desktop entry point. //! //! darkroom-desktop ... //! darkroom-desktop --version // TRACES: FR-PLAT-WIN-2 // A GUI-subsystem executable, or Windows opens a console window behind the // application for the life of the process. Release only: the console is where // the log goes when there is no file, and a debug build is run from one. // `--version` still prints under this — stdout is simply not attached when // launched from Explorer, which is not where anyone asks for a version. #![cfg_attr(all(windows, not(debug_assertions)), windows_subsystem = "windows")] use std::path::PathBuf; use dr_plat::diagnostics::Installed; /// What the log keeps when `RUST_LOG` does not say. #[cfg(not(target_os = "macos"))] const DEFAULT_LOG: &str = "info,wgpu_core=warn,wgpu_hal=warn,zbus=warn,tracing=warn,calloop=warn,rawler=warn"; /// The same, and `debug` from this application's own crates and from ONNX /// Runtime, whose `debug` is how many nodes each provider took /// (docs/dev/macos.md). Nobody here runs a Mac: every macOS build is in /// the hands of someone who can send us a log and cannot attach a debugger, /// so the log is written as if for a debug build. `dr_` is a prefix, and /// `env_logger` matches directives by prefix, so it names every `dr-*` /// crate — present and future — without naming a dependency. #[cfg(target_os = "macos")] const DEFAULT_LOG: &str = "info,dr_=debug,darkroom_desktop=debug,onnxruntime=debug,\ wgpu_core=warn,wgpu_hal=warn,zbus=warn,tracing=warn,calloop=warn,rawler=warn"; fn main() -> anyhow::Result<()> { // TRACES: FR-PLAT-WIN-3 // Before the logger, the crash hook and everything else: this exists so a // build made on a machine that cannot run the application — the Linux CI // producing the Windows binary, checked under Wine — has an exit that // proves the executable starts without opening a window or touching the // user's directories (docs/dev/windows.md §6). if std::env::args().nth(1).as_deref() == Some("--version") { println!("darkroom-desktop {}", env!("CARGO_PKG_VERSION")); return Ok(()); } // Built rather than `init`ed, so the same logger can be handed to the // diagnostics tee: `env_logger` keeps writing to stderr exactly as before, // and every record it accepts is also appended to the on-disk log // (NFR-OPS-1). `filter()` is asked afterwards because the environment may // have overridden the default below, and the file must not be quieter than // the terminal. let console = env_logger::Builder::from_env(env_logger::Env::default().default_filter_or(DEFAULT_LOG)) .build(); let level = console.filter(); let logging = dr_plat::diagnostics::install(Box::new(console), level); // Immediately after the logger and before anything that could fail. Until // now a panic on desktop went to stderr and died with the terminal, which // means every panic a user has ever hit was unreportable: the process // survives (the panicking worker does not), a control goes dead, and there // is nothing on disk to say why. The record is local and stays local — // there is no upload path, by design; see `dr_plat::crash`. dr_plat::crash::install(env!("CARGO_PKG_VERSION")); log::info!("DarkRoom v{}", env!("CARGO_PKG_VERSION")); // First thing in the file, so a user asked for "the log" can find it // without being told a path over the phone. match &logging { Installed::ToFile(path) => log::info!("logging to {}", path.display()), Installed::ConsoleOnly(why) => log::warn!("no log file this session: {why}"), } let paths: Vec = std::env::args().skip(1).map(PathBuf::from).collect(); if paths.is_empty() { eprintln!("usage: darkroom-desktop ..."); } // Before the window: the probe runs on its own thread and the first // frame does not wait for it, but the models a background job asks for // should already know where the runtime is (docs/dev/inference.md §4). dr_ui::inference::init(runtime_dirs()); dr_ui::run(paths)?; // Skip Rust's normal static/thread-local teardown on the way out: a // background zbus/keyring connection opened by dr_ui::launch_ui can // still be alive here, and unwinding through it races its async-io // reactor thread, panicking with "thread local ... during or after // destruction" when the window is closed. std::process::exit(0); } /// Where a desktop package may have put `libonnxruntime`, most specific /// first. None of these existing is the tract build, which is a complete /// application and not an error (docs/dev/inference.md §3). /// /// `DARKROOM_ORT_DIR` is for a developer pointing at a runtime that is not /// installed — the wheel's `capi` directory, say. Then beside the executable /// and in the package's private library directory, for a package that /// bundles its own; then the user's own `runtime/` beside the models, where /// `tools/fetch-desktop-runtime.sh` puts one; then the Flatpak prefix; then /// the system library directory, for a distribution that ships ONNX Runtime /// as a package of its own. The user's copy outranks the system's because /// the system's is the one most likely to be built without the GPU /// providers, or against the wrong cuDNN — and a system copy whose providers /// do not load is not a problem, only a slower app: the probe builds a real /// session before believing a provider. fn runtime_dirs() -> Vec { let mut dirs = Vec::new(); if let Some(dir) = std::env::var_os("DARKROOM_ORT_DIR") { dirs.push(PathBuf::from(dir)); } if let Ok(exe) = std::env::current_exe() { if let Some(bin) = exe.parent() { dirs.push(bin.to_path_buf()); dirs.push(bin.join("../lib/darkroom")); } } dirs.push(dr_ui::inference::user_runtime_dir()); #[cfg(target_os = "linux")] dirs.extend([ PathBuf::from("/app/lib/darkroom"), PathBuf::from("/usr/lib/darkroom"), PathBuf::from("/usr/lib"), ]); // An app bundle keeps its libraries in `Contents/Frameworks`, beside // the `Contents/MacOS` the executable is in; then Homebrew's // `onnxruntime`, Apple silicon's prefix before Intel's. Homebrew's build // may lack CoreML, which the probe finds out for itself. #[cfg(target_os = "macos")] { if let Ok(exe) = std::env::current_exe() { if let Some(bin) = exe.parent() { dirs.push(bin.join("../Frameworks")); } } dirs.extend([ PathBuf::from("/opt/homebrew/lib"), PathBuf::from("/usr/local/lib"), ]); } dirs }