mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-10-09 13:18:13 +08:00
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:
Generated
+1
@@ -2252,6 +2252,7 @@ dependencies = [
|
|||||||
"bytemuck",
|
"bytemuck",
|
||||||
"cef",
|
"cef",
|
||||||
"cef-dll-sys",
|
"cef-dll-sys",
|
||||||
|
"clap",
|
||||||
"core-foundation",
|
"core-foundation",
|
||||||
"derivative",
|
"derivative",
|
||||||
"dirs",
|
"dirs",
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ rfd = { workspace = true }
|
|||||||
open = { workspace = true }
|
open = { workspace = true }
|
||||||
rand = { workspace = true, features = ["thread_rng"] }
|
rand = { workspace = true, features = ["thread_rng"] }
|
||||||
serde = { workspace = true }
|
serde = { workspace = true }
|
||||||
|
clap = { workspace = true, features = ["derive"] }
|
||||||
|
|
||||||
# Hardware acceleration dependencies
|
# Hardware acceleration dependencies
|
||||||
ash = { version = "0.38", optional = true }
|
ash = { version = "0.38", optional = true }
|
||||||
|
|||||||
+33
-10
@@ -1,4 +1,6 @@
|
|||||||
use rfd::AsyncFileDialog;
|
use rfd::AsyncFileDialog;
|
||||||
|
use std::fs;
|
||||||
|
use std::path::PathBuf;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::sync::mpsc::Receiver;
|
use std::sync::mpsc::Receiver;
|
||||||
use std::sync::mpsc::Sender;
|
use std::sync::mpsc::Sender;
|
||||||
@@ -39,6 +41,7 @@ pub(crate) struct App {
|
|||||||
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>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl App {
|
impl App {
|
||||||
@@ -48,6 +51,7 @@ 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>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let rendering_app_event_scheduler = app_event_scheduler.clone();
|
let rendering_app_event_scheduler = app_event_scheduler.clone();
|
||||||
let (start_render_sender, start_render_receiver) = std::sync::mpsc::sync_channel(1);
|
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(),
|
web_communication_startup_buffer: Vec::new(),
|
||||||
persistent_data,
|
persistent_data,
|
||||||
native_window: Default::default(),
|
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()) };
|
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)
|
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 };
|
let message = DesktopWrapperMessage::OpenFileDialogResult { path, content, context };
|
||||||
app_event_scheduler.schedule(AppEvent::DesktopWrapperMessage(message));
|
app_event_scheduler.schedule(AppEvent::DesktopWrapperMessage(message));
|
||||||
@@ -135,7 +140,7 @@ impl App {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
DesktopFrontendMessage::WriteFile { path, content } => {
|
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);
|
tracing::error!("Failed to write file {}: {}", path.display(), e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -197,6 +202,14 @@ impl App {
|
|||||||
DesktopFrontendMessage::PersistenceUpdateDocumentsList { ids } => {
|
DesktopFrontendMessage::PersistenceUpdateDocumentsList { ids } => {
|
||||||
self.persistent_data.set_document_order(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 => {
|
DesktopFrontendMessage::PersistenceLoadCurrentDocument => {
|
||||||
if let Some((id, document)) = self.persistent_data.current_document() {
|
if let Some((id, document)) = self.persistent_data.current_document() {
|
||||||
let message = DesktopWrapperMessage::LoadDocument {
|
let message = DesktopWrapperMessage::LoadDocument {
|
||||||
@@ -232,13 +245,23 @@ impl App {
|
|||||||
responses.push(message);
|
responses.push(message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
DesktopFrontendMessage::PersistenceWritePreferences { preferences } => {
|
DesktopFrontendMessage::OpenLaunchDocuments => {
|
||||||
self.persistent_data.write_preferences(preferences);
|
if self.launch_documents.is_empty() {
|
||||||
}
|
return;
|
||||||
DesktopFrontendMessage::PersistenceLoadPreferences => {
|
}
|
||||||
let preferences = self.persistent_data.load_preferences();
|
let app_event_scheduler = self.app_event_scheduler.clone();
|
||||||
let message = DesktopWrapperMessage::LoadPreferences { preferences };
|
let launch_documents = std::mem::take(&mut self.launch_documents);
|
||||||
responses.push(message);
|
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, .. } => {
|
WindowEvent::DragDropped { paths, .. } => {
|
||||||
for path in paths {
|
for path in paths {
|
||||||
match std::fs::read(&path) {
|
match fs::read(&path) {
|
||||||
Ok(content) => {
|
Ok(content) => {
|
||||||
let message = DesktopWrapperMessage::OpenFile { path, content };
|
let message = DesktopWrapperMessage::OpenFile { path, content };
|
||||||
self.app_event_scheduler.schedule(AppEvent::DesktopWrapperMessage(message));
|
self.app_event_scheduler.schedule(AppEvent::DesktopWrapperMessage(message));
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ impl<H: CefEventHandler> CefContextBuilder<H> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
#[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 instance_dir = create_instance_dir();
|
||||||
|
|
||||||
let settings = Settings {
|
let settings = Settings {
|
||||||
@@ -77,11 +77,11 @@ impl<H: CefEventHandler> CefContextBuilder<H> {
|
|||||||
|
|
||||||
self.initialize_inner(&event_handler, settings)?;
|
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"))]
|
#[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 instance_dir = create_instance_dir();
|
||||||
|
|
||||||
let settings = Settings {
|
let settings = Settings {
|
||||||
@@ -94,7 +94,7 @@ impl<H: CefEventHandler> CefContextBuilder<H> {
|
|||||||
|
|
||||||
self.initialize_inner(&event_handler, settings)?;
|
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) => {
|
Ok(context) => {
|
||||||
super::multithreaded::CONTEXT.with(|b| {
|
super::multithreaded::CONTEXT.with(|b| {
|
||||||
*b.borrow_mut() = Some(context);
|
*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 render_handler = RenderHandler::new(RenderHandlerImpl::new(event_handler.clone()));
|
||||||
let mut client = Client::new(BrowserProcessClientImpl::new(render_handler, 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 {
|
let window_info = WindowInfo {
|
||||||
windowless_rendering_enabled: 1,
|
windowless_rendering_enabled: 1,
|
||||||
#[cfg(feature = "accelerated_paint")]
|
#[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()
|
..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,
|
||||||
|
}
|
||||||
+7
-2
@@ -1,3 +1,4 @@
|
|||||||
|
use clap::Parser;
|
||||||
use std::process::exit;
|
use std::process::exit;
|
||||||
use tracing_subscriber::EnvFilter;
|
use tracing_subscriber::EnvFilter;
|
||||||
use winit::event_loop::EventLoop;
|
use winit::event_loop::EventLoop;
|
||||||
@@ -6,6 +7,7 @@ pub(crate) mod consts;
|
|||||||
|
|
||||||
mod app;
|
mod app;
|
||||||
mod cef;
|
mod cef;
|
||||||
|
mod cli;
|
||||||
mod dirs;
|
mod dirs;
|
||||||
mod event;
|
mod event;
|
||||||
mod native_window;
|
mod native_window;
|
||||||
@@ -16,6 +18,7 @@ mod gpu_context;
|
|||||||
|
|
||||||
use app::App;
|
use app::App;
|
||||||
use cef::CefHandler;
|
use cef::CefHandler;
|
||||||
|
use cli::Cli;
|
||||||
use event::CreateAppEventSchedulerEventLoopExt;
|
use event::CreateAppEventSchedulerEventLoopExt;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
@@ -31,6 +34,8 @@ fn main() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let cli = Cli::parse();
|
||||||
|
|
||||||
let wgpu_context = futures::executor::block_on(gpu_context::create_wgpu_context());
|
let wgpu_context = futures::executor::block_on(gpu_context::create_wgpu_context());
|
||||||
|
|
||||||
let event_loop = EventLoop::new().unwrap();
|
let event_loop = EventLoop::new().unwrap();
|
||||||
@@ -40,7 +45,7 @@ fn main() {
|
|||||||
let (window_size_sender, window_size_receiver) = std::sync::mpsc::channel();
|
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_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) => {
|
Ok(c) => {
|
||||||
tracing::info!("CEF initialized successfully");
|
tracing::info!("CEF initialized successfully");
|
||||||
c
|
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();
|
event_loop.run_app(&mut app).unwrap();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -110,6 +110,9 @@ pub(super) fn intercept_frontend_message(dispatcher: &mut DesktopWrapperMessageD
|
|||||||
FrontendMessage::TriggerLoadRestAutoSaveDocuments => {
|
FrontendMessage::TriggerLoadRestAutoSaveDocuments => {
|
||||||
dispatcher.respond(DesktopFrontendMessage::PersistenceLoadRemainingDocuments);
|
dispatcher.respond(DesktopFrontendMessage::PersistenceLoadRemainingDocuments);
|
||||||
}
|
}
|
||||||
|
FrontendMessage::TriggerOpenLaunchDocuments => {
|
||||||
|
dispatcher.respond(DesktopFrontendMessage::OpenLaunchDocuments);
|
||||||
|
}
|
||||||
FrontendMessage::TriggerSavePreferences { preferences } => {
|
FrontendMessage::TriggerSavePreferences { preferences } => {
|
||||||
dispatcher.respond(DesktopFrontendMessage::PersistenceWritePreferences { preferences });
|
dispatcher.respond(DesktopFrontendMessage::PersistenceWritePreferences { preferences });
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ pub use graphite_editor::messages::prelude::PreferencesMessageHandler as Prefere
|
|||||||
|
|
||||||
pub enum DesktopFrontendMessage {
|
pub enum DesktopFrontendMessage {
|
||||||
ToWeb(Vec<FrontendMessage>),
|
ToWeb(Vec<FrontendMessage>),
|
||||||
|
OpenLaunchDocuments,
|
||||||
OpenFileDialog {
|
OpenFileDialog {
|
||||||
title: String,
|
title: String,
|
||||||
filters: Vec<FileFilter>,
|
filters: Vec<FileFilter>,
|
||||||
|
|||||||
@@ -102,6 +102,7 @@ pub enum FrontendMessage {
|
|||||||
},
|
},
|
||||||
TriggerLoadFirstAutoSaveDocument,
|
TriggerLoadFirstAutoSaveDocument,
|
||||||
TriggerLoadRestAutoSaveDocuments,
|
TriggerLoadRestAutoSaveDocuments,
|
||||||
|
TriggerOpenLaunchDocuments,
|
||||||
TriggerLoadPreferences,
|
TriggerLoadPreferences,
|
||||||
TriggerOpenDocument,
|
TriggerOpenDocument,
|
||||||
TriggerPaste,
|
TriggerPaste,
|
||||||
|
|||||||
@@ -153,6 +153,8 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
|
|||||||
|
|
||||||
// Tell frontend to finish loading persistent documents
|
// Tell frontend to finish loading persistent documents
|
||||||
responses.add(FrontendMessage::TriggerLoadRestAutoSaveDocuments);
|
responses.add(FrontendMessage::TriggerLoadRestAutoSaveDocuments);
|
||||||
|
|
||||||
|
responses.add(FrontendMessage::TriggerOpenLaunchDocuments);
|
||||||
}
|
}
|
||||||
PortfolioMessage::DocumentPassMessage { document_id, message } => {
|
PortfolioMessage::DocumentPassMessage { document_id, message } => {
|
||||||
if let Some(document) = self.documents.get_mut(&document_id) {
|
if let Some(document) = self.documents.get_mut(&document_id) {
|
||||||
|
|||||||
Reference in New Issue
Block a user