Desktop: Restart without UI acceleration when it fails on Linux (#3668)

* Desktop: Restart without UI Acceleration when it fails on Linux

* fix spelling
This commit is contained in:
Timon 2026-01-26 15:33:09 +01:00 committed by GitHub
parent b4e9d7b9eb
commit 5e61d59e50
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 70 additions and 27 deletions

View File

@ -1,17 +1,17 @@
use rand::Rng; use rand::Rng;
use rfd::AsyncFileDialog; use rfd::AsyncFileDialog;
use std::fs; use std::fs;
use std::path::PathBuf;
use std::sync::mpsc::{Receiver, Sender, SyncSender}; use std::sync::mpsc::{Receiver, Sender, SyncSender};
use std::thread; use std::thread;
use std::time::{Duration, Instant}; use std::time::{Duration, Instant};
use winit::application::ApplicationHandler; use winit::application::ApplicationHandler;
use winit::dpi::{PhysicalPosition, PhysicalSize}; use winit::dpi::{PhysicalPosition, PhysicalSize};
use winit::event::{ButtonSource, ElementState, MouseButton, WindowEvent}; 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 winit::window::WindowId;
use crate::cef; use crate::cef;
use crate::cli::Cli;
use crate::consts::CEF_MESSAGE_LOOP_MAX_ITERATIONS; use crate::consts::CEF_MESSAGE_LOOP_MAX_ITERATIONS;
use crate::event::{AppEvent, AppEventScheduler}; use crate::event::{AppEvent, AppEventScheduler};
use crate::persist::PersistentData; use crate::persist::PersistentData;
@ -37,13 +37,14 @@ pub(crate) struct App {
cef_context: Box<dyn cef::CefContext>, cef_context: Box<dyn cef::CefContext>,
cef_schedule: Option<Instant>, cef_schedule: Option<Instant>,
cef_view_info_sender: Sender<cef::ViewInfoUpdate>, cef_view_info_sender: Sender<cef::ViewInfoUpdate>,
last_ui_update: Instant, cef_init_successful: bool,
avg_frame_time: f32,
start_render_sender: SyncSender<()>, start_render_sender: SyncSender<()>,
web_communication_initialized: bool, web_communication_initialized: bool,
web_communication_startup_buffer: Vec<Vec<u8>>, web_communication_startup_buffer: Vec<Vec<u8>>,
persistent_data: PersistentData, persistent_data: PersistentData,
launch_documents: Vec<PathBuf>, cli: Cli,
startup_time: Option<Instant>,
exit_reason: ExitReason,
} }
impl App { impl App {
@ -57,12 +58,12 @@ impl App {
wgpu_context: WgpuContext, wgpu_context: WgpuContext,
app_event_receiver: Receiver<AppEvent>, app_event_receiver: Receiver<AppEvent>,
app_event_scheduler: AppEventScheduler, app_event_scheduler: AppEventScheduler,
launch_documents: Vec<PathBuf>, cli: Cli,
) -> Self { ) -> Self {
let ctrlc_app_event_scheduler = app_event_scheduler.clone(); let ctrlc_app_event_scheduler = app_event_scheduler.clone();
ctrlc::set_handler(move || { ctrlc::set_handler(move || {
tracing::info!("Termination signal received, exiting..."); 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"); .expect("Error setting Ctrl-C handler");
@ -95,19 +96,32 @@ impl App {
app_event_receiver, app_event_receiver,
app_event_scheduler, app_event_scheduler,
desktop_wrapper, desktop_wrapper,
last_ui_update: Instant::now(),
cef_context, cef_context,
cef_schedule: Some(Instant::now()), cef_schedule: Some(Instant::now()),
cef_view_info_sender, cef_view_info_sender,
avg_frame_time: 0., cef_init_successful: false,
start_render_sender, start_render_sender,
web_communication_initialized: false, web_communication_initialized: false,
web_communication_startup_buffer: Vec::new(), web_communication_startup_buffer: Vec::new(),
persistent_data, 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<ExitReason>) {
if let Some(reason) = reason {
self.exit_reason = reason;
}
self.app_event_scheduler.schedule(AppEvent::Exit);
}
fn resize(&mut self) { fn resize(&mut self) {
let Some(window) = &self.window else { let Some(window) = &self.window else {
tracing::error!("Resize failed due to missing window"); tracing::error!("Resize failed due to missing window");
@ -302,11 +316,11 @@ impl App {
} }
} }
DesktopFrontendMessage::OpenLaunchDocuments => { DesktopFrontendMessage::OpenLaunchDocuments => {
if self.launch_documents.is_empty() { if self.cli.files.is_empty() {
return; return;
} }
let app_event_scheduler = self.app_event_scheduler.clone(); 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 || { let _ = thread::spawn(move || {
for path in launch_documents { for path in launch_documents {
tracing::info!("Opening file from command line: {}", path.display()); tracing::info!("Opening file from command line: {}", path.display());
@ -343,7 +357,7 @@ impl App {
} }
} }
DesktopFrontendMessage::WindowClose => { DesktopFrontendMessage::WindowClose => {
self.app_event_scheduler.schedule(AppEvent::CloseWindow); self.app_event_scheduler.schedule(AppEvent::Exit);
} }
DesktopFrontendMessage::WindowMinimize => { DesktopFrontendMessage::WindowMinimize => {
if let Some(window) = &self.window { if let Some(window) = &self.window {
@ -431,15 +445,13 @@ impl App {
AppEvent::UiUpdate(texture) => { AppEvent::UiUpdate(texture) => {
if let Some(render_state) = self.render_state.as_mut() { if let Some(render_state) = self.render_state.as_mut() {
render_state.bind_ui_texture(texture); 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 { if let Some(window) = &self.window {
window.request_redraw(); window.request_redraw();
} }
if !self.cef_init_successful {
self.cef_init_successful = true;
}
} }
AppEvent::ScheduleBrowserWork(instant) => { AppEvent::ScheduleBrowserWork(instant) => {
if instant <= Instant::now() { if instant <= Instant::now() {
@ -453,9 +465,7 @@ impl App {
window.set_cursor(event_loop, cursor); window.set_cursor(event_loop, cursor);
} }
} }
AppEvent::CloseWindow => { AppEvent::Exit => {
// TODO: Implement graceful shutdown
tracing::info!("Exiting main event loop"); tracing::info!("Exiting main event loop");
event_loop.exit(); event_loop.exit();
} }
@ -481,6 +491,8 @@ impl ApplicationHandler for App {
self.resize(); self.resize();
self.desktop_wrapper.init(self.wgpu_context.clone()); self.desktop_wrapper.init(self.wgpu_context.clone());
self.startup_time = Some(Instant::now());
} }
fn proxy_wake_up(&mut self, event_loop: &dyn ActiveEventLoop) { 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 // Handle pointer lock release
if let Some(pointer_lock_position) = self.pointer_lock_position if let Some(pointer_lock_position) = self.pointer_lock_position
&& let WindowEvent::PointerButton { && let WindowEvent::PointerButton {
@ -514,7 +526,7 @@ impl ApplicationHandler for App {
match event { match event {
WindowEvent::CloseRequested => { WindowEvent::CloseRequested => {
self.app_event_scheduler.schedule(AppEvent::CloseWindow); self.app_event_scheduler.schedule(AppEvent::Exit);
} }
WindowEvent::SurfaceResized(_) | WindowEvent::ScaleFactorChanged { .. } => { WindowEvent::SurfaceResized(_) | WindowEvent::ScaleFactorChanged { .. } => {
self.resize(); self.resize();
@ -539,12 +551,22 @@ impl ApplicationHandler for App {
} }
Err(RenderError::SurfaceError(wgpu::SurfaceError::OutOfMemory)) => { Err(RenderError::SurfaceError(wgpu::SurfaceError::OutOfMemory)) => {
tracing::error!("GPU out of memory"); tracing::error!("GPU out of memory");
event_loop.exit(); self.exit(None);
} }
Err(RenderError::SurfaceError(e)) => tracing::error!("Render error: {:?}", e), Err(RenderError::SurfaceError(e)) => tracing::error!("Render error: {:?}", e),
} }
let _ = self.start_render_sender.try_send(()); 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, .. } => { WindowEvent::DragDropped { paths, .. } => {
for path in paths { for path in paths {
@ -629,3 +651,8 @@ impl ApplicationHandler for App {
event_loop.set_control_flow(ControlFlow::WaitUntil(wait_until)); event_loop.set_control_flow(ControlFlow::WaitUntil(wait_until));
} }
} }
pub(crate) enum ExitReason {
Shutdown,
UiAccelerationFailure,
}

View File

@ -8,7 +8,7 @@ pub(crate) enum AppEvent {
WebCommunicationInitialized, WebCommunicationInitialized,
DesktopWrapperMessage(DesktopWrapperMessage), DesktopWrapperMessage(DesktopWrapperMessage),
NodeGraphExecutionResult(NodeGraphExecutionResult), NodeGraphExecutionResult(NodeGraphExecutionResult),
CloseWindow, Exit,
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
MenuEvent { MenuEvent {
id: String, id: String,

View File

@ -77,6 +77,10 @@ pub fn start() {
let (cef_view_info_sender, cef_view_info_receiver) = std::sync::mpsc::channel(); 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_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) { let cef_context = match cef_context_builder.initialize(cef_handler, cli.disable_ui_acceleration) {
Ok(context) => { 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 // Explicitly drop the instance lock
drop(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. // 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. // The issue causes the window to hang for a few seconds before closing.
// Appears to be related to CEF object destruction order. // Appears to be related to CEF object destruction order.