Extract CEF rendered UI into a separate process and crate

This commit is contained in:
Timon
2026-07-08 23:34:00 +00:00
parent 97f8113fe4
commit 5daea64585
79 changed files with 4159 additions and 1257 deletions

View File

@@ -5,7 +5,7 @@ use std::io::Read;
use std::path::PathBuf;
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::mpsc::{Receiver, Sender, SyncSender};
use std::sync::mpsc::{Receiver, SyncSender};
use std::thread;
use std::time::{Duration, Instant};
use winit::application::ApplicationHandler;
@@ -14,8 +14,8 @@ use winit::event::{ButtonSource, ElementState, MouseButton, StartCause, WindowEv
use winit::event_loop::{ActiveEventLoop, ControlFlow, EventLoop};
use winit::window::WindowId;
use crate::cef;
use crate::consts::CEF_MESSAGE_LOOP_MAX_ITERATIONS;
use graphite_desktop_ui::{UiCommand, UiInstance};
use crate::dirs;
use crate::event::{AppEvent, AppEventScheduler};
use crate::persist;
@@ -40,10 +40,8 @@ pub(crate) struct App {
app_event_receiver: Receiver<AppEvent>,
app_event_scheduler: AppEventScheduler,
desktop_wrapper: DesktopWrapper,
cef_context: Box<dyn cef::CefContext>,
cef_schedule: Option<Instant>,
cef_view_info_sender: Sender<cef::ViewInfoUpdate>,
cef_init_successful: bool,
ui: UiInstance,
ui_frame_received: bool,
start_render_sender: SyncSender<()>,
web_communication_initialized: bool,
web_communication_startup_buffer: Vec<Vec<u8>>,
@@ -59,10 +57,8 @@ impl App {
Window::init();
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn new(
cef_context: Box<dyn cef::CefContext>,
cef_view_info_sender: Sender<cef::ViewInfoUpdate>,
ui: UiInstance,
wgpu_context: WgpuContext,
app_event_receiver: Receiver<AppEvent>,
app_event_scheduler: AppEventScheduler,
@@ -117,10 +113,8 @@ impl App {
app_event_receiver,
app_event_scheduler,
desktop_wrapper,
cef_context,
cef_schedule: Some(Instant::now()),
cef_view_info_sender,
cef_init_successful: false,
ui,
ui_frame_received: false,
start_render_sender,
web_communication_initialized: false,
web_communication_startup_buffer: Vec::new(),
@@ -177,16 +171,16 @@ impl App {
}
if is_new_size {
let _ = self.cef_view_info_sender.send(cef::ViewInfoUpdate::Size {
self.ui.send(UiCommand::Resized {
width: size.width,
height: size.height,
});
}
if is_new_scale {
let _ = self.cef_view_info_sender.send(cef::ViewInfoUpdate::Scale(scale));
self.ui.send(UiCommand::ScaleChanged(scale));
}
self.cef_context.notify_view_info_changed();
self.ui.send(UiCommand::Refresh);
if let Some(render_state) = &mut self.render_state {
render_state.resize(size.width, size.height);
@@ -430,7 +424,7 @@ impl App {
fn send_or_queue_web_message(&mut self, message: Vec<u8>) {
if self.web_communication_initialized {
self.cef_context.send_web_message(message);
self.ui.send(UiCommand::Message(message));
} else {
self.web_communication_startup_buffer.push(message);
}
@@ -441,7 +435,7 @@ impl App {
AppEvent::WebCommunicationInitialized => {
self.web_communication_initialized = true;
for message in self.web_communication_startup_buffer.drain(..) {
self.cef_context.send_web_message(message);
self.ui.send(UiCommand::Message(message));
}
}
AppEvent::DesktopWrapperMessage(message) => self.dispatch_desktop_wrapper_message(message),
@@ -465,15 +459,8 @@ impl App {
if let Some(window) = &self.window {
window.request_redraw();
}
if !self.cef_init_successful {
self.cef_init_successful = true;
}
}
AppEvent::ScheduleBrowserWork(instant) => {
if instant <= Instant::now() {
self.cef_context.work();
} else {
self.cef_schedule = Some(instant);
if !self.ui_frame_received {
self.ui_frame_received = true;
}
}
AppEvent::CursorChange(cursor) => {
@@ -485,6 +472,10 @@ impl App {
tracing::info!("Exiting main event loop");
event_loop.exit();
}
AppEvent::UiCrashed => {
tracing::error!("UI process crashed, exiting.");
self.exit(Some(ExitReason::Shutdown));
}
AppEvent::OpenFiles(paths) => {
// Accumulate launch documents until OpenLaunchDocuments message is received
if let Some(launch_documents) = &mut self.launch_documents {
@@ -556,15 +547,15 @@ impl ApplicationHandler for App {
if let Some(window) = &self.window {
window.end_pointer_lock();
}
self.cef_context.handle_window_event(&WindowEvent::PointerMoved {
self.ui.send(UiCommand::Input(WindowEvent::PointerMoved {
device_id: None,
position: pointer_lock_position,
primary: true,
source: winit::event::PointerSource::Mouse,
});
}));
}
self.cef_context.handle_window_event(&event);
self.ui.send(UiCommand::Input(event.clone()));
match event {
WindowEvent::CloseRequested => {
@@ -586,7 +577,7 @@ impl ApplicationHandler for App {
match render_state.render(window) {
Ok(_) => {}
Err(RenderError::OutdatedUITextureError) => {
self.cef_context.notify_view_info_changed();
self.ui.send(UiCommand::Refresh);
}
Err(RenderError::SurfaceLost) => {
tracing::warn!("lost surface");
@@ -596,7 +587,7 @@ impl ApplicationHandler for App {
let _ = self.start_render_sender.try_send(());
}
if !self.cef_init_successful
if !self.ui_frame_received
&& !self.preferences.disable_ui_acceleration
&& self.web_communication_initialized
&& let Some(startup_time) = self.startup_time
@@ -670,9 +661,6 @@ impl ApplicationHandler for App {
_ => {}
}
// Notify cef of possible input events
self.cef_context.work();
}
fn device_event(&mut self, _event_loop: &dyn ActiveEventLoop, _device_id: Option<winit::event::DeviceId>, event: winit::event::DeviceEvent) {
@@ -693,20 +681,7 @@ impl ApplicationHandler for App {
}
fn about_to_wait(&mut self, event_loop: &dyn ActiveEventLoop) {
// Set a timeout in case we miss any cef schedule requests
let mut wait_until = Instant::now() + Duration::from_millis(10);
if let Some(schedule) = self.cef_schedule
&& schedule < Instant::now()
{
self.cef_schedule = None;
// Poll cef message loop multiple times to avoid message loop starvation
for _ in 0..CEF_MESSAGE_LOOP_MAX_ITERATIONS {
self.cef_context.work();
}
} else if let Some(cef_schedule) = self.cef_schedule {
wait_until = wait_until.min(cef_schedule);
}
event_loop.set_control_flow(ControlFlow::WaitUntil(wait_until));
event_loop.set_control_flow(ControlFlow::WaitUntil(Instant::now() + Duration::from_millis(10)));
}
}

View File

@@ -1,245 +0,0 @@
//! CEF (Chromium Embedded Framework) integration for Graphite Desktop
//!
//! This module provides CEF browser integration with hardware-accelerated texture sharing.
//!
//! # Hardware Acceleration
//!
//! The texture import system supports platform-specific hardware acceleration:
//!
//! - **Linux**: DMA-BUF via Vulkan external memory (`accelerated_paint_dmabuf` feature)
//! - **Windows**: D3D11 shared textures via either Vulkan or D3D12 interop (`accelerated_paint_d3d11` feature)
//! - **macOS**: IOSurface via Metal/Vulkan interop (`accelerated_paint_iosurface` feature)
//!
//! The system gracefully falls back to CPU textures when hardware acceleration is unavailable.
use std::fs::File;
use std::io;
use std::io::Read;
use std::path::PathBuf;
use std::sync::mpsc::Receiver;
use std::sync::{Arc, Mutex};
use std::time::Instant;
use crate::event::{AppEvent, AppEventScheduler};
use crate::window::Cursor;
use crate::wrapper::deserialize_editor_message;
mod consts;
mod context;
mod input;
mod internal;
mod ipc;
mod platform;
mod utility;
mod view;
pub(crate) use context::{CefContext, CefContextBuilder, InitError};
pub(crate) use view::View;
pub(crate) trait CefEventHandler: Send + Sync + 'static {
fn view_info(&self) -> ViewInfo;
fn draw(&self, view: &View);
fn load_resource(&self, path: PathBuf) -> Option<Resource>;
fn cursor_change(&self, cursor: Cursor);
/// Schedule the main event loop to run the CEF event loop after the timeout.
/// See [`_cef_browser_process_handler_t::on_schedule_message_pump_work`] for more documentation.
fn schedule_cef_message_loop_work(&self, scheduled_time: Instant);
fn initialized_web_communication(&self);
fn receive_web_message(&self, message: &[u8]);
fn duplicate(&self) -> Self
where
Self: Sized;
}
#[derive(Clone, Copy)]
pub(crate) struct ViewInfo {
width: u32,
height: u32,
scale: f64,
}
impl ViewInfo {
pub(crate) fn new() -> Self {
Self { width: 1, height: 1, scale: 1. }
}
pub(crate) fn apply_update(&mut self, update: ViewInfoUpdate) {
match update {
ViewInfoUpdate::Size { width, height } if width > 0 && height > 0 => {
self.width = width;
self.height = height;
}
ViewInfoUpdate::Scale(scale) if scale > 0. => {
self.scale = scale;
}
_ => {}
}
}
pub(crate) fn zoom(&self) -> f64 {
self.scale.ln() / 1.2_f64.ln()
}
pub(crate) fn width(&self) -> u32 {
self.width
}
pub(crate) fn height(&self) -> u32 {
self.height
}
}
impl Default for ViewInfo {
fn default() -> Self {
Self::new()
}
}
pub(crate) enum ViewInfoUpdate {
Size { width: u32, height: u32 },
Scale(f64),
}
#[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(io::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),
}
}
}
pub(crate) struct CefHandler {
app_event_scheduler: AppEventScheduler,
view_info_receiver: Arc<Mutex<ViewInfoReceiver>>,
}
impl CefHandler {
pub(crate) fn new(app_event_scheduler: AppEventScheduler, view_info_receiver: Receiver<ViewInfoUpdate>) -> Self {
Self {
app_event_scheduler,
view_info_receiver: Arc::new(Mutex::new(ViewInfoReceiver::new(view_info_receiver))),
}
}
}
impl CefEventHandler for CefHandler {
fn view_info(&self) -> ViewInfo {
let Ok(mut guard) = self.view_info_receiver.lock() else {
tracing::error!("Failed to lock view_info_receiver");
return ViewInfo::new();
};
let ViewInfoReceiver { receiver, view_info } = &mut *guard;
for update in receiver.try_iter() {
view_info.apply_update(update);
}
*view_info
}
fn draw(&self, view: &View) {
if let Some(texture) = view.texture() {
self.app_event_scheduler.schedule(AppEvent::UiUpdate(texture));
}
}
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(io::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 cursor_change(&self, cursor: Cursor) {
self.app_event_scheduler.schedule(AppEvent::CursorChange(cursor));
}
fn schedule_cef_message_loop_work(&self, scheduled_time: std::time::Instant) {
self.app_event_scheduler.schedule(AppEvent::ScheduleBrowserWork(scheduled_time));
}
fn initialized_web_communication(&self) {
self.app_event_scheduler.schedule(AppEvent::WebCommunicationInitialized);
}
fn receive_web_message(&self, message: &[u8]) {
let Some(desktop_wrapper_message) = deserialize_editor_message(message) else {
tracing::error!("Failed to deserialize web message");
return;
};
self.app_event_scheduler.schedule(AppEvent::DesktopWrapperMessage(desktop_wrapper_message));
}
fn duplicate(&self) -> Self
where
Self: Sized,
{
Self {
app_event_scheduler: self.app_event_scheduler.clone(),
view_info_receiver: self.view_info_receiver.clone(),
}
}
}
struct ViewInfoReceiver {
view_info: ViewInfo,
receiver: Receiver<ViewInfoUpdate>,
}
impl ViewInfoReceiver {
fn new(receiver: Receiver<ViewInfoUpdate>) -> Self {
Self { view_info: ViewInfo::new(), receiver }
}
}

View File

@@ -1,23 +0,0 @@
use graphite_desktop_wrapper::DOUBLE_CLICK_MILLISECONDS;
use std::time::Duration;
pub(crate) const RESOURCE_SCHEME: &str = "resources";
pub(crate) const RESOURCE_DOMAIN: &str = "resources";
pub(crate) const SCROLL_LINE_HEIGHT: usize = 40;
pub(crate) const SCROLL_LINE_WIDTH: usize = 40;
#[cfg(target_os = "linux")]
pub(crate) const SCROLL_SPEED_X: f32 = 3.;
#[cfg(target_os = "linux")]
pub(crate) const SCROLL_SPEED_Y: f32 = 3.;
#[cfg(not(target_os = "linux"))]
pub(crate) const SCROLL_SPEED_X: f32 = 1.;
#[cfg(not(target_os = "linux"))]
pub(crate) const SCROLL_SPEED_Y: f32 = 1.;
pub(crate) const PINCH_ZOOM_SPEED: f64 = 300.;
pub(crate) const MULTICLICK_TIMEOUT: Duration = Duration::from_millis(DOUBLE_CLICK_MILLISECONDS);
pub(crate) const MULTICLICK_ALLOWED_TRAVEL: usize = 4;

View File

@@ -1,16 +0,0 @@
#[cfg(not(target_os = "macos"))]
mod multithreaded;
mod singlethreaded;
mod builder;
pub(crate) use builder::{CefContextBuilder, InitError};
pub(crate) trait CefContext {
fn work(&mut self);
fn handle_window_event(&mut self, event: &winit::event::WindowEvent);
fn notify_view_info_changed(&self);
fn send_web_message(&self, message: Vec<u8>);
}

View File

@@ -1,221 +0,0 @@
use cef::args::Args;
use cef::sys::{CEF_API_VERSION_LAST, cef_log_severity_t};
use cef::{
App, BrowserSettings, CefString, Client, DictionaryValue, ImplCommandLine, ImplRequestContext, LogSeverity, RequestContextSettings, SchemeHandlerFactory, Settings, WindowInfo, api_hash,
browser_host_create_browser_sync, execute_process,
};
use std::path::Path;
use super::CefContext;
use super::singlethreaded::SingleThreadedCefContext;
use crate::cef::CefEventHandler;
use crate::cef::consts::{RESOURCE_DOMAIN, RESOURCE_SCHEME};
use crate::cef::input::InputState;
use crate::cef::internal::{BrowserProcessAppImpl, BrowserProcessClientImpl, RenderProcessAppImpl, SchemeHandlerFactoryImpl};
use crate::dirs::TempDir;
use crate::wrapper::WgpuContext;
pub(crate) struct CefContextBuilder<H: CefEventHandler> {
pub(crate) args: Args,
pub(crate) is_sub_process: bool,
_marker: std::marker::PhantomData<H>,
}
unsafe impl<H: CefEventHandler> Send for CefContextBuilder<H> {}
impl<H: CefEventHandler> CefContextBuilder<H> {
pub(crate) fn new() -> Self {
Self::new_impl(false)
}
pub(crate) fn new_helper() -> Self {
Self::new_impl(true)
}
fn new_impl(helper: bool) -> Self {
#[cfg(target_os = "macos")]
let _loader = {
let loader = cef::library_loader::LibraryLoader::new(&std::env::current_exe().unwrap(), helper);
assert!(loader.load());
loader
};
#[cfg(not(target_os = "macos"))]
let _ = helper;
let _ = api_hash(CEF_API_VERSION_LAST, 0);
let args = Args::new();
let is_sub_process = args.as_cmd_line().unwrap().has_switch(Some(&"type".into())) == 1;
Self {
args,
is_sub_process,
_marker: std::marker::PhantomData,
}
}
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 process_type = CefString::from(&cmd.switch_value(Some(&"type".into())));
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())
} else {
SetupError::Subprocess
}
}
#[cfg(target_os = "macos")]
pub(crate) fn create(self, event_handler: H, wgpu_context: WgpuContext, disable_gpu_acceleration: bool) -> Result<impl CefContext, InitError> {
let instance_dir = TempDir::new().expect("Failed to create temporary directory for CEF instance");
let accelerated_paint = accelerated_paint(disable_gpu_acceleration);
self.build_inner(&event_handler, instance_dir.as_ref(), accelerated_paint)?;
create_browser(event_handler, wgpu_context, instance_dir, accelerated_paint)
}
#[cfg(not(target_os = "macos"))]
pub(crate) fn create(self, event_handler: H, wgpu_context: WgpuContext, disable_gpu_acceleration: bool) -> Result<impl CefContext, InitError> {
let instance_dir = TempDir::new().expect("Failed to create temporary directory for CEF instance");
let accelerated_paint = accelerated_paint(disable_gpu_acceleration);
self.build_inner(&event_handler, instance_dir.as_ref(), accelerated_paint)?;
super::multithreaded::run_on_ui_thread(move || match create_browser(event_handler, wgpu_context, instance_dir, accelerated_paint) {
Ok(context) => super::multithreaded::CONTEXT.with(|b| *b.borrow_mut() = Some(context)),
Err(e) => panic!("Failed to initialize CEF context: {:?}", e),
});
Ok(super::multithreaded::MultiThreadedCefContextProxy)
}
fn build_inner(self, event_handler: &H, instance_dir: &Path, accelerated_paint: bool) -> Result<(), InitError> {
let mut cef_app = App::new(BrowserProcessAppImpl::new(event_handler.duplicate(), accelerated_paint));
let result = cef::initialize(Some(self.args.as_main_args()), Some(&platform_settings(instance_dir)), Some(&mut cef_app), std::ptr::null_mut());
if result != 1 {
return Err(InitError::InitializationFailureCode(cef::get_exit_code() as u32));
}
Ok(())
}
}
fn accelerated_paint(disable_gpu_acceleration: bool) -> bool {
#[cfg(feature = "accelerated_paint")]
{
!disable_gpu_acceleration && crate::cef::platform::should_enable_hardware_acceleration()
}
#[cfg(not(feature = "accelerated_paint"))]
{
let _ = disable_gpu_acceleration;
false
}
}
fn platform_settings(instance_dir: &Path) -> Settings {
let log_severity = match std::env::var("GRAPHITE_BROWSER_LOG").unwrap_or_default().to_lowercase().as_str() {
"debug" => cef_log_severity_t::LOGSEVERITY_VERBOSE,
"info" => cef_log_severity_t::LOGSEVERITY_INFO,
"warn" => cef_log_severity_t::LOGSEVERITY_WARNING,
"error" => cef_log_severity_t::LOGSEVERITY_ERROR,
"none" => cef_log_severity_t::LOGSEVERITY_DISABLE,
_ => cef_log_severity_t::LOGSEVERITY_FATAL,
};
let base = Settings {
windowless_rendering_enabled: 1,
root_cache_path: instance_dir.to_str().map(CefString::from).unwrap(),
cache_path: "".into(),
disable_signal_handlers: 1,
log_severity: LogSeverity::from(log_severity),
..Default::default()
};
#[cfg(target_os = "macos")]
{
let exe = std::env::current_exe().expect("cannot get current exe path");
let app_root = exe.parent().and_then(|p| p.parent()).expect("bad path structure").parent().expect("bad path structure");
Settings {
main_bundle_path: app_root.to_str().map(CefString::from).unwrap(),
multi_threaded_message_loop: 0,
external_message_pump: 1,
no_sandbox: 1, // GPU helper crashes when running with sandbox
..base
}
}
#[cfg(not(target_os = "macos"))]
Settings {
multi_threaded_message_loop: 1,
#[cfg(target_os = "linux")]
no_sandbox: 1,
..base
}
}
fn create_browser<H: CefEventHandler>(event_handler: H, wgpu_context: WgpuContext, instance_dir: TempDir, accelerated_paint: bool) -> Result<SingleThreadedCefContext, InitError> {
let mut client = Client::new(BrowserProcessClientImpl::new(&event_handler, wgpu_context));
let window_info = WindowInfo {
windowless_rendering_enabled: 1,
#[cfg(feature = "accelerated_paint")]
shared_texture_enabled: accelerated_paint as i32,
..Default::default()
};
let settings = BrowserSettings {
windowless_frame_rate: crate::consts::CEF_WINDOWLESS_FRAME_RATE,
background_color: 0x0,
..Default::default()
};
let Some(mut incognito_request_context) = cef::request_context_create_context(
Some(&RequestContextSettings {
persist_session_cookies: 0,
cache_path: "".into(),
..Default::default()
}),
Option::<&mut cef::RequestContextHandler>::None,
) else {
return Err(InitError::RequestContextCreationFailed);
};
let mut scheme_handler_factory = SchemeHandlerFactory::new(SchemeHandlerFactoryImpl::new(event_handler.duplicate()));
incognito_request_context.clear_scheme_handler_factories();
incognito_request_context.register_scheme_handler_factory(Some(&RESOURCE_SCHEME.into()), Some(&RESOURCE_DOMAIN.into()), Some(&mut scheme_handler_factory));
let url = format!("{RESOURCE_SCHEME}://{RESOURCE_DOMAIN}/");
browser_host_create_browser_sync(
Some(&window_info),
Some(&mut client),
Some(&url.as_str().into()),
Some(&settings),
Option::<&mut DictionaryValue>::None,
Some(&mut incognito_request_context),
)
.map(|browser| SingleThreadedCefContext {
event_handler: Box::new(event_handler),
browser,
input_state: InputState::default(),
_instance_dir: instance_dir,
})
.ok_or_else(|| {
tracing::error!("Failed to create browser");
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: {0}")]
SubprocessFailed(String),
}
#[derive(thiserror::Error, Debug)]
pub(crate) enum InitError {
#[error("Initialization failed with code: {0}")]
InitializationFailureCode(u32),
#[error("Browser creation failed")]
BrowserCreationFailed,
#[error("Request context creation failed")]
RequestContextCreationFailed,
}

View File

@@ -1,73 +0,0 @@
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<Option<SingleThreadedCefContext>> = 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_view_info_changed(&self) {
run_on_ui_thread(move || {
CONTEXT.with(|b| {
if let Some(context) = b.borrow_mut().as_mut() {
context.notify_view_info_changed();
}
});
});
}
fn send_web_message(&self, message: Vec<u8>) {
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) {
// Force dropping underlying context on the UI thread
let (sync_drop_tx, sync_drop_rx) = std::sync::mpsc::channel();
run_on_ui_thread(move || {
drop(CONTEXT.take());
let _ = sync_drop_tx.send(());
});
let _ = sync_drop_rx.recv();
}
}
pub(super) fn run_on_ui_thread<F>(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));
}

View File

@@ -1,61 +0,0 @@
use cef::{Browser, ImplBrowser, ImplBrowserHost};
use winit::event::WindowEvent;
use crate::cef::input::InputState;
use crate::cef::ipc::{MessageType, SendMessage};
use crate::cef::{CefEventHandler, input};
use crate::dirs::TempDir;
use super::CefContext;
pub(super) struct SingleThreadedCefContext {
pub(super) event_handler: Box<dyn CefEventHandler>,
pub(super) browser: Browser,
pub(super) input_state: InputState,
pub(super) _instance_dir: TempDir,
}
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_view_info_changed(&self) {
let view_info = self.event_handler.view_info();
let host = self.browser.host().unwrap();
host.set_zoom_level(view_info.zoom());
host.was_resized();
// Fix for CEF not updating the view after resize
// TODO: remove once https://github.com/chromiumembedded/cef/issues/3822 is fixed
host.invalidate(cef::PaintElementType::default());
}
fn send_web_message(&self, message: Vec<u8>) {
self.send_message(MessageType::SendToJS, &message);
}
}
impl Drop for SingleThreadedCefContext {
fn drop(&mut self) {
tracing::debug!("Shutting down CEF");
// CEF wants us to close the browser before shutting down, otherwise it may run longer that necessary.
self.browser.host().unwrap().close_browser(1);
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);
}
}

View File

@@ -1,149 +0,0 @@
use cef::sys::{cef_key_event_type_t, cef_mouse_button_type_t};
use cef::{Browser, ImplBrowser, ImplBrowserHost, KeyEvent, MouseEvent};
use winit::event::{ButtonSource, ElementState, MouseButton, MouseScrollDelta, WindowEvent};
mod keymap;
use keymap::{ToCharRepresentation, ToNativeKeycode, ToVKBits};
mod state;
pub(crate) use state::{CefModifiers, InputState};
use super::consts::{PINCH_ZOOM_SPEED, SCROLL_LINE_HEIGHT, SCROLL_LINE_WIDTH, SCROLL_SPEED_X, SCROLL_SPEED_Y};
pub(crate) fn handle_window_event(browser: &Browser, input_state: &mut InputState, event: &WindowEvent) {
match event {
WindowEvent::PointerMoved { position, .. } | WindowEvent::PointerEntered { position, .. } => {
if !input_state.cursor_move(position) {
return;
}
let Some(host) = browser.host() else { return };
host.send_mouse_move_event(Some(&input_state.into()), 0);
}
WindowEvent::PointerLeft { position, .. } => {
if let Some(position) = position {
let _ = input_state.cursor_move(position);
}
let Some(host) = browser.host() else { return };
host.send_mouse_move_event(Some(&(input_state.into())), 1);
}
WindowEvent::PointerButton { state, button, .. } => {
let mouse_button = match button {
ButtonSource::Mouse(mouse_button) => mouse_button,
_ => {
return; // TODO: Handle touch input
}
};
let cef_click_count = input_state.mouse_input(mouse_button, state).into();
let cef_mouse_up = match state {
ElementState::Pressed => 0,
ElementState::Released => 1,
};
let cef_button = match mouse_button {
MouseButton::Left => cef::MouseButtonType::from(cef_mouse_button_type_t::MBT_LEFT),
MouseButton::Right => cef::MouseButtonType::from(cef_mouse_button_type_t::MBT_RIGHT),
MouseButton::Middle => cef::MouseButtonType::from(cef_mouse_button_type_t::MBT_MIDDLE),
_ => return,
};
let Some(host) = browser.host() else { return };
host.send_mouse_click_event(Some(&input_state.into()), cef_button, cef_mouse_up, cef_click_count);
}
WindowEvent::MouseWheel { delta, phase: _, device_id: _, .. } => {
let mouse_event = input_state.into();
let (mut delta_x, mut delta_y) = match delta {
MouseScrollDelta::LineDelta(x, y) => (x * SCROLL_LINE_WIDTH as f32, y * SCROLL_LINE_HEIGHT as f32),
MouseScrollDelta::PixelDelta(physical_position) => (physical_position.x as f32, physical_position.y as f32),
};
delta_x *= SCROLL_SPEED_X;
delta_y *= SCROLL_SPEED_Y;
let Some(host) = browser.host() else { return };
host.send_mouse_wheel_event(Some(&mouse_event), delta_x as i32, delta_y as i32);
}
WindowEvent::ModifiersChanged(modifiers) => {
input_state.modifiers_changed(&modifiers.state());
}
WindowEvent::KeyboardInput { device_id: _, event, is_synthetic: _ } => {
let Some(host) = browser.host() else { return };
input_state.modifiers_apply_key_event(&event.logical_key, &event.state);
let mut key_event = KeyEvent {
type_: match (event.state, &event.logical_key) {
(ElementState::Pressed, winit::keyboard::Key::Character(_)) => cef_key_event_type_t::KEYEVENT_CHAR,
(ElementState::Pressed, _) => cef_key_event_type_t::KEYEVENT_RAWKEYDOWN,
(ElementState::Released, _) => cef_key_event_type_t::KEYEVENT_KEYUP,
}
.into(),
..Default::default()
};
key_event.modifiers = input_state.cef_modifiers(&event.location, event.repeat).into();
key_event.windows_key_code = match &event.logical_key {
winit::keyboard::Key::Named(named) => named.to_vk_bits(),
winit::keyboard::Key::Character(char) => char.chars().next().unwrap_or_default().to_vk_bits(),
_ => 0,
};
key_event.native_key_code = event.physical_key.to_native_keycode();
key_event.character = event.logical_key.to_char_representation() as u16;
if event.state == ElementState::Pressed && key_event.character != 0 {
key_event.type_ = cef_key_event_type_t::KEYEVENT_CHAR.into();
}
// Mitigation for CEF on Mac bug to prevent NSMenu being triggered by this key event.
//
// CEF converts the key event into an `NSEvent` internally and passes that to Chromium.
// In some cases the `NSEvent` gets to the native Cocoa application, is considered "unhandled" and can trigger menus.
//
// Why mitigation works:
// Leaving `key_event.unmodified_character = 0` still leads to CEF forwarding a "unhandled" event to the native application
// but that event is discarded because `key_event.unmodified_character = 0` is considered non-printable and not used for shortcut matching.
//
// See https://github.com/chromiumembedded/cef/issues/3857
//
// TODO: Remove mitigation once bug is fixed or a better solution is found.
#[cfg(not(target_os = "macos"))]
{
key_event.unmodified_character = event.key_without_modifiers.to_char_representation() as u16;
}
#[cfg(target_os = "macos")] // See https://www.magpcss.org/ceforum/viewtopic.php?start=10&t=11650
if key_event.character == 0 && key_event.unmodified_character == 0 && event.text_with_all_modifiers.is_some() {
key_event.character = 1;
}
if key_event.type_ == cef_key_event_type_t::KEYEVENT_CHAR.into() {
let mut key_down_event = key_event.clone();
key_down_event.type_ = cef_key_event_type_t::KEYEVENT_RAWKEYDOWN.into();
host.send_key_event(Some(&key_down_event));
key_event.windows_key_code = event.logical_key.to_char_representation() as i32;
}
host.send_key_event(Some(&key_event));
}
WindowEvent::PinchGesture { delta, .. } => {
if !delta.is_normal() {
return;
}
let Some(host) = browser.host() else { return };
let mouse_event = MouseEvent {
modifiers: CefModifiers::PINCH_MODIFIERS.into(),
..input_state.into()
};
let delta = (delta * PINCH_ZOOM_SPEED).round() as i32;
host.send_mouse_wheel_event(Some(&mouse_event), 0, delta);
}
_ => {}
}
}

View File

@@ -1,275 +0,0 @@
use winit::keyboard::{Key, NamedKey, PhysicalKey};
pub(crate) trait ToCharRepresentation {
fn to_char_representation(&self) -> char;
}
impl ToCharRepresentation for Key {
fn to_char_representation(&self) -> char {
match self {
Key::Named(named) => match named {
NamedKey::Tab => '\t',
NamedKey::Enter => '\r',
NamedKey::Backspace => '\x08',
NamedKey::Escape => '\x1b',
_ => '\0',
},
Key::Character(char) => char.chars().next().unwrap_or_default(),
_ => '\0',
}
}
}
pub(crate) trait ToNativeKeycode {
fn to_native_keycode(&self) -> i32;
}
impl ToNativeKeycode for PhysicalKey {
fn to_native_keycode(&self) -> i32 {
use winit::platform::scancode::PhysicalKeyExtScancode;
#[cfg(target_os = "linux")]
{
self.to_scancode().map(|evdev| (evdev + 8) as i32).unwrap_or_default()
}
#[cfg(any(target_os = "macos", target_os = "windows"))]
{
self.to_scancode().map(|c| c as i32).unwrap_or_default()
}
}
}
pub(crate) trait ToVKBits {
fn to_vk_bits(&self) -> i32;
}
macro_rules! map_enum {
($target:expr, $enum:ident, $( ($code:expr, $variant:ident), )+ ) => {
match $target {
$(
$enum::$variant => $code,
)+
_ => 0,
}
};
}
impl ToVKBits for winit::keyboard::NamedKey {
fn to_vk_bits(&self) -> i32 {
map_enum!(
self,
NamedKey,
(0x12, Alt),
(0xA5, AltGraph),
(0x14, CapsLock),
(0x11, Control),
(0x90, NumLock),
(0x91, ScrollLock),
(0x10, Shift),
(0x5B, Meta),
(0x0D, Enter),
(0x09, Tab),
(0x25, ArrowLeft),
(0x26, ArrowUp),
(0x27, ArrowRight),
(0x28, ArrowDown),
(0x23, End),
(0x24, Home),
(0x22, PageDown),
(0x21, PageUp),
(0x08, Backspace),
(0x0C, Clear),
(0xF7, CrSel),
(0x2E, Delete),
(0xF9, EraseEof),
(0xF8, ExSel),
(0x2D, Insert),
(0x1E, Accept),
(0xF6, Attn),
(0x03, Cancel),
(0x5D, ContextMenu),
(0x1B, Escape),
(0x2B, Execute),
(0x2F, Help),
(0x13, Pause),
(0xFA, Play),
(0x5D, Props),
(0x29, Select),
(0xFB, ZoomIn),
(0xFB, ZoomOut),
(0x2C, PrintScreen),
(0x5F, Standby),
(0x1C, Convert),
(0x18, FinalMode),
(0x1F, ModeChange),
(0x1D, NonConvert),
(0xE5, Process),
(0x15, HangulMode),
(0x19, HanjaMode),
(0x17, JunjaMode),
(0x15, KanaMode),
(0x19, KanjiMode),
(0xB0, MediaFastForward),
(0xB3, MediaPause),
(0xB3, MediaPlay),
(0xB3, MediaPlayPause),
(0xB1, MediaRewind),
(0xB2, MediaStop),
(0xB0, MediaTrackNext),
(0xB1, MediaTrackPrevious),
(0x2A, Print),
(0xAE, AudioVolumeDown),
(0xAF, AudioVolumeUp),
(0xAD, AudioVolumeMute),
(0xB6, LaunchApplication1),
(0xB7, LaunchApplication2),
(0xB4, LaunchMail),
(0xB5, LaunchMediaPlayer),
(0xB5, LaunchMusicPlayer),
(0xA6, BrowserBack),
(0xAB, BrowserFavorites),
(0xA7, BrowserForward),
(0xAC, BrowserHome),
(0xA8, BrowserRefresh),
(0xAA, BrowserSearch),
(0xA9, BrowserStop),
(0xFB, ZoomToggle),
(0x70, F1),
(0x71, F2),
(0x72, F3),
(0x73, F4),
(0x74, F5),
(0x75, F6),
(0x76, F7),
(0x77, F8),
(0x78, F9),
(0x79, F10),
(0x7A, F11),
(0x7B, F12),
(0x7C, F13),
(0x7D, F14),
(0x7E, F15),
(0x7F, F16),
(0x80, F17),
(0x81, F18),
(0x82, F19),
(0x83, F20),
(0x84, F21),
(0x85, F22),
(0x86, F23),
(0x87, F24),
)
}
}
macro_rules! map {
($target:expr, $( ($code:expr, $variant:literal), )+ ) => {
match $target {
$(
$variant => $code,
)+
_ => 0,
}
};
}
impl ToVKBits for char {
fn to_vk_bits(&self) -> i32 {
map!(
self,
(0x41, 'a'),
(0x42, 'b'),
(0x43, 'c'),
(0x44, 'd'),
(0x45, 'e'),
(0x46, 'f'),
(0x47, 'g'),
(0x48, 'h'),
(0x49, 'i'),
(0x4a, 'j'),
(0x4b, 'k'),
(0x4c, 'l'),
(0x4d, 'm'),
(0x4e, 'n'),
(0x4f, 'o'),
(0x50, 'p'),
(0x51, 'q'),
(0x52, 'r'),
(0x53, 's'),
(0x54, 't'),
(0x55, 'u'),
(0x56, 'v'),
(0x57, 'w'),
(0x58, 'x'),
(0x59, 'y'),
(0x5a, 'z'),
(0x41, 'A'),
(0x42, 'B'),
(0x43, 'C'),
(0x44, 'D'),
(0x45, 'E'),
(0x46, 'F'),
(0x47, 'G'),
(0x48, 'H'),
(0x49, 'I'),
(0x4a, 'J'),
(0x4b, 'K'),
(0x4c, 'L'),
(0x4d, 'M'),
(0x4e, 'N'),
(0x4f, 'O'),
(0x50, 'P'),
(0x51, 'Q'),
(0x52, 'R'),
(0x53, 'S'),
(0x54, 'T'),
(0x55, 'U'),
(0x56, 'V'),
(0x57, 'W'),
(0x58, 'X'),
(0x59, 'Y'),
(0x5a, 'Z'),
(0x31, '1'),
(0x32, '2'),
(0x33, '3'),
(0x34, '4'),
(0x35, '5'),
(0x36, '6'),
(0x37, '7'),
(0x38, '8'),
(0x39, '9'),
(0x30, '0'),
(0x31, '!'),
(0x32, '@'),
(0x33, '#'),
(0x34, '$'),
(0x35, '%'),
(0x36, '^'),
(0x37, '&'),
(0x38, '*'),
(0x39, '('),
(0x30, ')'),
(0xC0, '`'),
(0xC0, '~'),
(0xBD, '-'),
(0xBD, '_'),
(0xBB, '='),
(0xBB, '+'),
(0xDB, '['),
(0xDB, '{'),
(0xDD, ']'),
(0xDD, '}'),
(0xDC, '\\'),
(0xDC, '|'),
(0xBA, ';'),
(0xBA, ':'),
(0xBC, ','),
(0xBC, '<'),
(0xBE, '.'),
(0xBE, '>'),
(0xDE, '\''),
(0xDE, '"'),
(0xBF, '/'),
(0xBF, '?'),
(0x20, ' '),
)
}
}

View File

@@ -1,272 +0,0 @@
use cef::MouseEvent;
use cef::sys::cef_event_flags_t;
use std::time::Instant;
use winit::dpi::PhysicalPosition;
use winit::event::{ElementState, MouseButton};
use winit::keyboard::{Key, KeyLocation, ModifiersState, NamedKey};
use crate::cef::consts::{MULTICLICK_ALLOWED_TRAVEL, MULTICLICK_TIMEOUT};
#[derive(Default)]
pub(crate) struct InputState {
modifiers: ModifiersState,
mouse_position: MousePosition,
mouse_state: MouseState,
mouse_click_tracker: ClickTracker,
}
impl InputState {
pub(crate) fn modifiers_changed(&mut self, modifiers: &ModifiersState) {
self.modifiers = *modifiers;
}
pub(crate) fn modifiers_apply_key_event(&mut self, key: &Key, state: &ElementState) {
let bits = match key {
Key::Named(NamedKey::Shift) => ModifiersState::SHIFT,
Key::Named(NamedKey::Control) => ModifiersState::CONTROL,
Key::Named(NamedKey::Alt) => ModifiersState::ALT,
Key::Named(NamedKey::Meta) => ModifiersState::META,
_ => return,
};
let is_pressed = matches!(state, ElementState::Pressed);
self.modifiers.set(bits, is_pressed);
}
pub(crate) fn cursor_move(&mut self, position: &PhysicalPosition<f64>) -> bool {
let new = position.into();
if self.mouse_position == new {
return false;
}
self.mouse_position = new;
true
}
pub(crate) fn mouse_input(&mut self, button: &MouseButton, state: &ElementState) -> ClickCount {
self.mouse_state.update(button, state);
self.mouse_click_tracker.input(button, state, self.mouse_position)
}
pub(crate) fn cef_modifiers(&self, location: &KeyLocation, is_repeat: bool) -> CefModifiers {
CefModifiers::new(self, location, is_repeat)
}
pub(crate) fn cef_mouse_modifiers(&self) -> CefModifiers {
self.cef_modifiers(&KeyLocation::Standard, false)
}
}
impl From<InputState> for CefModifiers {
fn from(val: InputState) -> Self {
CefModifiers::new(&val, &KeyLocation::Standard, false)
}
}
impl From<&InputState> for MouseEvent {
fn from(val: &InputState) -> Self {
MouseEvent {
x: val.mouse_position.x as i32,
y: val.mouse_position.y as i32,
modifiers: val.cef_mouse_modifiers().into(),
}
}
}
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_mouse_modifiers().into(),
}
}
}
#[derive(Default, Clone, Copy, Eq, PartialEq)]
pub(crate) struct MousePosition {
x: usize,
y: usize,
}
impl From<&PhysicalPosition<f64>> for MousePosition {
fn from(position: &PhysicalPosition<f64>) -> Self {
Self {
x: position.x as usize,
y: position.y as usize,
}
}
}
#[derive(Default, Clone)]
pub(crate) struct MouseState {
left: bool,
right: bool,
middle: bool,
}
impl MouseState {
pub(crate) fn update(&mut self, button: &MouseButton, state: &ElementState) {
match state {
ElementState::Pressed => match button {
MouseButton::Left => self.left = true,
MouseButton::Right => self.right = true,
MouseButton::Middle => self.middle = true,
_ => {}
},
ElementState::Released => match button {
MouseButton::Left => self.left = false,
MouseButton::Right => self.right = false,
MouseButton::Middle => self.middle = false,
_ => {}
},
}
}
}
#[derive(Default)]
struct ClickTracker {
left: Option<ClickRecord>,
middle: Option<ClickRecord>,
right: Option<ClickRecord>,
}
impl ClickTracker {
fn input(&mut self, button: &MouseButton, state: &ElementState, position: MousePosition) -> ClickCount {
let record = match button {
MouseButton::Left => &mut self.left,
MouseButton::Right => &mut self.right,
MouseButton::Middle => &mut self.middle,
_ => return ClickCount::Single,
};
let Some(record) = record else {
*record = Some(ClickRecord {
down_position: position,
up_position: position,
..Default::default()
});
return ClickCount::Single;
};
let now = Instant::now();
let within_time = now.saturating_duration_since(record.time) <= MULTICLICK_TIMEOUT;
let (prev_count, prev_position) = match state {
ElementState::Pressed => (record.down_count, record.down_position),
ElementState::Released => (record.up_count, record.up_position),
};
let dx = position.x.abs_diff(prev_position.x);
let dy = position.y.abs_diff(prev_position.y);
let within_dist = dx <= MULTICLICK_ALLOWED_TRAVEL && dy <= MULTICLICK_ALLOWED_TRAVEL;
let count = match (prev_count, within_time, within_dist) {
(ClickCount::Single, true, true) => ClickCount::Double,
(ClickCount::Double, true, true) => ClickCount::Triple,
(ClickCount::Triple, true, true) => ClickCount::Double,
_ => ClickCount::Single,
};
record.time = now;
match state {
ElementState::Pressed => {
record.down_position = position;
record.down_count = count;
}
ElementState::Released => {
record.up_position = position;
record.up_count = count;
}
}
count
}
}
#[derive(Clone, Copy, PartialEq, Default)]
pub(crate) enum ClickCount {
#[default]
Single,
Double,
Triple,
}
impl From<ClickCount> for i32 {
fn from(count: ClickCount) -> i32 {
match count {
ClickCount::Single => 1,
ClickCount::Double => 2,
ClickCount::Triple => 3,
}
}
}
#[derive(Clone, Copy)]
struct ClickRecord {
time: Instant,
down_position: MousePosition,
up_position: MousePosition,
down_count: ClickCount,
up_count: ClickCount,
}
impl Default for ClickRecord {
fn default() -> Self {
Self {
time: Instant::now(),
down_position: Default::default(),
up_position: Default::default(),
down_count: Default::default(),
up_count: Default::default(),
}
}
}
pub(crate) struct CefModifiers(cef_event_flags_t);
impl CefModifiers {
fn new(input_state: &InputState, location: &KeyLocation, is_repeat: bool) -> Self {
let mut inner = cef_event_flags_t::EVENTFLAG_NONE;
if input_state.modifiers.shift_key() {
inner |= cef_event_flags_t::EVENTFLAG_SHIFT_DOWN;
}
if input_state.modifiers.control_key() {
inner |= cef_event_flags_t::EVENTFLAG_CONTROL_DOWN;
}
if input_state.modifiers.alt_key() {
inner |= cef_event_flags_t::EVENTFLAG_ALT_DOWN;
}
if input_state.modifiers.meta_key() {
inner |= cef_event_flags_t::EVENTFLAG_COMMAND_DOWN;
}
if input_state.mouse_state.left {
inner |= cef_event_flags_t::EVENTFLAG_LEFT_MOUSE_BUTTON;
}
if input_state.mouse_state.right {
inner |= cef_event_flags_t::EVENTFLAG_RIGHT_MOUSE_BUTTON;
}
if input_state.mouse_state.middle {
inner |= cef_event_flags_t::EVENTFLAG_MIDDLE_MOUSE_BUTTON;
}
if is_repeat {
inner |= cef_event_flags_t::EVENTFLAG_IS_REPEAT;
}
inner |= match location {
KeyLocation::Left => cef_event_flags_t::EVENTFLAG_IS_LEFT,
KeyLocation::Right => cef_event_flags_t::EVENTFLAG_IS_RIGHT,
KeyLocation::Numpad => cef_event_flags_t::EVENTFLAG_IS_KEY_PAD,
KeyLocation::Standard => cef_event_flags_t::EVENTFLAG_NONE,
};
Self(inner)
}
pub(super) const PINCH_MODIFIERS: Self = Self(cef_event_flags_t(
cef_event_flags_t::EVENTFLAG_CONTROL_DOWN.0 | cef_event_flags_t::EVENTFLAG_PRECISION_SCROLLING_DELTA.0,
));
}
impl From<CefModifiers> for u32 {
fn from(val: CefModifiers) -> Self {
#[cfg(not(target_os = "windows"))]
return val.0.0;
#[cfg(target_os = "windows")]
return val.0.0 as u32;
}
}

View File

@@ -1,26 +0,0 @@
mod browser_process_app;
mod browser_process_client;
mod browser_process_handler;
mod render_process_app;
mod render_process_handler;
mod render_process_v8_handler;
mod context_menu_handler;
mod display_handler;
mod life_span_handler;
mod load_handler;
mod request_handler;
mod resource_handler;
mod resource_request_handler;
mod scheme_handler_factory;
pub(super) mod render_handler;
#[cfg(not(target_os = "macos"))]
pub(super) mod task;
pub(super) use browser_process_app::BrowserProcessAppImpl;
pub(super) use browser_process_client::BrowserProcessClientImpl;
pub(super) use render_process_app::RenderProcessAppImpl;
pub(super) use scheme_handler_factory::SchemeHandlerFactoryImpl;

View File

@@ -1,138 +0,0 @@
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 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>,
event_handler: H,
accelerated_paint: bool,
}
impl<H: CefEventHandler> BrowserProcessAppImpl<H> {
pub(crate) fn new(event_handler: H, accelerated_paint: bool) -> Self {
Self {
object: std::ptr::null_mut(),
event_handler,
accelerated_paint,
}
}
}
impl<H: CefEventHandler> ImplApp for BrowserProcessAppImpl<H> {
fn browser_process_handler(&self) -> Option<BrowserProcessHandler> {
Some(BrowserProcessHandler::new(BrowserProcessHandlerImpl::new(self.event_handler.duplicate())))
}
fn on_register_custom_schemes(&self, registrar: Option<&mut SchemeRegistrar>) {
SchemeHandlerFactoryImpl::<H>::register_schemes(registrar);
}
fn on_before_command_line_processing(&self, _process_type: Option<&cef::CefString>, command_line: Option<&mut cef::CommandLine>) {
if let Some(cmd) = command_line {
cmd.append_switch_with_value(Some(&"renderer-process-limit".into()), Some(&"1".into()));
cmd.append_switch_with_value(Some(&"password-store".into()), Some(&"basic".into()));
cmd.append_switch_with_value(Some(&"disk-cache-size".into()), Some(&"0".into()));
cmd.append_switch(Some(&"no-sandbox".into()));
cmd.append_switch(Some(&"no-first-run".into()));
cmd.append_switch(Some(&"noerrdialogs".into()));
cmd.append_switch(Some(&"no-default-browser-check".into()));
cmd.append_switch(Some(&"mute-audio".into()));
cmd.append_switch(Some(&"use-fake-device-for-media-stream".into()));
cmd.append_switch(Some(&"incognito".into()));
cmd.append_switch(Some(&"disable-sync".into()));
cmd.append_switch(Some(&"disable-file-system".into()));
cmd.append_switch(Some(&"disable-component-update".into()));
cmd.append_switch(Some(&"disable-geolocation".into()));
cmd.append_switch(Some(&"disable-notifications".into()));
cmd.append_switch(Some(&"disable-background-networking".into()));
cmd.append_switch(Some(&"disable-default-apps".into()));
cmd.append_switch(Some(&"disable-breakpad".into()));
cmd.append_switch_with_value(Some(&"disable-blink-features".into()), Some(&"WebBluetooth,WebUSB,Serial".into()));
let extra_disabled_features = ["OptimizationHints", "OnDeviceModelService", "TranslateUI"];
let disabled_features_switch = Some(&"disable-features".into());
let mut disabled_features: Vec<String> = CefString::from(&cmd.switch_value(disabled_features_switch))
.to_string()
.split(',')
.filter(|feature| !feature.is_empty())
.map(ToOwned::to_owned)
.collect();
disabled_features.extend(extra_disabled_features.into_iter().map(ToOwned::to_owned));
cmd.append_switch_with_value(disabled_features_switch, Some(&disabled_features.join(",").as_str().into()));
if self.accelerated_paint {
cmd.append_switch(Some(&"enable-gpu".into()));
cmd.append_switch(Some(&"enable-gpu-compositing".into()));
cmd.append_switch(Some(&"enable-begin-frame-scheduling".into()));
cmd.append_switch(Some(&"off-screen-rendering-enabled".into()));
cmd.append_switch(Some(&"enable-accelerated-2d-canvas".into()));
#[cfg(target_os = "linux")]
{
cmd.append_switch_with_value(Some(&"use-angle".into()), Some(&"gl-egl".into()));
let use_wayland = std::env::var("WAYLAND_DISPLAY")
.ok()
.filter(|var| !var.is_empty())
.or_else(|| std::env::var("WAYLAND_SOCKET").ok())
.filter(|var| !var.is_empty())
.is_some();
if use_wayland {
cmd.append_switch_with_value(Some(&"ozone-platform".into()), Some(&"wayland".into()));
}
}
} else {
cmd.append_switch(Some(&"disable-gpu".into()));
cmd.append_switch(Some(&"disable-gpu-compositing".into()));
}
#[cfg(target_os = "macos")]
{
// Hide user prompt asking for keychain access
cmd.append_switch(Some(&"use-mock-keychain".into()));
}
// Enable browser debugging via environment variable
if let Some(env) = std::env::var("GRAPHITE_BROWSER_DEBUG_PORT").ok()
&& let Some(port) = env.parse::<u16>().ok()
{
cmd.append_switch_with_value(Some(&"remote-debugging-port".into()), Some(&port.to_string().as_str().into()));
cmd.append_switch_with_value(Some(&"remote-allow-origins".into()), Some(&"*".into()));
}
}
}
fn get_raw(&self) -> *mut _cef_app_t {
self.object.cast()
}
}
impl<H: CefEventHandler> Clone for BrowserProcessAppImpl<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.duplicate(),
accelerated_paint: self.accelerated_paint,
}
}
}
impl<H: CefEventHandler> Rc for BrowserProcessAppImpl<H> {
fn as_base(&self) -> &cef_base_ref_counted_t {
unsafe {
let base = &*self.object;
std::mem::transmute(&base.cef_object)
}
}
}
impl<H: CefEventHandler> WrapApp for BrowserProcessAppImpl<H> {
fn wrap_rc(&mut self, object: *mut RcImpl<_cef_app_t, Self>) {
self.object = object;
}
}

View File

@@ -1,121 +0,0 @@
use cef::rc::{Rc, RcImpl};
use cef::sys::{_cef_client_t, cef_base_ref_counted_t};
use cef::{ContextMenuHandler, DisplayHandler, ImplClient, LifeSpanHandler, LoadHandler, RenderHandler, RequestHandler, WrapClient};
use crate::cef::CefEventHandler;
use crate::cef::ipc::{MessageType, UnpackMessage, UnpackedMessage};
use crate::wrapper::WgpuContext;
use super::context_menu_handler::ContextMenuHandlerImpl;
use super::display_handler::DisplayHandlerImpl;
use super::life_span_handler::LifeSpanHandlerImpl;
use super::load_handler::LoadHandlerImpl;
use super::render_handler::RenderHandlerImpl;
use super::request_handler::RequestHandlerImpl;
pub(crate) struct BrowserProcessClientImpl<H: CefEventHandler> {
object: *mut RcImpl<_cef_client_t, Self>,
event_handler: H,
load_handler: LoadHandler,
render_handler: RenderHandler,
display_handler: DisplayHandler,
request_handler: RequestHandler,
}
impl<H: CefEventHandler> BrowserProcessClientImpl<H> {
pub(crate) fn new(event_handler: &H, wgpu_context: WgpuContext) -> Self {
Self {
object: std::ptr::null_mut(),
event_handler: event_handler.duplicate(),
load_handler: LoadHandler::new(LoadHandlerImpl::new(event_handler.duplicate())),
render_handler: RenderHandler::new(RenderHandlerImpl::new(event_handler.duplicate(), wgpu_context)),
display_handler: DisplayHandler::new(DisplayHandlerImpl::new(event_handler.duplicate())),
request_handler: RequestHandler::new(RequestHandlerImpl::new()),
}
}
}
impl<H: CefEventHandler> ImplClient for BrowserProcessClientImpl<H> {
fn on_process_message_received(
&self,
_browser: Option<&mut cef::Browser>,
_frame: Option<&mut cef::Frame>,
_source_process: cef::ProcessId,
message: Option<&mut cef::ProcessMessage>,
) -> std::ffi::c_int {
let unpacked_message = unsafe { message.and_then(|m| m.unpack()) };
match unpacked_message {
Some(UnpackedMessage {
message_type: MessageType::Initialized,
data: _,
}) => self.event_handler.initialized_web_communication(),
Some(UnpackedMessage {
message_type: MessageType::SendToNative,
data,
}) => self.event_handler.receive_web_message(data),
_ => {
tracing::error!("Unexpected message type received in browser process");
return 0;
}
}
1
}
fn load_handler(&self) -> Option<cef::LoadHandler> {
Some(self.load_handler.clone())
}
fn render_handler(&self) -> Option<RenderHandler> {
Some(self.render_handler.clone())
}
fn life_span_handler(&self) -> Option<cef::LifeSpanHandler> {
Some(LifeSpanHandler::new(LifeSpanHandlerImpl::new()))
}
fn display_handler(&self) -> Option<cef::DisplayHandler> {
Some(self.display_handler.clone())
}
fn request_handler(&self) -> Option<cef::RequestHandler> {
Some(self.request_handler.clone())
}
fn context_menu_handler(&self) -> Option<cef::ContextMenuHandler> {
Some(ContextMenuHandler::new(ContextMenuHandlerImpl::new()))
}
fn get_raw(&self) -> *mut _cef_client_t {
self.object.cast()
}
}
impl<H: CefEventHandler> Clone for BrowserProcessClientImpl<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.duplicate(),
load_handler: self.load_handler.clone(),
render_handler: self.render_handler.clone(),
display_handler: self.display_handler.clone(),
request_handler: self.request_handler.clone(),
}
}
}
impl<H: CefEventHandler> Rc for BrowserProcessClientImpl<H> {
fn as_base(&self) -> &cef_base_ref_counted_t {
unsafe {
let base = &*self.object;
std::mem::transmute(&base.cef_object)
}
}
}
impl<H: CefEventHandler> WrapClient for BrowserProcessClientImpl<H> {
fn wrap_rc(&mut self, object: *mut RcImpl<_cef_client_t, Self>) {
self.object = object;
}
}

View File

@@ -1,60 +0,0 @@
use std::time::{Duration, Instant};
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, WrapBrowserProcessHandler};
use crate::cef::CefEventHandler;
pub(crate) struct BrowserProcessHandlerImpl<H: CefEventHandler> {
object: *mut RcImpl<cef_browser_process_handler_t, Self>,
event_handler: H,
}
impl<H: CefEventHandler> BrowserProcessHandlerImpl<H> {
pub(crate) fn new(event_handler: H) -> Self {
Self {
object: std::ptr::null_mut(),
event_handler,
}
}
}
impl<H: CefEventHandler> ImplBrowserProcessHandler for BrowserProcessHandlerImpl<H> {
fn on_schedule_message_pump_work(&self, delay_ms: i64) {
self.event_handler.schedule_cef_message_loop_work(Instant::now() + Duration::from_millis(delay_ms as u64));
}
fn on_already_running_app_relaunch(&self, _command_line: Option<&mut cef::CommandLine>, _current_directory: Option<&CefString>) -> std::ffi::c_int {
1 // Return 1 to prevent default behavior of opening a empty browser window
}
fn get_raw(&self) -> *mut _cef_browser_process_handler_t {
self.object.cast()
}
}
impl<H: CefEventHandler> Clone for BrowserProcessHandlerImpl<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.duplicate(),
}
}
}
impl<H: CefEventHandler> Rc for BrowserProcessHandlerImpl<H> {
fn as_base(&self) -> &cef_base_ref_counted_t {
unsafe {
let base = &*self.object;
std::mem::transmute(&base.cef_object)
}
}
}
impl<H: CefEventHandler> WrapBrowserProcessHandler for BrowserProcessHandlerImpl<H> {
fn wrap_rc(&mut self, object: *mut RcImpl<_cef_browser_process_handler_t, Self>) {
self.object = object;
}
}

View File

@@ -1,66 +0,0 @@
use cef::rc::{Rc, RcImpl};
use cef::sys::{_cef_context_menu_handler_t, cef_base_ref_counted_t};
use cef::{ImplContextMenuHandler, WrapContextMenuHandler};
pub(crate) struct ContextMenuHandlerImpl {
object: *mut RcImpl<_cef_context_menu_handler_t, Self>,
}
impl ContextMenuHandlerImpl {
pub(crate) fn new() -> Self {
Self { object: std::ptr::null_mut() }
}
}
impl ImplContextMenuHandler for ContextMenuHandlerImpl {
fn run_context_menu(
&self,
_browser: Option<&mut cef::Browser>,
_frame: Option<&mut cef::Frame>,
_params: Option<&mut cef::ContextMenuParams>,
_model: Option<&mut cef::MenuModel>,
_callback: Option<&mut cef::RunContextMenuCallback>,
) -> std::ffi::c_int {
// Prevent context menu
1
}
fn run_quick_menu(
&self,
_browser: Option<&mut cef::Browser>,
_frame: Option<&mut cef::Frame>,
_location: Option<&cef::Point>,
_size: Option<&cef::Size>,
_edit_state_flags: cef::QuickMenuEditStateFlags,
_callback: Option<&mut cef::RunQuickMenuCallback>,
) -> std::ffi::c_int {
// Prevent quick menu
1
}
fn get_raw(&self) -> *mut _cef_context_menu_handler_t {
self.object.cast()
}
}
impl Clone for ContextMenuHandlerImpl {
fn clone(&self) -> Self {
unsafe {
let rc_impl = &mut *self.object;
rc_impl.interface.add_ref();
}
Self { object: self.object }
}
}
impl Rc for ContextMenuHandlerImpl {
fn as_base(&self) -> &cef_base_ref_counted_t {
unsafe {
let base = &*self.object;
std::mem::transmute(&base.cef_object)
}
}
}
impl WrapContextMenuHandler for ContextMenuHandlerImpl {
fn wrap_rc(&mut self, object: *mut RcImpl<_cef_context_menu_handler_t, Self>) {
self.object = object;
}
}

View File

@@ -1,150 +0,0 @@
use cef::rc::{Rc, RcImpl};
use cef::sys::{_cef_display_handler_t, cef_base_ref_counted_t, cef_cursor_type_t::*, cef_log_severity_t::*};
use cef::{CefString, ImplDisplayHandler, Point, Size, WrapDisplayHandler};
use winit::cursor::CursorIcon;
use crate::cef::CefEventHandler;
pub(crate) struct DisplayHandlerImpl<H: CefEventHandler> {
object: *mut RcImpl<_cef_display_handler_t, Self>,
event_handler: H,
}
impl<H: CefEventHandler> DisplayHandlerImpl<H> {
pub fn new(event_handler: H) -> Self {
Self {
object: std::ptr::null_mut(),
event_handler,
}
}
}
#[cfg(not(target_os = "macos"))]
type CefCursorHandle = cef::CursorHandle;
#[cfg(target_os = "macos")]
type CefCursorHandle = *mut u8;
impl<H: CefEventHandler> ImplDisplayHandler for DisplayHandlerImpl<H> {
fn on_cursor_change(&self, _browser: Option<&mut cef::Browser>, _cursor: CefCursorHandle, cursor_type: cef::CursorType, custom_cursor_info: Option<&cef::CursorInfo>) -> std::ffi::c_int {
if let Some(custom_cursor_info) = custom_cursor_info {
let Size { width, height } = custom_cursor_info.size;
let Point { x: hotspot_x, y: hotspot_y } = custom_cursor_info.hotspot;
let buffer_size = (width * height * 4) as usize;
let buffer_ptr = custom_cursor_info.buffer as *const u8;
if !buffer_ptr.is_null() && buffer_ptr.align_offset(std::mem::align_of::<u8>()) == 0 {
let buffer = unsafe { std::slice::from_raw_parts(buffer_ptr, buffer_size) }.to_vec();
let cursor = winit::cursor::CustomCursorSource::from_rgba(buffer, width as u16, height as u16, hotspot_x as u16, hotspot_y as u16).unwrap();
self.event_handler.cursor_change(cursor.into());
return 1; // We handled the cursor change.
}
}
let cursor = match cursor_type.into() {
CT_POINTER => CursorIcon::Default,
CT_CROSS => CursorIcon::Crosshair,
CT_HAND => CursorIcon::Pointer,
CT_IBEAM => CursorIcon::Text,
CT_WAIT => CursorIcon::Wait,
CT_HELP => CursorIcon::Help,
CT_EASTRESIZE => CursorIcon::EResize,
CT_NORTHRESIZE => CursorIcon::NResize,
CT_NORTHEASTRESIZE => CursorIcon::NeResize,
CT_NORTHWESTRESIZE => CursorIcon::NwResize,
CT_SOUTHRESIZE => CursorIcon::SResize,
CT_SOUTHEASTRESIZE => CursorIcon::SeResize,
CT_SOUTHWESTRESIZE => CursorIcon::SwResize,
CT_WESTRESIZE => CursorIcon::WResize,
CT_NORTHSOUTHRESIZE => CursorIcon::NsResize,
CT_EASTWESTRESIZE => CursorIcon::EwResize,
CT_NORTHEASTSOUTHWESTRESIZE => CursorIcon::NeswResize,
CT_NORTHWESTSOUTHEASTRESIZE => CursorIcon::NwseResize,
CT_COLUMNRESIZE => CursorIcon::ColResize,
CT_ROWRESIZE => CursorIcon::RowResize,
CT_MIDDLEPANNING => CursorIcon::AllScroll,
CT_EASTPANNING => CursorIcon::AllScroll,
CT_NORTHPANNING => CursorIcon::AllScroll,
CT_NORTHEASTPANNING => CursorIcon::AllScroll,
CT_NORTHWESTPANNING => CursorIcon::AllScroll,
CT_SOUTHPANNING => CursorIcon::AllScroll,
CT_SOUTHEASTPANNING => CursorIcon::AllScroll,
CT_SOUTHWESTPANNING => CursorIcon::AllScroll,
CT_WESTPANNING => CursorIcon::AllScroll,
CT_MOVE => CursorIcon::Move,
CT_VERTICALTEXT => CursorIcon::VerticalText,
CT_CELL => CursorIcon::Cell,
CT_CONTEXTMENU => CursorIcon::ContextMenu,
CT_ALIAS => CursorIcon::Alias,
CT_PROGRESS => CursorIcon::Progress,
CT_NODROP => CursorIcon::NoDrop,
CT_COPY => CursorIcon::Copy,
CT_NOTALLOWED => CursorIcon::NotAllowed,
CT_ZOOMIN => CursorIcon::ZoomIn,
CT_ZOOMOUT => CursorIcon::ZoomOut,
CT_GRAB => CursorIcon::Grab,
CT_GRABBING => CursorIcon::Grabbing,
CT_MIDDLE_PANNING_VERTICAL => CursorIcon::AllScroll,
CT_MIDDLE_PANNING_HORIZONTAL => CursorIcon::AllScroll,
CT_DND_NONE => CursorIcon::Default,
CT_DND_MOVE => CursorIcon::Move,
CT_DND_COPY => CursorIcon::Copy,
CT_DND_LINK => CursorIcon::Alias,
CT_NUM_VALUES => CursorIcon::Default,
CT_NONE => {
self.event_handler.cursor_change(crate::window::Cursor::None);
return 1; // We handled the cursor change.
}
_ => CursorIcon::Default,
};
self.event_handler.cursor_change(cursor.into());
1 // We handled the cursor change.
}
fn on_console_message(&self, _browser: Option<&mut cef::Browser>, level: cef::LogSeverity, message: Option<&CefString>, source: Option<&CefString>, line: std::ffi::c_int) -> std::ffi::c_int {
let message = message.map(|m| m.to_string()).unwrap_or_default();
let source = source.map(|s| s.to_string()).unwrap_or_default();
let line = line as i64;
let browser_source = format!("{source}:{line}");
static BROWSER: &str = "browser";
match level.as_ref() {
LOGSEVERITY_FATAL | LOGSEVERITY_ERROR => tracing::error!(target: BROWSER, "{browser_source} {message}"),
LOGSEVERITY_WARNING => tracing::warn!(target: BROWSER, "{browser_source} {message}"),
LOGSEVERITY_INFO => tracing::info!(target: BROWSER, "{browser_source} {message}"),
LOGSEVERITY_DEFAULT | LOGSEVERITY_VERBOSE => tracing::debug!(target: BROWSER, "{browser_source} {message}"),
_ => tracing::trace!(target: BROWSER, "{browser_source} {message}"),
}
0
}
fn get_raw(&self) -> *mut _cef_display_handler_t {
self.object.cast()
}
}
impl<H: CefEventHandler> Clone for DisplayHandlerImpl<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.duplicate(),
}
}
}
impl<H: CefEventHandler> Rc for DisplayHandlerImpl<H> {
fn as_base(&self) -> &cef_base_ref_counted_t {
unsafe {
let base = &*self.object;
std::mem::transmute(&base.cef_object)
}
}
}
impl<H: CefEventHandler> WrapDisplayHandler for DisplayHandlerImpl<H> {
fn wrap_rc(&mut self, object: *mut RcImpl<_cef_display_handler_t, Self>) {
self.object = object;
}
}

View File

@@ -1,64 +0,0 @@
use cef::rc::{Rc, RcImpl};
use cef::sys::{_cef_life_span_handler_t, cef_base_ref_counted_t};
use cef::{ImplLifeSpanHandler, WrapLifeSpanHandler};
pub(crate) struct LifeSpanHandlerImpl {
object: *mut RcImpl<_cef_life_span_handler_t, Self>,
}
impl LifeSpanHandlerImpl {
pub(crate) fn new() -> Self {
Self { object: std::ptr::null_mut() }
}
}
impl ImplLifeSpanHandler for LifeSpanHandlerImpl {
fn on_before_popup(
&self,
_browser: Option<&mut cef::Browser>,
_frame: Option<&mut cef::Frame>,
_popup_id: std::ffi::c_int,
target_url: Option<&cef::CefString>,
_target_frame_name: Option<&cef::CefString>,
_target_disposition: cef::WindowOpenDisposition,
_user_gesture: std::ffi::c_int,
_popup_features: Option<&cef::PopupFeatures>,
_window_info: Option<&mut cef::WindowInfo>,
_client: Option<&mut Option<cef::Client>>,
_settings: Option<&mut cef::BrowserSettings>,
_extra_info: Option<&mut Option<cef::DictionaryValue>>,
_no_javascript_access: Option<&mut std::ffi::c_int>,
) -> std::ffi::c_int {
let target = target_url.map(|url| url.to_string()).unwrap_or("unknown".to_string());
tracing::error!("Browser tried to open a popup at URL: {}", target);
// Deny any popup by returning 1
1
}
fn get_raw(&self) -> *mut _cef_life_span_handler_t {
self.object.cast()
}
}
impl Clone for LifeSpanHandlerImpl {
fn clone(&self) -> Self {
unsafe {
let rc_impl = &mut *self.object;
rc_impl.interface.add_ref();
}
Self { object: self.object }
}
}
impl Rc for LifeSpanHandlerImpl {
fn as_base(&self) -> &cef_base_ref_counted_t {
unsafe {
let base = &*self.object;
std::mem::transmute(&base.cef_object)
}
}
}
impl WrapLifeSpanHandler for LifeSpanHandlerImpl {
fn wrap_rc(&mut self, object: *mut RcImpl<_cef_life_span_handler_t, Self>) {
self.object = object;
}
}

View File

@@ -1,60 +0,0 @@
use cef::rc::{Rc, RcImpl};
use cef::sys::{_cef_load_handler_t, cef_base_ref_counted_t, cef_load_handler_t};
use cef::{ImplBrowser, ImplBrowserHost, ImplLoadHandler, WrapLoadHandler};
use crate::cef::CefEventHandler;
pub(crate) struct LoadHandlerImpl<H: CefEventHandler> {
object: *mut RcImpl<cef_load_handler_t, Self>,
event_handler: H,
}
impl<H: CefEventHandler> LoadHandlerImpl<H> {
pub(crate) fn new(event_handler: H) -> Self {
Self {
object: std::ptr::null_mut(),
event_handler,
}
}
}
impl<H: CefEventHandler> ImplLoadHandler for LoadHandlerImpl<H> {
fn on_loading_state_change(&self, browser: Option<&mut cef::Browser>, is_loading: std::ffi::c_int, _can_go_back: std::ffi::c_int, _can_go_forward: std::ffi::c_int) {
let view_info = self.event_handler.view_info();
if let Some(browser) = browser
&& is_loading == 0
{
browser.host().unwrap().set_zoom_level(view_info.zoom());
}
}
fn get_raw(&self) -> *mut _cef_load_handler_t {
self.object.cast()
}
}
impl<H: CefEventHandler> Clone for LoadHandlerImpl<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.duplicate(),
}
}
}
impl<H: CefEventHandler> Rc for LoadHandlerImpl<H> {
fn as_base(&self) -> &cef_base_ref_counted_t {
unsafe {
let base = &*self.object;
std::mem::transmute(&base.cef_object)
}
}
}
impl<H: CefEventHandler> WrapLoadHandler for LoadHandlerImpl<H> {
fn wrap_rc(&mut self, object: *mut RcImpl<_cef_load_handler_t, Self>) {
self.object = object;
}
}

View File

@@ -1,88 +0,0 @@
use cef::rc::{Rc, RcImpl};
use cef::sys::{_cef_render_handler_t, cef_base_ref_counted_t};
use cef::{Browser, ImplRenderHandler, PaintElementType, Rect, WrapRenderHandler};
use crate::cef::{CefEventHandler, View};
use crate::wrapper::WgpuContext;
pub(crate) struct RenderHandlerImpl<H: CefEventHandler> {
object: *mut RcImpl<_cef_render_handler_t, Self>,
event_handler: H,
view: View,
}
impl<H: CefEventHandler> RenderHandlerImpl<H> {
pub(crate) fn new(event_handler: H, wgpu_context: WgpuContext) -> Self {
Self {
object: std::ptr::null_mut(),
event_handler,
view: View::new(wgpu_context),
}
}
}
impl<H: CefEventHandler> ImplRenderHandler for RenderHandlerImpl<H> {
fn view_rect(&self, _browser: Option<&mut Browser>, rect: Option<&mut Rect>) {
if let Some(rect) = rect {
let view_info = self.event_handler.view_info();
*rect = Rect {
x: 0,
y: 0,
width: view_info.width() as i32,
height: view_info.height() as i32,
};
}
}
fn on_paint(&self, _browser: Option<&mut Browser>, type_: PaintElementType, dirty_rects: Option<&[Rect]>, buffer: *const u8, width: std::ffi::c_int, height: std::ffi::c_int) {
if type_ != PaintElementType::default() {
return;
}
let buffer_size = (width * height * 4) as usize;
let buffer_slice = unsafe { std::slice::from_raw_parts(buffer, buffer_size) };
self.view.upload_frame_buffer(buffer_slice, width as u32, height as u32, dirty_rects.unwrap_or(&[]));
self.event_handler.draw(&self.view)
}
#[cfg(feature = "accelerated_paint")]
fn on_accelerated_paint(&self, _browser: Option<&mut Browser>, type_: PaintElementType, _dirty_rects: Option<&[Rect]>, info: Option<&cef::AcceleratedPaintInfo>) {
if type_ != PaintElementType::default() {
return;
}
self.view.import_shared_texture(info.unwrap());
self.event_handler.draw(&self.view)
}
fn get_raw(&self) -> *mut _cef_render_handler_t {
self.object.cast()
}
}
impl<H: CefEventHandler> Clone for RenderHandlerImpl<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.duplicate(),
view: self.view.clone(),
}
}
}
impl<H: CefEventHandler> Rc for RenderHandlerImpl<H> {
fn as_base(&self) -> &cef_base_ref_counted_t {
unsafe {
let base = &*self.object;
std::mem::transmute(&base.cef_object)
}
}
}
impl<H: CefEventHandler> WrapRenderHandler for RenderHandlerImpl<H> {
fn wrap_rc(&mut self, object: *mut RcImpl<_cef_render_handler_t, Self>) {
self.object = object;
}
}

View File

@@ -1,60 +0,0 @@
use cef::rc::{Rc, RcImpl};
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 super::scheme_handler_factory::SchemeHandlerFactoryImpl;
use crate::cef::CefEventHandler;
pub(crate) struct RenderProcessAppImpl<H: CefEventHandler> {
object: *mut RcImpl<_cef_app_t, Self>,
render_process_handler: RenderProcessHandler,
}
impl<H: CefEventHandler> RenderProcessAppImpl<H> {
pub(crate) fn app() -> App {
App::new(Self {
object: std::ptr::null_mut(),
render_process_handler: RenderProcessHandler::new(RenderProcessHandlerImpl::new()),
})
}
}
impl<H: CefEventHandler> ImplApp for RenderProcessAppImpl<H> {
fn on_register_custom_schemes(&self, registrar: Option<&mut SchemeRegistrar>) {
SchemeHandlerFactoryImpl::<H>::register_schemes(registrar);
}
fn render_process_handler(&self) -> Option<RenderProcessHandler> {
Some(self.render_process_handler.clone())
}
fn get_raw(&self) -> *mut _cef_app_t {
self.object.cast()
}
}
impl<H: CefEventHandler> Clone for RenderProcessAppImpl<H> {
fn clone(&self) -> Self {
unsafe {
let rc_impl = &mut *self.object;
rc_impl.interface.add_ref();
}
Self {
object: self.object,
render_process_handler: self.render_process_handler.clone(),
}
}
}
impl<H: CefEventHandler> Rc for RenderProcessAppImpl<H> {
fn as_base(&self) -> &cef_base_ref_counted_t {
unsafe {
let base = &*self.object;
std::mem::transmute(&base.cef_object)
}
}
}
impl<H: CefEventHandler> WrapApp for RenderProcessAppImpl<H> {
fn wrap_rc(&mut self, object: *mut RcImpl<_cef_app_t, Self>) {
self.object = object;
}
}

View File

@@ -1,127 +0,0 @@
use cef::rc::{ConvertReturnValue, Rc, RcImpl};
use cef::sys::{_cef_render_process_handler_t, cef_base_ref_counted_t, cef_render_process_handler_t, cef_v8_propertyattribute_t, cef_v8_value_create_array_buffer_with_copy};
use cef::{ImplFrame, ImplRenderProcessHandler, ImplV8Context, ImplV8Value, V8Handler, V8Propertyattribute, V8Value, WrapRenderProcessHandler, v8_value_create_function};
use crate::cef::ipc::{MessageType, UnpackMessage, UnpackedMessage};
use super::render_process_v8_handler::RenderProcessV8HandlerImpl;
pub(crate) struct RenderProcessHandlerImpl {
object: *mut RcImpl<cef_render_process_handler_t, Self>,
}
impl RenderProcessHandlerImpl {
pub(crate) fn new() -> Self {
Self { object: std::ptr::null_mut() }
}
}
impl ImplRenderProcessHandler for RenderProcessHandlerImpl {
fn on_process_message_received(
&self,
_browser: Option<&mut cef::Browser>,
frame: Option<&mut cef::Frame>,
_source_process: cef::ProcessId,
message: Option<&mut cef::ProcessMessage>,
) -> std::ffi::c_int {
let unpacked_message = unsafe { message.and_then(|m| m.unpack()) };
match unpacked_message {
Some(UnpackedMessage {
message_type: MessageType::SendToJS,
data,
}) => {
let Some(frame) = frame else {
tracing::error!("Frame is not available");
return 0;
};
let Some(context) = frame.v8_context() else {
tracing::error!("V8 context is not available");
return 0;
};
if context.enter() == 0 {
tracing::error!("Failed to enter V8 context");
return 0;
}
let mut value: V8Value = unsafe { cef_v8_value_create_array_buffer_with_copy(data.as_ptr() as *mut std::ffi::c_void, data.len()) }.wrap_result();
let Some(global) = context.global() else {
tracing::error!("Global object is not available in V8 context");
return 0;
};
let function_name = "receiveNativeMessage";
let property_name = "receiveNativeMessageData";
let function_call = format!("window.{function_name}(window.{property_name})");
global.set_value_bykey(Some(&property_name.into()), Some(&mut value), cef_v8_propertyattribute_t::V8_PROPERTY_ATTRIBUTE_READONLY.wrap_result());
if global.value_bykey(Some(&function_name.into())).is_some() {
frame.execute_java_script(Some(&function_call.as_str().into()), None, 0);
}
if context.exit() == 0 {
tracing::error!("Failed to exit V8 context");
return 0;
}
}
_ => {
tracing::error!("Unexpected message type received in render process");
return 0;
}
}
1
}
fn on_context_created(&self, _browser: Option<&mut cef::Browser>, _frame: Option<&mut cef::Frame>, context: Option<&mut cef::V8Context>) {
let register_js_function = |context: &mut cef::V8Context, name: &'static str| {
let mut v8_handler = V8Handler::new(RenderProcessV8HandlerImpl::new());
let Some(mut function) = v8_value_create_function(Some(&name.into()), Some(&mut v8_handler)) else {
tracing::error!("Failed to create V8 function {name}");
return;
};
let Some(global) = context.global() else {
tracing::error!("Global object is not available in V8 context");
return;
};
global.set_value_bykey(Some(&name.into()), Some(&mut function), V8Propertyattribute::default());
};
let Some(context) = context else {
tracing::error!("V8 context is not available");
return;
};
let initialized_function_name = "initializeNativeCommunication";
let send_function_name = "sendNativeMessage";
register_js_function(context, initialized_function_name);
register_js_function(context, send_function_name);
}
fn get_raw(&self) -> *mut _cef_render_process_handler_t {
self.object.cast()
}
}
impl Clone for RenderProcessHandlerImpl {
fn clone(&self) -> Self {
unsafe {
let rc_impl = &mut *self.object;
rc_impl.interface.add_ref();
}
Self { object: self.object }
}
}
impl Rc for RenderProcessHandlerImpl {
fn as_base(&self) -> &cef_base_ref_counted_t {
unsafe {
let base = &*self.object;
std::mem::transmute(&base.cef_object)
}
}
}
impl WrapRenderProcessHandler for RenderProcessHandlerImpl {
fn wrap_rc(&mut self, object: *mut RcImpl<_cef_render_process_handler_t, Self>) {
self.object = object;
}
}

View File

@@ -1,86 +0,0 @@
use cef::{ImplV8Handler, ImplV8Value, V8Value, WrapV8Handler, rc::Rc, v8_context_get_current_context};
use crate::cef::ipc::{MessageType, SendMessage};
pub struct RenderProcessV8HandlerImpl {
object: *mut cef::rc::RcImpl<cef::sys::_cef_v8_handler_t, Self>,
}
impl RenderProcessV8HandlerImpl {
pub(crate) fn new() -> Self {
Self { object: std::ptr::null_mut() }
}
}
impl ImplV8Handler for RenderProcessV8HandlerImpl {
fn execute(
&self,
name: Option<&cef::CefString>,
_object: Option<&mut V8Value>,
arguments: Option<&[Option<V8Value>]>,
_retval: Option<&mut Option<V8Value>>,
_exception: Option<&mut cef::CefString>,
) -> std::ffi::c_int {
match name.map(|s| s.to_string()).unwrap_or_default().as_str() {
"initializeNativeCommunication" => {
v8_context_get_current_context().send_message(MessageType::Initialized, vec![0u8].as_slice());
}
"sendNativeMessage" => {
let Some(args) = arguments else {
tracing::error!("No arguments provided to sendNativeMessage");
return 0;
};
let Some(arg1) = args.first() else {
tracing::error!("No arguments provided to sendNativeMessage");
return 0;
};
let Some(arg1) = arg1.as_ref() else {
tracing::error!("First argument to sendNativeMessage is not an ArrayBuffer");
return 0;
};
if arg1.is_array_buffer() == 0 {
tracing::error!("First argument to sendNativeMessage is not an ArrayBuffer");
return 0;
}
let size = arg1.array_buffer_byte_length();
let ptr = arg1.array_buffer_data();
let data = unsafe { std::slice::from_raw_parts_mut(ptr as *mut u8, size) };
v8_context_get_current_context().send_message(MessageType::SendToNative, data);
return 1;
}
name => {
tracing::error!("Unknown V8 function called: {}", name);
}
}
1
}
fn get_raw(&self) -> *mut cef::sys::_cef_v8_handler_t {
self.object.cast()
}
}
impl Clone for RenderProcessV8HandlerImpl {
fn clone(&self) -> Self {
unsafe {
let rc_impl = &mut *self.object;
rc_impl.interface.add_ref();
}
Self { object: self.object }
}
}
impl Rc for RenderProcessV8HandlerImpl {
fn as_base(&self) -> &cef::sys::cef_base_ref_counted_t {
unsafe {
let base = &*self.object;
std::mem::transmute(&base.cef_object)
}
}
}
impl WrapV8Handler for RenderProcessV8HandlerImpl {
fn wrap_rc(&mut self, object: *mut cef::rc::RcImpl<cef::sys::_cef_v8_handler_t, Self>) {
self.object = object;
}
}

View File

@@ -1,84 +0,0 @@
use cef::rc::{Rc, RcImpl};
use cef::sys::{_cef_request_handler_t, cef_base_ref_counted_t};
use cef::{AuthCallback, Browser, CefString, Frame, ImplRequest, ImplRequestHandler, Request, ResourceRequestHandler, WrapRequestHandler};
use std::ffi::c_int;
use super::resource_request_handler::ResourceRequestHandlerImpl;
use crate::cef::consts::{RESOURCE_DOMAIN, RESOURCE_SCHEME};
pub(crate) struct RequestHandlerImpl {
object: *mut RcImpl<_cef_request_handler_t, Self>,
}
impl RequestHandlerImpl {
pub(crate) fn new() -> Self {
Self { object: std::ptr::null_mut() }
}
}
impl ImplRequestHandler for RequestHandlerImpl {
fn on_before_browse(&self, _browser: Option<&mut Browser>, _frame: Option<&mut Frame>, request: Option<&mut Request>, _user_gesture: c_int, _is_redirect: c_int) -> c_int {
let Some(request) = request else { return 1 };
let url = CefString::from(&request.url()).to_string();
if url.starts_with(&format!("{RESOURCE_SCHEME}://{RESOURCE_DOMAIN}/")) {
0
} else {
tracing::warn!("Blocked navigation to: {}", url);
1
}
}
fn resource_request_handler(
&self,
_browser: Option<&mut Browser>,
_frame: Option<&mut Frame>,
_request: Option<&mut Request>,
_is_navigation: c_int,
_is_download: c_int,
_request_initiator: Option<&CefString>,
_disable_default_handling: Option<&mut c_int>,
) -> Option<ResourceRequestHandler> {
Some(ResourceRequestHandler::new(ResourceRequestHandlerImpl::new()))
}
fn auth_credentials(
&self,
_browser: Option<&mut Browser>,
_origin_url: Option<&CefString>,
_is_proxy: c_int,
_host: Option<&CefString>,
_port: c_int,
_realm: Option<&CefString>,
_scheme: Option<&CefString>,
_callback: Option<&mut AuthCallback>,
) -> c_int {
0
}
fn get_raw(&self) -> *mut _cef_request_handler_t {
self.object.cast()
}
}
impl Clone for RequestHandlerImpl {
fn clone(&self) -> Self {
unsafe {
let rc_impl = &mut *self.object;
rc_impl.interface.add_ref();
}
Self { object: self.object }
}
}
impl Rc for RequestHandlerImpl {
fn as_base(&self) -> &cef_base_ref_counted_t {
unsafe {
let base = &*self.object;
std::mem::transmute(&base.cef_object)
}
}
}
impl WrapRequestHandler for RequestHandlerImpl {
fn wrap_rc(&mut self, object: *mut RcImpl<_cef_request_handler_t, Self>) {
self.object = object;
}
}

View File

@@ -1,107 +0,0 @@
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 {
response.set_mime_type(Some(&mimetype.as_str().into()));
} else {
response.set_mime_type(None);
}
response.set_status(200);
} else {
response.set_status(404);
response.set_mime_type(Some(&"text/plain".into()));
}
}
}
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

@@ -1,60 +0,0 @@
use cef::rc::{Rc, RcImpl};
use cef::sys::{_cef_resource_request_handler_t, cef_base_ref_counted_t};
use cef::{Browser, Callback, CefString, Frame, ImplRequest, ImplResourceRequestHandler, Request, ReturnValue, WrapResourceRequestHandler};
use crate::cef::consts::{RESOURCE_DOMAIN, RESOURCE_SCHEME};
// TODO: Deny all external requests once we stop relying on google fonts for font preview
fn is_allowed_url(url: &str) -> bool {
url.starts_with(&format!("{RESOURCE_SCHEME}://{RESOURCE_DOMAIN}/")) || url.starts_with("https://fonts.googleapis.com/css2") || url.starts_with("https://fonts.gstatic.com/")
}
pub(crate) struct ResourceRequestHandlerImpl {
object: *mut RcImpl<_cef_resource_request_handler_t, Self>,
}
impl ResourceRequestHandlerImpl {
pub(crate) fn new() -> Self {
Self { object: std::ptr::null_mut() }
}
}
impl ImplResourceRequestHandler for ResourceRequestHandlerImpl {
fn on_before_resource_load(&self, _browser: Option<&mut Browser>, _frame: Option<&mut Frame>, request: Option<&mut Request>, _callback: Option<&mut Callback>) -> ReturnValue {
let Some(request) = request else { return ReturnValue::CANCEL };
let url = CefString::from(&request.url()).to_string();
if is_allowed_url(&url) {
ReturnValue::CONTINUE
} else {
tracing::error!("Blocked resource load: {}", url);
ReturnValue::CANCEL
}
}
fn get_raw(&self) -> *mut _cef_resource_request_handler_t {
self.object.cast()
}
}
impl Clone for ResourceRequestHandlerImpl {
fn clone(&self) -> Self {
unsafe {
let rc_impl = &mut *self.object;
rc_impl.interface.add_ref();
}
Self { object: self.object }
}
}
impl Rc for ResourceRequestHandlerImpl {
fn as_base(&self) -> &cef_base_ref_counted_t {
unsafe {
let base = &*self.object;
std::mem::transmute(&base.cef_object)
}
}
}
impl WrapResourceRequestHandler for ResourceRequestHandlerImpl {
fn wrap_rc(&mut self, object: *mut RcImpl<_cef_resource_request_handler_t, Self>) {
self.object = object;
}
}

View File

@@ -1,74 +0,0 @@
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(&RESOURCE_SCHEME.into()), 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(request) = request {
let url = CefString::from(&request.url()).to_string();
let path = url
.strip_prefix(&format!("{RESOURCE_SCHEME}://{RESOURCE_DOMAIN}/"))
.expect("CEF should only call this for our custom scheme and domain that we registered this factory for");
let resource = self.event_handler.load_resource(path.to_string().into());
return Some(ResourceHandler::new(ResourceHandlerImpl::new(resource)));
}
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.duplicate(),
}
}
}
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,61 +0,0 @@
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<F> {
pub(crate) object: *mut RcImpl<_cef_task_t, Self>,
pub(crate) closure: RefCell<Option<F>>,
}
impl<F: FnOnce() + Send + 'static> ClosureTask<F> {
pub fn new(closure: F) -> Self {
Self {
object: std::ptr::null_mut(),
closure: RefCell::new(Some(closure)),
}
}
}
impl<F: FnOnce() + Send + 'static> ImplTask for ClosureTask<F> {
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<F: FnOnce() + Send + 'static> Clone for ClosureTask<F> {
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<F: FnOnce() + Send + 'static> Rc for ClosureTask<F> {
fn as_base(&self) -> &cef_base_ref_counted_t {
unsafe {
let base = &*self.object;
std::mem::transmute(&base.cef_object)
}
}
}
impl<F: FnOnce() + Send + 'static> WrapTask for ClosureTask<F> {
fn wrap_rc(&mut self, object: *mut RcImpl<_cef_task_t, Self>) {
self.object = object;
}
}

View File

@@ -1,112 +0,0 @@
use cef::{Frame, ImplBinaryValue, ImplFrame, ImplListValue, ImplProcessMessage, ImplV8Context, ProcessId, V8Context, sys::cef_process_id_t};
pub(crate) enum MessageType {
Initialized,
SendToJS,
SendToNative,
}
impl From<MessageType> for MessageInfo {
fn from(val: MessageType) -> Self {
match val {
MessageType::Initialized => MessageInfo {
name: "initialized".to_string(),
target: cef_process_id_t::PID_BROWSER.into(),
},
MessageType::SendToJS => MessageInfo {
name: "send_to_js".to_string(),
target: cef_process_id_t::PID_RENDERER.into(),
},
MessageType::SendToNative => MessageInfo {
name: "send_to_native".to_string(),
target: cef_process_id_t::PID_BROWSER.into(),
},
}
}
}
impl TryFrom<String> for MessageType {
type Error = ();
fn try_from(value: String) -> Result<Self, Self::Error> {
match value.as_str() {
"initialized" => Ok(MessageType::Initialized),
"send_to_js" => Ok(MessageType::SendToJS),
"send_to_native" => Ok(MessageType::SendToNative),
_ => Err(()),
}
}
}
pub(crate) struct MessageInfo {
name: String,
target: ProcessId,
}
pub(crate) trait SendMessage {
fn send_message(&self, message_type: MessageType, message: &[u8]);
}
impl SendMessage for Option<V8Context> {
fn send_message(&self, message_type: MessageType, message: &[u8]) {
let Some(context) = self else {
tracing::error!("Current V8 context is not available, cannot send message");
return;
};
context.send_message(message_type, message);
}
}
impl SendMessage for V8Context {
fn send_message(&self, message_type: MessageType, message: &[u8]) {
let Some(frame) = self.frame() else {
tracing::error!("Current V8 context does not have a frame, cannot send message");
return;
};
frame.send_message(message_type, message);
}
}
impl SendMessage for Frame {
fn send_message(&self, message_type: MessageType, message: &[u8]) {
let MessageInfo { name, target } = message_type.into();
let Some(mut process_message) = cef::process_message_create(Some(&name.as_str().into())) else {
tracing::error!("Failed to create process message: {}", name);
return;
};
let Some(arg_list) = process_message.argument_list() else { return };
let mut value = ::cef::binary_value_create(Some(message));
arg_list.set_binary(0, value.as_mut());
self.send_process_message(target, Some(&mut process_message));
}
}
pub(crate) struct UnpackedMessage<'a> {
pub(crate) message_type: MessageType,
pub(crate) data: &'a [u8],
}
trait Sealed {}
impl Sealed for cef::ProcessMessage {}
#[allow(private_bounds)]
pub(crate) trait UnpackMessage: Sealed {
/// # Safety
///
/// The caller must ensure that the message is valid.
/// Message should come from cef.
unsafe fn unpack(&self) -> Option<UnpackedMessage<'_>>;
}
impl UnpackMessage for cef::ProcessMessage {
unsafe fn unpack(&self) -> Option<UnpackedMessage<'_>> {
let pointer: *mut cef::sys::_cef_string_utf16_t = self.name().into();
let message = unsafe { super::utility::pointer_to_string(pointer) };
let Ok(message_type) = message.try_into() else {
tracing::error!("Failed to get message type from process message");
return None;
};
let arglist = self.argument_list()?;
let binary = arglist.binary(0)?;
let size = binary.size();
let ptr = binary.raw_data();
let buffer = unsafe { std::slice::from_raw_parts(ptr as *const u8, size) };
Some(UnpackedMessage { message_type, data: buffer })
}
}

View File

@@ -1,59 +0,0 @@
#[cfg(feature = "accelerated_paint")]
pub fn should_enable_hardware_acceleration() -> bool {
#[cfg(target_os = "linux")]
{
// Check if running on Wayland or X11
let has_wayland = std::env::var("WAYLAND_DISPLAY")
.ok()
.filter(|var| !var.is_empty())
.or_else(|| std::env::var("WAYLAND_SOCKET").ok())
.filter(|var| !var.is_empty())
.is_some();
let has_x11 = std::env::var("DISPLAY").ok().filter(|var| !var.is_empty()).is_some();
if !has_wayland && !has_x11 {
tracing::warn!("No display server detected, disabling hardware acceleration");
return false;
}
// Check for NVIDIA proprietary driver (known to have issues)
if let Ok(driver_info) = std::fs::read_to_string("/proc/driver/nvidia/version") {
if driver_info.contains("NVIDIA") {
tracing::warn!("NVIDIA proprietary driver detected, hardware acceleration may be unstable");
// Still return true but with warning
}
}
// Check for basic GPU capabilities
if has_wayland {
tracing::info!("Wayland detected, enabling hardware acceleration");
true
} else if has_x11 {
tracing::info!("X11 detected, enabling hardware acceleration");
true
} else {
false
}
}
#[cfg(target_os = "windows")]
{
// Windows generally has good D3D11 support
tracing::info!("Windows detected, enabling hardware acceleration");
true
}
#[cfg(target_os = "macos")]
{
// macOS has good Metal/IOSurface support
tracing::info!("macOS detected, enabling hardware acceleration");
true
}
#[cfg(not(any(target_os = "linux", target_os = "windows", target_os = "macos")))]
{
tracing::warn!("Unsupported platform for hardware acceleration");
false
}
}

View File

@@ -1,6 +0,0 @@
pub unsafe fn pointer_to_string(pointer: *mut cef::sys::_cef_string_utf16_t) -> String {
let str = unsafe { (*pointer).str_ };
let len = unsafe { (*pointer).length };
let slice = unsafe { std::slice::from_raw_parts(str, len) };
String::from_utf16(slice).unwrap()
}

View File

@@ -1,108 +0,0 @@
use cef::Rect;
use std::sync::{Arc, Mutex};
use crate::wrapper::WgpuContext;
#[derive(Clone)]
pub(crate) struct View {
context: WgpuContext,
texture: Arc<Mutex<Option<wgpu::Texture>>>,
}
impl View {
pub(crate) fn new(context: WgpuContext) -> Self {
Self {
context,
texture: Arc::new(Mutex::new(None)),
}
}
pub(crate) fn texture(&self) -> Option<wgpu::Texture> {
let Ok(texture) = self.texture.lock() else {
tracing::error!("Failed to lock view texture");
return None;
};
texture.clone()
}
pub(super) fn upload_frame_buffer(&self, buffer: &[u8], width: u32, height: u32, dirty_rects: &[Rect]) {
debug_assert_eq!(buffer.len(), width as usize * height as usize * 4);
let Ok(mut slot) = self.texture.lock() else {
tracing::error!("Failed to lock view texture");
return;
};
let needs_new_texture = slot.as_ref().is_none_or(|texture| texture.width() != width || texture.height() != height);
if needs_new_texture {
*slot = Some(self.context.device.create_texture(&wgpu::TextureDescriptor {
label: Some("CEF Texture"),
size: wgpu::Extent3d {
width,
height,
depth_or_array_layers: 1,
},
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: wgpu::TextureFormat::Bgra8Unorm,
usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
view_formats: &[],
}));
}
let texture = slot.as_ref().expect("Texture was just created");
let full_frame = [Rect {
x: 0,
y: 0,
width: width as i32,
height: height as i32,
}];
let rects = if needs_new_texture || dirty_rects.is_empty() { &full_frame } else { dirty_rects };
for rect in rects {
let x = (rect.x.max(0) as u32).min(width);
let y = (rect.y.max(0) as u32).min(height);
let rect_width = (rect.width.max(0) as u32).min(width - x);
let rect_height = (rect.height.max(0) as u32).min(height - y);
if rect_width == 0 || rect_height == 0 {
continue;
}
self.context.queue.write_texture(
wgpu::TexelCopyTextureInfo {
texture,
mip_level: 0,
origin: wgpu::Origin3d { x, y, z: 0 },
aspect: wgpu::TextureAspect::All,
},
buffer,
wgpu::TexelCopyBufferLayout {
offset: 4 * (y as u64 * width as u64 + x as u64),
bytes_per_row: Some(4 * width),
rows_per_image: None,
},
wgpu::Extent3d {
width: rect_width,
height: rect_height,
depth_or_array_layers: 1,
},
);
}
}
#[cfg(feature = "accelerated_paint")]
pub(super) fn import_shared_texture(&self, info: &cef::AcceleratedPaintInfo) {
let texture = match cef::osr_texture_import::SharedTextureHandle::new(info).import_texture(&self.context.device) {
Ok(texture) => texture,
Err(e) => {
tracing::error!("Failed to import shared texture: {e}");
return;
}
};
let Ok(mut slot) = self.texture.lock() else {
tracing::error!("Failed to lock view texture");
return;
};
*slot = Some(texture);
}
}

View File

@@ -12,7 +12,3 @@ pub(crate) const APP_STATE_FILE_NAME: &str = "state.ron";
pub(crate) const APP_PREFERENCES_FILE_NAME: &str = "preferences.ron";
pub(crate) const APP_DOCUMENTS_DIRECTORY_NAME: &str = "documents";
pub(crate) const APP_RESOURCES_DIRECTORY_NAME: &str = "resources";
// CEF configuration constants
pub(crate) const CEF_WINDOWLESS_FRAME_RATE: i32 = 60;
pub(crate) const CEF_MESSAGE_LOOP_MAX_ITERATIONS: usize = 10;

View File

@@ -1,6 +1,5 @@
use std::fs;
use std::io;
use std::path::{Path, PathBuf};
use std::path::PathBuf;
use crate::consts::{APP_DIRECTORY_NAME, APP_DOCUMENTS_DIRECTORY_NAME, APP_RESOURCES_DIRECTORY_NAME};
@@ -10,7 +9,7 @@ pub(crate) fn ensure_dir_exists(path: &PathBuf) {
}
}
fn clear_dir(path: &PathBuf) {
pub(crate) fn clear_dir(path: &PathBuf) {
let Ok(entries) = fs::read_dir(path) else {
tracing::error!("Failed to read directory at {path:?}");
return;
@@ -35,16 +34,6 @@ pub(crate) fn app_data_dir() -> PathBuf {
path
}
fn app_tmp_dir() -> PathBuf {
let path = std::env::temp_dir().join(APP_DIRECTORY_NAME);
ensure_dir_exists(&path);
path
}
pub(crate) fn app_tmp_dir_cleanup() {
clear_dir(&app_tmp_dir());
}
pub(crate) fn app_autosave_documents_dir() -> PathBuf {
let path = app_data_dir().join(APP_DOCUMENTS_DIRECTORY_NAME);
ensure_dir_exists(&path);
@@ -57,40 +46,6 @@ pub(crate) fn app_resources_dir() -> PathBuf {
path
}
/// Temporary directory that is automatically deleted when dropped.
pub struct TempDir {
path: PathBuf,
}
impl TempDir {
pub fn new() -> io::Result<Self> {
Self::new_with_parent(app_tmp_dir())
}
pub fn new_with_parent(parent: impl AsRef<Path>) -> io::Result<Self> {
let random_suffix = format!("{:032x}", rand::random::<u128>());
let name = format!("{}_{}", std::process::id(), random_suffix);
let path = parent.as_ref().join(name);
fs::create_dir_all(&path)?;
Ok(Self { path })
}
}
impl Drop for TempDir {
fn drop(&mut self) {
let result = fs::remove_dir_all(&self.path);
if let Err(e) = result {
tracing::error!("Failed to remove temporary directory at {:?}: {}", self.path, e);
}
}
}
impl AsRef<Path> for TempDir {
fn as_ref(&self) -> &Path {
&self.path
}
}
// TODO: Eventually remove this cleanup code for the old "browser" CEF directory
pub(crate) fn delete_old_cef_browser_directory() {
let old_browser_dir = crate::dirs::app_data_dir().join("browser");

View File

@@ -3,12 +3,12 @@ use crate::wrapper::messages::DesktopWrapperMessage;
pub(crate) enum AppEvent {
UiUpdate(wgpu::Texture),
CursorChange(crate::window::Cursor),
ScheduleBrowserWork(std::time::Instant),
CursorChange(graphite_desktop_ui::Cursor),
WebCommunicationInitialized,
DesktopWrapperMessage(DesktopWrapperMessage),
NodeGraphExecutionResult(NodeGraphExecutionResult),
Exit,
UiCrashed,
OpenFiles(Vec<std::path::PathBuf>),
#[cfg(target_os = "macos")]
MenuEvent {

View File

@@ -1,17 +1,17 @@
use crate::app::App;
use crate::cef::CefHandler;
use crate::cli::Cli;
use crate::consts::APP_LOCK_FILE_NAME;
use crate::event::CreateAppEventSchedulerEventLoopExt;
use crate::event::{AppEvent, CreateAppEventSchedulerEventLoopExt};
use clap::Parser;
use graphite_desktop_ui::{Acceleration, UiConfig, UiContext, UiEvent, UiSetupResult};
use std::io::Write;
use std::process::ExitCode;
use tracing_subscriber::EnvFilter;
use winit::event_loop::EventLoop;
pub(crate) use graphite_desktop_wrapper as wrapper;
mod app;
mod cef;
mod cli;
mod dirs;
mod event;
@@ -24,18 +24,17 @@ mod window;
pub(crate) mod consts;
pub fn start() {
pub fn start() -> ExitCode {
tracing_subscriber::fmt().with_env_filter(EnvFilter::from_default_env()).init();
let cef_context_builder = cef::CefContextBuilder::<CefHandler>::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::warn!("Cef subprocess failed with error: {error}");
return;
}
let ui_context = match UiContext::setup() {
UiSetupResult::Ready(context) => context,
UiSetupResult::Helper(code) => return code,
UiSetupResult::Failed => {
eprintln!("Failed to set up the UI runtime");
return ExitCode::FAILURE;
}
};
let cli = Cli::parse();
@@ -63,13 +62,13 @@ pub fn start() {
&& let Err(error) = socket::send(socket::Message::OpenFiles(cli.files))
{
tracing::error!("Failed to send socket message to running instance: {}", error);
std::process::exit(1);
return ExitCode::FAILURE;
}
return;
return ExitCode::SUCCESS;
}
};
dirs::app_tmp_dir_cleanup();
dirs::clear_dir(&graphite_desktop_ui::temp_dir_root());
// TODO: Eventually remove this cleanup code for the old "browser" CEF directory
dirs::delete_old_cef_browser_directory();
@@ -87,8 +86,6 @@ pub fn start() {
let _socket_handle = socket::start(app_event_scheduler.clone());
let (cef_view_info_sender, cef_view_info_receiver) = std::sync::mpsc::channel();
if cli.disable_ui_acceleration {
prefs.disable_ui_acceleration = true;
}
@@ -96,27 +93,46 @@ pub fn start() {
println!("UI acceleration is disabled");
}
let cef_handler = cef::CefHandler::new(app_event_scheduler.clone(), cef_view_info_receiver);
let cef_context = match cef_context_builder.create(cef_handler, wgpu_context.clone(), prefs.disable_ui_acceleration) {
Ok(context) => {
tracing::info!("CEF initialized successfully");
context
}
Err(cef::InitError::InitializationFailureCode(code)) => {
panic!("CEF initialization failed with code: {code}");
}
Err(cef::InitError::BrowserCreationFailed) => {
panic!("Failed to create CEF browser");
}
Err(cef::InitError::RequestContextCreationFailed) => {
panic!("Failed to create CEF request context");
}
};
let acceleration = if prefs.disable_ui_acceleration { Acceleration::Disabled } else { Acceleration::Auto };
let ui_context = ui_context.start(UiConfig { acceleration }).unwrap_or_else(|error| panic!("Failed to start the UI runtime: {error}"));
let ui = ui_context
.instance(&wgpu_context.device, &wgpu_context.queue)
.unwrap_or_else(|error| panic!("Failed to start the UI: {error}"));
tracing::info!("UI runtime started successfully");
let app = App::new(Box::new(cef_context), cef_view_info_sender, wgpu_context, app_event_receiver, app_event_scheduler, prefs, cli.files);
{
let ui = ui.clone();
let scheduler = app_event_scheduler.clone();
std::thread::Builder::new()
.name("ui-events".to_string())
.spawn(move || {
while let Some(event) = ui.recv() {
match event {
UiEvent::Ready => scheduler.schedule(AppEvent::WebCommunicationInitialized),
UiEvent::Frame(texture) => scheduler.schedule(AppEvent::UiUpdate(texture)),
UiEvent::Cursor(cursor) => scheduler.schedule(AppEvent::CursorChange(cursor)),
UiEvent::Message(message) => match wrapper::deserialize_editor_message(&message) {
Some(message) => scheduler.schedule(AppEvent::DesktopWrapperMessage(message)),
None => tracing::error!("Failed to deserialize web message"),
},
UiEvent::InitFailed(error) => {
tracing::error!("UI initialization failed: {error}");
scheduler.schedule(AppEvent::UiCrashed);
}
UiEvent::Crashed => scheduler.schedule(AppEvent::UiCrashed),
}
}
})
.expect("Failed to spawn the UI event bridge thread");
}
let app = App::new(ui.clone(), wgpu_context, app_event_receiver, app_event_scheduler, prefs, cli.files);
let exit_reason = app.run(event_loop);
// ui needs to be shutdown before restarting
ui.shutdown();
// If exiting due to a UI acceleration failure, update preferences to disable it for next launch
if matches!(exit_reason, app::ExitReason::UiAccelerationFailure) {
tracing::error!("Disabling UI acceleration");
@@ -149,10 +165,7 @@ pub fn start() {
// TODO: Identify and fix the underlying CEF shutdown issue so this workaround can be removed.
#[cfg(target_os = "windows")]
std::process::exit(0);
}
pub fn start_helper() {
let cef_context_builder = cef::CefContextBuilder::<CefHandler>::new_helper();
assert!(cef_context_builder.is_sub_process());
cef_context_builder.execute_sub_process();
#[cfg(not(target_os = "windows"))]
ExitCode::SUCCESS
}

View File

@@ -1,3 +1,3 @@
fn main() {
graphite_desktop::start();
fn main() -> std::process::ExitCode {
graphite_desktop::start()
}

View File

@@ -2,9 +2,10 @@ use crate::consts::APP_NAME;
use crate::event::AppEventScheduler;
use crate::wrapper::messages::MenuItem;
use crate::wrapper::{WgpuInstance, WgpuSurface};
use graphite_desktop_ui::Cursor;
use std::collections::HashMap;
use std::sync::Arc;
use winit::cursor::{CursorIcon, CustomCursor, CustomCursorSource};
use winit::cursor::{CustomCursor, CustomCursorSource};
use winit::event_loop::ActiveEventLoop;
use winit::monitor::Fullscreen;
use winit::window::{Window as WinitWindow, WindowAttributes};
@@ -161,7 +162,17 @@ impl Window {
pub(crate) fn set_cursor(&mut self, event_loop: &dyn ActiveEventLoop, cursor: Cursor) {
let cursor = match cursor {
Cursor::Icon(cursor_icon) => cursor_icon.into(),
Cursor::Custom(custom_cursor_source) => {
Cursor::Custom {
rgba,
width,
height,
hotspot_x,
hotspot_y,
} => {
let Ok(custom_cursor_source) = CustomCursorSource::from_rgba(rgba, width, height, hotspot_x, hotspot_y) else {
tracing::error!("Invalid custom cursor image");
return;
};
let custom_cursor = match self.custom_cursors.get(&custom_cursor_source).cloned() {
Some(cursor) => cursor,
None => {
@@ -222,19 +233,3 @@ impl Window {
}
}
}
pub(crate) enum Cursor {
Icon(CursorIcon),
Custom(CustomCursorSource),
None,
}
impl From<CursorIcon> for Cursor {
fn from(icon: CursorIcon) -> Self {
Cursor::Icon(icon)
}
}
impl From<CustomCursorSource> for Cursor {
fn from(custom: CustomCursorSource) -> Self {
Cursor::Custom(custom)
}
}