Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
97 changes: 95 additions & 2 deletions desktop/bundle/src/mac.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ const EXEC_PATH: &str = "Contents/MacOS";
const FRAMEWORKS_PATH: &str = "Contents/Frameworks";
const RESOURCES_PATH: &str = "Contents/Resources";
const CEF_FRAMEWORK: &str = "Chromium Embedded Framework.framework";
const GRAPHITE_DOCUMENT_TYPE: &str = "art.graphite.document";
const GRAPHITE_FILE_EXTENSION: &str = "graphite";
const GRAPHITE_MIME_TYPE: &str = "application/graphite+json";

pub fn main() -> Result<(), Box<dyn Error>> {
let app_bin = build_bin("graphite-desktop-platform-mac", None)?;
Expand Down Expand Up @@ -73,7 +76,7 @@ fn create_info_plist(dir: &Path, id: &str, exec_name: &str, is_helper: bool) ->
cf_bundle_identifier: id.to_string(),
cf_bundle_display_name: exec_name.to_string(),
cf_bundle_executable: exec_name.to_string(),
cf_bundle_icon_file: ICONS_FILE_NAME.to_string(),
cf_bundle_icon_file: if is_helper { None } else { Some(ICONS_FILE_NAME.to_string()) },
cf_bundle_info_dictionary_version: "6.0".to_string(),
cf_bundle_package_type: "APPL".to_string(),
cf_bundle_signature: "????".to_string(),
Expand All @@ -85,13 +88,56 @@ fn create_info_plist(dir: &Path, id: &str, exec_name: &str, is_helper: bool) ->
ls_minimum_system_version: "11.0".to_string(),
ls_ui_element: if is_helper { Some("1".to_string()) } else { None },
ns_supports_automatic_graphics_switching: true,
cf_bundle_document_types: (!is_helper).then(document_types),
ut_exported_type_declarations: (!is_helper).then(exported_type_declarations),
};

let plist_file = dir.join("Info.plist");
plist::to_file_xml(plist_file, &info)?;
Ok(())
}

fn document_types() -> Vec<DocumentType> {
vec![
DocumentType {
cf_bundle_type_name: "Graphite Document".to_string(),
cf_bundle_type_role: "Editor".to_string(),
cf_bundle_type_extensions: Some(vec![GRAPHITE_FILE_EXTENSION.to_string()]),
cf_bundle_type_icon_file: Some(ICONS_FILE_NAME.to_string()),
ls_handler_rank: Some("Owner".to_string()),
ls_item_content_types: vec![GRAPHITE_DOCUMENT_TYPE.to_string()],
},
DocumentType {
cf_bundle_type_name: "SVG Image".to_string(),
cf_bundle_type_role: "Editor".to_string(),
cf_bundle_type_extensions: Some(vec!["svg".to_string()]),
cf_bundle_type_icon_file: None,
ls_handler_rank: Some("Alternate".to_string()),
ls_item_content_types: vec!["public.svg-image".to_string()],
},
DocumentType {
cf_bundle_type_name: "Image".to_string(),
cf_bundle_type_role: "Editor".to_string(),
cf_bundle_type_extensions: None,
cf_bundle_type_icon_file: None,
ls_handler_rank: Some("Alternate".to_string()),
ls_item_content_types: vec!["public.image".to_string()],
},
]
}

fn exported_type_declarations() -> Vec<ExportedTypeDeclaration> {
vec![ExportedTypeDeclaration {
ut_type_identifier: GRAPHITE_DOCUMENT_TYPE.to_string(),
ut_type_description: "Graphite Document".to_string(),
ut_type_conforms_to: vec!["public.json".to_string()],
ut_type_tag_specification: TypeTagSpecification {
public_filename_extension: vec![GRAPHITE_FILE_EXTENSION.to_string()],
public_mime_type: GRAPHITE_MIME_TYPE.to_string(),
},
}]
}

#[derive(serde::Serialize)]
struct InfoPlist {
#[serde(rename = "CFBundleName")]
Expand All @@ -103,7 +149,8 @@ struct InfoPlist {
#[serde(rename = "CFBundleExecutable")]
cf_bundle_executable: String,
#[serde(rename = "CFBundleIconFile")]
cf_bundle_icon_file: String,
#[serde(skip_serializing_if = "Option::is_none")]
cf_bundle_icon_file: Option<String>,
#[serde(rename = "CFBundleInfoDictionaryVersion")]
cf_bundle_info_dictionary_version: String,
#[serde(rename = "CFBundlePackageType")]
Expand All @@ -123,7 +170,53 @@ struct InfoPlist {
#[serde(rename = "LSMinimumSystemVersion")]
ls_minimum_system_version: String,
#[serde(rename = "LSUIElement")]
#[serde(skip_serializing_if = "Option::is_none")]
ls_ui_element: Option<String>,
#[serde(rename = "NSSupportsAutomaticGraphicsSwitching")]
ns_supports_automatic_graphics_switching: bool,
#[serde(rename = "CFBundleDocumentTypes")]
#[serde(skip_serializing_if = "Option::is_none")]
cf_bundle_document_types: Option<Vec<DocumentType>>,
#[serde(rename = "UTExportedTypeDeclarations")]
#[serde(skip_serializing_if = "Option::is_none")]
ut_exported_type_declarations: Option<Vec<ExportedTypeDeclaration>>,
}

#[derive(serde::Serialize)]
struct DocumentType {
#[serde(rename = "CFBundleTypeName")]
cf_bundle_type_name: String,
#[serde(rename = "CFBundleTypeRole")]
cf_bundle_type_role: String,
#[serde(rename = "CFBundleTypeExtensions")]
#[serde(skip_serializing_if = "Option::is_none")]
cf_bundle_type_extensions: Option<Vec<String>>,
#[serde(rename = "CFBundleTypeIconFile")]
#[serde(skip_serializing_if = "Option::is_none")]
cf_bundle_type_icon_file: Option<String>,
#[serde(rename = "LSHandlerRank")]
#[serde(skip_serializing_if = "Option::is_none")]
ls_handler_rank: Option<String>,
#[serde(rename = "LSItemContentTypes")]
ls_item_content_types: Vec<String>,
}

#[derive(serde::Serialize)]
struct ExportedTypeDeclaration {
#[serde(rename = "UTTypeIdentifier")]
ut_type_identifier: String,
#[serde(rename = "UTTypeDescription")]
ut_type_description: String,
#[serde(rename = "UTTypeConformsTo")]
ut_type_conforms_to: Vec<String>,
#[serde(rename = "UTTypeTagSpecification")]
ut_type_tag_specification: TypeTagSpecification,
}

#[derive(serde::Serialize)]
struct TypeTagSpecification {
#[serde(rename = "public.filename-extension")]
public_filename_extension: Vec<String>,
#[serde(rename = "public.mime-type")]
public_mime_type: String,
}
56 changes: 36 additions & 20 deletions desktop/src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use rand::Rng;
use rfd::AsyncFileDialog;
use std::fs;
use std::io::Read;
use std::path::PathBuf;
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::mpsc::{Receiver, Sender, SyncSender};
Expand All @@ -14,7 +15,6 @@ 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;
Expand Down Expand Up @@ -47,7 +47,7 @@ pub(crate) struct App {
web_communication_startup_buffer: Vec<Vec<u8>>,
#[cfg_attr(not(target_os = "macos"), expect(unused))]
preferences: Preferences,
cli: Cli,
launch_documents: Option<Vec<PathBuf>>,
startup_time: Option<Instant>,
exiting: Arc<AtomicBool>,
exit_reason: ExitReason,
Expand All @@ -58,14 +58,15 @@ impl App {
Window::init();
}

#[allow(clippy::too_many_arguments)]
pub(crate) fn new(
cef_context: Box<dyn cef::CefContext>,
cef_view_info_sender: Sender<cef::ViewInfoUpdate>,
wgpu_context: WgpuContext,
app_event_receiver: Receiver<AppEvent>,
app_event_scheduler: AppEventScheduler,
preferences: Preferences,
cli: Cli,
launch_documents: Vec<PathBuf>,
) -> Self {
let ctrlc_app_event_scheduler = app_event_scheduler.clone();
ctrlc::set_handler(move || {
Expand Down Expand Up @@ -115,7 +116,7 @@ impl App {
web_communication_initialized: false,
web_communication_startup_buffer: Vec::new(),
preferences,
cli,
launch_documents: Some(launch_documents),
startup_time: None,
exiting,
exit_reason: ExitReason::Shutdown,
Expand Down Expand Up @@ -307,22 +308,11 @@ impl App {
responses.push(message);
}
DesktopFrontendMessage::OpenLaunchDocuments => {
if self.cli.files.is_empty() {
let Some(launch_documents) = std::mem::take(&mut self.launch_documents) else {
tracing::error!("OpenLaunchDocuments should only be sent once");
return;
}
let app_event_scheduler = self.app_event_scheduler.clone();
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());
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());
}
}
});
};
self.open_files(launch_documents);
}
DesktopFrontendMessage::UpdateMenu { entries } => {
if let Some(window) = &self.window {
Expand Down Expand Up @@ -476,11 +466,37 @@ impl App {
event_loop.exit();
}
#[cfg(target_os = "macos")]
AppEvent::AddLaunchDocuments(paths) => {
if let Some(launch_documents) = &mut self.launch_documents {
launch_documents.extend(paths);
} else {
self.open_files(paths);
}
}
#[cfg(target_os = "macos")]
AppEvent::MenuEvent { id } => {
self.dispatch_desktop_wrapper_message(DesktopWrapperMessage::MenuEvent { id });
}
}
}

fn open_files(&mut self, paths: Vec<PathBuf>) {
if paths.is_empty() {
return;
}
let app_event_scheduler = self.app_event_scheduler.clone();
let _ = thread::spawn(move || {
for path in paths {
tracing::info!("Opening file: {}", 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());
}
}
});
}
}
impl ApplicationHandler for App {
fn can_create_surfaces(&mut self, event_loop: &dyn ActiveEventLoop) {
Expand Down Expand Up @@ -570,7 +586,7 @@ impl ApplicationHandler for App {
}

if !self.cef_init_successful
&& !self.cli.disable_ui_acceleration
&& !self.preferences.disable_ui_acceleration
&& self.web_communication_initialized
&& let Some(startup_time) = self.startup_time
&& startup_time.elapsed() > Duration::from_secs(3)
Expand Down
4 changes: 2 additions & 2 deletions desktop/src/cef/context/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -131,13 +131,13 @@ fn platform_settings(instance_dir: &Path) -> Settings {
{
let exe = std::env::current_exe().expect("cannot get current exe path");
let app_root = exe.parent().and_then(|p| p.parent()).expect("bad path structure").parent().expect("bad path structure");
return Settings {
Settings {
main_bundle_path: app_root.to_str().map(CefString::from).unwrap(),
multi_threaded_message_loop: 0,
external_message_pump: 1,
no_sandbox: 1, // GPU helper crashes when running with sandbox
..base
};
}
}

#[cfg(not(target_os = "macos"))]
Expand Down
2 changes: 2 additions & 0 deletions desktop/src/event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ pub(crate) enum AppEvent {
NodeGraphExecutionResult(NodeGraphExecutionResult),
Exit,
#[cfg(target_os = "macos")]
AddLaunchDocuments(Vec<std::path::PathBuf>),
#[cfg(target_os = "macos")]
MenuEvent {
id: String,
},
Expand Down
12 changes: 7 additions & 5 deletions desktop/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ pub fn start() {
// TODO: Eventually remove this cleanup code for the old "browser" CEF directory
dirs::delete_old_cef_browser_directory();

let prefs = preferences::read();
let mut prefs = preferences::read();

// Must be called before event loop initialization or native window integrations will break
App::init();
Expand All @@ -80,13 +80,15 @@ pub fn start() {

let (cef_view_info_sender, cef_view_info_receiver) = std::sync::mpsc::channel();

let disable_ui_acceleration = prefs.disable_ui_acceleration || cli.disable_ui_acceleration;
if disable_ui_acceleration {
if cli.disable_ui_acceleration {
prefs.disable_ui_acceleration = true;
}
if prefs.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.create(cef_handler, disable_ui_acceleration) {
let cef_context = match cef_context_builder.create(cef_handler, prefs.disable_ui_acceleration) {
Ok(context) => {
tracing::info!("CEF initialized successfully");
context
Expand All @@ -102,7 +104,7 @@ pub fn start() {
}
};

let app = App::new(Box::new(cef_context), cef_view_info_sender, wgpu_context, app_event_receiver, app_event_scheduler, prefs, cli);
let app = App::new(Box::new(cef_context), cef_view_info_sender, wgpu_context, app_event_receiver, app_event_scheduler, prefs, cli.files);

let exit_reason = app.run(event_loop);

Expand Down
2 changes: 1 addition & 1 deletion desktop/src/window/mac.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ impl super::NativeWindow for NativeWindowImpl {
}

fn new(_window: &dyn Window, app_event_scheduler: AppEventScheduler) -> Self {
app::setup(app_event_scheduler.clone());
let menu = menu::Menu::new(app_event_scheduler);

NativeWindowImpl { menu }
}

Expand Down
Loading
Loading