use std::time::Duration; use ely_gpui_component::Assets; use ely_gpui_component::theme::ActiveTheme; use ely_platform::{AppId, env, graphics, logging, startup}; use gpui::{ App, Bounds, Entity, Render, TitlebarOptions, Window, WindowBounds, WindowDecorations, WindowOptions, px, size, }; use crate::theme; /// Starts `app`; `ELY_SMOKE=1` draws a frame, `hold` stays up. pub fn run( app: AppId, root: impl FnOnce(AppId, &mut Window, &mut App) -> Entity + 'static, ) { if let Err(error) = logging::init(app) { startup::fail(app, &format!("logging did not start: {error:#}")); } if let Err(error) = graphics::check() { startup::fail(app, &format!("{error:#}")); } startup::guard(app); startup::no_window_restore(); let smoke = Smoke::from_env(); let application = gpui_platform::application().with_assets(Assets); let asked = crate::opening::listen(&application); application.run(move |cx: &mut App| { crate::opening::keep(asked, cx); open(app, smoke, root, cx) }); } #[derive(Clone, Copy, PartialEq)] enum Smoke { Off, Once, Hold, } impl Smoke { fn from_env() -> Self { Self::from_value(env::value("ELY_SMOKE").as_deref()) } fn from_value(value: Option<&str>) -> Self { match value { None => Smoke::Off, Some("1") => Smoke::Once, Some("hold") => Smoke::Hold, Some(other) => { log::warn!("run: ELY_SMOKE={other} is not 1 or hold, ignored"); Smoke::Off } } } } fn open( app: AppId, smoke: Smoke, root: impl FnOnce(AppId, &mut Window, &mut App) -> Entity + 'static, cx: &mut App, ) { if let Err(error) = ely_gpui_component::init(cx) { startup::fail(app, &format!("the components did not start: {error:#}")); } crate::settings::install(app, cx); // Clears asked for last time; nothing opens what stays. if let Err(error) = ely_platform::storage::clear_marked(app) { startup::fail(app, &format!("the data you asked to clear could not be deleted, so it is not opened; the next start tries again: {error}")); } theme::install(cx); crate::i18n::install(cx); crate::commands::bind_keys(cx); crate::commands::bind_quit(cx); cx.on_action(move |_: &crate::commands::OpenSettings, cx| crate::settings::open(app, cx)); let mut options = WindowOptions { focus: smoke == Smoke::Off, inactive_frame_interval: (smoke != Smoke::Hold).then_some(Duration::from_millis(250)), ..window_options(app, size(px(1100.0), px(720.0)), cx) }; if let Some(bounds) = crate::settings::restored_frame(app, cx) { options.window_bounds = Some(bounds); } let opened = cx.open_window(options, |window, cx| { window.on_next_frame(move |_, cx| { startup::started(); if smoke == Smoke::Once { println!("ready {}", app.name()); cx.quit(); } }); theme::follow_system(window); crate::settings::follow_frame(app, window, cx); root(app, window, cx) }); if let Err(error) = opened { startup::fail(app, &format!("the window did not open: {error:#}")); } cx.on_window_closed(|cx, _| { if cx.windows().is_empty() { cx.quit(); } }) .detach(); if smoke == Smoke::Off { cx.activate(true); } // Benchmarks only: idle apps do nothing (PERF-02). if ely_platform::env::flag("ELY_STALLS") == Some(true) { watch_stalls(cx); } log::info!("{} window open", app.name()); } /// Logs each main-thread stretch busy past 100 ms. fn watch_stalls(cx: &mut App) { const BEAT: Duration = Duration::from_millis(20); cx.spawn(async move |cx: &mut gpui::AsyncApp| { loop { let asked = std::time::Instant::now(); cx.background_executor().timer(BEAT).await; let late = asked.elapsed().saturating_sub(BEAT); if late > Duration::from_millis(100) { log::warn!("ui: the main thread was busy {} ms", late.as_millis()); } } }) .detach(); } /// A window of `app`, `size` large, with Ely's title bar. pub(crate) fn window_options(app: AppId, size: gpui::Size, cx: &App) -> WindowOptions { WindowOptions { window_bounds: Some(WindowBounds::Windowed(Bounds::centered(None, size, cx))), titlebar: Some(TitlebarOptions { title: Some(app.name().into()), appears_transparent: true, traffic_light_position: Some(cx.theme().traffic_light_origin()), }), window_decorations: Some(WindowDecorations::Client), window_min_size: Some(gpui::size(px(640.0), px(420.0))), app_id: Some(app.slug().into()), inactive_frame_interval: Some(Duration::from_millis(250)), ..Default::default() } } #[cfg(test)] mod tests { use super::Smoke; #[test] fn only_one_and_hold_start_a_smoke_run() { assert!(Smoke::from_value(Some("1")) == Smoke::Once); assert!(Smoke::from_value(Some("hold")) == Smoke::Hold); for other in [None, Some("0"), Some(""), Some("true")] { assert!(Smoke::from_value(other) == Smoke::Off, "{other:?}"); } } }