Files
Graphite/desktop/src/lib.rs
Timon f88896a887 Desktop: Stop compiling the editor into the wasm wrapper (#4348)
* Make wasm wrapper not depend on editor on native

* Introduce pkg-native

* Fix tests

* Fix fmt

* Correct span and make cargo fmt work inside editor_commands block

* Review

* Fix lint
2026-07-18 10:04:07 +00:00

176 lines
5.3 KiB
Rust

use crate::app::App;
use crate::cli::Cli;
use crate::consts::APP_LOCK_FILE_NAME;
use crate::event::{AppEvent, CreateAppEventSchedulerEventLoopExt};
use clap::Parser;
use std::io::Write;
use std::process::ExitCode;
use tracing_subscriber::EnvFilter;
use ui::{Acceleration, UiConfig, UiContext, UiEvent, UiSetupResult};
use winit::event_loop::EventLoop;
pub(crate) use graphite_desktop_ui as ui;
pub(crate) use graphite_desktop_wrapper as wrapper;
mod app;
mod cli;
mod dirs;
mod event;
mod gpu_context;
mod persist;
mod preferences;
mod render;
mod socket;
mod window;
pub(crate) mod consts;
pub fn start() -> ExitCode {
tracing_subscriber::fmt().with_env_filter(EnvFilter::from_default_env()).init();
let ui_context = match UiContext::setup() {
UiSetupResult::Ready(context) => context,
UiSetupResult::Helper(code) => return code,
UiSetupResult::Failed => {
tracing::error!("Failed to set up the UI runtime");
return ExitCode::FAILURE;
}
};
let cli = Cli::parse();
let Ok(lock_file) = std::fs::OpenOptions::new()
.read(true)
.write(true)
.create(true)
.truncate(true)
.open(dirs::app_data_dir().join(APP_LOCK_FILE_NAME))
else {
tracing::error!("Failed to open lock file.");
return ExitCode::FAILURE;
};
let mut lock = fd_lock::RwLock::new(lock_file);
let lock = match lock.try_write() {
Ok(mut guard) => {
tracing::info!("Acquired application lock");
let _ = guard.set_len(0);
let _ = write!(guard, "{}", std::process::id());
let _ = guard.sync_all();
guard
}
Err(_) => {
tracing::error!("Another instance is already running, Exiting.");
if !cli.files.is_empty()
&& let Err(error) = socket::send(socket::Message::OpenFiles(cli.files))
{
tracing::error!("Failed to send socket message to running instance: {}", error);
return ExitCode::FAILURE;
}
return ExitCode::SUCCESS;
}
};
dirs::clear_dir(&ui::temp_dir_root());
// TODO: Eventually remove this cleanup code for the old "browser" CEF directory
dirs::delete_old_cef_browser_directory();
let mut prefs = preferences::read();
// Must be called before event loop initialization or native window integrations will break
App::init();
let wgpu_context = futures::executor::block_on(gpu_context::create_wgpu_context());
let event_loop = EventLoop::new().unwrap();
let (app_event_sender, app_event_receiver) = std::sync::mpsc::channel();
let app_event_scheduler = event_loop.create_app_event_scheduler(app_event_sender);
let _socket_handle = socket::start(app_event_scheduler.clone());
if cli.disable_ui_acceleration {
prefs.disable_ui_acceleration = true;
}
if prefs.disable_ui_acceleration {
println!("UI acceleration is disabled");
}
let acceleration = if prefs.disable_ui_acceleration { Acceleration::Disabled } else { Acceleration::Auto };
let ui_context = match ui_context.start(UiConfig { acceleration }) {
Ok(context) => context,
Err(error) => {
tracing::error!("Failed to start the UI runtime: {error}");
return ExitCode::FAILURE;
}
};
let ui = match ui_context.instance(&wgpu_context.device, &wgpu_context.queue) {
Ok(ui) => ui,
Err(error) => {
tracing::error!("Failed to start the UI: {error}");
return ExitCode::FAILURE;
}
};
tracing::info!("UI runtime started successfully");
{
let ui = ui.clone();
let scheduler = app_event_scheduler.clone();
let spawned = std::thread::Builder::new().name("ui-events".to_string()).spawn(move || {
while let Some(event) = ui.recv() {
match event {
UiEvent::Ready => scheduler.schedule(AppEvent::WebCommunicationInitialized),
UiEvent::Frame(texture) => scheduler.schedule(AppEvent::UiUpdate(texture)),
UiEvent::Cursor(cursor) => scheduler.schedule(AppEvent::CursorChange(cursor)),
UiEvent::Message(message) => match wrapper::deserialize_editor_message(&message) {
Some(message) => scheduler.schedule(AppEvent::DesktopWrapperMessage(message)),
None => tracing::error!("Failed to deserialize web message"),
},
UiEvent::Failure(error) => {
tracing::error!("UI failure: {error}");
scheduler.schedule(AppEvent::UiCrashed);
}
UiEvent::Crashed => scheduler.schedule(AppEvent::UiCrashed),
}
}
});
if let Err(error) = spawned {
tracing::error!("Failed to spawn the UI event bridge thread: {error}");
return ExitCode::FAILURE;
}
}
let app = App::new(ui.clone(), wgpu_context, app_event_receiver, app_event_scheduler, prefs, cli.files);
let exit_reason = app.run(event_loop);
// ui needs to be shutdown before restarting
ui.shutdown();
// If exiting due to a UI acceleration failure, update preferences to disable it for next launch
if matches!(exit_reason, app::ExitReason::UiAccelerationFailure) {
tracing::error!("Disabling UI acceleration");
preferences::modify(|prefs| {
prefs.disable_ui_acceleration = true;
});
}
// Explicitly drop the instance lock
drop(lock);
match exit_reason {
app::ExitReason::Restart | app::ExitReason::UiAccelerationFailure => {
tracing::info!("Restarting application");
let mut command = std::process::Command::new(std::env::current_exe().unwrap());
#[cfg(target_family = "unix")]
let _ = std::os::unix::process::CommandExt::exec(&mut command);
#[cfg(target_family = "unix")]
tracing::error!("Failed to restart application");
#[cfg(not(target_family = "unix"))]
let _ = command.spawn();
}
_ => {}
}
ExitCode::SUCCESS
}