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:
parent
95ef8a5343
commit
1d4d1026d4
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ members = [
|
|||
"editor",
|
||||
"desktop",
|
||||
"desktop/wrapper",
|
||||
"desktop/embedded-resources",
|
||||
"proc-macros",
|
||||
"frontend/wasm",
|
||||
"node-graph/gapplication-io",
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
|
|
|
|||
|
|
@ -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)'] }
|
||||
|
|
@ -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?");
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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";
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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>) {
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in New Issue