diff --git a/desktop/src/app.rs b/desktop/src/app.rs index df18bcfaa..2c77b68ca 100644 --- a/desktop/src/app.rs +++ b/desktop/src/app.rs @@ -24,7 +24,7 @@ use winit::window::WindowId; use crate::cef; pub(crate) struct WinitApp { - cef_context: cef::Context, + cef_context: Box, window: Option>, cef_schedule: Option, window_size_sender: Sender, @@ -38,7 +38,7 @@ pub(crate) struct WinitApp { } impl WinitApp { - pub(crate) fn new(cef_context: cef::Context, window_size_sender: Sender, wgpu_context: WgpuContext, event_loop_proxy: EventLoopProxy) -> Self { + pub(crate) fn new(cef_context: Box, window_size_sender: Sender, wgpu_context: WgpuContext, event_loop_proxy: EventLoopProxy) -> Self { let rendering_loop_proxy = event_loop_proxy.clone(); let (start_render_sender, start_render_receiver) = std::sync::mpsc::sync_channel(1); std::thread::spawn(move || { @@ -71,7 +71,7 @@ impl WinitApp { tracing::error!("Failed to serialize frontend messages"); return; }; - self.cef_context.send_web_message(bytes.as_slice()); + self.cef_context.send_web_message(bytes); } DesktopFrontendMessage::OpenFileDialog { title, filters, context } => { let event_loop_proxy = self.event_loop_proxy.clone(); @@ -254,7 +254,7 @@ impl ApplicationHandler for WinitApp { } fn window_event(&mut self, event_loop: &ActiveEventLoop, _window_id: WindowId, event: WindowEvent) { - let Some(event) = self.cef_context.handle_window_event(event) else { return }; + self.cef_context.handle_window_event(&event); match event { WindowEvent::CloseRequested => { diff --git a/desktop/src/cef.rs b/desktop/src/cef.rs index 7e3ac8084..6424fa593 100644 --- a/desktop/src/cef.rs +++ b/desktop/src/cef.rs @@ -20,6 +20,7 @@ use std::sync::mpsc::Receiver; use std::sync::{Arc, Mutex}; use std::time::Instant; +mod consts; mod context; mod dirs; mod input; @@ -34,7 +35,7 @@ mod texture_import; #[cfg(feature = "accelerated_paint")] use texture_import::SharedTextureHandle; -pub(crate) use context::{Context, InitError, Initialized, Setup, SetupError}; +pub(crate) use context::{CefContext, CefContextBuilder, InitError}; use winit::event_loop::EventLoopProxy; pub(crate) trait CefEventHandler: Clone { diff --git a/desktop/src/cef/consts.rs b/desktop/src/cef/consts.rs new file mode 100644 index 000000000..a0031c885 --- /dev/null +++ b/desktop/src/cef/consts.rs @@ -0,0 +1,2 @@ +pub(crate) const GRAPHITE_SCHEME: &str = "graphite-static"; +pub(crate) const FRONTEND_DOMAIN: &str = "frontend"; diff --git a/desktop/src/cef/context.rs b/desktop/src/cef/context.rs index c5791568b..2880663a1 100644 --- a/desktop/src/cef/context.rs +++ b/desktop/src/cef/context.rs @@ -1,164 +1,15 @@ -use cef::sys::{CEF_API_VERSION_LAST, cef_resultcode_t}; -use cef::{App, BrowserSettings, Client, DictionaryValue, ImplBrowser, ImplBrowserHost, ImplCommandLine, RenderHandler, RequestContext, WindowInfo, browser_host_create_browser_sync, initialize}; -use cef::{Browser, CefString, Settings, api_hash, args::Args, execute_process}; -use thiserror::Error; -use winit::event::WindowEvent; +mod multithreaded; +mod singlethreaded; -use crate::cef::dirs::{cef_cache_dir, cef_data_dir}; +mod builder; +pub(crate) use builder::{CefContextBuilder, InitError}; -use super::input::InputState; -use super::ipc::{MessageType, SendMessage}; -use super::scheme_handler::{FRONTEND_DOMAIN, GRAPHITE_SCHEME}; -use super::{CefEventHandler, input}; +pub(crate) trait CefContext { + fn work(&mut self); -use super::internal::{BrowserProcessAppImpl, BrowserProcessClientImpl, RenderHandlerImpl, RenderProcessAppImpl}; + fn handle_window_event(&mut self, event: &winit::event::WindowEvent); -pub(crate) struct Setup {} -pub(crate) struct Initialized {} -pub(crate) trait ContextState {} -impl ContextState for Setup {} -impl ContextState for Initialized {} + fn notify_of_resize(&self); -pub(crate) struct Context { - args: Args, - pub(crate) browser: Option, - pub(crate) input_state: InputState, - marker: std::marker::PhantomData, -} - -impl Context { - pub(crate) fn new() -> Result, SetupError> { - #[cfg(target_os = "macos")] - let _loader = { - let loader = library_loader::LibraryLoader::new(&std::env::current_exe().unwrap(), false); - assert!(loader.load()); - loader - }; - let _ = api_hash(CEF_API_VERSION_LAST, 0); - - let args = Args::new(); - let cmd = args.as_cmd_line().unwrap(); - let switch = CefString::from("type"); - let is_browser_process = cmd.has_switch(Some(&switch)) != 1; - - if !is_browser_process { - let process_type = CefString::from(&cmd.switch_value(Some(&switch))); - let mut app = RenderProcessAppImpl::app(); - let ret = execute_process(Some(args.as_main_args()), Some(&mut app), std::ptr::null_mut()); - if ret >= 0 { - return Err(SetupError::SubprocessFailed(process_type.to_string())); - } else { - return Err(SetupError::Subprocess); - } - } - - Ok(Context { - args, - browser: None, - input_state: InputState::default(), - marker: std::marker::PhantomData::, - }) - } - - pub(crate) fn init(self, event_handler: impl CefEventHandler) -> Result, InitError> { - let settings = Settings { - windowless_rendering_enabled: 1, - multi_threaded_message_loop: 0, - external_message_pump: 1, - root_cache_path: cef_data_dir().to_str().map(CefString::from).unwrap(), - cache_path: cef_cache_dir().to_str().map(CefString::from).unwrap(), - ..Default::default() - }; - - // Attention! Wrapping this in an extra App is necessary, otherwise the program still compiles but segfaults - let mut cef_app = App::new(BrowserProcessAppImpl::new(event_handler.clone())); - - let result = initialize(Some(self.args.as_main_args()), Some(&settings), Some(&mut cef_app), std::ptr::null_mut()); - if result != 1 { - let cef_exit_code = cef::get_exit_code() as u32; - if cef_exit_code == cef_resultcode_t::CEF_RESULT_CODE_NORMAL_EXIT_PROCESS_NOTIFIED as u32 { - return Err(InitError::AlreadyRunning); - } - return Err(InitError::InitializationFailed(cef_exit_code)); - } - - 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("chrome://gpu"); - - let window_info = WindowInfo { - windowless_rendering_enabled: 1, - #[cfg(feature = "accelerated_paint")] - shared_texture_enabled: if crate::cef::platform::should_enable_hardware_acceleration() { 1 } else { 0 }, - ..Default::default() - }; - - let settings = BrowserSettings { - windowless_frame_rate: crate::consts::CEF_WINDOWLESS_FRAME_RATE, - background_color: 0x0, - ..Default::default() - }; - - let browser = browser_host_create_browser_sync( - Some(&window_info), - Some(&mut client), - Some(&url), - Some(&settings), - Option::<&mut DictionaryValue>::None, - Option::<&mut RequestContext>::None, - ); - - Ok(Context { - args: self.args.clone(), - browser, - input_state: self.input_state.clone(), - marker: std::marker::PhantomData::, - }) - } -} - -impl Context { - pub(crate) fn work(&mut self) { - cef::do_message_loop_work(); - } - - pub(crate) fn handle_window_event(&mut self, event: WindowEvent) -> Option { - input::handle_window_event(self, event) - } - - pub(crate) fn notify_of_resize(&self) { - if let Some(browser) = &self.browser { - browser.host().unwrap().was_resized(); - } - } - - pub(crate) fn send_web_message(&self, message: &[u8]) { - self.send_message(MessageType::SendToJS, message); - } -} - -impl Drop for Context { - fn drop(&mut self) { - if self.browser.is_some() { - cef::shutdown(); - } - } -} - -#[derive(Error, Debug)] -pub(crate) enum SetupError { - #[error("this is the sub process should exit immediately")] - Subprocess, - #[error("subprocess returned non zero exit code")] - SubprocessFailed(String), -} - -#[derive(Error, Debug)] -pub(crate) enum InitError { - #[error("initialization failed")] - InitializationFailed(u32), - #[error("Another instance is already running")] - AlreadyRunning, + fn send_web_message(&self, message: Vec); } diff --git a/desktop/src/cef/context/builder.rs b/desktop/src/cef/context/builder.rs new file mode 100644 index 000000000..c1a92b92e --- /dev/null +++ b/desktop/src/cef/context/builder.rs @@ -0,0 +1,171 @@ +use cef::args::Args; +use cef::sys::{CEF_API_VERSION_LAST, cef_resultcode_t}; +use cef::{ + App, BrowserSettings, CefString, Client, DictionaryValue, ImplCommandLine, RenderHandler, RequestContext, Settings, WindowInfo, api_hash, browser_host_create_browser_sync, execute_process, +}; + +use super::CefContext; +use super::singlethreaded::SingleThreadedCefContext; +use crate::cef::CefHandler; +use crate::cef::consts::{FRONTEND_DOMAIN, GRAPHITE_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) args: Args, + pub(crate) is_sub_process: bool, +} + +unsafe impl Send for CefContextBuilder {} + +impl CefContextBuilder { + pub(crate) fn new() -> Self { + #[cfg(target_os = "macos")] + let _loader = { + let loader = library_loader::LibraryLoader::new(&std::env::current_exe().unwrap(), false); + assert!(loader.load()); + loader + }; + let _ = api_hash(CEF_API_VERSION_LAST, 0); + + let args = Args::new(); + let cmd = args.as_cmd_line().unwrap(); + let switch = CefString::from("type"); + let is_sub_process = cmd.has_switch(Some(&switch)) == 1; + + Self { args, is_sub_process } + } + + pub(crate) fn is_sub_process(&self) -> bool { + self.is_sub_process + } + + pub(crate) fn execute_sub_process(&self) -> SetupError { + 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 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()) + } else { + SetupError::Subprocess + } + } + + #[cfg(target_os = "macos")] + pub(crate) fn initialize(self, event_handler: CefHandler) -> Result { + let settings = Settings { + windowless_rendering_enabled: 1, + multi_threaded_message_loop: 0, + external_message_pump: 1, + root_cache_path: cef_data_dir().to_str().map(CefString::from).unwrap(), + cache_path: cef_cache_dir().to_str().map(CefString::from).unwrap(), + ..Default::default() + }; + + self.initialize_inner(&event_handler, settings)?; + + create_browser(event_handler) + } + + #[cfg(not(target_os = "macos"))] + pub(crate) fn initialize(self, event_handler: CefHandler) -> Result { + let settings = Settings { + windowless_rendering_enabled: 1, + multi_threaded_message_loop: 1, + root_cache_path: cef_data_dir().to_str().map(CefString::from).unwrap(), + cache_path: cef_cache_dir().to_str().map(CefString::from).unwrap(), + ..Default::default() + }; + + self.initialize_inner(&event_handler, settings)?; + + super::multithreaded::run_on_ui_thread(move || match create_browser(event_handler) { + Ok(context) => { + super::multithreaded::CONTEXT.with(|b| { + *b.borrow_mut() = Some(context); + }); + } + Err(e) => { + tracing::error!("Failed to initialize CEF context: {:?}", e); + std::process::exit(1); + } + }); + + Ok(super::multithreaded::MultiThreadedCefContextProxy) + } + + fn initialize_inner(self, event_handler: &CefHandler, 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 + + if result != 1 { + let cef_exit_code = cef::get_exit_code() as u32; + if cef_exit_code == cef_resultcode_t::CEF_RESULT_CODE_NORMAL_EXIT_PROCESS_NOTIFIED as u32 { + return Err(InitError::AlreadyRunning); + } + return Err(InitError::InitializationFailed(cef_exit_code)); + } + Ok(()) + } +} + +fn create_browser(event_handler: CefHandler) -> 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 window_info = WindowInfo { + windowless_rendering_enabled: 1, + #[cfg(feature = "accelerated_paint")] + shared_texture_enabled: if crate::cef::platform::should_enable_hardware_acceleration() { 1 } else { 0 }, + ..Default::default() + }; + + let settings = BrowserSettings { + windowless_frame_rate: crate::consts::CEF_WINDOWLESS_FRAME_RATE, + background_color: 0x0, + ..Default::default() + }; + + let browser = browser_host_create_browser_sync( + Some(&window_info), + Some(&mut client), + Some(&url), + Some(&settings), + Option::<&mut DictionaryValue>::None, + Option::<&mut RequestContext>::None, + ); + + if let Some(browser) = browser { + Ok(SingleThreadedCefContext { + browser, + input_state: InputState::default(), + }) + } else { + tracing::error!("Failed to create browser"); + Err(InitError::BrowserCreationFailed) + } +} + +#[derive(thiserror::Error, Debug)] +pub(crate) enum SetupError { + #[error("This is the sub process should exit immediately")] + Subprocess, + #[error("Subprocess returned non zero exit code")] + SubprocessFailed(String), +} + +#[derive(thiserror::Error, Debug)] +pub(crate) enum InitError { + #[error("Initialization failed")] + InitializationFailed(u32), + #[error("Browser creation failed")] + BrowserCreationFailed, + #[error("Another instance is already running")] + AlreadyRunning, +} diff --git a/desktop/src/cef/context/multithreaded.rs b/desktop/src/cef/context/multithreaded.rs new file mode 100644 index 000000000..9434a99d8 --- /dev/null +++ b/desktop/src/cef/context/multithreaded.rs @@ -0,0 +1,67 @@ +use cef::sys::cef_thread_id_t; +use cef::{Task, ThreadId, post_task}; +use std::cell::RefCell; +use winit::event::WindowEvent; + +use crate::cef::internal::task::ClosureTask; + +use super::CefContext; +use super::singlethreaded::SingleThreadedCefContext; + +thread_local! { + pub(super) static CONTEXT: RefCell> = const { RefCell::new(None) }; +} + +pub(super) struct MultiThreadedCefContextProxy; + +impl CefContext for MultiThreadedCefContextProxy { + fn work(&mut self) { + // CEF handles its own message loop in multi-threaded mode + } + + fn handle_window_event(&mut self, event: &WindowEvent) { + let event_clone = event.clone(); + run_on_ui_thread(move || { + CONTEXT.with(|b| { + if let Some(context) = b.borrow_mut().as_mut() { + context.handle_window_event(&event_clone); + } + }); + }); + } + + fn notify_of_resize(&self) { + run_on_ui_thread(move || { + CONTEXT.with(|b| { + if let Some(context) = b.borrow_mut().as_mut() { + context.notify_of_resize(); + } + }); + }); + } + + fn send_web_message(&self, message: Vec) { + run_on_ui_thread(move || { + CONTEXT.with(|b| { + if let Some(context) = b.borrow_mut().as_mut() { + context.send_web_message(message); + } + }); + }); + } +} + +impl Drop for MultiThreadedCefContextProxy { + fn drop(&mut self) { + cef::shutdown(); + } +} + +pub(super) fn run_on_ui_thread(closure: F) +where + F: FnOnce() + Send + 'static, +{ + let closure_task = ClosureTask::new(closure); + let mut task = Task::new(closure_task); + post_task(ThreadId::from(cef_thread_id_t::TID_UI), Some(&mut task)); +} diff --git a/desktop/src/cef/context/singlethreaded.rs b/desktop/src/cef/context/singlethreaded.rs new file mode 100644 index 000000000..2222c8521 --- /dev/null +++ b/desktop/src/cef/context/singlethreaded.rs @@ -0,0 +1,48 @@ +use cef::{Browser, ImplBrowser, ImplBrowserHost}; +use winit::event::WindowEvent; + +use crate::cef::input; +use crate::cef::input::InputState; +use crate::cef::ipc::{MessageType, SendMessage}; + +use super::CefContext; + +pub(super) struct SingleThreadedCefContext { + pub(super) browser: Browser, + pub(super) input_state: InputState, +} + +impl CefContext for SingleThreadedCefContext { + fn work(&mut self) { + cef::do_message_loop_work(); + } + + fn handle_window_event(&mut self, event: &WindowEvent) { + input::handle_window_event(&self.browser, &mut self.input_state, event) + } + + fn notify_of_resize(&self) { + self.browser.host().unwrap().was_resized(); + } + + fn send_web_message(&self, message: Vec) { + self.send_message(MessageType::SendToJS, &message); + } +} + +impl Drop for SingleThreadedCefContext { + fn drop(&mut self) { + cef::shutdown(); + } +} + +impl SendMessage for SingleThreadedCefContext { + fn send_message(&self, message_type: MessageType, message: &[u8]) { + let Some(frame) = self.browser.main_frame() else { + tracing::error!("Main frame is not available, cannot send message"); + return; + }; + + frame.send_message(message_type, message); + } +} diff --git a/desktop/src/cef/input.rs b/desktop/src/cef/input.rs index 103838a18..8b4e39ecd 100644 --- a/desktop/src/cef/input.rs +++ b/desktop/src/cef/input.rs @@ -1,164 +1,153 @@ use cef::sys::{cef_event_flags_t, cef_key_event_type_t, cef_mouse_button_type_t}; -use cef::{ImplBrowser, ImplBrowserHost, KeyEvent, KeyEventType, MouseEvent}; +use cef::{Browser, ImplBrowser, ImplBrowserHost, KeyEvent, KeyEventType, MouseEvent}; use winit::dpi::PhysicalPosition; use winit::event::{ElementState, MouseButton, MouseScrollDelta, WindowEvent}; -use super::context::{Context, Initialized}; - mod keymap; use keymap::{ToDomBits, ToVKBits}; -pub(crate) fn handle_window_event(context: &mut Context, event: WindowEvent) -> Option { +pub(crate) fn handle_window_event(browser: &Browser, input_state: &mut InputState, event: &WindowEvent) { match event { WindowEvent::CursorMoved { position, .. } => { - if let Some(browser) = &context.browser { - if let Some(host) = browser.host() { - host.set_focus(1); - } - - context.input_state.update_mouse_position(&position); - let mouse_event: MouseEvent = (&context.input_state).into(); - browser.host().unwrap().send_mouse_move_event(Some(&mouse_event), 0); + if let Some(host) = browser.host() { + host.set_focus(1); } + + input_state.update_mouse_position(position); + let mouse_event: MouseEvent = (input_state).into(); + browser.host().unwrap().send_mouse_move_event(Some(&mouse_event), 0); } WindowEvent::MouseInput { state, button, .. } => { - if let Some(browser) = &context.browser { - if let Some(host) = browser.host() { - host.set_focus(1); + if let Some(host) = browser.host() { + host.set_focus(1); - let mouse_up = match state { - ElementState::Pressed => 0, - ElementState::Released => 1, - }; + let mouse_up = match state { + ElementState::Pressed => 0, + ElementState::Released => 1, + }; - let cef_button = match button { - MouseButton::Left => Some(cef::MouseButtonType::from(cef_mouse_button_type_t::MBT_LEFT)), - MouseButton::Right => Some(cef::MouseButtonType::from(cef_mouse_button_type_t::MBT_RIGHT)), - MouseButton::Middle => Some(cef::MouseButtonType::from(cef_mouse_button_type_t::MBT_MIDDLE)), - MouseButton::Forward => None, //TODO: Handle Forward button - MouseButton::Back => None, //TODO: Handle Back button - _ => None, - }; + let cef_button = match button { + MouseButton::Left => Some(cef::MouseButtonType::from(cef_mouse_button_type_t::MBT_LEFT)), + MouseButton::Right => Some(cef::MouseButtonType::from(cef_mouse_button_type_t::MBT_RIGHT)), + MouseButton::Middle => Some(cef::MouseButtonType::from(cef_mouse_button_type_t::MBT_MIDDLE)), + MouseButton::Forward => None, //TODO: Handle Forward button + MouseButton::Back => None, //TODO: Handle Back button + _ => None, + }; - let mut mouse_state = context.input_state.mouse_state.clone(); - match button { - MouseButton::Left => { - mouse_state.left = match state { - ElementState::Pressed => true, - ElementState::Released => false, - } + let mut mouse_state = input_state.mouse_state.clone(); + match button { + MouseButton::Left => { + mouse_state.left = match state { + ElementState::Pressed => true, + ElementState::Released => false, } - MouseButton::Right => { - mouse_state.right = match state { - ElementState::Pressed => true, - ElementState::Released => false, - } - } - MouseButton::Middle => { - mouse_state.middle = match state { - ElementState::Pressed => true, - ElementState::Released => false, - } - } - _ => {} - }; - context.input_state.update_mouse_state(mouse_state); - - let mouse_event: MouseEvent = (&context.input_state).into(); - - if let Some(button) = cef_button { - host.send_mouse_click_event( - Some(&mouse_event), - button, - mouse_up, - 1, // click count - ); } + MouseButton::Right => { + mouse_state.right = match state { + ElementState::Pressed => true, + ElementState::Released => false, + } + } + MouseButton::Middle => { + mouse_state.middle = match state { + ElementState::Pressed => true, + ElementState::Released => false, + } + } + _ => {} + }; + input_state.update_mouse_state(mouse_state); + + let mouse_event: MouseEvent = input_state.into(); + + if let Some(button) = cef_button { + host.send_mouse_click_event( + Some(&mouse_event), + button, + mouse_up, + 1, // click count + ); } } } WindowEvent::MouseWheel { delta, phase: _, device_id: _, .. } => { - if let Some(browser) = &context.browser { - if let Some(host) = browser.host() { - let mouse_event = (&context.input_state).into(); - let line_width = 40; //feels about right, TODO: replace with correct value - let line_height = 30; //feels about right, TODO: replace with correct value - let (delta_x, delta_y) = match delta { - MouseScrollDelta::LineDelta(x, y) => (x * line_width as f32, y * line_height as f32), - MouseScrollDelta::PixelDelta(physical_position) => (physical_position.x as f32, physical_position.y as f32), - }; - host.send_mouse_wheel_event(Some(&mouse_event), delta_x as i32, delta_y as i32); - } + if let Some(host) = browser.host() { + let mouse_event = input_state.into(); + let line_width = 40; //feels about right, TODO: replace with correct value + let line_height = 30; //feels about right, TODO: replace with correct value + let (delta_x, delta_y) = match delta { + MouseScrollDelta::LineDelta(x, y) => (x * line_width as f32, y * line_height as f32), + MouseScrollDelta::PixelDelta(physical_position) => (physical_position.x as f32, physical_position.y as f32), + }; + host.send_mouse_wheel_event(Some(&mouse_event), delta_x as i32, delta_y as i32); } } WindowEvent::ModifiersChanged(modifiers) => { - context.input_state.update_modifiers(&modifiers.state()); + input_state.update_modifiers(&modifiers.state()); } WindowEvent::KeyboardInput { device_id: _, event, is_synthetic: _ } => { - if let Some(browser) = &context.browser { - if let Some(host) = browser.host() { - host.set_focus(1); + if let Some(host) = browser.host() { + host.set_focus(1); - let (named_key, character) = match &event.logical_key { - winit::keyboard::Key::Named(named_key) => ( - Some(named_key), - match named_key { - winit::keyboard::NamedKey::Space => Some(' '), - winit::keyboard::NamedKey::Enter => Some('\u{000d}'), - _ => None, - }, - ), - winit::keyboard::Key::Character(str) => { - let char = str.chars().next().unwrap_or('\0'); - (None, Some(char)) - } - _ => return None, - }; - - let mut key_event = KeyEvent { - size: size_of::(), - focus_on_editable_field: 1, - modifiers: context.input_state.cef_modifiers(&event.location, event.repeat).raw(), - is_system_key: 0, - ..Default::default() - }; - - if let Some(named_key) = named_key { - key_event.native_key_code = named_key.to_dom_bits(); - key_event.windows_key_code = named_key.to_vk_bits(); - } else if let Some(char) = character { - key_event.native_key_code = char.to_dom_bits(); - key_event.windows_key_code = char.to_vk_bits(); + let (named_key, character) = match &event.logical_key { + winit::keyboard::Key::Named(named_key) => ( + Some(named_key), + match named_key { + winit::keyboard::NamedKey::Space => Some(' '), + winit::keyboard::NamedKey::Enter => Some('\u{000d}'), + _ => None, + }, + ), + winit::keyboard::Key::Character(str) => { + let char = str.chars().next().unwrap_or('\0'); + (None, Some(char)) } + _ => return, + }; - match event.state { - ElementState::Pressed => { - key_event.type_ = KeyEventType::from(cef_key_event_type_t::KEYEVENT_RAWKEYDOWN); - host.send_key_event(Some(&key_event)); + let mut key_event = KeyEvent { + size: size_of::(), + focus_on_editable_field: 1, + modifiers: input_state.cef_modifiers(&event.location, event.repeat).raw(), + is_system_key: 0, + ..Default::default() + }; - if let Some(char) = character { - let mut buf = [0; 2]; - char.encode_utf16(&mut buf); - key_event.character = buf[0]; - let mut buf = [0; 2]; - char.to_lowercase().next().unwrap().encode_utf16(&mut buf); - key_event.unmodified_character = buf[0]; - - key_event.type_ = KeyEventType::from(cef_key_event_type_t::KEYEVENT_CHAR); - host.send_key_event(Some(&key_event)); - } - } - ElementState::Released => { - key_event.type_ = KeyEventType::from(cef_key_event_type_t::KEYEVENT_KEYUP); - host.send_key_event(Some(&key_event)); - } - }; + if let Some(named_key) = named_key { + key_event.native_key_code = named_key.to_dom_bits(); + key_event.windows_key_code = named_key.to_vk_bits(); + } else if let Some(char) = character { + key_event.native_key_code = char.to_dom_bits(); + key_event.windows_key_code = char.to_vk_bits(); } + + match event.state { + ElementState::Pressed => { + key_event.type_ = KeyEventType::from(cef_key_event_type_t::KEYEVENT_RAWKEYDOWN); + host.send_key_event(Some(&key_event)); + + if let Some(char) = character { + let mut buf = [0; 2]; + char.encode_utf16(&mut buf); + key_event.character = buf[0]; + let mut buf = [0; 2]; + char.to_lowercase().next().unwrap().encode_utf16(&mut buf); + key_event.unmodified_character = buf[0]; + + key_event.type_ = KeyEventType::from(cef_key_event_type_t::KEYEVENT_CHAR); + host.send_key_event(Some(&key_event)); + } + } + ElementState::Released => { + key_event.type_ = KeyEventType::from(cef_key_event_type_t::KEYEVENT_KEYUP); + host.send_key_event(Some(&key_event)); + } + }; } } - e => return Some(e), + _ => {} } - None } #[derive(Default, Clone)] @@ -227,6 +216,15 @@ impl From<&InputState> for MouseEvent { } } } +impl From<&mut InputState> for MouseEvent { + fn from(val: &mut InputState) -> Self { + MouseEvent { + x: val.mouse_position.x as i32, + y: val.mouse_position.y as i32, + modifiers: val.cef_modifiers_mouse_event().raw(), + } + } +} struct CefModifiers(u32); diff --git a/desktop/src/cef/internal.rs b/desktop/src/cef/internal.rs index 1f5030be7..515785245 100644 --- a/desktop/src/cef/internal.rs +++ b/desktop/src/cef/internal.rs @@ -2,11 +2,13 @@ mod browser_process_app; mod browser_process_client; mod browser_process_handler; mod browser_process_life_span_handler; -pub mod render_handler; mod render_process_app; mod render_process_handler; mod render_process_v8_handler; +pub(super) mod render_handler; +pub(super) mod task; + pub(super) use browser_process_app::BrowserProcessAppImpl; pub(super) use browser_process_client::BrowserProcessClientImpl; pub(super) use render_handler::RenderHandlerImpl; diff --git a/desktop/src/cef/internal/browser_process_app.rs b/desktop/src/cef/internal/browser_process_app.rs index b417922ea..165d27ce4 100644 --- a/desktop/src/cef/internal/browser_process_app.rs +++ b/desktop/src/cef/internal/browser_process_app.rs @@ -1,3 +1,4 @@ +#[cfg(target_os = "linux")] use std::env; use cef::rc::{Rc, RcImpl}; diff --git a/desktop/src/cef/internal/browser_process_handler.rs b/desktop/src/cef/internal/browser_process_handler.rs index 4edacc5c1..cd80ce608 100644 --- a/desktop/src/cef/internal/browser_process_handler.rs +++ b/desktop/src/cef/internal/browser_process_handler.rs @@ -5,7 +5,8 @@ use cef::sys::{_cef_browser_process_handler_t, cef_base_ref_counted_t, cef_brows use cef::{CefString, ImplBrowserProcessHandler, SchemeHandlerFactory, WrapBrowserProcessHandler}; use crate::cef::CefEventHandler; -use crate::cef::scheme_handler::{GRAPHITE_SCHEME, GraphiteSchemeHandlerFactory}; +use crate::cef::consts::GRAPHITE_SCHEME; +use crate::cef::scheme_handler::GraphiteSchemeHandlerFactory; pub(crate) struct BrowserProcessHandlerImpl { object: *mut RcImpl, diff --git a/desktop/src/cef/internal/task.rs b/desktop/src/cef/internal/task.rs new file mode 100644 index 000000000..fe29e3de9 --- /dev/null +++ b/desktop/src/cef/internal/task.rs @@ -0,0 +1,61 @@ +use cef::rc::{Rc, RcImpl}; +use cef::sys::{_cef_task_t, cef_base_ref_counted_t}; +use cef::{ImplTask, WrapTask}; +use std::cell::RefCell; + +// Closure-based task wrapper following CEF patterns +pub struct ClosureTask { + pub(crate) object: *mut RcImpl<_cef_task_t, Self>, + pub(crate) closure: RefCell>, +} + +impl ClosureTask { + pub fn new(closure: F) -> Self { + Self { + object: std::ptr::null_mut(), + closure: RefCell::new(Some(closure)), + } + } +} + +impl ImplTask for ClosureTask { + fn execute(&self) { + if let Some(closure) = self.closure.borrow_mut().take() { + closure(); + } + } + + fn get_raw(&self) -> *mut _cef_task_t { + self.object.cast() + } +} + +impl Clone for ClosureTask { + fn clone(&self) -> Self { + unsafe { + if !self.object.is_null() { + let rc_impl = &mut *self.object; + rc_impl.interface.add_ref(); + } + } + Self { + object: self.object, + closure: RefCell::new(None), // Closure can only be executed once + } + } +} + +impl Rc for ClosureTask { + fn as_base(&self) -> &cef_base_ref_counted_t { + unsafe { + let base = &*self.object; + std::mem::transmute(&base.cef_object) + } + } +} + +impl WrapTask for ClosureTask { + fn wrap_rc(&mut self, object: *mut RcImpl<_cef_task_t, Self>) { + self.object = object; + } +} diff --git a/desktop/src/cef/ipc.rs b/desktop/src/cef/ipc.rs index 69b344a46..8d2080fdd 100644 --- a/desktop/src/cef/ipc.rs +++ b/desktop/src/cef/ipc.rs @@ -1,6 +1,4 @@ -use cef::{CefString, Frame, ImplBinaryValue, ImplBrowser, ImplFrame, ImplListValue, ImplProcessMessage, ImplV8Context, ProcessId, V8Context, sys::cef_process_id_t}; - -use super::{Context, Initialized}; +use cef::{CefString, Frame, ImplBinaryValue, ImplFrame, ImplListValue, ImplProcessMessage, ImplV8Context, ProcessId, V8Context, sys::cef_process_id_t}; pub(crate) enum MessageType { SendToJS, @@ -39,21 +37,6 @@ pub(crate) struct MessageInfo { pub(crate) trait SendMessage { fn send_message(&self, message_type: MessageType, message: &[u8]); } -impl SendMessage for Context { - fn send_message(&self, message_type: MessageType, message: &[u8]) { - let Some(browser) = &self.browser else { - tracing::error!("Browser is not initialized, cannot send message"); - return; - }; - - let Some(frame) = browser.main_frame() else { - tracing::error!("Main frame is not available, cannot send message"); - return; - }; - - frame.send_message(message_type, message); - } -} impl SendMessage for Option { fn send_message(&self, message_type: MessageType, message: &[u8]) { let Some(context) = self else { diff --git a/desktop/src/cef/scheme_handler.rs b/desktop/src/cef/scheme_handler.rs index 0cbb8bb54..a3b507545 100644 --- a/desktop/src/cef/scheme_handler.rs +++ b/desktop/src/cef/scheme_handler.rs @@ -11,8 +11,7 @@ use cef::{ }; use include_dir::{Dir, include_dir}; -pub(crate) const GRAPHITE_SCHEME: &str = "graphite-static"; -pub(crate) const FRONTEND_DOMAIN: &str = "frontend"; +use super::consts::{FRONTEND_DOMAIN, GRAPHITE_SCHEME}; pub(crate) struct GraphiteSchemeHandlerFactory { object: *mut RcImpl<_cef_scheme_handler_factory_t, Self>, diff --git a/desktop/src/cef/texture_import/d3d11.rs b/desktop/src/cef/texture_import/d3d11.rs index d384706d4..f769d06b3 100644 --- a/desktop/src/cef/texture_import/d3d11.rs +++ b/desktop/src/cef/texture_import/d3d11.rs @@ -87,22 +87,15 @@ impl D3D11Importer { // Wrap D3D12 resource in wgpu-hal texture let hal_texture = ::Device::texture_from_raw( d3d12_resource, - &wgpu::hal::TextureDescriptor { - label: Some("CEF D3D11→D3D12 Shared Texture"), - size: wgpu::Extent3d { - width: self.width, - height: self.height, - depth_or_array_layers: 1, - }, - mip_level_count: 1, - sample_count: 1, - dimension: wgpu::TextureDimension::D2, - format: format::cef_to_wgpu(self.format)?, - usage: wgpu::TextureUses::COPY_DST | wgpu::TextureUses::RESOURCE, - memory_flags: wgpu::hal::MemoryFlags::empty(), - view_formats: vec![], + format::cef_to_wgpu(self.format)?, + wgpu::TextureDimension::D2, + wgpu::Extent3d { + width: self.width, + height: self.height, + depth_or_array_layers: 1, }, - None, // drop_callback + 1, // mip_level_count + 1, // sample_count ); Ok(hal_texture) @@ -286,7 +279,7 @@ impl D3D11Importer { unsafe { let mut shared_resource: Option = None; d3d12_device - .OpenSharedHandle(windows::Win32::Foundation::HANDLE(self.handle as isize), &ID3D12Resource::IID, &mut shared_resource as *mut _ as *mut _) + .OpenSharedHandle(windows::Win32::Foundation::HANDLE(self.handle), &mut shared_resource) .map_err(|e| TextureImportError::PlatformError { message: format!("Failed to open D3D11 shared handle on D3D12: {:?}", e), })?; diff --git a/desktop/src/main.rs b/desktop/src/main.rs index ba79e9b0e..bc86f1d89 100644 --- a/desktop/src/main.rs +++ b/desktop/src/main.rs @@ -7,7 +7,6 @@ use winit::event_loop::EventLoop; pub(crate) mod consts; mod cef; -use cef::Setup; mod render; @@ -29,21 +28,24 @@ pub(crate) enum CustomEvent { fn main() { tracing_subscriber::fmt().with_env_filter(EnvFilter::from_default_env()).init(); - let cef_context = match cef::Context::::new() { - Ok(c) => c, - Err(cef::SetupError::Subprocess) => exit(0), - Err(cef::SetupError::SubprocessFailed(t)) => { - tracing::error!("Subprocess of type {t} failed"); - exit(1); - } - }; + let cef_context_builder = cef::CefContextBuilder::new(); + + if cef_context_builder.is_sub_process() { + // We are in a CEF subprocess + // This will block until the CEF subprocess quits + let error = cef_context_builder.execute_sub_process(); + tracing::error!("Cef subprocess failed with error: {error}"); + return; + } + + let wgpu_context = futures::executor::block_on(WgpuContext::new()).unwrap(); let event_loop = EventLoop::::with_user_event().build().unwrap(); let (window_size_sender, window_size_receiver) = std::sync::mpsc::channel(); - let wgpu_context = futures::executor::block_on(WgpuContext::new()).unwrap(); - let cef_context = match cef_context.init(cef::CefHandler::new(window_size_receiver, event_loop.create_proxy(), wgpu_context.clone())) { + let cef_handler = cef::CefHandler::new(window_size_receiver, event_loop.create_proxy(), wgpu_context.clone()); + let cef_context = match cef_context_builder.initialize(cef_handler) { Ok(c) => c, Err(cef::InitError::AlreadyRunning) => { tracing::error!("Another instance is already running, Exiting."); @@ -53,11 +55,15 @@ fn main() { tracing::error!("Cef initialization failed with code: {code}"); exit(1); } + Err(cef::InitError::BrowserCreationFailed) => { + tracing::error!("Failed to create CEF browser"); + exit(1); + } }; - tracing::info!("Cef initialized successfully"); + tracing::info!("CEF initialized successfully"); - let mut winit_app = WinitApp::new(cef_context, window_size_sender, wgpu_context, event_loop.create_proxy()); + let mut winit_app = WinitApp::new(Box::new(cef_context), window_size_sender, wgpu_context, event_loop.create_proxy()); event_loop.run_app(&mut winit_app).unwrap(); }