From 1d4d1026d4604eca4a5e4a1c4dadf48ab012dd10 Mon Sep 17 00:00:00 2001 From: Timon Date: Thu, 28 Aug 2025 17:18:18 +0000 Subject: [PATCH] Desktop: Make embedded resources optional (#3094) * Make embedding resources optional * Move remaining cef rc to internal module * Move embedded resources to separate crate * Review fixup * Fix * Fix read * Add read error --- Cargo.lock | 9 +- Cargo.toml | 1 + desktop/Cargo.toml | 8 +- desktop/embedded-resources/Cargo.toml | 15 ++ desktop/embedded-resources/build.rs | 17 ++ desktop/embedded-resources/src/lib.rs | 10 + desktop/src/cef.rs | 108 +++++++-- desktop/src/cef/consts.rs | 4 +- desktop/src/cef/context/builder.rs | 29 ++- desktop/src/cef/internal.rs | 4 + .../src/cef/internal/browser_process_app.rs | 8 +- .../cef/internal/browser_process_handler.rs | 10 +- desktop/src/cef/internal/render_handler.rs | 2 +- .../src/cef/internal/render_process_app.rs | 17 +- .../cef/internal/render_process_v8_handler.rs | 3 - desktop/src/cef/internal/resource_handler.rs | 108 +++++++++ .../cef/internal/scheme_handler_factory.rs | 86 +++++++ desktop/src/cef/scheme_handler.rs | 223 ------------------ desktop/src/main.rs | 3 +- 19 files changed, 389 insertions(+), 276 deletions(-) create mode 100644 desktop/embedded-resources/Cargo.toml create mode 100644 desktop/embedded-resources/build.rs create mode 100644 desktop/embedded-resources/src/lib.rs create mode 100644 desktop/src/cef/internal/resource_handler.rs create mode 100644 desktop/src/cef/internal/scheme_handler_factory.rs delete mode 100644 desktop/src/cef/scheme_handler.rs diff --git a/Cargo.lock b/Cargo.lock index f23a90db..ea564896 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2087,8 +2087,8 @@ dependencies = [ "dirs", "futures", "glam", + "graphite-desktop-embedded-resources", "graphite-desktop-wrapper", - "include_dir", "libc", "objc2-io-surface", "objc2-metal 0.3.1", @@ -2104,6 +2104,13 @@ dependencies = [ "winit", ] +[[package]] +name = "graphite-desktop-embedded-resources" +version = "0.1.0" +dependencies = [ + "include_dir", +] + [[package]] name = "graphite-desktop-wrapper" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index 5a19b890..16fb0b78 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,6 +3,7 @@ members = [ "editor", "desktop", "desktop/wrapper", + "desktop/embedded-resources", "proc-macros", "frontend/wasm", "node-graph/gapplication-io", diff --git a/desktop/Cargo.toml b/desktop/Cargo.toml index 1ddf5262..fe97a9d1 100644 --- a/desktop/Cargo.toml +++ b/desktop/Cargo.toml @@ -9,7 +9,9 @@ edition = "2024" rust-version = "1.87" [features] -default = ["gpu", "accelerated_paint"] +default = ["recommended", "embedded_resources"] +recommended = ["gpu", "accelerated_paint"] +embedded_resources = ["dep:graphite-desktop-embedded-resources"] gpu = ["graphite-desktop-wrapper/gpu"] # Hardware acceleration features @@ -19,15 +21,15 @@ accelerated_paint_d3d11 = ["windows", "ash"] accelerated_paint_iosurface = ["objc2-io-surface", "objc2-metal", "core-foundation"] [dependencies] -# # Local dependencies +# Local dependencies graphite-desktop-wrapper = { path = "wrapper" } +graphite-desktop-embedded-resources = { path = "embedded-resources", optional = true } wgpu = { workspace = true } winit = { workspace = true, features = ["serde"] } thiserror = { workspace = true } futures = { workspace = true } cef = { workspace = true } -include_dir = { workspace = true } tracing-subscriber = { workspace = true } tracing = { workspace = true } dirs = { workspace = true } diff --git a/desktop/embedded-resources/Cargo.toml b/desktop/embedded-resources/Cargo.toml new file mode 100644 index 00000000..9b061b9c --- /dev/null +++ b/desktop/embedded-resources/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "graphite-desktop-embedded-resources" +version = "0.1.0" +description = "Graphite Desktop Embedded Resources" +authors = ["Graphite Authors "] +license = "Apache-2.0" +repository = "" +edition = "2024" +rust-version = "1.87" + +[dependencies] +include_dir = { workspace = true } + +[lints.rust] +unexpected_cfgs = { level = "warn", check-cfg = ['cfg(embedded_resources)'] } diff --git a/desktop/embedded-resources/build.rs b/desktop/embedded-resources/build.rs new file mode 100644 index 00000000..899780fc --- /dev/null +++ b/desktop/embedded-resources/build.rs @@ -0,0 +1,17 @@ +const RESOURCES: &str = "../../frontend/dist"; + +// Check if the directory `RESOURCES` exists and sets the embedded_resources cfg accordingly +// Absolute path of `RESOURCES` available via the `EMBEDDED_RESOURCES` environment variable +fn main() { + let crate_dir = std::path::PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap()); + + println!("cargo:rerun-if-changed={RESOURCES}"); + if let Ok(resources) = crate_dir.join(RESOURCES).canonicalize() + && resources.exists() + { + println!("cargo:rustc-cfg=embedded_resources"); + println!("cargo:rustc-env=EMBEDDED_RESOURCES={}", resources.to_string_lossy()); + } else { + println!("cargo:warning=Resource directory does not exist. Resources will not be embedded. Did you forget to build the frontend?"); + } +} diff --git a/desktop/embedded-resources/src/lib.rs b/desktop/embedded-resources/src/lib.rs new file mode 100644 index 00000000..fb007d81 --- /dev/null +++ b/desktop/embedded-resources/src/lib.rs @@ -0,0 +1,10 @@ +//! This crate provides `EMBEDDED_RESOURCES` that can be included in the desktop application binary. +//! It is intended to be used by the `embedded_resources` feature of the `graphite-desktop` crate. +//! The build script checks if the specified resources directory exists and sets the `embedded_resources` cfg flag accordingly. +//! If the resources directory does not exist, resources will not be embedded and a warning will be reported during compilation. + +#[cfg(embedded_resources)] +pub static EMBEDDED_RESOURCES: Option = Some(include_dir::include_dir!("$EMBEDDED_RESOURCES")); + +#[cfg(not(embedded_resources))] +pub static EMBEDDED_RESOURCES: Option = None; diff --git a/desktop/src/cef.rs b/desktop/src/cef.rs index 11c83e06..cbe60c21 100644 --- a/desktop/src/cef.rs +++ b/desktop/src/cef.rs @@ -16,6 +16,9 @@ use crate::CustomEvent; use crate::render::FrameBufferRef; use graphite_desktop_wrapper::{WgpuContext, deserialize_editor_message}; +use std::fs::File; +use std::io::{Cursor, Read}; +use std::path::PathBuf; use std::sync::mpsc::Receiver; use std::sync::{Arc, Mutex}; use std::time::Instant; @@ -27,7 +30,6 @@ mod input; mod internal; mod ipc; mod platform; -mod scheme_handler; mod utility; #[cfg(feature = "accelerated_paint")] @@ -38,11 +40,12 @@ use texture_import::SharedTextureHandle; pub(crate) use context::{CefContext, CefContextBuilder, InitError}; use winit::event_loop::EventLoopProxy; -pub(crate) trait CefEventHandler: Clone { +pub(crate) trait CefEventHandler: Clone + Send + Sync + 'static { fn window_size(&self) -> WindowSize; fn draw<'a>(&self, frame_buffer: FrameBufferRef<'a>); #[cfg(feature = "accelerated_paint")] fn draw_gpu(&self, shared_texture: SharedTextureHandle); + fn load_resource(&self, path: PathBuf) -> Option; /// Scheudule the main event loop to run the cef event loop after the timeout /// [`_cef_browser_process_handler_t::on_schedule_message_pump_work`] for more documentation. fn schedule_cef_message_loop_work(&self, scheduled_time: Instant); @@ -62,12 +65,34 @@ impl WindowSize { } } +#[derive(Clone)] +pub(crate) struct Resource { + pub(crate) reader: ResourceReader, + pub(crate) mimetype: Option, +} + +#[expect(dead_code)] +#[derive(Clone)] +pub(crate) enum ResourceReader { + Embedded(Cursor<&'static [u8]>), + File(Arc), +} +impl Read for ResourceReader { + fn read(&mut self, buf: &mut [u8]) -> std::io::Result { + match self { + ResourceReader::Embedded(cursor) => cursor.read(buf), + ResourceReader::File(file) => file.as_ref().read(buf), + } + } +} + #[derive(Clone)] pub(crate) struct CefHandler { window_size_receiver: Arc>, event_loop_proxy: EventLoopProxy, wgpu_context: WgpuContext, } + struct WindowSizeReceiver { receiver: Receiver, window_size: WindowSize, @@ -142,6 +167,73 @@ impl CefEventHandler for CefHandler { let _ = self.event_loop_proxy.send_event(CustomEvent::UiUpdate(texture)); } + #[cfg(feature = "accelerated_paint")] + fn draw_gpu(&self, shared_texture: SharedTextureHandle) { + match shared_texture.import_texture(&self.wgpu_context.device) { + Ok(texture) => { + let _ = self.event_loop_proxy.send_event(CustomEvent::UiUpdate(texture)); + } + Err(e) => { + tracing::error!("Failed to import shared texture: {}", e); + } + } + } + + fn load_resource(&self, path: PathBuf) -> Option { + let path = if path.as_os_str().is_empty() { PathBuf::from("index.html") } else { path }; + + let mimetype = match path.extension().and_then(|s| s.to_str()).unwrap_or("") { + "html" => Some("text/html".to_string()), + "css" => Some("text/css".to_string()), + "txt" => Some("text/plain".to_string()), + "wasm" => Some("application/wasm".to_string()), + "js" => Some("application/javascript".to_string()), + "png" => Some("image/png".to_string()), + "jpg" | "jpeg" => Some("image/jpeg".to_string()), + "svg" => Some("image/svg+xml".to_string()), + "xml" => Some("application/xml".to_string()), + "json" => Some("application/json".to_string()), + "ico" => Some("image/x-icon".to_string()), + "woff" => Some("font/woff".to_string()), + "woff2" => Some("font/woff2".to_string()), + "ttf" => Some("font/ttf".to_string()), + "otf" => Some("font/otf".to_string()), + "webmanifest" => Some("application/manifest+json".to_string()), + "graphite" => Some("application/graphite+json".to_string()), + _ => None, + }; + + #[cfg(feature = "embedded_resources")] + { + if let Some(resources) = &graphite_desktop_embedded_resources::EMBEDDED_RESOURCES + && let Some(file) = resources.get_file(&path) + { + return Some(Resource { + reader: ResourceReader::Embedded(Cursor::new(file.contents())), + mimetype, + }); + } + } + + #[cfg(not(feature = "embedded_resources"))] + { + use std::path::Path; + let asset_path_env = std::env::var("GRAPHITE_RESOURCES").ok()?; + let asset_path = Path::new(&asset_path_env); + let file_path = asset_path.join(path.strip_prefix("/").unwrap_or(&path)); + if file_path.exists() && file_path.is_file() { + if let Ok(file) = std::fs::File::open(file_path) { + return Some(Resource { + reader: ResourceReader::File(file.into()), + mimetype, + }); + } + } + } + + None + } + fn schedule_cef_message_loop_work(&self, scheduled_time: std::time::Instant) { let _ = self.event_loop_proxy.send_event(CustomEvent::ScheduleBrowserWork(scheduled_time)); } @@ -157,16 +249,4 @@ impl CefEventHandler for CefHandler { }; let _ = self.event_loop_proxy.send_event(CustomEvent::DesktopWrapperMessage(desktop_wrapper_message)); } - - #[cfg(feature = "accelerated_paint")] - fn draw_gpu(&self, shared_texture: SharedTextureHandle) { - match shared_texture.import_texture(&self.wgpu_context.device) { - Ok(texture) => { - let _ = self.event_loop_proxy.send_event(CustomEvent::UiUpdate(texture)); - } - Err(e) => { - tracing::error!("Failed to import shared texture: {}", e); - } - } - } } diff --git a/desktop/src/cef/consts.rs b/desktop/src/cef/consts.rs index a0031c88..99b269c7 100644 --- a/desktop/src/cef/consts.rs +++ b/desktop/src/cef/consts.rs @@ -1,2 +1,2 @@ -pub(crate) const GRAPHITE_SCHEME: &str = "graphite-static"; -pub(crate) const FRONTEND_DOMAIN: &str = "frontend"; +pub(crate) const RESOURCE_SCHEME: &str = "resources"; +pub(crate) const RESOURCE_DOMAIN: &str = "resources"; diff --git a/desktop/src/cef/context/builder.rs b/desktop/src/cef/context/builder.rs index c1a92b92..c771c4ae 100644 --- a/desktop/src/cef/context/builder.rs +++ b/desktop/src/cef/context/builder.rs @@ -6,20 +6,21 @@ use cef::{ use super::CefContext; use super::singlethreaded::SingleThreadedCefContext; -use crate::cef::CefHandler; -use crate::cef::consts::{FRONTEND_DOMAIN, GRAPHITE_SCHEME}; +use crate::cef::CefEventHandler; +use crate::cef::consts::{RESOURCE_DOMAIN, RESOURCE_SCHEME}; use crate::cef::dirs::{cef_cache_dir, cef_data_dir}; use crate::cef::input::InputState; use crate::cef::internal::{BrowserProcessAppImpl, BrowserProcessClientImpl, RenderHandlerImpl, RenderProcessAppImpl}; -pub(crate) struct CefContextBuilder { +pub(crate) struct CefContextBuilder { pub(crate) args: Args, pub(crate) is_sub_process: bool, + _marker: std::marker::PhantomData, } -unsafe impl Send for CefContextBuilder {} +unsafe impl Send for CefContextBuilder {} -impl CefContextBuilder { +impl CefContextBuilder { pub(crate) fn new() -> Self { #[cfg(target_os = "macos")] let _loader = { @@ -34,7 +35,11 @@ impl CefContextBuilder { let switch = CefString::from("type"); let is_sub_process = cmd.has_switch(Some(&switch)) == 1; - Self { args, is_sub_process } + Self { + args, + is_sub_process, + _marker: std::marker::PhantomData, + } } pub(crate) fn is_sub_process(&self) -> bool { @@ -45,7 +50,7 @@ impl CefContextBuilder { let cmd = self.args.as_cmd_line().unwrap(); let switch = CefString::from("type"); let process_type = CefString::from(&cmd.switch_value(Some(&switch))); - let mut app = RenderProcessAppImpl::app(); + let mut app = RenderProcessAppImpl::::app(); let ret = execute_process(Some(self.args.as_main_args()), Some(&mut app), std::ptr::null_mut()); if ret >= 0 { SetupError::SubprocessFailed(process_type.to_string()) @@ -55,7 +60,7 @@ impl CefContextBuilder { } #[cfg(target_os = "macos")] - pub(crate) fn initialize(self, event_handler: CefHandler) -> Result { + pub(crate) fn initialize(self, event_handler: H) -> Result { let settings = Settings { windowless_rendering_enabled: 1, multi_threaded_message_loop: 0, @@ -71,7 +76,7 @@ impl CefContextBuilder { } #[cfg(not(target_os = "macos"))] - pub(crate) fn initialize(self, event_handler: CefHandler) -> Result { + pub(crate) fn initialize(self, event_handler: H) -> Result { let settings = Settings { windowless_rendering_enabled: 1, multi_threaded_message_loop: 1, @@ -97,7 +102,7 @@ impl CefContextBuilder { Ok(super::multithreaded::MultiThreadedCefContextProxy) } - fn initialize_inner(self, event_handler: &CefHandler, settings: Settings) -> Result<(), InitError> { + fn initialize_inner(self, event_handler: &H, settings: Settings) -> Result<(), InitError> { let mut cef_app = App::new(BrowserProcessAppImpl::new(event_handler.clone())); let result = cef::initialize(Some(self.args.as_main_args()), Some(&settings), Some(&mut cef_app), std::ptr::null_mut()); // Attention! Wrapping this in an extra App is necessary, otherwise the program still compiles but segfaults @@ -113,11 +118,11 @@ impl CefContextBuilder { } } -fn create_browser(event_handler: CefHandler) -> Result { +fn create_browser(event_handler: H) -> Result { let render_handler = RenderHandler::new(RenderHandlerImpl::new(event_handler.clone())); let mut client = Client::new(BrowserProcessClientImpl::new(render_handler, event_handler.clone())); - let url = CefString::from(format!("{GRAPHITE_SCHEME}://{FRONTEND_DOMAIN}/").as_str()); + let url = CefString::from(format!("{RESOURCE_SCHEME}://{RESOURCE_DOMAIN}/").as_str()); let window_info = WindowInfo { windowless_rendering_enabled: 1, diff --git a/desktop/src/cef/internal.rs b/desktop/src/cef/internal.rs index 51578524..2e48c6df 100644 --- a/desktop/src/cef/internal.rs +++ b/desktop/src/cef/internal.rs @@ -2,10 +2,14 @@ mod browser_process_app; mod browser_process_client; mod browser_process_handler; mod browser_process_life_span_handler; + mod render_process_app; mod render_process_handler; mod render_process_v8_handler; +mod resource_handler; +mod scheme_handler_factory; + pub(super) mod render_handler; pub(super) mod task; diff --git a/desktop/src/cef/internal/browser_process_app.rs b/desktop/src/cef/internal/browser_process_app.rs index 165d27ce..7a0ec0fe 100644 --- a/desktop/src/cef/internal/browser_process_app.rs +++ b/desktop/src/cef/internal/browser_process_app.rs @@ -5,11 +5,9 @@ use cef::rc::{Rc, RcImpl}; use cef::sys::{_cef_app_t, cef_base_ref_counted_t}; use cef::{BrowserProcessHandler, CefString, ImplApp, ImplCommandLine, SchemeRegistrar, WrapApp}; -use crate::cef::CefEventHandler; - -use crate::cef::scheme_handler::GraphiteSchemeHandlerFactory; - use super::browser_process_handler::BrowserProcessHandlerImpl; +use super::scheme_handler_factory::SchemeHandlerFactoryImpl; +use crate::cef::CefEventHandler; pub(crate) struct BrowserProcessAppImpl { object: *mut RcImpl<_cef_app_t, Self>, @@ -30,7 +28,7 @@ impl ImplApp for BrowserProcessAppImpl { } fn on_register_custom_schemes(&self, registrar: Option<&mut SchemeRegistrar>) { - GraphiteSchemeHandlerFactory::register_schemes(registrar); + SchemeHandlerFactoryImpl::::register_schemes(registrar); } fn on_before_command_line_processing(&self, _process_type: Option<&cef::CefString>, command_line: Option<&mut cef::CommandLine>) { diff --git a/desktop/src/cef/internal/browser_process_handler.rs b/desktop/src/cef/internal/browser_process_handler.rs index cd80ce60..780218a6 100644 --- a/desktop/src/cef/internal/browser_process_handler.rs +++ b/desktop/src/cef/internal/browser_process_handler.rs @@ -4,9 +4,9 @@ use cef::rc::{Rc, RcImpl}; use cef::sys::{_cef_browser_process_handler_t, cef_base_ref_counted_t, cef_browser_process_handler_t}; use cef::{CefString, ImplBrowserProcessHandler, SchemeHandlerFactory, WrapBrowserProcessHandler}; +use super::scheme_handler_factory::SchemeHandlerFactoryImpl; use crate::cef::CefEventHandler; -use crate::cef::consts::GRAPHITE_SCHEME; -use crate::cef::scheme_handler::GraphiteSchemeHandlerFactory; +use crate::cef::consts::RESOURCE_SCHEME; pub(crate) struct BrowserProcessHandlerImpl { object: *mut RcImpl, @@ -23,7 +23,11 @@ impl BrowserProcessHandlerImpl { impl ImplBrowserProcessHandler for BrowserProcessHandlerImpl { fn on_context_initialized(&self) { - cef::register_scheme_handler_factory(Some(&CefString::from(GRAPHITE_SCHEME)), None, Some(&mut SchemeHandlerFactory::new(GraphiteSchemeHandlerFactory::new()))); + cef::register_scheme_handler_factory( + Some(&CefString::from(RESOURCE_SCHEME)), + None, + Some(&mut SchemeHandlerFactory::new(SchemeHandlerFactoryImpl::new(self.event_handler.clone()))), + ); } fn on_schedule_message_pump_work(&self, delay_ms: i64) { diff --git a/desktop/src/cef/internal/render_handler.rs b/desktop/src/cef/internal/render_handler.rs index c430630d..dd725a01 100644 --- a/desktop/src/cef/internal/render_handler.rs +++ b/desktop/src/cef/internal/render_handler.rs @@ -9,7 +9,6 @@ pub(crate) struct RenderHandlerImpl { object: *mut RcImpl<_cef_render_handler_t, Self>, event_handler: H, } - impl RenderHandlerImpl { pub(crate) fn new(event_handler: H) -> Self { Self { @@ -18,6 +17,7 @@ impl RenderHandlerImpl { } } } + impl ImplRenderHandler for RenderHandlerImpl { fn view_rect(&self, _browser: Option<&mut Browser>, rect: Option<&mut Rect>) { if let Some(rect) = rect { diff --git a/desktop/src/cef/internal/render_process_app.rs b/desktop/src/cef/internal/render_process_app.rs index d9e66baf..300690d7 100644 --- a/desktop/src/cef/internal/render_process_app.rs +++ b/desktop/src/cef/internal/render_process_app.rs @@ -3,13 +3,14 @@ use cef::sys::{_cef_app_t, cef_base_ref_counted_t}; use cef::{App, ImplApp, RenderProcessHandler, SchemeRegistrar, WrapApp}; use super::render_process_handler::RenderProcessHandlerImpl; -use crate::cef::scheme_handler::GraphiteSchemeHandlerFactory; +use super::scheme_handler_factory::SchemeHandlerFactoryImpl; +use crate::cef::CefEventHandler; -pub(crate) struct RenderProcessAppImpl { +pub(crate) struct RenderProcessAppImpl { object: *mut RcImpl<_cef_app_t, Self>, render_process_handler: RenderProcessHandler, } -impl RenderProcessAppImpl { +impl RenderProcessAppImpl { pub(crate) fn app() -> App { App::new(Self { object: std::ptr::null_mut(), @@ -18,9 +19,9 @@ impl RenderProcessAppImpl { } } -impl ImplApp for RenderProcessAppImpl { +impl ImplApp for RenderProcessAppImpl { fn on_register_custom_schemes(&self, registrar: Option<&mut SchemeRegistrar>) { - GraphiteSchemeHandlerFactory::register_schemes(registrar); + SchemeHandlerFactoryImpl::::register_schemes(registrar); } fn render_process_handler(&self) -> Option { @@ -32,7 +33,7 @@ impl ImplApp for RenderProcessAppImpl { } } -impl Clone for RenderProcessAppImpl { +impl Clone for RenderProcessAppImpl { fn clone(&self) -> Self { unsafe { let rc_impl = &mut *self.object; @@ -44,7 +45,7 @@ impl Clone for RenderProcessAppImpl { } } } -impl Rc for RenderProcessAppImpl { +impl Rc for RenderProcessAppImpl { fn as_base(&self) -> &cef_base_ref_counted_t { unsafe { let base = &*self.object; @@ -52,7 +53,7 @@ impl Rc for RenderProcessAppImpl { } } } -impl WrapApp for RenderProcessAppImpl { +impl WrapApp for RenderProcessAppImpl { fn wrap_rc(&mut self, object: *mut RcImpl<_cef_app_t, Self>) { self.object = object; } diff --git a/desktop/src/cef/internal/render_process_v8_handler.rs b/desktop/src/cef/internal/render_process_v8_handler.rs index 76cb136b..0ea935a1 100644 --- a/desktop/src/cef/internal/render_process_v8_handler.rs +++ b/desktop/src/cef/internal/render_process_v8_handler.rs @@ -5,7 +5,6 @@ use crate::cef::ipc::{MessageType, SendMessage}; pub struct BrowserProcessV8HandlerImpl { object: *mut cef::rc::RcImpl, } - impl BrowserProcessV8HandlerImpl { pub(crate) fn new() -> Self { Self { object: std::ptr::null_mut() } @@ -72,7 +71,6 @@ impl Clone for BrowserProcessV8HandlerImpl { Self { object: self.object } } } - impl Rc for BrowserProcessV8HandlerImpl { fn as_base(&self) -> &cef::sys::cef_base_ref_counted_t { unsafe { @@ -81,7 +79,6 @@ impl Rc for BrowserProcessV8HandlerImpl { } } } - impl WrapV8Handler for BrowserProcessV8HandlerImpl { fn wrap_rc(&mut self, object: *mut cef::rc::RcImpl) { self.object = object; diff --git a/desktop/src/cef/internal/resource_handler.rs b/desktop/src/cef/internal/resource_handler.rs new file mode 100644 index 00000000..8c21b2cf --- /dev/null +++ b/desktop/src/cef/internal/resource_handler.rs @@ -0,0 +1,108 @@ +use cef::rc::{Rc, RcImpl}; +use cef::sys::{_cef_resource_handler_t, cef_base_ref_counted_t}; +use cef::{Callback, CefString, ImplResourceHandler, ImplResponse, Request, ResourceReadCallback, Response, WrapResourceHandler}; +use std::cell::RefCell; +use std::ffi::c_int; +use std::io::Read; + +use crate::cef::{Resource, ResourceReader}; + +pub(crate) struct ResourceHandlerImpl { + object: *mut RcImpl<_cef_resource_handler_t, Self>, + reader: Option>, + mimetype: Option, +} + +impl ResourceHandlerImpl { + pub fn new(resource: Option) -> Self { + if let Some(resource) = resource { + Self { + object: std::ptr::null_mut(), + reader: Some(resource.reader.into()), + mimetype: resource.mimetype, + } + } else { + Self { + object: std::ptr::null_mut(), + reader: None, + mimetype: None, + } + } + } +} + +impl ImplResourceHandler for ResourceHandlerImpl { + fn open(&self, _request: Option<&mut Request>, handle_request: Option<&mut c_int>, _callback: Option<&mut Callback>) -> c_int { + if let Some(handle_request) = handle_request { + *handle_request = 1; + } + 1 + } + + fn response_headers(&self, response: Option<&mut Response>, response_length: Option<&mut i64>, _redirect_url: Option<&mut CefString>) { + if let Some(response_length) = response_length { + *response_length = -1; // Indicating that the length is unknown + } + if let Some(response) = response { + if self.reader.is_some() { + if let Some(mimetype) = &self.mimetype { + let cef_mime = CefString::from(mimetype.as_str()); + response.set_mime_type(Some(&cef_mime)); + } else { + response.set_mime_type(None); + } + response.set_status(200); + } else { + response.set_status(404); + response.set_mime_type(Some(&CefString::from("text/plain"))); + } + } + } + + fn read(&self, data_out: *mut u8, bytes_to_read: c_int, bytes_read: Option<&mut c_int>, _callback: Option<&mut ResourceReadCallback>) -> c_int { + let Some(bytes_read) = bytes_read else { unreachable!() }; + let out = unsafe { std::slice::from_raw_parts_mut(data_out, bytes_to_read as usize) }; + if let Some(reader) = &self.reader { + if let Ok(read) = reader.borrow_mut().read(out) { + *bytes_read = read as i32; + if read > 0 { + return 1; // Indicating that data was read + } + } else { + *bytes_read = -2; // Indicating ERR_FAILED + } + } + 0 // Indicating no data was read + } + + fn get_raw(&self) -> *mut _cef_resource_handler_t { + self.object.cast() + } +} + +impl Clone for ResourceHandlerImpl { + fn clone(&self) -> Self { + unsafe { + let rc_impl = &mut *self.object; + rc_impl.interface.add_ref(); + } + Self { + object: self.object, + reader: self.reader.clone(), + mimetype: self.mimetype.clone(), + } + } +} +impl Rc for ResourceHandlerImpl { + fn as_base(&self) -> &cef_base_ref_counted_t { + unsafe { + let base = &*self.object; + std::mem::transmute(&base.cef_object) + } + } +} +impl WrapResourceHandler for ResourceHandlerImpl { + fn wrap_rc(&mut self, object: *mut RcImpl<_cef_resource_handler_t, Self>) { + self.object = object; + } +} diff --git a/desktop/src/cef/internal/scheme_handler_factory.rs b/desktop/src/cef/internal/scheme_handler_factory.rs new file mode 100644 index 00000000..83c006c7 --- /dev/null +++ b/desktop/src/cef/internal/scheme_handler_factory.rs @@ -0,0 +1,86 @@ +use cef::rc::{Rc, RcImpl}; +use cef::sys::{_cef_scheme_handler_factory_t, cef_base_ref_counted_t, cef_scheme_options_t}; +use cef::{Browser, CefString, Frame, ImplRequest, ImplSchemeHandlerFactory, ImplSchemeRegistrar, Request, ResourceHandler, SchemeRegistrar, WrapSchemeHandlerFactory}; + +use super::resource_handler::ResourceHandlerImpl; +use crate::cef::CefEventHandler; +use crate::cef::consts::{RESOURCE_DOMAIN, RESOURCE_SCHEME}; + +pub(crate) struct SchemeHandlerFactoryImpl { + object: *mut RcImpl<_cef_scheme_handler_factory_t, Self>, + event_handler: H, +} +impl SchemeHandlerFactoryImpl { + pub(crate) fn new(event_handler: H) -> Self { + Self { + object: std::ptr::null_mut(), + event_handler, + } + } + + pub(crate) fn register_schemes(registrar: Option<&mut SchemeRegistrar>) { + if let Some(registrar) = registrar { + let mut scheme_options = 0; + scheme_options |= cef_scheme_options_t::CEF_SCHEME_OPTION_STANDARD as i32; + scheme_options |= cef_scheme_options_t::CEF_SCHEME_OPTION_FETCH_ENABLED as i32; + scheme_options |= cef_scheme_options_t::CEF_SCHEME_OPTION_SECURE as i32; + scheme_options |= cef_scheme_options_t::CEF_SCHEME_OPTION_CORS_ENABLED as i32; + registrar.add_custom_scheme(Some(&CefString::from(RESOURCE_SCHEME)), scheme_options); + } + } +} + +impl ImplSchemeHandlerFactory for SchemeHandlerFactoryImpl { + fn create(&self, _browser: Option<&mut Browser>, _frame: Option<&mut Frame>, scheme_name: Option<&CefString>, request: Option<&mut Request>) -> Option { + if let Some(scheme_name) = scheme_name { + if scheme_name.to_string() != RESOURCE_SCHEME { + return None; + } + if let Some(request) = request { + let url = CefString::from(&request.url()).to_string(); + let path = url.strip_prefix(&format!("{RESOURCE_SCHEME}://")).unwrap(); + let domain = path.split('/').next().unwrap_or(""); + let path = path.strip_prefix(domain).unwrap_or(""); + let path = path.trim_start_matches('/'); + return match domain { + RESOURCE_DOMAIN => { + let resource = self.event_handler.load_resource(path.to_string().into()); + Some(ResourceHandler::new(ResourceHandlerImpl::new(resource))) + } + _ => None, + }; + } + return None; + } + None + } + fn get_raw(&self) -> *mut _cef_scheme_handler_factory_t { + self.object.cast() + } +} + +impl Clone for SchemeHandlerFactoryImpl { + fn clone(&self) -> Self { + unsafe { + let rc_impl = &mut *self.object; + rc_impl.interface.add_ref(); + } + Self { + object: self.object, + event_handler: self.event_handler.clone(), + } + } +} +impl Rc for SchemeHandlerFactoryImpl { + fn as_base(&self) -> &cef_base_ref_counted_t { + unsafe { + let base = &*self.object; + std::mem::transmute(&base.cef_object) + } + } +} +impl WrapSchemeHandlerFactory for SchemeHandlerFactoryImpl { + fn wrap_rc(&mut self, object: *mut RcImpl<_cef_scheme_handler_factory_t, Self>) { + self.object = object; + } +} diff --git a/desktop/src/cef/scheme_handler.rs b/desktop/src/cef/scheme_handler.rs deleted file mode 100644 index 9c211f62..00000000 --- a/desktop/src/cef/scheme_handler.rs +++ /dev/null @@ -1,223 +0,0 @@ -use std::cell::RefCell; -use std::ffi::c_int; -use std::ops::DerefMut; -use std::slice::Iter; - -use cef::rc::{Rc, RcImpl}; -use cef::sys::{_cef_resource_handler_t, _cef_scheme_handler_factory_t, cef_base_ref_counted_t, cef_scheme_options_t}; -use cef::{ - Browser, Callback, CefString, Frame, ImplRequest, ImplResourceHandler, ImplResponse, ImplSchemeHandlerFactory, ImplSchemeRegistrar, Request, ResourceHandler, ResourceReadCallback, Response, - SchemeRegistrar, WrapResourceHandler, WrapSchemeHandlerFactory, -}; -use include_dir::{Dir, include_dir}; - -use super::consts::{FRONTEND_DOMAIN, GRAPHITE_SCHEME}; - -pub(crate) struct GraphiteSchemeHandlerFactory { - object: *mut RcImpl<_cef_scheme_handler_factory_t, Self>, -} -impl GraphiteSchemeHandlerFactory { - pub(crate) fn new() -> Self { - Self { object: std::ptr::null_mut() } - } - - pub(crate) fn register_schemes(registrar: Option<&mut SchemeRegistrar>) { - if let Some(registrar) = registrar { - let mut scheme_options = 0; - scheme_options |= cef_scheme_options_t::CEF_SCHEME_OPTION_STANDARD as i32; - scheme_options |= cef_scheme_options_t::CEF_SCHEME_OPTION_FETCH_ENABLED as i32; - scheme_options |= cef_scheme_options_t::CEF_SCHEME_OPTION_SECURE as i32; - scheme_options |= cef_scheme_options_t::CEF_SCHEME_OPTION_CORS_ENABLED as i32; - registrar.add_custom_scheme(Some(&CefString::from(GRAPHITE_SCHEME)), scheme_options); - } - } -} -impl ImplSchemeHandlerFactory for GraphiteSchemeHandlerFactory { - fn create(&self, _browser: Option<&mut Browser>, _frame: Option<&mut Frame>, scheme_name: Option<&CefString>, request: Option<&mut Request>) -> Option { - if let Some(scheme_name) = scheme_name { - if scheme_name.to_string() != GRAPHITE_SCHEME { - return None; - } - if let Some(request) = request { - let url = CefString::from(&request.url()).to_string(); - let path = url.strip_prefix(&format!("{GRAPHITE_SCHEME}://")).unwrap(); - let domain = path.split('/').next().unwrap_or(""); - let path = path.strip_prefix(domain).unwrap_or(""); - let path = path.trim_start_matches('/'); - return match domain { - FRONTEND_DOMAIN => { - if path.is_empty() { - Some(ResourceHandler::new(GraphiteFrontendResourceHandler::new("index.html"))) - } else { - Some(ResourceHandler::new(GraphiteFrontendResourceHandler::new(path))) - } - } - _ => None, - }; - } - return None; - } - None - } - fn get_raw(&self) -> *mut _cef_scheme_handler_factory_t { - self.object.cast() - } -} - -static FRONTEND: Dir = include_dir!("$CARGO_MANIFEST_DIR/../frontend/dist"); - -struct GraphiteFrontendResourceHandler<'a> { - object: *mut RcImpl<_cef_resource_handler_t, Self>, - data: Option>>, - mimetype: Option, -} -impl<'a> GraphiteFrontendResourceHandler<'a> { - pub fn new(path: &str) -> Self { - let file = FRONTEND.get_file(path); - let data = if let Some(file) = file { - Some(RefCell::new(file.contents().iter())) - } else { - tracing::error!("Failed to find asset at path: {}", path); - None - }; - let mimetype = if let Some(file) = file { - let ext = file.path().extension().and_then(|s| s.to_str()).unwrap_or(""); - - // We know what file types will be in the assets this should be fine - match ext { - "html" => Some("text/html".to_string()), - "css" => Some("text/css".to_string()), - "txt" => Some("text/plain".to_string()), - "wasm" => Some("application/wasm".to_string()), - "js" => Some("application/javascript".to_string()), - "png" => Some("image/png".to_string()), - "jpg" | "jpeg" => Some("image/jpeg".to_string()), - "svg" => Some("image/svg+xml".to_string()), - "xml" => Some("application/xml".to_string()), - "json" => Some("application/json".to_string()), - "ico" => Some("image/x-icon".to_string()), - "woff" => Some("font/woff".to_string()), - "woff2" => Some("font/woff2".to_string()), - "ttf" => Some("font/ttf".to_string()), - "otf" => Some("font/otf".to_string()), - "webmanifest" => Some("application/manifest+json".to_string()), - "graphite" => Some("application/graphite+json".to_string()), - _ => None, - } - } else { - None - }; - Self { - object: std::ptr::null_mut(), - data, - mimetype, - } - } -} -impl<'a> ImplResourceHandler for GraphiteFrontendResourceHandler<'a> { - fn open(&self, _request: Option<&mut Request>, handle_request: Option<&mut c_int>, _callback: Option<&mut Callback>) -> c_int { - if let Some(handle_request) = handle_request { - *handle_request = 1; - } - 1 - } - - fn response_headers(&self, response: Option<&mut Response>, response_length: Option<&mut i64>, _redirect_url: Option<&mut CefString>) { - if let Some(response_length) = response_length { - *response_length = -1; // Indicating that the length is unknown - } - if let Some(response) = response { - if self.data.is_some() { - if let Some(mimetype) = &self.mimetype { - let cef_mime = CefString::from(mimetype.as_str()); - response.set_mime_type(Some(&cef_mime)); - } else { - response.set_mime_type(None); - } - response.set_status(200); - } else { - response.set_status(404); - response.set_mime_type(Some(&CefString::from("text/plain"))); - } - } - } - - fn read(&self, data_out: *mut u8, bytes_to_read: c_int, bytes_read: Option<&mut c_int>, _callback: Option<&mut ResourceReadCallback>) -> c_int { - let mut read = 0; - - let out = unsafe { std::slice::from_raw_parts_mut(data_out, bytes_to_read as usize) }; - if let Some(data) = &self.data { - let mut data = data.borrow_mut(); - - for (out, &data) in out.iter_mut().zip(data.deref_mut()) { - *out = data; - read += 1; - } - } - - if let Some(bytes_read) = bytes_read { - *bytes_read = read; - } - - if read > 0 { - 1 // Indicating that data was read - } else { - 0 // Indicating no data was read - } - } - - fn get_raw(&self) -> *mut _cef_resource_handler_t { - self.object.cast() - } -} - -impl WrapSchemeHandlerFactory for GraphiteSchemeHandlerFactory { - fn wrap_rc(&mut self, object: *mut RcImpl<_cef_scheme_handler_factory_t, Self>) { - self.object = object; - } -} -impl<'a> WrapResourceHandler for GraphiteFrontendResourceHandler<'a> { - fn wrap_rc(&mut self, object: *mut RcImpl<_cef_resource_handler_t, Self>) { - self.object = object; - } -} - -impl Clone for GraphiteSchemeHandlerFactory { - fn clone(&self) -> Self { - unsafe { - let rc_impl = &mut *self.object; - rc_impl.interface.add_ref(); - } - Self { object: self.object } - } -} -impl<'a> Clone for GraphiteFrontendResourceHandler<'a> { - fn clone(&self) -> Self { - unsafe { - let rc_impl = &mut *self.object; - rc_impl.interface.add_ref(); - } - Self { - object: self.object, - data: self.data.clone(), - mimetype: self.mimetype.clone(), - } - } -} - -impl Rc for GraphiteSchemeHandlerFactory { - fn as_base(&self) -> &cef_base_ref_counted_t { - unsafe { - let base = &*self.object; - std::mem::transmute(&base.cef_object) - } - } -} -impl<'a> Rc for GraphiteFrontendResourceHandler<'a> { - fn as_base(&self) -> &cef_base_ref_counted_t { - unsafe { - let base = &*self.object; - std::mem::transmute(&base.cef_object) - } - } -} diff --git a/desktop/src/main.rs b/desktop/src/main.rs index 944899cf..080d05b6 100644 --- a/desktop/src/main.rs +++ b/desktop/src/main.rs @@ -1,6 +1,7 @@ use std::process::exit; use std::time::Instant; +use cef::CefHandler; use tracing_subscriber::EnvFilter; use winit::event_loop::EventLoop; @@ -30,7 +31,7 @@ pub(crate) enum CustomEvent { fn main() { tracing_subscriber::fmt().with_env_filter(EnvFilter::from_default_env()).init(); - let cef_context_builder = cef::CefContextBuilder::new(); + let cef_context_builder = cef::CefContextBuilder::::new(); if cef_context_builder.is_sub_process() { // We are in a CEF subprocess