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
This commit is contained in:
Timon 2025-08-28 17:18:18 +00:00 committed by GitHub
parent 95ef8a5343
commit 1d4d1026d4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
19 changed files with 389 additions and 276 deletions

9
Cargo.lock generated
View File

@ -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"

View File

@ -3,6 +3,7 @@ members = [
"editor",
"desktop",
"desktop/wrapper",
"desktop/embedded-resources",
"proc-macros",
"frontend/wasm",
"node-graph/gapplication-io",

View File

@ -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 }

View File

@ -0,0 +1,15 @@
[package]
name = "graphite-desktop-embedded-resources"
version = "0.1.0"
description = "Graphite Desktop Embedded Resources"
authors = ["Graphite Authors <contact@graphite.rs>"]
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)'] }

View File

@ -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?");
}
}

View File

@ -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<include_dir::Dir> = Some(include_dir::include_dir!("$EMBEDDED_RESOURCES"));
#[cfg(not(embedded_resources))]
pub static EMBEDDED_RESOURCES: Option<include_dir::Dir> = None;

View File

@ -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<Resource>;
/// 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<String>,
}
#[expect(dead_code)]
#[derive(Clone)]
pub(crate) enum ResourceReader {
Embedded(Cursor<&'static [u8]>),
File(Arc<File>),
}
impl Read for ResourceReader {
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
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<Mutex<WindowSizeReceiver>>,
event_loop_proxy: EventLoopProxy<CustomEvent>,
wgpu_context: WgpuContext,
}
struct WindowSizeReceiver {
receiver: Receiver<WindowSize>,
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<Resource> {
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);
}
}
}
}

View File

@ -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";

View File

@ -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<H: CefEventHandler> {
pub(crate) args: Args,
pub(crate) is_sub_process: bool,
_marker: std::marker::PhantomData<H>,
}
unsafe impl Send for CefContextBuilder {}
unsafe impl<H: CefEventHandler> Send for CefContextBuilder<H> {}
impl CefContextBuilder {
impl<H: CefEventHandler> CefContextBuilder<H> {
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::<H>::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<impl CefContext, InitError> {
pub(crate) fn initialize(self, event_handler: H) -> Result<impl CefContext, InitError> {
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<impl CefContext, InitError> {
pub(crate) fn initialize(self, event_handler: H) -> Result<impl CefContext, InitError> {
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<SingleThreadedCefContext, InitError> {
fn create_browser<H: CefEventHandler>(event_handler: H) -> 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()));
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,

View File

@ -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;

View File

@ -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<H: CefEventHandler> {
object: *mut RcImpl<_cef_app_t, Self>,
@ -30,7 +28,7 @@ impl<H: CefEventHandler + Clone> ImplApp for BrowserProcessAppImpl<H> {
}
fn on_register_custom_schemes(&self, registrar: Option<&mut SchemeRegistrar>) {
GraphiteSchemeHandlerFactory::register_schemes(registrar);
SchemeHandlerFactoryImpl::<H>::register_schemes(registrar);
}
fn on_before_command_line_processing(&self, _process_type: Option<&cef::CefString>, command_line: Option<&mut cef::CommandLine>) {

View File

@ -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<H: CefEventHandler> {
object: *mut RcImpl<cef_browser_process_handler_t, Self>,
@ -23,7 +23,11 @@ impl<H: CefEventHandler> BrowserProcessHandlerImpl<H> {
impl<H: CefEventHandler + Clone> ImplBrowserProcessHandler for BrowserProcessHandlerImpl<H> {
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) {

View File

@ -9,7 +9,6 @@ pub(crate) struct RenderHandlerImpl<H: CefEventHandler> {
object: *mut RcImpl<_cef_render_handler_t, Self>,
event_handler: H,
}
impl<H: CefEventHandler> RenderHandlerImpl<H> {
pub(crate) fn new(event_handler: H) -> Self {
Self {
@ -18,6 +17,7 @@ impl<H: CefEventHandler> RenderHandlerImpl<H> {
}
}
}
impl<H: CefEventHandler> ImplRenderHandler for RenderHandlerImpl<H> {
fn view_rect(&self, _browser: Option<&mut Browser>, rect: Option<&mut Rect>) {
if let Some(rect) = rect {

View File

@ -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<H: CefEventHandler> {
object: *mut RcImpl<_cef_app_t, Self>,
render_process_handler: RenderProcessHandler,
}
impl RenderProcessAppImpl {
impl<H: CefEventHandler> RenderProcessAppImpl<H> {
pub(crate) fn app() -> App {
App::new(Self {
object: std::ptr::null_mut(),
@ -18,9 +19,9 @@ impl RenderProcessAppImpl {
}
}
impl ImplApp for RenderProcessAppImpl {
impl<H: CefEventHandler> ImplApp for RenderProcessAppImpl<H> {
fn on_register_custom_schemes(&self, registrar: Option<&mut SchemeRegistrar>) {
GraphiteSchemeHandlerFactory::register_schemes(registrar);
SchemeHandlerFactoryImpl::<H>::register_schemes(registrar);
}
fn render_process_handler(&self) -> Option<RenderProcessHandler> {
@ -32,7 +33,7 @@ impl ImplApp for RenderProcessAppImpl {
}
}
impl Clone for RenderProcessAppImpl {
impl<H: CefEventHandler> Clone for RenderProcessAppImpl<H> {
fn clone(&self) -> Self {
unsafe {
let rc_impl = &mut *self.object;
@ -44,7 +45,7 @@ impl Clone for RenderProcessAppImpl {
}
}
}
impl Rc for RenderProcessAppImpl {
impl<H: CefEventHandler> Rc for RenderProcessAppImpl<H> {
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<H: CefEventHandler> WrapApp for RenderProcessAppImpl<H> {
fn wrap_rc(&mut self, object: *mut RcImpl<_cef_app_t, Self>) {
self.object = object;
}

View File

@ -5,7 +5,6 @@ use crate::cef::ipc::{MessageType, SendMessage};
pub struct BrowserProcessV8HandlerImpl {
object: *mut cef::rc::RcImpl<cef::sys::_cef_v8_handler_t, Self>,
}
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<cef::sys::_cef_v8_handler_t, Self>) {
self.object = object;

View File

@ -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<RefCell<ResourceReader>>,
mimetype: Option<String>,
}
impl ResourceHandlerImpl {
pub fn new(resource: Option<Resource>) -> 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;
}
}

View File

@ -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<H: CefEventHandler> {
object: *mut RcImpl<_cef_scheme_handler_factory_t, Self>,
event_handler: H,
}
impl<H: CefEventHandler> SchemeHandlerFactoryImpl<H> {
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<H: CefEventHandler> ImplSchemeHandlerFactory for SchemeHandlerFactoryImpl<H> {
fn create(&self, _browser: Option<&mut Browser>, _frame: Option<&mut Frame>, scheme_name: Option<&CefString>, request: Option<&mut Request>) -> Option<ResourceHandler> {
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<H: CefEventHandler> Clone for SchemeHandlerFactoryImpl<H> {
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<H: CefEventHandler> Rc for SchemeHandlerFactoryImpl<H> {
fn as_base(&self) -> &cef_base_ref_counted_t {
unsafe {
let base = &*self.object;
std::mem::transmute(&base.cef_object)
}
}
}
impl<H: CefEventHandler> WrapSchemeHandlerFactory for SchemeHandlerFactoryImpl<H> {
fn wrap_rc(&mut self, object: *mut RcImpl<_cef_scheme_handler_factory_t, Self>) {
self.object = object;
}
}

View File

@ -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<ResourceHandler> {
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<RefCell<Iter<'a, u8>>>,
mimetype: Option<String>,
}
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)
}
}
}

View File

@ -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::<CefHandler>::new();
if cef_context_builder.is_sub_process() {
// We are in a CEF subprocess