diff --git a/desktop/src/app.rs b/desktop/src/app.rs index bafec9c1d2..3583f230e7 100644 --- a/desktop/src/app.rs +++ b/desktop/src/app.rs @@ -1,17 +1,17 @@ use rand::Rng; use rfd::AsyncFileDialog; use std::fs; -use std::path::PathBuf; use std::sync::mpsc::{Receiver, Sender, SyncSender}; use std::thread; use std::time::{Duration, Instant}; use winit::application::ApplicationHandler; use winit::dpi::{PhysicalPosition, PhysicalSize}; use winit::event::{ButtonSource, ElementState, MouseButton, WindowEvent}; -use winit::event_loop::{ActiveEventLoop, ControlFlow}; +use winit::event_loop::{ActiveEventLoop, ControlFlow, EventLoop}; use winit::window::WindowId; use crate::cef; +use crate::cli::Cli; use crate::consts::CEF_MESSAGE_LOOP_MAX_ITERATIONS; use crate::event::{AppEvent, AppEventScheduler}; use crate::persist::PersistentData; @@ -37,13 +37,14 @@ pub(crate) struct App { cef_context: Box, cef_schedule: Option, cef_view_info_sender: Sender, - last_ui_update: Instant, - avg_frame_time: f32, + cef_init_successful: bool, start_render_sender: SyncSender<()>, web_communication_initialized: bool, web_communication_startup_buffer: Vec>, persistent_data: PersistentData, - launch_documents: Vec, + cli: Cli, + startup_time: Option, + exit_reason: ExitReason, } impl App { @@ -57,12 +58,12 @@ impl App { wgpu_context: WgpuContext, app_event_receiver: Receiver, app_event_scheduler: AppEventScheduler, - launch_documents: Vec, + cli: Cli, ) -> Self { let ctrlc_app_event_scheduler = app_event_scheduler.clone(); ctrlc::set_handler(move || { tracing::info!("Termination signal received, exiting..."); - ctrlc_app_event_scheduler.schedule(AppEvent::CloseWindow); + ctrlc_app_event_scheduler.schedule(AppEvent::Exit); }) .expect("Error setting Ctrl-C handler"); @@ -95,19 +96,32 @@ impl App { app_event_receiver, app_event_scheduler, desktop_wrapper, - last_ui_update: Instant::now(), cef_context, cef_schedule: Some(Instant::now()), cef_view_info_sender, - avg_frame_time: 0., + cef_init_successful: false, start_render_sender, web_communication_initialized: false, web_communication_startup_buffer: Vec::new(), persistent_data, - launch_documents, + cli, + exit_reason: ExitReason::Shutdown, + startup_time: None, } } + pub(crate) fn run(mut self, event_loop: EventLoop) -> ExitReason { + event_loop.run_app(&mut self).unwrap(); + self.exit_reason + } + + fn exit(&mut self, reason: Option) { + if let Some(reason) = reason { + self.exit_reason = reason; + } + self.app_event_scheduler.schedule(AppEvent::Exit); + } + fn resize(&mut self) { let Some(window) = &self.window else { tracing::error!("Resize failed due to missing window"); @@ -302,11 +316,11 @@ impl App { } } DesktopFrontendMessage::OpenLaunchDocuments => { - if self.launch_documents.is_empty() { + if self.cli.files.is_empty() { return; } let app_event_scheduler = self.app_event_scheduler.clone(); - let launch_documents = std::mem::take(&mut self.launch_documents); + let launch_documents = std::mem::take(&mut self.cli.files); let _ = thread::spawn(move || { for path in launch_documents { tracing::info!("Opening file from command line: {}", path.display()); @@ -343,7 +357,7 @@ impl App { } } DesktopFrontendMessage::WindowClose => { - self.app_event_scheduler.schedule(AppEvent::CloseWindow); + self.app_event_scheduler.schedule(AppEvent::Exit); } DesktopFrontendMessage::WindowMinimize => { if let Some(window) = &self.window { @@ -431,15 +445,13 @@ impl App { AppEvent::UiUpdate(texture) => { if let Some(render_state) = self.render_state.as_mut() { render_state.bind_ui_texture(texture); - let elapsed = self.last_ui_update.elapsed().as_secs_f32(); - self.last_ui_update = Instant::now(); - if elapsed < 0.5 { - self.avg_frame_time = (self.avg_frame_time * 3. + elapsed) / 4.; - } } if let Some(window) = &self.window { window.request_redraw(); } + if !self.cef_init_successful { + self.cef_init_successful = true; + } } AppEvent::ScheduleBrowserWork(instant) => { if instant <= Instant::now() { @@ -453,9 +465,7 @@ impl App { window.set_cursor(event_loop, cursor); } } - AppEvent::CloseWindow => { - // TODO: Implement graceful shutdown - + AppEvent::Exit => { tracing::info!("Exiting main event loop"); event_loop.exit(); } @@ -481,6 +491,8 @@ impl ApplicationHandler for App { self.resize(); self.desktop_wrapper.init(self.wgpu_context.clone()); + + self.startup_time = Some(Instant::now()); } fn proxy_wake_up(&mut self, event_loop: &dyn ActiveEventLoop) { @@ -489,7 +501,7 @@ impl ApplicationHandler for App { } } - fn window_event(&mut self, event_loop: &dyn ActiveEventLoop, _window_id: WindowId, event: WindowEvent) { + fn window_event(&mut self, _event_loop: &dyn ActiveEventLoop, _window_id: WindowId, event: WindowEvent) { // Handle pointer lock release if let Some(pointer_lock_position) = self.pointer_lock_position && let WindowEvent::PointerButton { @@ -514,7 +526,7 @@ impl ApplicationHandler for App { match event { WindowEvent::CloseRequested => { - self.app_event_scheduler.schedule(AppEvent::CloseWindow); + self.app_event_scheduler.schedule(AppEvent::Exit); } WindowEvent::SurfaceResized(_) | WindowEvent::ScaleFactorChanged { .. } => { self.resize(); @@ -539,12 +551,22 @@ impl ApplicationHandler for App { } Err(RenderError::SurfaceError(wgpu::SurfaceError::OutOfMemory)) => { tracing::error!("GPU out of memory"); - event_loop.exit(); + self.exit(None); } Err(RenderError::SurfaceError(e)) => tracing::error!("Render error: {:?}", e), } let _ = self.start_render_sender.try_send(()); } + + if !self.cef_init_successful + && !self.cli.disable_ui_acceleration + && self.web_communication_initialized + && let Some(startup_time) = self.startup_time + && startup_time.elapsed() > Duration::from_secs(3) + { + tracing::error!("UI acceleration not working, exiting."); + self.exit(Some(ExitReason::UiAccelerationFailure)); + } } WindowEvent::DragDropped { paths, .. } => { for path in paths { @@ -629,3 +651,8 @@ impl ApplicationHandler for App { event_loop.set_control_flow(ControlFlow::WaitUntil(wait_until)); } } + +pub(crate) enum ExitReason { + Shutdown, + UiAccelerationFailure, +} diff --git a/desktop/src/event.rs b/desktop/src/event.rs index f334b0879c..9e8bb2bf4e 100644 --- a/desktop/src/event.rs +++ b/desktop/src/event.rs @@ -8,7 +8,7 @@ pub(crate) enum AppEvent { WebCommunicationInitialized, DesktopWrapperMessage(DesktopWrapperMessage), NodeGraphExecutionResult(NodeGraphExecutionResult), - CloseWindow, + Exit, #[cfg(target_os = "macos")] MenuEvent { id: String, diff --git a/desktop/src/lib.rs b/desktop/src/lib.rs index a685b2b2fa..f113376323 100644 --- a/desktop/src/lib.rs +++ b/desktop/src/lib.rs @@ -77,6 +77,10 @@ pub fn start() { let (cef_view_info_sender, cef_view_info_receiver) = std::sync::mpsc::channel(); + if cli.disable_ui_acceleration { + println!("UI acceleration is disabled"); + } + let cef_handler = cef::CefHandler::new(wgpu_context.clone(), app_event_scheduler.clone(), cef_view_info_receiver); let cef_context = match cef_context_builder.initialize(cef_handler, cli.disable_ui_acceleration) { Ok(context) => { @@ -101,13 +105,25 @@ pub fn start() { } }; - let mut app = App::new(Box::new(cef_context), cef_view_info_sender, wgpu_context, app_event_receiver, app_event_scheduler, cli.files); + let app = App::new(Box::new(cef_context), cef_view_info_sender, wgpu_context, app_event_receiver, app_event_scheduler, cli); - event_loop.run_app(&mut app).unwrap(); + let exit_reason = app.run(event_loop); // Explicitly drop the instance lock drop(lock); + match exit_reason { + #[cfg(target_os = "linux")] + app::ExitReason::UiAccelerationFailure => { + use std::os::unix::process::CommandExt; + + tracing::error!("Restarting application without UI acceleration"); + let _ = std::process::Command::new(std::env::current_exe().unwrap()).arg("--disable-ui-acceleration").exec(); + tracing::error!("Failed to restart application"); + } + _ => {} + } + // Workaround for a Windows-specific exception that occurs when `app` is dropped. // The issue causes the window to hang for a few seconds before closing. // Appears to be related to CEF object destruction order.