Desktop: Add simple cli interface (#3059)
* Open document from cli and add some files to .gitignore * Revert .gitignore * Revert .gitignore * Implement cli properly * Add ui-accelerated-painting option * Format code * Revert changes in .vscode folder * Don't use global variables for cli * Apply suggested changes * Make ready for merge * Improve names * remove comment --------- Co-authored-by: Timon <me@timon.zip>
This commit is contained in:
parent
e366e4d64e
commit
497758c273
|
|
@ -2252,6 +2252,7 @@ dependencies = [
|
|||
"bytemuck",
|
||||
"cef",
|
||||
"cef-dll-sys",
|
||||
"clap",
|
||||
"core-foundation",
|
||||
"derivative",
|
||||
"dirs",
|
||||
|
|
|
|||
|
|
@ -43,6 +43,7 @@ rfd = { workspace = true }
|
|||
open = { workspace = true }
|
||||
rand = { workspace = true, features = ["thread_rng"] }
|
||||
serde = { workspace = true }
|
||||
clap = { workspace = true, features = ["derive"] }
|
||||
|
||||
# Hardware acceleration dependencies
|
||||
ash = { version = "0.38", optional = true }
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
use rfd::AsyncFileDialog;
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
use std::sync::mpsc::Receiver;
|
||||
use std::sync::mpsc::Sender;
|
||||
|
|
@ -39,6 +41,7 @@ pub(crate) struct App {
|
|||
web_communication_initialized: bool,
|
||||
web_communication_startup_buffer: Vec<Vec<u8>>,
|
||||
persistent_data: PersistentData,
|
||||
launch_documents: Vec<PathBuf>,
|
||||
}
|
||||
|
||||
impl App {
|
||||
|
|
@ -48,6 +51,7 @@ impl App {
|
|||
wgpu_context: WgpuContext,
|
||||
app_event_receiver: Receiver<AppEvent>,
|
||||
app_event_scheduler: AppEventScheduler,
|
||||
launch_documents: Vec<PathBuf>,
|
||||
) -> Self {
|
||||
let rendering_app_event_scheduler = app_event_scheduler.clone();
|
||||
let (start_render_sender, start_render_receiver) = std::sync::mpsc::sync_channel(1);
|
||||
|
|
@ -79,6 +83,7 @@ impl App {
|
|||
web_communication_startup_buffer: Vec::new(),
|
||||
persistent_data,
|
||||
native_window: Default::default(),
|
||||
launch_documents,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -102,7 +107,7 @@ impl App {
|
|||
let show_dialog = async move { dialog.pick_file().await.map(|f| f.path().to_path_buf()) };
|
||||
|
||||
if let Some(path) = futures::executor::block_on(show_dialog)
|
||||
&& let Ok(content) = std::fs::read(&path)
|
||||
&& let Ok(content) = fs::read(&path)
|
||||
{
|
||||
let message = DesktopWrapperMessage::OpenFileDialogResult { path, content, context };
|
||||
app_event_scheduler.schedule(AppEvent::DesktopWrapperMessage(message));
|
||||
|
|
@ -135,7 +140,7 @@ impl App {
|
|||
});
|
||||
}
|
||||
DesktopFrontendMessage::WriteFile { path, content } => {
|
||||
if let Err(e) = std::fs::write(&path, content) {
|
||||
if let Err(e) = fs::write(&path, content) {
|
||||
tracing::error!("Failed to write file {}: {}", path.display(), e);
|
||||
}
|
||||
}
|
||||
|
|
@ -197,6 +202,14 @@ impl App {
|
|||
DesktopFrontendMessage::PersistenceUpdateDocumentsList { ids } => {
|
||||
self.persistent_data.set_document_order(ids);
|
||||
}
|
||||
DesktopFrontendMessage::PersistenceWritePreferences { preferences } => {
|
||||
self.persistent_data.write_preferences(preferences);
|
||||
}
|
||||
DesktopFrontendMessage::PersistenceLoadPreferences => {
|
||||
let preferences = self.persistent_data.load_preferences();
|
||||
let message = DesktopWrapperMessage::LoadPreferences { preferences };
|
||||
responses.push(message);
|
||||
}
|
||||
DesktopFrontendMessage::PersistenceLoadCurrentDocument => {
|
||||
if let Some((id, document)) = self.persistent_data.current_document() {
|
||||
let message = DesktopWrapperMessage::LoadDocument {
|
||||
|
|
@ -232,13 +245,23 @@ impl App {
|
|||
responses.push(message);
|
||||
}
|
||||
}
|
||||
DesktopFrontendMessage::PersistenceWritePreferences { preferences } => {
|
||||
self.persistent_data.write_preferences(preferences);
|
||||
}
|
||||
DesktopFrontendMessage::PersistenceLoadPreferences => {
|
||||
let preferences = self.persistent_data.load_preferences();
|
||||
let message = DesktopWrapperMessage::LoadPreferences { preferences };
|
||||
responses.push(message);
|
||||
DesktopFrontendMessage::OpenLaunchDocuments => {
|
||||
if self.launch_documents.is_empty() {
|
||||
return;
|
||||
}
|
||||
let app_event_scheduler = self.app_event_scheduler.clone();
|
||||
let launch_documents = std::mem::take(&mut self.launch_documents);
|
||||
let _ = thread::spawn(move || {
|
||||
for path in launch_documents {
|
||||
tracing::info!("Opening file from command line: {}", path.display());
|
||||
if let Ok(content) = fs::read(&path) {
|
||||
let message = DesktopWrapperMessage::OpenFile { path, content };
|
||||
app_event_scheduler.schedule(AppEvent::DesktopWrapperMessage(message));
|
||||
} else {
|
||||
tracing::error!("Failed to read file: {}", path.display());
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -389,7 +412,7 @@ impl ApplicationHandler for App {
|
|||
}
|
||||
WindowEvent::DragDropped { paths, .. } => {
|
||||
for path in paths {
|
||||
match std::fs::read(&path) {
|
||||
match fs::read(&path) {
|
||||
Ok(content) => {
|
||||
let message = DesktopWrapperMessage::OpenFile { path, content };
|
||||
self.app_event_scheduler.schedule(AppEvent::DesktopWrapperMessage(message));
|
||||
|
|
|
|||
|
|
@ -63,7 +63,7 @@ impl<H: CefEventHandler> CefContextBuilder<H> {
|
|||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
pub(crate) fn initialize(self, event_handler: H) -> Result<impl CefContext, InitError> {
|
||||
pub(crate) fn initialize(self, event_handler: H, disable_gpu_acceleration: bool) -> Result<impl CefContext, InitError> {
|
||||
let instance_dir = create_instance_dir();
|
||||
|
||||
let settings = Settings {
|
||||
|
|
@ -77,11 +77,11 @@ impl<H: CefEventHandler> CefContextBuilder<H> {
|
|||
|
||||
self.initialize_inner(&event_handler, settings)?;
|
||||
|
||||
create_browser(event_handler, instance_dir)
|
||||
create_browser(event_handler, instance_dir, disable_gpu_acceleration)
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
pub(crate) fn initialize(self, event_handler: H) -> Result<impl CefContext, InitError> {
|
||||
pub(crate) fn initialize(self, event_handler: H, disable_gpu_acceleration: bool) -> Result<impl CefContext, InitError> {
|
||||
let instance_dir = create_instance_dir();
|
||||
|
||||
let settings = Settings {
|
||||
|
|
@ -94,7 +94,7 @@ impl<H: CefEventHandler> CefContextBuilder<H> {
|
|||
|
||||
self.initialize_inner(&event_handler, settings)?;
|
||||
|
||||
super::multithreaded::run_on_ui_thread(move || match create_browser(event_handler, instance_dir) {
|
||||
super::multithreaded::run_on_ui_thread(move || match create_browser(event_handler, instance_dir, disable_gpu_acceleration) {
|
||||
Ok(context) => {
|
||||
super::multithreaded::CONTEXT.with(|b| {
|
||||
*b.borrow_mut() = Some(context);
|
||||
|
|
@ -125,14 +125,21 @@ impl<H: CefEventHandler> CefContextBuilder<H> {
|
|||
}
|
||||
}
|
||||
|
||||
fn create_browser<H: CefEventHandler>(event_handler: H, instance_dir: PathBuf) -> Result<SingleThreadedCefContext, InitError> {
|
||||
fn create_browser<H: CefEventHandler>(event_handler: H, instance_dir: PathBuf, disable_gpu_acceleration: bool) -> Result<SingleThreadedCefContext, InitError> {
|
||||
let render_handler = RenderHandler::new(RenderHandlerImpl::new(event_handler.clone()));
|
||||
let mut client = Client::new(BrowserProcessClientImpl::new(render_handler, event_handler.clone()));
|
||||
|
||||
#[cfg(feature = "accelerated_paint")]
|
||||
let use_accelerated_paint = if disable_gpu_acceleration {
|
||||
false
|
||||
} else {
|
||||
crate::cef::platform::should_enable_hardware_acceleration()
|
||||
};
|
||||
|
||||
let window_info = WindowInfo {
|
||||
windowless_rendering_enabled: 1,
|
||||
#[cfg(feature = "accelerated_paint")]
|
||||
shared_texture_enabled: if crate::cef::platform::should_enable_hardware_acceleration() { 1 } else { 0 },
|
||||
shared_texture_enabled: use_accelerated_paint as i32,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,9 @@
|
|||
#[derive(clap::Parser)]
|
||||
#[clap(name = "graphite-editor", version)]
|
||||
pub struct Cli {
|
||||
#[arg(help = "Files to open on startup")]
|
||||
pub files: Vec<std::path::PathBuf>,
|
||||
|
||||
#[arg(long, action = clap::ArgAction::SetTrue, help = "Disable hardware accelerated UI rendering")]
|
||||
pub disable_ui_acceleration: bool,
|
||||
}
|
||||
|
|
@ -1,3 +1,4 @@
|
|||
use clap::Parser;
|
||||
use std::process::exit;
|
||||
use tracing_subscriber::EnvFilter;
|
||||
use winit::event_loop::EventLoop;
|
||||
|
|
@ -6,6 +7,7 @@ pub(crate) mod consts;
|
|||
|
||||
mod app;
|
||||
mod cef;
|
||||
mod cli;
|
||||
mod dirs;
|
||||
mod event;
|
||||
mod native_window;
|
||||
|
|
@ -16,6 +18,7 @@ mod gpu_context;
|
|||
|
||||
use app::App;
|
||||
use cef::CefHandler;
|
||||
use cli::Cli;
|
||||
use event::CreateAppEventSchedulerEventLoopExt;
|
||||
|
||||
fn main() {
|
||||
|
|
@ -31,6 +34,8 @@ fn main() {
|
|||
return;
|
||||
}
|
||||
|
||||
let cli = Cli::parse();
|
||||
|
||||
let wgpu_context = futures::executor::block_on(gpu_context::create_wgpu_context());
|
||||
|
||||
let event_loop = EventLoop::new().unwrap();
|
||||
|
|
@ -40,7 +45,7 @@ fn main() {
|
|||
let (window_size_sender, window_size_receiver) = std::sync::mpsc::channel();
|
||||
|
||||
let cef_handler = cef::CefHandler::new(wgpu_context.clone(), app_event_scheduler.clone(), window_size_receiver);
|
||||
let cef_context = match cef_context_builder.initialize(cef_handler) {
|
||||
let cef_context = match cef_context_builder.initialize(cef_handler, cli.disable_ui_acceleration) {
|
||||
Ok(c) => {
|
||||
tracing::info!("CEF initialized successfully");
|
||||
c
|
||||
|
|
@ -63,7 +68,7 @@ fn main() {
|
|||
}
|
||||
};
|
||||
|
||||
let mut app = App::new(Box::new(cef_context), window_size_sender, wgpu_context, app_event_receiver, app_event_scheduler);
|
||||
let mut app = App::new(Box::new(cef_context), window_size_sender, wgpu_context, app_event_receiver, app_event_scheduler, cli.files);
|
||||
|
||||
event_loop.run_app(&mut app).unwrap();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -110,6 +110,9 @@ pub(super) fn intercept_frontend_message(dispatcher: &mut DesktopWrapperMessageD
|
|||
FrontendMessage::TriggerLoadRestAutoSaveDocuments => {
|
||||
dispatcher.respond(DesktopFrontendMessage::PersistenceLoadRemainingDocuments);
|
||||
}
|
||||
FrontendMessage::TriggerOpenLaunchDocuments => {
|
||||
dispatcher.respond(DesktopFrontendMessage::OpenLaunchDocuments);
|
||||
}
|
||||
FrontendMessage::TriggerSavePreferences { preferences } => {
|
||||
dispatcher.respond(DesktopFrontendMessage::PersistenceWritePreferences { preferences });
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ pub use graphite_editor::messages::prelude::PreferencesMessageHandler as Prefere
|
|||
|
||||
pub enum DesktopFrontendMessage {
|
||||
ToWeb(Vec<FrontendMessage>),
|
||||
OpenLaunchDocuments,
|
||||
OpenFileDialog {
|
||||
title: String,
|
||||
filters: Vec<FileFilter>,
|
||||
|
|
|
|||
|
|
@ -102,6 +102,7 @@ pub enum FrontendMessage {
|
|||
},
|
||||
TriggerLoadFirstAutoSaveDocument,
|
||||
TriggerLoadRestAutoSaveDocuments,
|
||||
TriggerOpenLaunchDocuments,
|
||||
TriggerLoadPreferences,
|
||||
TriggerOpenDocument,
|
||||
TriggerPaste,
|
||||
|
|
|
|||
|
|
@ -153,6 +153,8 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
|
|||
|
||||
// Tell frontend to finish loading persistent documents
|
||||
responses.add(FrontendMessage::TriggerLoadRestAutoSaveDocuments);
|
||||
|
||||
responses.add(FrontendMessage::TriggerOpenLaunchDocuments);
|
||||
}
|
||||
PortfolioMessage::DocumentPassMessage { document_id, message } => {
|
||||
if let Some(document) = self.documents.get_mut(&document_id) {
|
||||
|
|
|
|||
Loading…
Reference in New Issue