INIT
This commit is contained in:
22
crates/bevy_cef_core/Cargo.toml
Normal file
22
crates/bevy_cef_core/Cargo.toml
Normal file
@@ -0,0 +1,22 @@
|
||||
[package]
|
||||
name = "bevy_cef_core"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
authors.workspace = true
|
||||
repository.workspace = true
|
||||
|
||||
[dependencies]
|
||||
bevy = { workspace = true }
|
||||
bevy_remote = { workspace = true }
|
||||
uuid = { version = "1" }
|
||||
cef = { workspace = true }
|
||||
cef-dll-sys = { workspace = true }
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
serde_json = { workspace = true }
|
||||
async-channel = { workspace = true }
|
||||
raw-window-handle = { workspace = true }
|
||||
|
||||
[features]
|
||||
default = []
|
||||
debug = []
|
||||
18
crates/bevy_cef_core/src/browser_process.rs
Normal file
18
crates/bevy_cef_core/src/browser_process.rs
Normal file
@@ -0,0 +1,18 @@
|
||||
mod app;
|
||||
mod browser_process_handler;
|
||||
mod browsers;
|
||||
mod client_handler;
|
||||
mod context_menu_handler;
|
||||
mod display_handler;
|
||||
mod localhost;
|
||||
mod renderer_handler;
|
||||
mod request_context_handler;
|
||||
|
||||
pub use app::*;
|
||||
pub use browser_process_handler::*;
|
||||
pub use browsers::*;
|
||||
pub use client_handler::*;
|
||||
pub use context_menu_handler::*;
|
||||
pub use localhost::*;
|
||||
pub use renderer_handler::*;
|
||||
pub use request_context_handler::*;
|
||||
85
crates/bevy_cef_core/src/browser_process/app.rs
Normal file
85
crates/bevy_cef_core/src/browser_process/app.rs
Normal file
@@ -0,0 +1,85 @@
|
||||
use crate::browser_process::browser_process_handler::BrowserProcessHandlerBuilder;
|
||||
use crate::util::{SCHEME_CEF, cef_scheme_flags};
|
||||
use cef::rc::{Rc, RcImpl};
|
||||
use cef::{
|
||||
BrowserProcessHandler, CefString, CommandLine, ImplApp, ImplCommandLine, ImplSchemeRegistrar,
|
||||
SchemeRegistrar, WrapApp,
|
||||
};
|
||||
use cef_dll_sys::{_cef_app_t, cef_base_ref_counted_t};
|
||||
|
||||
/// ## Reference
|
||||
///
|
||||
/// - [`CefApp Class Reference`](https://cef-builds.spotifycdn.com/docs/106.1/classCefApp.html)
|
||||
#[derive(Default)]
|
||||
pub struct BrowserProcessAppBuilder {
|
||||
object: *mut RcImpl<_cef_app_t, Self>,
|
||||
}
|
||||
|
||||
impl BrowserProcessAppBuilder {
|
||||
pub fn build() -> cef::App {
|
||||
cef::App::new(Self {
|
||||
object: core::ptr::null_mut(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for BrowserProcessAppBuilder {
|
||||
fn clone(&self) -> Self {
|
||||
let object = unsafe {
|
||||
let rc_impl = &mut *self.object;
|
||||
rc_impl.interface.add_ref();
|
||||
self.object
|
||||
};
|
||||
Self { object }
|
||||
}
|
||||
}
|
||||
|
||||
impl Rc for BrowserProcessAppBuilder {
|
||||
fn as_base(&self) -> &cef_base_ref_counted_t {
|
||||
unsafe {
|
||||
let base = &*self.object;
|
||||
std::mem::transmute(&base.cef_object)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ImplApp for BrowserProcessAppBuilder {
|
||||
fn on_before_command_line_processing(
|
||||
&self,
|
||||
_: Option<&CefString>,
|
||||
command_line: Option<&mut CommandLine>,
|
||||
) {
|
||||
let Some(command_line) = command_line else {
|
||||
return;
|
||||
};
|
||||
//TODO: フラグで切り替えるようにする
|
||||
command_line.append_switch(Some(&"use-mock-keychain".into()));
|
||||
#[cfg(feature = "debug")]
|
||||
{
|
||||
command_line.append_switch(Some(&"disable-gpu".into()));
|
||||
command_line.append_switch(Some(&"disable-gpu-compositing".into()));
|
||||
command_line.append_switch(Some(&"disable-software-rasterizer".into()));
|
||||
}
|
||||
}
|
||||
|
||||
fn browser_process_handler(&self) -> Option<BrowserProcessHandler> {
|
||||
Some(BrowserProcessHandlerBuilder::build())
|
||||
}
|
||||
|
||||
fn on_register_custom_schemes(&self, registrar: Option<&mut SchemeRegistrar>) {
|
||||
if let Some(registrar) = registrar {
|
||||
registrar.add_custom_scheme(Some(&SCHEME_CEF.into()), cef_scheme_flags() as _);
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn get_raw(&self) -> *mut _cef_app_t {
|
||||
self.object as *mut cef::sys::_cef_app_t
|
||||
}
|
||||
}
|
||||
|
||||
impl WrapApp for BrowserProcessAppBuilder {
|
||||
fn wrap_rc(&mut self, object: *mut RcImpl<_cef_app_t, Self>) {
|
||||
self.object = object;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
use cef::rc::{Rc, RcImpl};
|
||||
use cef::*;
|
||||
|
||||
/// ## Reference
|
||||
///
|
||||
/// - [`CefBrowserProcessHandler Class Reference`](https://cef-builds.spotifycdn.com/docs/106.1/classCefBrowserProcessHandler.html)
|
||||
pub struct BrowserProcessHandlerBuilder {
|
||||
object: *mut RcImpl<cef_dll_sys::cef_browser_process_handler_t, Self>,
|
||||
}
|
||||
|
||||
impl BrowserProcessHandlerBuilder {
|
||||
pub fn build() -> BrowserProcessHandler {
|
||||
BrowserProcessHandler::new(Self {
|
||||
object: core::ptr::null_mut(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Rc for BrowserProcessHandlerBuilder {
|
||||
fn as_base(&self) -> &cef_dll_sys::cef_base_ref_counted_t {
|
||||
unsafe {
|
||||
let base = &*self.object;
|
||||
std::mem::transmute(&base.cef_object)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl WrapBrowserProcessHandler for BrowserProcessHandlerBuilder {
|
||||
fn wrap_rc(&mut self, object: *mut RcImpl<cef_dll_sys::_cef_browser_process_handler_t, Self>) {
|
||||
self.object = object;
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for BrowserProcessHandlerBuilder {
|
||||
fn clone(&self) -> Self {
|
||||
let object = unsafe {
|
||||
let rc_impl = &mut *self.object;
|
||||
rc_impl.interface.add_ref();
|
||||
rc_impl
|
||||
};
|
||||
|
||||
Self { object }
|
||||
}
|
||||
}
|
||||
|
||||
impl ImplBrowserProcessHandler for BrowserProcessHandlerBuilder {
|
||||
fn on_before_child_process_launch(&self, command_line: Option<&mut CommandLine>) {
|
||||
let Some(command_line) = command_line else {
|
||||
return;
|
||||
};
|
||||
|
||||
command_line.append_switch(Some(&"disable-web-security".into()));
|
||||
command_line.append_switch(Some(&"allow-running-insecure-content".into()));
|
||||
command_line.append_switch(Some(&"disable-session-crashed-bubble".into()));
|
||||
command_line.append_switch(Some(&"ignore-certificate-errors".into()));
|
||||
command_line.append_switch(Some(&"ignore-ssl-errors".into()));
|
||||
command_line.append_switch(Some(&"enable-logging=stderr".into()));
|
||||
}
|
||||
#[inline]
|
||||
fn get_raw(&self) -> *mut cef_dll_sys::_cef_browser_process_handler_t {
|
||||
self.object.cast()
|
||||
}
|
||||
}
|
||||
492
crates/bevy_cef_core/src/browser_process/browsers.rs
Normal file
492
crates/bevy_cef_core/src/browser_process/browsers.rs
Normal file
@@ -0,0 +1,492 @@
|
||||
use crate::browser_process::BrpHandler;
|
||||
use crate::browser_process::ClientHandlerBuilder;
|
||||
use crate::browser_process::client_handler::{IpcEventRaw, JsEmitEventHandler};
|
||||
use crate::prelude::IntoString;
|
||||
use crate::prelude::*;
|
||||
use async_channel::{Sender, TryRecvError};
|
||||
use bevy::platform::collections::HashMap;
|
||||
use bevy::prelude::*;
|
||||
use bevy_remote::BrpMessage;
|
||||
use cef::{
|
||||
Browser, BrowserHost, BrowserSettings, Client, CompositionUnderline, ImplBrowser,
|
||||
ImplBrowserHost, ImplFrame, ImplListValue, ImplProcessMessage, ImplRequestContext,
|
||||
MouseButtonType, ProcessId, Range, RequestContext, RequestContextSettings, WindowInfo,
|
||||
browser_host_create_browser_sync, process_message_create,
|
||||
};
|
||||
use cef_dll_sys::{cef_event_flags_t, cef_mouse_button_type_t};
|
||||
#[allow(deprecated)]
|
||||
use raw_window_handle::RawWindowHandle;
|
||||
use std::cell::Cell;
|
||||
use std::rc::Rc;
|
||||
|
||||
mod devtool_render_handler;
|
||||
mod keyboard;
|
||||
|
||||
use crate::browser_process::browsers::devtool_render_handler::DevToolRenderHandlerBuilder;
|
||||
use crate::browser_process::display_handler::{DisplayHandlerBuilder, SystemCursorIconSenderInner};
|
||||
pub use keyboard::*;
|
||||
|
||||
pub struct WebviewBrowser {
|
||||
pub client: Browser,
|
||||
pub host: BrowserHost,
|
||||
pub size: SharedViewSize,
|
||||
}
|
||||
|
||||
pub struct Browsers {
|
||||
browsers: HashMap<Entity, WebviewBrowser>,
|
||||
sender: TextureSender,
|
||||
receiver: TextureReceiver,
|
||||
ime_caret: SharedImeCaret,
|
||||
}
|
||||
|
||||
impl Default for Browsers {
|
||||
fn default() -> Self {
|
||||
let (sender, receiver) = async_channel::unbounded::<RenderTexture>();
|
||||
Browsers {
|
||||
browsers: HashMap::default(),
|
||||
sender,
|
||||
receiver,
|
||||
ime_caret: Rc::new(Cell::new(0)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Browsers {
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn create_browser(
|
||||
&mut self,
|
||||
webview: Entity,
|
||||
uri: &str,
|
||||
webview_size: Vec2,
|
||||
requester: Requester,
|
||||
ipc_event_sender: Sender<IpcEventRaw>,
|
||||
brp_sender: Sender<BrpMessage>,
|
||||
system_cursor_icon_sender: SystemCursorIconSenderInner,
|
||||
window_handle: Option<RawWindowHandle>,
|
||||
) {
|
||||
let mut context = Self::request_context(requester);
|
||||
let size = Rc::new(Cell::new(webview_size));
|
||||
let browser = browser_host_create_browser_sync(
|
||||
Some(&WindowInfo {
|
||||
windowless_rendering_enabled: true as _,
|
||||
external_begin_frame_enabled: true as _,
|
||||
parent_view: match window_handle {
|
||||
Some(RawWindowHandle::AppKit(handle)) => handle.ns_view.as_ptr(),
|
||||
Some(RawWindowHandle::Win32(handle)) => handle.hwnd.get() as _,
|
||||
Some(RawWindowHandle::Xlib(handle)) => handle.window as _,
|
||||
Some(RawWindowHandle::Wayland(handle)) => handle.surface.as_ptr(),
|
||||
_ => std::ptr::null_mut(),
|
||||
},
|
||||
// shared_texture_enabled: true as _,
|
||||
..Default::default()
|
||||
}),
|
||||
Some(&mut self.client_handler(
|
||||
webview,
|
||||
size.clone(),
|
||||
ipc_event_sender,
|
||||
brp_sender,
|
||||
system_cursor_icon_sender,
|
||||
)),
|
||||
Some(&uri.into()),
|
||||
Some(&BrowserSettings {
|
||||
windowless_frame_rate: 60,
|
||||
..Default::default()
|
||||
}),
|
||||
None,
|
||||
context.as_mut(),
|
||||
)
|
||||
.expect("Failed to create browser");
|
||||
self.browsers.insert(
|
||||
webview,
|
||||
WebviewBrowser {
|
||||
host: browser.host().expect("Failed to get browser host"),
|
||||
client: browser,
|
||||
size,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
pub fn send_external_begin_frame(&mut self) {
|
||||
for browser in self.browsers.values_mut() {
|
||||
browser.host.send_external_begin_frame();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn send_mouse_move<'a>(
|
||||
&self,
|
||||
webview: &Entity,
|
||||
buttons: impl IntoIterator<Item = &'a MouseButton>,
|
||||
position: Vec2,
|
||||
mouse_leave: bool,
|
||||
) {
|
||||
if let Some(browser) = self.get_focused_browser(webview) {
|
||||
let mouse_event = cef::MouseEvent {
|
||||
x: position.x as i32,
|
||||
y: position.y as i32,
|
||||
modifiers: modifiers_from_mouse_buttons(buttons),
|
||||
};
|
||||
browser
|
||||
.host
|
||||
.send_mouse_move_event(Some(&mouse_event), mouse_leave as _);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn send_mouse_click(
|
||||
&self,
|
||||
webview: &Entity,
|
||||
position: Vec2,
|
||||
button: PointerButton,
|
||||
mouse_up: bool,
|
||||
) {
|
||||
if let Some(browser) = self.get_focused_browser(webview) {
|
||||
let mouse_event = cef::MouseEvent {
|
||||
x: position.x as i32,
|
||||
y: position.y as i32,
|
||||
modifiers: match button {
|
||||
PointerButton::Primary => cef_event_flags_t::EVENTFLAG_LEFT_MOUSE_BUTTON,
|
||||
PointerButton::Secondary => cef_event_flags_t::EVENTFLAG_RIGHT_MOUSE_BUTTON,
|
||||
PointerButton::Middle => cef_event_flags_t::EVENTFLAG_MIDDLE_MOUSE_BUTTON,
|
||||
} as _, // No modifiers for simplicity
|
||||
};
|
||||
let mouse_button = match button {
|
||||
PointerButton::Secondary => cef_mouse_button_type_t::MBT_RIGHT,
|
||||
PointerButton::Middle => cef_mouse_button_type_t::MBT_MIDDLE,
|
||||
_ => cef_mouse_button_type_t::MBT_LEFT,
|
||||
};
|
||||
browser.host.set_focus(true as _);
|
||||
browser.host.send_mouse_click_event(
|
||||
Some(&mouse_event),
|
||||
MouseButtonType::from(mouse_button),
|
||||
mouse_up as _,
|
||||
1,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// [`SendMouseWheelEvent`](https://cef-builds.spotifycdn.com/docs/106.1/classCefBrowserHost.html#acd5d057bd5230baa9a94b7853ba755f7)
|
||||
pub fn send_mouse_wheel(&self, webview: &Entity, position: Vec2, delta: Vec2) {
|
||||
if let Some(browser) = self.get_focused_browser(webview) {
|
||||
let mouse_event = cef::MouseEvent {
|
||||
x: position.x as i32,
|
||||
y: position.y as i32,
|
||||
modifiers: 0,
|
||||
};
|
||||
browser
|
||||
.host
|
||||
.send_mouse_wheel_event(Some(&mouse_event), delta.x as _, delta.y as _);
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn send_key(&self, webview: &Entity, event: cef::KeyEvent) {
|
||||
if let Some(browser) = self.get_focused_browser(webview) {
|
||||
browser.host.send_key_event(Some(&event));
|
||||
}
|
||||
}
|
||||
|
||||
pub fn emit_event(&self, webview: &Entity, id: impl Into<String>, event: &serde_json::Value) {
|
||||
if let Some(mut process_message) =
|
||||
process_message_create(Some(&PROCESS_MESSAGE_HOST_EMIT.into()))
|
||||
&& let Some(argument_list) = process_message.argument_list()
|
||||
&& let Some(browser) = self.browsers.get(webview)
|
||||
&& let Some(frame) = browser.client.main_frame()
|
||||
{
|
||||
argument_list.set_string(0, Some(&id.into().as_str().into()));
|
||||
argument_list.set_string(1, Some(&event.to_string().as_str().into()));
|
||||
frame.send_process_message(
|
||||
ProcessId::from(cef_dll_sys::cef_process_id_t::PID_RENDERER),
|
||||
Some(&mut process_message),
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
pub fn resize(&self, webview: &Entity, size: Vec2) {
|
||||
if let Some(browser) = self.browsers.get(webview) {
|
||||
browser.size.set(size);
|
||||
browser.host.was_resized();
|
||||
}
|
||||
}
|
||||
|
||||
/// Closes the browser associated with the given webview entity.
|
||||
///
|
||||
/// The browser will be removed from the hash map after closing.
|
||||
pub fn close(&mut self, webview: &Entity) {
|
||||
if let Some(browser) = self.browsers.remove(webview) {
|
||||
browser.host.close_browser(true as _);
|
||||
debug!("Closed browser with webview: {:?}", webview);
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn try_receive_texture(&self) -> core::result::Result<RenderTexture, TryRecvError> {
|
||||
self.receiver.try_recv()
|
||||
}
|
||||
|
||||
/// Shows the DevTools for the specified webview.
|
||||
pub fn show_devtool(&self, webview: &Entity) {
|
||||
let Some(browser) = self.browsers.get(webview) else {
|
||||
return;
|
||||
};
|
||||
browser.host.show_dev_tools(
|
||||
Some(&WindowInfo::default()),
|
||||
Some(&mut ClientHandlerBuilder::new(DevToolRenderHandlerBuilder::build()).build()),
|
||||
Some(&BrowserSettings::default()),
|
||||
None,
|
||||
);
|
||||
}
|
||||
|
||||
/// Closes the DevTools for the specified webview.
|
||||
pub fn close_devtools(&self, webview: &Entity) {
|
||||
if let Some(browser) = self.browsers.get(webview) {
|
||||
browser.host.close_dev_tools();
|
||||
}
|
||||
}
|
||||
|
||||
/// Navigate backwards.
|
||||
///
|
||||
/// ## Reference
|
||||
///
|
||||
/// - [`GoBack`](https://cef-builds.spotifycdn.com/docs/122.0/classCefBrowser.html#a85b02760885c070e4ad2a2705cea56cb)
|
||||
pub fn go_back(&self, webview: &Entity) {
|
||||
if let Some(browser) = self.browsers.get(webview)
|
||||
&& browser.client.can_go_back() == 1
|
||||
{
|
||||
browser.client.go_back();
|
||||
}
|
||||
}
|
||||
|
||||
/// Navigate forwards.
|
||||
///
|
||||
/// ## Reference
|
||||
///
|
||||
/// - [`GoForward`](https://cef-builds.spotifycdn.com/docs/122.0/classCefBrowser.html#aa8e97fc210ee0e73f16b2d98482419d0)
|
||||
pub fn go_forward(&self, webview: &Entity) {
|
||||
if let Some(browser) = self.browsers.get(webview)
|
||||
&& browser.client.can_go_forward() == 1
|
||||
{
|
||||
browser.client.go_forward();
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the current zoom level for the specified webview.
|
||||
///
|
||||
/// ## Reference
|
||||
///
|
||||
/// - [`GetZoomLevel`](https://cef-builds.spotifycdn.com/docs/122.0/classCefBrowserHost.html#a524d4a358287dab284c0dfec6d6d229e)
|
||||
pub fn zoom_level(&self, webview: &Entity) -> Option<f64> {
|
||||
self.browsers
|
||||
.get(webview)
|
||||
.map(|browser| browser.host.zoom_level())
|
||||
}
|
||||
|
||||
/// Sets the zoom level for the specified webview.
|
||||
///
|
||||
/// ## Reference
|
||||
///
|
||||
/// - [`SetZoomLevel`](https://cef-builds.spotifycdn.com/docs/122.0/classCefBrowserHost.html#af2b7bf250ac78345117cd575190f2f7b)
|
||||
pub fn set_zoom_level(&self, webview: &Entity, zoom_level: f64) {
|
||||
if let Some(browser) = self.browsers.get(webview) {
|
||||
browser.host.set_zoom_level(zoom_level);
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets whether the audio is muted for the specified webview.
|
||||
///
|
||||
/// ## Reference
|
||||
///
|
||||
/// - [`SetAudioMuted`](https://cef-builds.spotifycdn.com/docs/122.0/classCefBrowserHost.html#a153d179c9ff202c8bb8869d2e9a820a2)
|
||||
pub fn set_audio_muted(&self, webview: &Entity, muted: bool) {
|
||||
if let Some(browser) = self.browsers.get(webview) {
|
||||
browser.host.set_audio_muted(muted as _);
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn reload(&self) {
|
||||
for browser in self.browsers.values() {
|
||||
if let Some(frame) = browser.client.main_frame() {
|
||||
let url = frame.url().into_string();
|
||||
info!("Reloading browser with URL: {}", url);
|
||||
frame.load_url(Some(&url.as_str().into()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// ## Reference
|
||||
///
|
||||
/// - [`ImeSetComposition`](https://cef-builds.spotifycdn.com/docs/122.0/classCefBrowserHost.html#a567b41fb2d3917843ece3b57adc21ebe)
|
||||
pub fn set_ime_composition(&self, text: &str, cursor_utf16: Option<u32>) {
|
||||
let underlines = make_underlines_for(text, cursor_utf16.map(|i| (i, i)));
|
||||
let i = text.encode_utf16().count();
|
||||
let selection_range = Range {
|
||||
from: i as _,
|
||||
to: i as _,
|
||||
};
|
||||
let replacement_range = self.ime_caret_range();
|
||||
for browser in self
|
||||
.browsers
|
||||
.values()
|
||||
.filter(|b| b.client.focused_frame().is_some())
|
||||
{
|
||||
browser.host.ime_set_composition(
|
||||
Some(&text.into()),
|
||||
underlines.len(),
|
||||
Some(&underlines[0]),
|
||||
Some(&replacement_range),
|
||||
Some(&selection_range),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// ## Reference
|
||||
///
|
||||
/// [`ImeSetComposition`](https://cef-builds.spotifycdn.com/docs/122.0/classCefBrowserHost.html#a567b41fb2d3917843ece3b57adc21ebe)
|
||||
pub fn ime_finish_composition(&self, keep_selection: bool) {
|
||||
for browser in self
|
||||
.browsers
|
||||
.values()
|
||||
.filter(|b| b.client.focused_frame().is_some())
|
||||
{
|
||||
browser.host.ime_finish_composing_text(keep_selection as _);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_ime_commit_text(&self, text: &str) {
|
||||
let replacement_range = self.ime_caret_range();
|
||||
for browser in self
|
||||
.browsers
|
||||
.values()
|
||||
.filter(|b| b.client.focused_frame().is_some())
|
||||
{
|
||||
browser
|
||||
.host
|
||||
.ime_commit_text(Some(&text.into()), Some(&replacement_range), 0)
|
||||
}
|
||||
}
|
||||
|
||||
fn request_context(requester: Requester) -> Option<RequestContext> {
|
||||
let mut context = cef::request_context_create_context(
|
||||
Some(&RequestContextSettings::default()),
|
||||
Some(&mut RequestContextHandlerBuilder::build()),
|
||||
);
|
||||
if let Some(context) = context.as_mut() {
|
||||
context.register_scheme_handler_factory(
|
||||
Some(&SCHEME_CEF.into()),
|
||||
Some(&HOST_CEF.into()),
|
||||
Some(&mut LocalSchemaHandlerBuilder::build(requester)),
|
||||
);
|
||||
}
|
||||
context
|
||||
}
|
||||
|
||||
fn client_handler(
|
||||
&self,
|
||||
webview: Entity,
|
||||
size: SharedViewSize,
|
||||
ipc_event_sender: Sender<IpcEventRaw>,
|
||||
brp_sender: Sender<BrpMessage>,
|
||||
system_cursor_icon_sender: SystemCursorIconSenderInner,
|
||||
) -> Client {
|
||||
ClientHandlerBuilder::new(RenderHandlerBuilder::build(
|
||||
webview,
|
||||
self.sender.clone(),
|
||||
size.clone(),
|
||||
self.ime_caret.clone(),
|
||||
))
|
||||
.with_display_handler(DisplayHandlerBuilder::build(system_cursor_icon_sender))
|
||||
.with_message_handler(JsEmitEventHandler::new(webview, ipc_event_sender))
|
||||
.with_message_handler(BrpHandler::new(brp_sender))
|
||||
.build()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn ime_caret_range(&self) -> Range {
|
||||
let caret = self.ime_caret.get();
|
||||
Range {
|
||||
from: caret,
|
||||
to: caret,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn get_focused_browser(&self, webview: &Entity) -> Option<&WebviewBrowser> {
|
||||
self.browsers
|
||||
.get(webview)
|
||||
.and_then(|b| b.client.focused_frame().is_some().then_some(b))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn modifiers_from_mouse_buttons<'a>(buttons: impl IntoIterator<Item = &'a MouseButton>) -> u32 {
|
||||
let mut modifiers = cef_event_flags_t::EVENTFLAG_NONE as u32;
|
||||
for button in buttons {
|
||||
match button {
|
||||
MouseButton::Left => modifiers |= cef_event_flags_t::EVENTFLAG_LEFT_MOUSE_BUTTON as u32,
|
||||
MouseButton::Right => {
|
||||
modifiers |= cef_event_flags_t::EVENTFLAG_RIGHT_MOUSE_BUTTON as u32
|
||||
}
|
||||
MouseButton::Middle => {
|
||||
modifiers |= cef_event_flags_t::EVENTFLAG_MIDDLE_MOUSE_BUTTON as u32
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
modifiers
|
||||
}
|
||||
|
||||
pub fn make_underlines_for(
|
||||
text: &str,
|
||||
selection_utf16: Option<(u32, u32)>,
|
||||
) -> Vec<CompositionUnderline> {
|
||||
let len16 = utf16_len(text);
|
||||
|
||||
let base = CompositionUnderline {
|
||||
size: size_of::<CompositionUnderline>(),
|
||||
range: Range { from: 0, to: len16 },
|
||||
color: 0,
|
||||
background_color: 0,
|
||||
thick: 0,
|
||||
style: Default::default(),
|
||||
};
|
||||
|
||||
if let Some((from, to)) = selection_utf16
|
||||
&& from < to
|
||||
{
|
||||
let sel = CompositionUnderline {
|
||||
size: size_of::<CompositionUnderline>(),
|
||||
range: Range { from, to },
|
||||
color: 0,
|
||||
background_color: 0,
|
||||
thick: 1,
|
||||
style: Default::default(),
|
||||
};
|
||||
return vec![base, sel];
|
||||
}
|
||||
vec![base]
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn utf16_len(s: &str) -> u32 {
|
||||
s.encode_utf16().count() as u32
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn utf16_index_from_byte(s: &str, byte_idx: usize) -> u32 {
|
||||
s[..byte_idx].encode_utf16().count() as u32
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::prelude::modifiers_from_mouse_buttons;
|
||||
use bevy::prelude::*;
|
||||
|
||||
#[test]
|
||||
fn test_modifiers_from_mouse_buttons() {
|
||||
let buttons = vec![&MouseButton::Left, &MouseButton::Right];
|
||||
let modifiers = modifiers_from_mouse_buttons(buttons);
|
||||
assert_eq!(
|
||||
modifiers,
|
||||
cef_dll_sys::cef_event_flags_t::EVENTFLAG_LEFT_MOUSE_BUTTON as u32
|
||||
| cef_dll_sys::cef_event_flags_t::EVENTFLAG_RIGHT_MOUSE_BUTTON as u32
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
use cef::rc::{Rc, RcImpl};
|
||||
use cef::{Browser, ImplRenderHandler, Rect, RenderHandler, WrapRenderHandler, sys};
|
||||
|
||||
/// ## Reference
|
||||
///
|
||||
/// - [`CefRenderHandler Class Reference`](https://cef-builds.spotifycdn.com/docs/106.1/classCefRenderHandler.html)
|
||||
pub struct DevToolRenderHandlerBuilder {
|
||||
object: *mut RcImpl<sys::cef_render_handler_t, Self>,
|
||||
}
|
||||
|
||||
impl DevToolRenderHandlerBuilder {
|
||||
pub fn build() -> RenderHandler {
|
||||
RenderHandler::new(Self {
|
||||
object: std::ptr::null_mut(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Rc for DevToolRenderHandlerBuilder {
|
||||
fn as_base(&self) -> &sys::cef_base_ref_counted_t {
|
||||
unsafe {
|
||||
let base = &*self.object;
|
||||
std::mem::transmute(&base.cef_object)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl WrapRenderHandler for DevToolRenderHandlerBuilder {
|
||||
fn wrap_rc(&mut self, object: *mut RcImpl<sys::_cef_render_handler_t, Self>) {
|
||||
self.object = object;
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for DevToolRenderHandlerBuilder {
|
||||
fn clone(&self) -> Self {
|
||||
let object = unsafe {
|
||||
let rc_impl = &mut *self.object;
|
||||
rc_impl.interface.add_ref();
|
||||
rc_impl
|
||||
};
|
||||
Self { object }
|
||||
}
|
||||
}
|
||||
|
||||
impl ImplRenderHandler for DevToolRenderHandlerBuilder {
|
||||
fn view_rect(&self, _browser: Option<&mut Browser>, rect: Option<&mut Rect>) {
|
||||
if let Some(rect) = rect {
|
||||
rect.width = 800;
|
||||
rect.height = 800;
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn get_raw(&self) -> *mut sys::_cef_render_handler_t {
|
||||
self.object.cast()
|
||||
}
|
||||
}
|
||||
979
crates/bevy_cef_core/src/browser_process/browsers/keyboard.rs
Normal file
979
crates/bevy_cef_core/src/browser_process/browsers/keyboard.rs
Normal file
@@ -0,0 +1,979 @@
|
||||
//! ## Reference
|
||||
//!
|
||||
//! - [`cef_key_event_t`](https://cef-builds.spotifycdn.com/docs/106.1/structcef__key__event__t.html)
|
||||
//! - [KeyboardCodes](https://chromium.googlesource.com/external/Webkit/+/safari-4-branch/WebCore/platform/KeyboardCodes.h)
|
||||
|
||||
use bevy::input::ButtonState;
|
||||
use bevy::input::keyboard::KeyboardInput;
|
||||
use bevy::prelude::{ButtonInput, KeyCode};
|
||||
use cef_dll_sys::{cef_event_flags_t, cef_key_event_t, cef_key_event_type_t};
|
||||
|
||||
pub fn keyboard_modifiers(input: &ButtonInput<KeyCode>) -> u32 {
|
||||
let mut flags = 0u32;
|
||||
|
||||
if input.pressed(KeyCode::ControlLeft) || input.pressed(KeyCode::ControlRight) {
|
||||
flags |= cef_event_flags_t::EVENTFLAG_CONTROL_DOWN as u32;
|
||||
}
|
||||
if input.pressed(KeyCode::AltLeft) || input.pressed(KeyCode::AltRight) {
|
||||
flags |= cef_event_flags_t::EVENTFLAG_ALT_DOWN as u32;
|
||||
}
|
||||
if input.pressed(KeyCode::ShiftLeft) || input.pressed(KeyCode::ShiftRight) {
|
||||
flags |= cef_event_flags_t::EVENTFLAG_SHIFT_DOWN as u32;
|
||||
}
|
||||
if input.pressed(KeyCode::SuperLeft) || input.pressed(KeyCode::SuperRight) {
|
||||
flags |= cef_event_flags_t::EVENTFLAG_COMMAND_DOWN as u32;
|
||||
}
|
||||
if input.pressed(KeyCode::CapsLock) {
|
||||
flags |= cef_event_flags_t::EVENTFLAG_CAPS_LOCK_ON as u32;
|
||||
}
|
||||
if input.pressed(KeyCode::NumLock) {
|
||||
flags |= cef_event_flags_t::EVENTFLAG_NUM_LOCK_ON as u32;
|
||||
}
|
||||
|
||||
flags
|
||||
}
|
||||
|
||||
pub fn create_cef_key_event(
|
||||
modifiers: u32,
|
||||
_input: &ButtonInput<KeyCode>,
|
||||
key_event: &KeyboardInput,
|
||||
) -> Option<cef::KeyEvent> {
|
||||
let key_type = match key_event.state {
|
||||
// ButtonState::Pressed if input.just_pressed(key_event.key_code) => {
|
||||
// cef_key_event_type_t::KEYEVENT_RAWKEYDOWN
|
||||
// }
|
||||
ButtonState::Pressed => cef_key_event_type_t::KEYEVENT_CHAR,
|
||||
ButtonState::Released => cef_key_event_type_t::KEYEVENT_KEYUP,
|
||||
};
|
||||
let windows_key_code = keycode_to_windows_vk(key_event.key_code);
|
||||
|
||||
let character = key_event
|
||||
.text
|
||||
.as_ref()
|
||||
.and_then(|text| text.chars().next())
|
||||
.unwrap_or('\0') as u16;
|
||||
|
||||
Some(cef::KeyEvent::from(cef_key_event_t {
|
||||
size: core::mem::size_of::<cef_key_event_t>(),
|
||||
type_: key_type,
|
||||
modifiers,
|
||||
windows_key_code,
|
||||
native_key_code: to_native_key_code(&key_event.key_code) as _,
|
||||
character,
|
||||
unmodified_character: character,
|
||||
is_system_key: false as _,
|
||||
focus_on_editable_field: false as _,
|
||||
}))
|
||||
}
|
||||
|
||||
// fn is_not_character_key_code(keycode: &KeyCode) -> bool {
|
||||
// match keycode {
|
||||
// // Function keys are not character keys
|
||||
// KeyCode::F1
|
||||
// | KeyCode::F2
|
||||
// | KeyCode::F3
|
||||
// | KeyCode::F4
|
||||
// | KeyCode::F5
|
||||
// | KeyCode::F6
|
||||
// | KeyCode::F7
|
||||
// | KeyCode::F8
|
||||
// | KeyCode::F9
|
||||
// | KeyCode::F10
|
||||
// | KeyCode::F11
|
||||
// | KeyCode::F12 => true,
|
||||
//
|
||||
// // Navigation keys are not character keys
|
||||
// KeyCode::ArrowLeft
|
||||
// | KeyCode::ArrowUp
|
||||
// | KeyCode::ArrowRight
|
||||
// | KeyCode::ArrowDown
|
||||
// | KeyCode::Home
|
||||
// | KeyCode::End
|
||||
// | KeyCode::PageUp
|
||||
// | KeyCode::PageDown => true,
|
||||
//
|
||||
// // Modifier keys are not character keys
|
||||
// KeyCode::ShiftLeft
|
||||
// | KeyCode::ShiftRight
|
||||
// | KeyCode::ControlLeft
|
||||
// | KeyCode::ControlRight
|
||||
// | KeyCode::AltLeft
|
||||
// | KeyCode::AltRight
|
||||
// | KeyCode::SuperLeft
|
||||
// | KeyCode::SuperRight => true,
|
||||
//
|
||||
// // Lock keys are not character keys
|
||||
// KeyCode::CapsLock | KeyCode::NumLock | KeyCode::ScrollLock => true,
|
||||
//
|
||||
// // Special control keys are not character keys
|
||||
// KeyCode::Escape
|
||||
// | KeyCode::Tab
|
||||
// | KeyCode::Enter
|
||||
// | KeyCode::Backspace
|
||||
// | KeyCode::Delete
|
||||
// | KeyCode::Insert => true,
|
||||
//
|
||||
// // All other keys (letters, numbers, punctuation, space, numpad) are character keys
|
||||
// _ => false,
|
||||
// }
|
||||
// }
|
||||
|
||||
fn keycode_to_windows_vk(keycode: KeyCode) -> i32 {
|
||||
match keycode {
|
||||
// Letters
|
||||
KeyCode::KeyA => 0x41,
|
||||
KeyCode::KeyB => 0x42,
|
||||
KeyCode::KeyC => 0x43,
|
||||
KeyCode::KeyD => 0x44,
|
||||
KeyCode::KeyE => 0x45,
|
||||
KeyCode::KeyF => 0x46,
|
||||
KeyCode::KeyG => 0x47,
|
||||
KeyCode::KeyH => 0x48,
|
||||
KeyCode::KeyI => 0x49,
|
||||
KeyCode::KeyJ => 0x4A,
|
||||
KeyCode::KeyK => 0x4B,
|
||||
KeyCode::KeyL => 0x4C,
|
||||
KeyCode::KeyM => 0x4D,
|
||||
KeyCode::KeyN => 0x4E,
|
||||
KeyCode::KeyO => 0x4F,
|
||||
KeyCode::KeyP => 0x50,
|
||||
KeyCode::KeyQ => 0x51,
|
||||
KeyCode::KeyR => 0x52,
|
||||
KeyCode::KeyS => 0x53,
|
||||
KeyCode::KeyT => 0x54,
|
||||
KeyCode::KeyU => 0x55,
|
||||
KeyCode::KeyV => 0x56,
|
||||
KeyCode::KeyW => 0x57,
|
||||
KeyCode::KeyX => 0x58,
|
||||
KeyCode::KeyY => 0x59,
|
||||
KeyCode::KeyZ => 0x5A,
|
||||
|
||||
// Numbers
|
||||
KeyCode::Digit0 => 0x30,
|
||||
KeyCode::Digit1 => 0x31,
|
||||
KeyCode::Digit2 => 0x32,
|
||||
KeyCode::Digit3 => 0x33,
|
||||
KeyCode::Digit4 => 0x34,
|
||||
KeyCode::Digit5 => 0x35,
|
||||
KeyCode::Digit6 => 0x36,
|
||||
KeyCode::Digit7 => 0x37,
|
||||
KeyCode::Digit8 => 0x38,
|
||||
KeyCode::Digit9 => 0x39,
|
||||
|
||||
// Function keys
|
||||
KeyCode::F1 => 0x70,
|
||||
KeyCode::F2 => 0x71,
|
||||
KeyCode::F3 => 0x72,
|
||||
KeyCode::F4 => 0x73,
|
||||
KeyCode::F5 => 0x74,
|
||||
KeyCode::F6 => 0x75,
|
||||
KeyCode::F7 => 0x76,
|
||||
KeyCode::F8 => 0x77,
|
||||
KeyCode::F9 => 0x78,
|
||||
KeyCode::F10 => 0x79,
|
||||
KeyCode::F11 => 0x7A,
|
||||
KeyCode::F12 => 0x7B,
|
||||
|
||||
// Special keys
|
||||
KeyCode::Enter => 0x0D,
|
||||
KeyCode::Space => 0x20,
|
||||
KeyCode::Backspace => 0x08,
|
||||
KeyCode::Delete => 0x2E,
|
||||
KeyCode::Tab => 0x09,
|
||||
KeyCode::Escape => 0x1B,
|
||||
KeyCode::Insert => 0x2D,
|
||||
KeyCode::Home => 0x24,
|
||||
KeyCode::End => 0x23,
|
||||
KeyCode::PageUp => 0x21,
|
||||
KeyCode::PageDown => 0x22,
|
||||
|
||||
// Arrow keys
|
||||
KeyCode::ArrowLeft => 0x25,
|
||||
KeyCode::ArrowUp => 0x26,
|
||||
KeyCode::ArrowRight => 0x27,
|
||||
KeyCode::ArrowDown => 0x28,
|
||||
|
||||
// Modifier keys
|
||||
KeyCode::ShiftLeft | KeyCode::ShiftRight => 0x10,
|
||||
KeyCode::ControlLeft | KeyCode::ControlRight => 0x11,
|
||||
KeyCode::AltLeft | KeyCode::AltRight => 0x12,
|
||||
KeyCode::SuperLeft => 0x5B, // Left Windows key
|
||||
KeyCode::SuperRight => 0x5C, // Right Windows key
|
||||
|
||||
// Lock keys
|
||||
KeyCode::CapsLock => 0x14,
|
||||
KeyCode::NumLock => 0x90,
|
||||
KeyCode::ScrollLock => 0x91,
|
||||
|
||||
// Punctuation
|
||||
KeyCode::Semicolon => 0xBA,
|
||||
KeyCode::Equal => 0xBB,
|
||||
KeyCode::Comma => 0xBC,
|
||||
KeyCode::Minus => 0xBD,
|
||||
KeyCode::Period => 0xBE,
|
||||
KeyCode::Slash => 0xBF,
|
||||
KeyCode::Backquote => 0xC0,
|
||||
KeyCode::BracketLeft => 0xDB,
|
||||
KeyCode::Backslash => 0xDC,
|
||||
KeyCode::BracketRight => 0xDD,
|
||||
KeyCode::Quote => 0xDE,
|
||||
|
||||
// Numpad
|
||||
KeyCode::Numpad0 => 0x60,
|
||||
KeyCode::Numpad1 => 0x61,
|
||||
KeyCode::Numpad2 => 0x62,
|
||||
KeyCode::Numpad3 => 0x63,
|
||||
KeyCode::Numpad4 => 0x64,
|
||||
KeyCode::Numpad5 => 0x65,
|
||||
KeyCode::Numpad6 => 0x66,
|
||||
KeyCode::Numpad7 => 0x67,
|
||||
KeyCode::Numpad8 => 0x68,
|
||||
KeyCode::Numpad9 => 0x69,
|
||||
KeyCode::NumpadMultiply => 0x6A,
|
||||
KeyCode::NumpadAdd => 0x6B,
|
||||
KeyCode::NumpadSubtract => 0x6D,
|
||||
KeyCode::NumpadDecimal => 0x6E,
|
||||
KeyCode::NumpadDivide => 0x6F,
|
||||
|
||||
// Default case for unhandled keys
|
||||
_ => 0,
|
||||
}
|
||||
}
|
||||
|
||||
// fn is_special_key(keycode: &KeyCode) -> bool {
|
||||
// matches!(
|
||||
// keycode,
|
||||
// KeyCode::Enter
|
||||
// | KeyCode::Space
|
||||
// | KeyCode::Backspace
|
||||
// | KeyCode::Delete
|
||||
// | KeyCode::Tab
|
||||
// | KeyCode::Escape
|
||||
// | KeyCode::Insert
|
||||
// | KeyCode::Home
|
||||
// | KeyCode::End
|
||||
// | KeyCode::PageUp
|
||||
// | KeyCode::PageDown
|
||||
// )
|
||||
// }
|
||||
|
||||
/// Native key codes for different platforms based on MDN documentation
|
||||
/// [`Keyboard_event_key_values`](https://developer.mozilla.org/en-US/docs/Web/API/UI_Events/Keyboard_event_key_values)
|
||||
fn to_native_key_code(keycode: &KeyCode) -> u32 {
|
||||
match keycode {
|
||||
// Letters - Platform specific native codes
|
||||
KeyCode::KeyA => {
|
||||
if cfg!(target_os = "macos") {
|
||||
0x00
|
||||
} else {
|
||||
0x41
|
||||
} // Linux/default
|
||||
}
|
||||
KeyCode::KeyB => {
|
||||
if cfg!(target_os = "macos") {
|
||||
0x0B
|
||||
} else {
|
||||
0x42
|
||||
}
|
||||
}
|
||||
KeyCode::KeyC => {
|
||||
if cfg!(target_os = "macos") {
|
||||
0x08
|
||||
} else {
|
||||
0x43
|
||||
}
|
||||
}
|
||||
KeyCode::KeyD => {
|
||||
if cfg!(target_os = "macos") {
|
||||
0x02
|
||||
} else {
|
||||
0x44
|
||||
}
|
||||
}
|
||||
KeyCode::KeyE => {
|
||||
if cfg!(target_os = "macos") {
|
||||
0x0E
|
||||
} else {
|
||||
0x45
|
||||
}
|
||||
}
|
||||
KeyCode::KeyF => {
|
||||
if cfg!(target_os = "macos") {
|
||||
0x03
|
||||
} else {
|
||||
0x46
|
||||
}
|
||||
}
|
||||
KeyCode::KeyG => {
|
||||
if cfg!(target_os = "macos") {
|
||||
0x05
|
||||
} else {
|
||||
0x47
|
||||
}
|
||||
}
|
||||
KeyCode::KeyH => {
|
||||
if cfg!(target_os = "macos") {
|
||||
0x04
|
||||
} else {
|
||||
0x48
|
||||
}
|
||||
}
|
||||
KeyCode::KeyI => {
|
||||
if cfg!(target_os = "macos") {
|
||||
0x22
|
||||
} else {
|
||||
0x49
|
||||
}
|
||||
}
|
||||
KeyCode::KeyJ => {
|
||||
if cfg!(target_os = "macos") {
|
||||
0x26
|
||||
} else {
|
||||
0x4A
|
||||
}
|
||||
}
|
||||
KeyCode::KeyK => {
|
||||
if cfg!(target_os = "macos") {
|
||||
0x28
|
||||
} else {
|
||||
0x4B
|
||||
}
|
||||
}
|
||||
KeyCode::KeyL => {
|
||||
if cfg!(target_os = "macos") {
|
||||
0x25
|
||||
} else {
|
||||
0x4C
|
||||
}
|
||||
}
|
||||
KeyCode::KeyM => {
|
||||
if cfg!(target_os = "macos") {
|
||||
0x2E
|
||||
} else {
|
||||
0x4D
|
||||
}
|
||||
}
|
||||
KeyCode::KeyN => {
|
||||
if cfg!(target_os = "macos") {
|
||||
0x2D
|
||||
} else {
|
||||
0x4E
|
||||
}
|
||||
}
|
||||
KeyCode::KeyO => {
|
||||
if cfg!(target_os = "macos") {
|
||||
0x1F
|
||||
} else {
|
||||
0x4F
|
||||
}
|
||||
}
|
||||
KeyCode::KeyP => {
|
||||
if cfg!(target_os = "macos") {
|
||||
0x23
|
||||
} else {
|
||||
0x50
|
||||
}
|
||||
}
|
||||
KeyCode::KeyQ => {
|
||||
if cfg!(target_os = "macos") {
|
||||
0x0C
|
||||
} else {
|
||||
0x51
|
||||
}
|
||||
}
|
||||
KeyCode::KeyR => {
|
||||
if cfg!(target_os = "macos") {
|
||||
0x0F
|
||||
} else {
|
||||
0x52
|
||||
}
|
||||
}
|
||||
KeyCode::KeyS => {
|
||||
if cfg!(target_os = "macos") {
|
||||
0x01
|
||||
} else {
|
||||
0x53
|
||||
}
|
||||
}
|
||||
KeyCode::KeyT => {
|
||||
if cfg!(target_os = "macos") {
|
||||
0x11
|
||||
} else {
|
||||
0x54
|
||||
}
|
||||
}
|
||||
KeyCode::KeyU => {
|
||||
if cfg!(target_os = "macos") {
|
||||
0x20
|
||||
} else {
|
||||
0x55
|
||||
}
|
||||
}
|
||||
KeyCode::KeyV => {
|
||||
if cfg!(target_os = "macos") {
|
||||
0x09
|
||||
} else {
|
||||
0x56
|
||||
}
|
||||
}
|
||||
KeyCode::KeyW => {
|
||||
if cfg!(target_os = "macos") {
|
||||
0x0D
|
||||
} else {
|
||||
0x57
|
||||
}
|
||||
}
|
||||
KeyCode::KeyX => {
|
||||
if cfg!(target_os = "macos") {
|
||||
0x07
|
||||
} else {
|
||||
0x58
|
||||
}
|
||||
}
|
||||
KeyCode::KeyY => {
|
||||
if cfg!(target_os = "macos") {
|
||||
0x10
|
||||
} else {
|
||||
0x59
|
||||
}
|
||||
}
|
||||
KeyCode::KeyZ => {
|
||||
if cfg!(target_os = "macos") {
|
||||
0x06
|
||||
} else {
|
||||
0x5A
|
||||
}
|
||||
}
|
||||
|
||||
// Numbers
|
||||
KeyCode::Digit0 => {
|
||||
if cfg!(target_os = "macos") {
|
||||
0x1D
|
||||
} else {
|
||||
0x30
|
||||
}
|
||||
}
|
||||
KeyCode::Digit1 => {
|
||||
if cfg!(target_os = "macos") {
|
||||
0x12
|
||||
} else {
|
||||
0x31
|
||||
}
|
||||
}
|
||||
KeyCode::Digit2 => {
|
||||
if cfg!(target_os = "macos") {
|
||||
0x13
|
||||
} else {
|
||||
0x32
|
||||
}
|
||||
}
|
||||
KeyCode::Digit3 => {
|
||||
if cfg!(target_os = "macos") {
|
||||
0x14
|
||||
} else {
|
||||
0x33
|
||||
}
|
||||
}
|
||||
KeyCode::Digit4 => {
|
||||
if cfg!(target_os = "macos") {
|
||||
0x15
|
||||
} else {
|
||||
0x34
|
||||
}
|
||||
}
|
||||
KeyCode::Digit5 => {
|
||||
if cfg!(target_os = "macos") {
|
||||
0x17
|
||||
} else {
|
||||
0x35
|
||||
}
|
||||
}
|
||||
KeyCode::Digit6 => {
|
||||
if cfg!(target_os = "macos") {
|
||||
0x16
|
||||
} else {
|
||||
0x36
|
||||
}
|
||||
}
|
||||
KeyCode::Digit7 => {
|
||||
if cfg!(target_os = "macos") {
|
||||
0x1A
|
||||
} else {
|
||||
0x37
|
||||
}
|
||||
}
|
||||
KeyCode::Digit8 => {
|
||||
if cfg!(target_os = "macos") {
|
||||
0x1C
|
||||
} else {
|
||||
0x38
|
||||
}
|
||||
}
|
||||
KeyCode::Digit9 => {
|
||||
if cfg!(target_os = "macos") {
|
||||
0x19
|
||||
} else {
|
||||
0x39
|
||||
}
|
||||
}
|
||||
|
||||
// Function keys
|
||||
KeyCode::F1 => {
|
||||
if cfg!(target_os = "macos") {
|
||||
0x7A
|
||||
} else {
|
||||
0x70
|
||||
}
|
||||
}
|
||||
KeyCode::F2 => {
|
||||
if cfg!(target_os = "macos") {
|
||||
0x78
|
||||
} else {
|
||||
0x71
|
||||
}
|
||||
}
|
||||
KeyCode::F3 => {
|
||||
if cfg!(target_os = "macos") {
|
||||
0x63
|
||||
} else {
|
||||
0x72
|
||||
}
|
||||
}
|
||||
KeyCode::F4 => {
|
||||
if cfg!(target_os = "macos") {
|
||||
0x76
|
||||
} else {
|
||||
0x73
|
||||
}
|
||||
}
|
||||
KeyCode::F5 => {
|
||||
if cfg!(target_os = "macos") {
|
||||
0x60
|
||||
} else {
|
||||
0x74
|
||||
}
|
||||
}
|
||||
KeyCode::F6 => {
|
||||
if cfg!(target_os = "macos") {
|
||||
0x61
|
||||
} else {
|
||||
0x75
|
||||
}
|
||||
}
|
||||
KeyCode::F7 => {
|
||||
if cfg!(target_os = "macos") {
|
||||
0x62
|
||||
} else {
|
||||
0x76
|
||||
}
|
||||
}
|
||||
KeyCode::F8 => {
|
||||
if cfg!(target_os = "macos") {
|
||||
0x64
|
||||
} else {
|
||||
0x77
|
||||
}
|
||||
}
|
||||
KeyCode::F9 => {
|
||||
if cfg!(target_os = "macos") {
|
||||
0x65
|
||||
} else {
|
||||
0x78
|
||||
}
|
||||
}
|
||||
KeyCode::F10 => {
|
||||
if cfg!(target_os = "macos") {
|
||||
0x6D
|
||||
} else {
|
||||
0x79
|
||||
}
|
||||
}
|
||||
KeyCode::F11 => {
|
||||
if cfg!(target_os = "macos") {
|
||||
0x67
|
||||
} else {
|
||||
0x7A
|
||||
}
|
||||
}
|
||||
KeyCode::F12 => {
|
||||
if cfg!(target_os = "macos") {
|
||||
0x6F
|
||||
} else {
|
||||
0x7B
|
||||
}
|
||||
}
|
||||
|
||||
// Special keys
|
||||
KeyCode::Enter => {
|
||||
if cfg!(target_os = "macos") {
|
||||
0x24
|
||||
} else {
|
||||
0x0D
|
||||
}
|
||||
}
|
||||
KeyCode::Space => {
|
||||
if cfg!(target_os = "macos") {
|
||||
0x31
|
||||
} else {
|
||||
0x20
|
||||
}
|
||||
}
|
||||
KeyCode::Backspace => {
|
||||
if cfg!(target_os = "macos") {
|
||||
0x33
|
||||
} else {
|
||||
0x08
|
||||
}
|
||||
}
|
||||
KeyCode::Delete => {
|
||||
if cfg!(target_os = "macos") {
|
||||
0x75
|
||||
} else {
|
||||
0x2E
|
||||
}
|
||||
}
|
||||
KeyCode::Tab => {
|
||||
if cfg!(target_os = "macos") {
|
||||
0x30
|
||||
} else {
|
||||
0x09
|
||||
}
|
||||
}
|
||||
KeyCode::Escape => {
|
||||
if cfg!(target_os = "macos") {
|
||||
0x35
|
||||
} else {
|
||||
0x1B
|
||||
}
|
||||
}
|
||||
KeyCode::Insert => {
|
||||
if cfg!(target_os = "macos") {
|
||||
0x72
|
||||
} else {
|
||||
0x2D
|
||||
}
|
||||
}
|
||||
KeyCode::Home => {
|
||||
if cfg!(target_os = "macos") {
|
||||
0x73
|
||||
} else {
|
||||
0x24
|
||||
}
|
||||
}
|
||||
KeyCode::End => {
|
||||
if cfg!(target_os = "macos") {
|
||||
0x77
|
||||
} else {
|
||||
0x23
|
||||
}
|
||||
}
|
||||
KeyCode::PageUp => {
|
||||
if cfg!(target_os = "macos") {
|
||||
0x74
|
||||
} else {
|
||||
0x21
|
||||
}
|
||||
}
|
||||
KeyCode::PageDown => {
|
||||
if cfg!(target_os = "macos") {
|
||||
0x79
|
||||
} else {
|
||||
0x22
|
||||
}
|
||||
}
|
||||
|
||||
// Arrow keys
|
||||
KeyCode::ArrowLeft => {
|
||||
if cfg!(target_os = "macos") {
|
||||
0x7B
|
||||
} else {
|
||||
0x25
|
||||
}
|
||||
}
|
||||
KeyCode::ArrowUp => {
|
||||
if cfg!(target_os = "macos") {
|
||||
0x7E
|
||||
} else {
|
||||
0x26
|
||||
}
|
||||
}
|
||||
KeyCode::ArrowRight => {
|
||||
if cfg!(target_os = "macos") {
|
||||
0x7C
|
||||
} else {
|
||||
0x27
|
||||
}
|
||||
}
|
||||
KeyCode::ArrowDown => {
|
||||
if cfg!(target_os = "macos") {
|
||||
0x7D
|
||||
} else {
|
||||
0x28
|
||||
}
|
||||
}
|
||||
|
||||
// Modifier keys
|
||||
KeyCode::ShiftLeft => {
|
||||
if cfg!(target_os = "macos") {
|
||||
0x38
|
||||
} else {
|
||||
0xA0
|
||||
}
|
||||
}
|
||||
KeyCode::ShiftRight => {
|
||||
if cfg!(target_os = "macos") {
|
||||
0x3C
|
||||
} else {
|
||||
0xA1
|
||||
}
|
||||
}
|
||||
KeyCode::ControlLeft => {
|
||||
if cfg!(target_os = "macos") {
|
||||
0x3B
|
||||
} else {
|
||||
0xA2
|
||||
}
|
||||
}
|
||||
KeyCode::ControlRight => {
|
||||
if cfg!(target_os = "macos") {
|
||||
0x3E
|
||||
} else {
|
||||
0xA3
|
||||
}
|
||||
}
|
||||
KeyCode::AltLeft => {
|
||||
if cfg!(target_os = "macos") {
|
||||
0x3A
|
||||
} else {
|
||||
0xA4
|
||||
}
|
||||
}
|
||||
KeyCode::AltRight => {
|
||||
if cfg!(target_os = "macos") {
|
||||
0x3D
|
||||
} else {
|
||||
0xA5
|
||||
}
|
||||
}
|
||||
KeyCode::SuperLeft => {
|
||||
if cfg!(target_os = "macos") {
|
||||
0x37
|
||||
} else {
|
||||
0x5B
|
||||
}
|
||||
}
|
||||
KeyCode::SuperRight => {
|
||||
if cfg!(target_os = "macos") {
|
||||
0x36
|
||||
} else {
|
||||
0x5C
|
||||
}
|
||||
}
|
||||
|
||||
// Lock keys
|
||||
KeyCode::CapsLock => {
|
||||
if cfg!(target_os = "macos") {
|
||||
0x39
|
||||
} else {
|
||||
0x14
|
||||
}
|
||||
}
|
||||
KeyCode::NumLock => {
|
||||
if cfg!(target_os = "macos") {
|
||||
0x47
|
||||
} else {
|
||||
0x90
|
||||
}
|
||||
}
|
||||
KeyCode::ScrollLock => 0x91,
|
||||
|
||||
// Punctuation
|
||||
KeyCode::Semicolon => {
|
||||
if cfg!(target_os = "macos") {
|
||||
0x29
|
||||
} else {
|
||||
0xBA
|
||||
}
|
||||
}
|
||||
KeyCode::Equal => {
|
||||
if cfg!(target_os = "macos") {
|
||||
0x18
|
||||
} else {
|
||||
0xBB
|
||||
}
|
||||
}
|
||||
KeyCode::Comma => {
|
||||
if cfg!(target_os = "macos") {
|
||||
0x2B
|
||||
} else {
|
||||
0xBC
|
||||
}
|
||||
}
|
||||
KeyCode::Minus => {
|
||||
if cfg!(target_os = "macos") {
|
||||
0x1B
|
||||
} else {
|
||||
0xBD
|
||||
}
|
||||
}
|
||||
KeyCode::Period => {
|
||||
if cfg!(target_os = "macos") {
|
||||
0x2F
|
||||
} else {
|
||||
0xBE
|
||||
}
|
||||
}
|
||||
KeyCode::Slash => {
|
||||
if cfg!(target_os = "macos") {
|
||||
0x2C
|
||||
} else {
|
||||
0xBF
|
||||
}
|
||||
}
|
||||
KeyCode::Backquote => {
|
||||
if cfg!(target_os = "macos") {
|
||||
0x32
|
||||
} else {
|
||||
0xC0
|
||||
}
|
||||
}
|
||||
KeyCode::BracketLeft => {
|
||||
if cfg!(target_os = "macos") {
|
||||
0x21
|
||||
} else {
|
||||
0xDB
|
||||
}
|
||||
}
|
||||
KeyCode::Backslash => {
|
||||
if cfg!(target_os = "macos") {
|
||||
0x2A
|
||||
} else {
|
||||
0xDC
|
||||
}
|
||||
}
|
||||
KeyCode::BracketRight => {
|
||||
if cfg!(target_os = "macos") {
|
||||
0x1E
|
||||
} else {
|
||||
0xDD
|
||||
}
|
||||
}
|
||||
KeyCode::Quote => {
|
||||
if cfg!(target_os = "macos") {
|
||||
0x27
|
||||
} else {
|
||||
0xDE
|
||||
}
|
||||
}
|
||||
|
||||
// Numpad
|
||||
KeyCode::Numpad0 => {
|
||||
if cfg!(target_os = "macos") {
|
||||
0x52
|
||||
} else {
|
||||
0x60
|
||||
}
|
||||
}
|
||||
KeyCode::Numpad1 => {
|
||||
if cfg!(target_os = "macos") {
|
||||
0x53
|
||||
} else {
|
||||
0x61
|
||||
}
|
||||
}
|
||||
KeyCode::Numpad2 => {
|
||||
if cfg!(target_os = "macos") {
|
||||
0x54
|
||||
} else {
|
||||
0x62
|
||||
}
|
||||
}
|
||||
KeyCode::Numpad3 => {
|
||||
if cfg!(target_os = "macos") {
|
||||
0x55
|
||||
} else {
|
||||
0x63
|
||||
}
|
||||
}
|
||||
KeyCode::Numpad4 => {
|
||||
if cfg!(target_os = "macos") {
|
||||
0x56
|
||||
} else {
|
||||
0x64
|
||||
}
|
||||
}
|
||||
KeyCode::Numpad5 => {
|
||||
if cfg!(target_os = "macos") {
|
||||
0x57
|
||||
} else {
|
||||
0x65
|
||||
}
|
||||
}
|
||||
KeyCode::Numpad6 => {
|
||||
if cfg!(target_os = "macos") {
|
||||
0x58
|
||||
} else {
|
||||
0x66
|
||||
}
|
||||
}
|
||||
KeyCode::Numpad7 => {
|
||||
if cfg!(target_os = "macos") {
|
||||
0x59
|
||||
} else {
|
||||
0x67
|
||||
}
|
||||
}
|
||||
KeyCode::Numpad8 => {
|
||||
if cfg!(target_os = "macos") {
|
||||
0x5B
|
||||
} else {
|
||||
0x68
|
||||
}
|
||||
}
|
||||
KeyCode::Numpad9 => {
|
||||
if cfg!(target_os = "macos") {
|
||||
0x5C
|
||||
} else {
|
||||
0x69
|
||||
}
|
||||
}
|
||||
KeyCode::NumpadMultiply => {
|
||||
if cfg!(target_os = "macos") {
|
||||
0x43
|
||||
} else {
|
||||
0x6A
|
||||
}
|
||||
}
|
||||
KeyCode::NumpadAdd => {
|
||||
if cfg!(target_os = "macos") {
|
||||
0x45
|
||||
} else {
|
||||
0x6B
|
||||
}
|
||||
}
|
||||
KeyCode::NumpadSubtract => {
|
||||
if cfg!(target_os = "macos") {
|
||||
0x4E
|
||||
} else {
|
||||
0x6D
|
||||
}
|
||||
}
|
||||
KeyCode::NumpadDecimal => {
|
||||
if cfg!(target_os = "macos") {
|
||||
0x41
|
||||
} else {
|
||||
0x6E
|
||||
}
|
||||
}
|
||||
KeyCode::NumpadDivide => {
|
||||
if cfg!(target_os = "macos") {
|
||||
0x4B
|
||||
} else {
|
||||
0x6F
|
||||
}
|
||||
}
|
||||
|
||||
// Default case for unhandled keys
|
||||
_ => 0,
|
||||
}
|
||||
}
|
||||
129
crates/bevy_cef_core/src/browser_process/client_handler.rs
Normal file
129
crates/bevy_cef_core/src/browser_process/client_handler.rs
Normal file
@@ -0,0 +1,129 @@
|
||||
mod brp_handler;
|
||||
mod js_emit_event_handler;
|
||||
|
||||
use crate::browser_process::ContextMenuHandlerBuilder;
|
||||
use crate::prelude::IntoString;
|
||||
use cef::rc::{Rc, RcImpl};
|
||||
use cef::{
|
||||
Browser, Client, ContextMenuHandler, DisplayHandler, Frame, ImplClient, ImplProcessMessage,
|
||||
ListValue, ProcessId, ProcessMessage, RenderHandler, WrapClient, sys,
|
||||
};
|
||||
use std::os::raw::c_int;
|
||||
|
||||
pub use brp_handler::BrpHandler;
|
||||
pub use js_emit_event_handler::{IpcEventRaw, JsEmitEventHandler};
|
||||
|
||||
pub trait ProcessMessageHandler {
|
||||
fn process_name(&self) -> &'static str;
|
||||
|
||||
fn handle_message(&self, browser: &mut Browser, frame: &mut Frame, args: Option<ListValue>);
|
||||
}
|
||||
|
||||
/// ## Reference
|
||||
///
|
||||
/// - [`CefBrowser Class Reference`](https://cef-builds.spotifycdn.com/docs/106.1/classCefBrowser.html)
|
||||
pub struct ClientHandlerBuilder {
|
||||
object: *mut RcImpl<sys::cef_client_t, Self>,
|
||||
render_handler: RenderHandler,
|
||||
context_menu_handler: ContextMenuHandler,
|
||||
message_handlers: Vec<std::rc::Rc<dyn ProcessMessageHandler>>,
|
||||
display_handler: Option<DisplayHandler>,
|
||||
}
|
||||
|
||||
impl ClientHandlerBuilder {
|
||||
pub fn new(render_handler: RenderHandler) -> Self {
|
||||
Self {
|
||||
object: std::ptr::null_mut(),
|
||||
render_handler,
|
||||
context_menu_handler: ContextMenuHandlerBuilder::build(),
|
||||
message_handlers: Vec::new(),
|
||||
display_handler: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_display_handler(mut self, display_handler: DisplayHandler) -> Self {
|
||||
self.display_handler = Some(display_handler);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_message_handler(mut self, handler: impl ProcessMessageHandler + 'static) -> Self {
|
||||
self.message_handlers.push(std::rc::Rc::new(handler));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn build(self) -> Client {
|
||||
Client::new(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl Rc for ClientHandlerBuilder {
|
||||
fn as_base(&self) -> &sys::cef_base_ref_counted_t {
|
||||
unsafe {
|
||||
let base = &*self.object;
|
||||
std::mem::transmute(&base.cef_object)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl WrapClient for ClientHandlerBuilder {
|
||||
fn wrap_rc(&mut self, object: *mut RcImpl<sys::cef_client_t, Self>) {
|
||||
self.object = object;
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for ClientHandlerBuilder {
|
||||
fn clone(&self) -> Self {
|
||||
let object = unsafe {
|
||||
let rc_impl = &mut *self.object;
|
||||
rc_impl.interface.add_ref();
|
||||
rc_impl
|
||||
};
|
||||
|
||||
Self {
|
||||
object,
|
||||
render_handler: self.render_handler.clone(),
|
||||
context_menu_handler: self.context_menu_handler.clone(),
|
||||
message_handlers: self.message_handlers.clone(),
|
||||
display_handler: self.display_handler.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ImplClient for ClientHandlerBuilder {
|
||||
fn render_handler(&self) -> Option<RenderHandler> {
|
||||
Some(self.render_handler.clone())
|
||||
}
|
||||
|
||||
fn display_handler(&self) -> Option<DisplayHandler> {
|
||||
self.display_handler.clone()
|
||||
}
|
||||
|
||||
fn on_process_message_received(
|
||||
&self,
|
||||
browser: Option<&mut Browser>,
|
||||
frame: Option<&mut Frame>,
|
||||
_: ProcessId,
|
||||
message: Option<&mut ProcessMessage>,
|
||||
) -> c_int {
|
||||
if let Some(message) = message
|
||||
&& let Some(browser) = browser
|
||||
&& let Some(frame) = frame
|
||||
&& let Some(name) = Some(message.name().into_string())
|
||||
&& let Some(handler) = self
|
||||
.message_handlers
|
||||
.iter()
|
||||
.find(|h| h.process_name() == name.as_str())
|
||||
{
|
||||
{
|
||||
let args = message.argument_list();
|
||||
handler.handle_message(browser, frame, args);
|
||||
}
|
||||
};
|
||||
1
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn get_raw(&self) -> *mut sys::_cef_client_t {
|
||||
self.object.cast()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
use crate::browser_process::client_handler::ProcessMessageHandler;
|
||||
use crate::prelude::PROCESS_MESSAGE_BRP;
|
||||
use crate::util::IntoString;
|
||||
use async_channel::Sender;
|
||||
use bevy::tasks::IoTaskPool;
|
||||
use bevy_remote::{BrpMessage, BrpRequest};
|
||||
use cef::{
|
||||
Browser, Frame, ImplFrame, ImplListValue, ImplProcessMessage, ListValue, ProcessId,
|
||||
process_message_create,
|
||||
};
|
||||
use cef_dll_sys::cef_process_id_t;
|
||||
|
||||
pub struct BrpHandler {
|
||||
sender: Sender<BrpMessage>,
|
||||
}
|
||||
|
||||
impl BrpHandler {
|
||||
pub const fn new(sender: Sender<BrpMessage>) -> Self {
|
||||
Self { sender }
|
||||
}
|
||||
}
|
||||
|
||||
impl ProcessMessageHandler for BrpHandler {
|
||||
fn process_name(&self) -> &'static str {
|
||||
PROCESS_MESSAGE_BRP
|
||||
}
|
||||
|
||||
fn handle_message(&self, _browser: &mut Browser, frame: &mut Frame, args: Option<ListValue>) {
|
||||
if let Some(args) = args
|
||||
&& let Ok(request) = serde_json::from_str::<BrpRequest>(&args.string(1).into_string())
|
||||
{
|
||||
let id = args.string(0).into_string();
|
||||
let frame = frame.clone();
|
||||
let brp_sender = self.sender.clone();
|
||||
IoTaskPool::get()
|
||||
.spawn(async move {
|
||||
let (tx, rx) = async_channel::unbounded();
|
||||
if brp_sender
|
||||
.send(BrpMessage {
|
||||
method: request.method,
|
||||
params: request.params,
|
||||
sender: tx,
|
||||
})
|
||||
.await
|
||||
.is_err()
|
||||
{
|
||||
return;
|
||||
}
|
||||
if let Ok(result) = rx.recv().await
|
||||
&& let Some(mut message) =
|
||||
process_message_create(Some(&PROCESS_MESSAGE_BRP.into()))
|
||||
&& let Some(argument_list) = message.argument_list()
|
||||
{
|
||||
argument_list.set_string(0, Some(&id.as_str().into()));
|
||||
argument_list.set_string(
|
||||
1,
|
||||
Some(&serde_json::to_string(&result).unwrap().as_str().into()),
|
||||
);
|
||||
frame.send_process_message(
|
||||
ProcessId::from(cef_process_id_t::PID_RENDERER),
|
||||
Some(&mut message),
|
||||
);
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
use crate::browser_process::client_handler::ProcessMessageHandler;
|
||||
use crate::prelude::{IntoString, PROCESS_MESSAGE_JS_EMIT};
|
||||
use async_channel::Sender;
|
||||
use bevy::prelude::Entity;
|
||||
use cef::{Browser, Frame, ImplListValue, ListValue};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
|
||||
pub struct IpcEventRaw {
|
||||
pub webview: Entity,
|
||||
pub payload: String,
|
||||
}
|
||||
|
||||
pub struct JsEmitEventHandler {
|
||||
webview: Entity,
|
||||
sender: Sender<IpcEventRaw>,
|
||||
}
|
||||
|
||||
impl JsEmitEventHandler {
|
||||
pub const fn new(webview: Entity, sender: Sender<IpcEventRaw>) -> Self {
|
||||
Self { sender, webview }
|
||||
}
|
||||
}
|
||||
|
||||
impl ProcessMessageHandler for JsEmitEventHandler {
|
||||
fn process_name(&self) -> &'static str {
|
||||
PROCESS_MESSAGE_JS_EMIT
|
||||
}
|
||||
|
||||
fn handle_message(&self, _browser: &mut Browser, _frame: &mut Frame, args: Option<ListValue>) {
|
||||
if let Some(args) = args {
|
||||
let event = IpcEventRaw {
|
||||
webview: self.webview, // Placeholder, should be set correctly
|
||||
payload: args.string(0).into_string(),
|
||||
};
|
||||
let _ = self.sender.send_blocking(event);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
use cef::rc::{Rc, RcImpl};
|
||||
use cef::{ImplContextMenuHandler, WrapContextMenuHandler, sys};
|
||||
|
||||
/// ## Reference
|
||||
///
|
||||
/// - [`CefContextMenuHandler Class Reference`](https://cef-builds.spotifycdn.com/docs/106.1/classCefContextMenuHandler.html)
|
||||
pub struct ContextMenuHandlerBuilder {
|
||||
object: *mut RcImpl<sys::_cef_context_menu_handler_t, Self>,
|
||||
}
|
||||
|
||||
impl ContextMenuHandlerBuilder {
|
||||
pub fn build() -> cef::ContextMenuHandler {
|
||||
cef::ContextMenuHandler::new(Self {
|
||||
object: core::ptr::null_mut(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl WrapContextMenuHandler for ContextMenuHandlerBuilder {
|
||||
fn wrap_rc(&mut self, object: *mut RcImpl<sys::_cef_context_menu_handler_t, Self>) {
|
||||
self.object = object;
|
||||
}
|
||||
}
|
||||
|
||||
impl Rc for ContextMenuHandlerBuilder {
|
||||
fn as_base(&self) -> &sys::cef_base_ref_counted_t {
|
||||
unsafe {
|
||||
let base = &*self.object;
|
||||
core::mem::transmute(&base.cef_object)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for ContextMenuHandlerBuilder {
|
||||
fn clone(&self) -> Self {
|
||||
let object = unsafe {
|
||||
let rc_impl = &mut *self.object;
|
||||
rc_impl.interface.add_ref();
|
||||
rc_impl
|
||||
};
|
||||
Self { object }
|
||||
}
|
||||
}
|
||||
|
||||
impl ImplContextMenuHandler for ContextMenuHandlerBuilder {
|
||||
#[inline]
|
||||
fn get_raw(&self) -> *mut sys::cef_context_menu_handler_t {
|
||||
self.object as *mut sys::cef_context_menu_handler_t
|
||||
}
|
||||
}
|
||||
165
crates/bevy_cef_core/src/browser_process/display_handler.rs
Normal file
165
crates/bevy_cef_core/src/browser_process/display_handler.rs
Normal file
@@ -0,0 +1,165 @@
|
||||
use async_channel::Sender;
|
||||
use bevy::log::{error, info, trace, warn};
|
||||
use bevy::window::SystemCursorIcon;
|
||||
use cef::rc::{ConvertParam, Rc, RcImpl};
|
||||
use cef::{
|
||||
Browser, CefString, CursorInfo, CursorType, ImplDisplayHandler, LogSeverity,
|
||||
WrapDisplayHandler, sys,
|
||||
};
|
||||
use cef_dll_sys::{cef_cursor_type_t, cef_log_severity_t};
|
||||
use std::os::raw::c_int;
|
||||
|
||||
pub type SystemCursorIconSenderInner = Sender<SystemCursorIcon>;
|
||||
|
||||
/// ## Reference
|
||||
///
|
||||
/// - [`CefDisplayHandler Class Reference`](https://cef-builds.spotifycdn.com/docs/112.3/classCefDisplayHandler.html#af1cc8410a0b1a97166923428d3794636)
|
||||
pub struct DisplayHandlerBuilder {
|
||||
object: *mut RcImpl<sys::cef_display_handler_t, Self>,
|
||||
cursor_icon: SystemCursorIconSenderInner,
|
||||
}
|
||||
|
||||
impl DisplayHandlerBuilder {
|
||||
pub fn build(cursor_icon: SystemCursorIconSenderInner) -> cef::DisplayHandler {
|
||||
cef::DisplayHandler::new(Self {
|
||||
object: core::ptr::null_mut(),
|
||||
cursor_icon,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Rc for DisplayHandlerBuilder {
|
||||
fn as_base(&self) -> &sys::cef_base_ref_counted_t {
|
||||
unsafe {
|
||||
let base = &*self.object;
|
||||
core::mem::transmute(&base.cef_object)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for DisplayHandlerBuilder {
|
||||
fn clone(&self) -> Self {
|
||||
let object = unsafe {
|
||||
let rc_impl = &mut *self.object;
|
||||
rc_impl.interface.add_ref();
|
||||
rc_impl
|
||||
};
|
||||
Self {
|
||||
object,
|
||||
cursor_icon: self.cursor_icon.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl WrapDisplayHandler for DisplayHandlerBuilder {
|
||||
fn wrap_rc(&mut self, object: *mut RcImpl<sys::cef_display_handler_t, Self>) {
|
||||
self.object = object;
|
||||
}
|
||||
}
|
||||
|
||||
impl ImplDisplayHandler for DisplayHandlerBuilder {
|
||||
fn on_console_message(
|
||||
&self,
|
||||
_: Option<&mut Browser>,
|
||||
level: LogSeverity,
|
||||
message: Option<&CefString>,
|
||||
source: Option<&CefString>,
|
||||
line: c_int,
|
||||
) -> c_int {
|
||||
let message = format!(
|
||||
"{}\nline:{line}\n{}",
|
||||
source.map(|s| s.to_string()).unwrap_or_default(),
|
||||
message.map(|m| m.to_string()).unwrap_or_default()
|
||||
);
|
||||
match level.into_raw() {
|
||||
cef_log_severity_t::LOGSEVERITY_ERROR => {
|
||||
error!("{message}");
|
||||
}
|
||||
cef_log_severity_t::LOGSEVERITY_WARNING => {
|
||||
warn!("{message}");
|
||||
}
|
||||
cef_log_severity_t::LOGSEVERITY_VERBOSE => {
|
||||
trace!("{message}");
|
||||
}
|
||||
_ => {
|
||||
info!("{message}");
|
||||
}
|
||||
}
|
||||
1
|
||||
}
|
||||
|
||||
fn on_cursor_change(
|
||||
&self,
|
||||
_browser: Option<&mut Browser>,
|
||||
_cursor: *mut u8,
|
||||
type_: CursorType,
|
||||
_: Option<&CursorInfo>,
|
||||
) -> c_int {
|
||||
let _ = self
|
||||
.cursor_icon
|
||||
.send_blocking(to_system_cursor_icon(type_.into_raw()));
|
||||
1
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn get_raw(&self) -> *mut sys::cef_display_handler_t {
|
||||
self.object.cast()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_system_cursor_icon(cursor_type: cef_dll_sys::cef_cursor_type_t) -> SystemCursorIcon {
|
||||
match cursor_type {
|
||||
cef_cursor_type_t::CT_POINTER => SystemCursorIcon::Default,
|
||||
cef_cursor_type_t::CT_CROSS => SystemCursorIcon::Crosshair,
|
||||
cef_cursor_type_t::CT_HAND => SystemCursorIcon::Pointer,
|
||||
cef_cursor_type_t::CT_IBEAM => SystemCursorIcon::Text,
|
||||
cef_cursor_type_t::CT_WAIT => SystemCursorIcon::Wait,
|
||||
cef_cursor_type_t::CT_HELP => SystemCursorIcon::Help,
|
||||
cef_cursor_type_t::CT_EASTRESIZE => SystemCursorIcon::EResize,
|
||||
cef_cursor_type_t::CT_NORTHRESIZE => SystemCursorIcon::NResize,
|
||||
cef_cursor_type_t::CT_NORTHEASTRESIZE => SystemCursorIcon::NeResize,
|
||||
cef_cursor_type_t::CT_NORTHWESTRESIZE => SystemCursorIcon::NwResize,
|
||||
cef_cursor_type_t::CT_SOUTHRESIZE => SystemCursorIcon::SResize,
|
||||
cef_cursor_type_t::CT_SOUTHEASTRESIZE => SystemCursorIcon::SeResize,
|
||||
cef_cursor_type_t::CT_SOUTHWESTRESIZE => SystemCursorIcon::SwResize,
|
||||
cef_cursor_type_t::CT_WESTRESIZE => SystemCursorIcon::WResize,
|
||||
cef_cursor_type_t::CT_NORTHSOUTHRESIZE => SystemCursorIcon::NsResize,
|
||||
cef_cursor_type_t::CT_EASTWESTRESIZE => SystemCursorIcon::EwResize,
|
||||
cef_cursor_type_t::CT_NORTHEASTSOUTHWESTRESIZE => SystemCursorIcon::NeswResize,
|
||||
cef_cursor_type_t::CT_NORTHWESTSOUTHEASTRESIZE => SystemCursorIcon::NwseResize,
|
||||
cef_cursor_type_t::CT_COLUMNRESIZE => SystemCursorIcon::ColResize,
|
||||
cef_cursor_type_t::CT_ROWRESIZE => SystemCursorIcon::RowResize,
|
||||
cef_cursor_type_t::CT_MIDDLEPANNING => SystemCursorIcon::AllScroll,
|
||||
cef_cursor_type_t::CT_EASTPANNING => SystemCursorIcon::AllScroll,
|
||||
cef_cursor_type_t::CT_NORTHPANNING => SystemCursorIcon::AllScroll,
|
||||
cef_cursor_type_t::CT_NORTHEASTPANNING => SystemCursorIcon::AllScroll,
|
||||
cef_cursor_type_t::CT_NORTHWESTPANNING => SystemCursorIcon::AllScroll,
|
||||
cef_cursor_type_t::CT_SOUTHPANNING => SystemCursorIcon::AllScroll,
|
||||
cef_cursor_type_t::CT_SOUTHEASTPANNING => SystemCursorIcon::AllScroll,
|
||||
cef_cursor_type_t::CT_SOUTHWESTPANNING => SystemCursorIcon::AllScroll,
|
||||
cef_cursor_type_t::CT_WESTPANNING => SystemCursorIcon::AllScroll,
|
||||
cef_cursor_type_t::CT_MOVE => SystemCursorIcon::Move,
|
||||
cef_cursor_type_t::CT_VERTICALTEXT => SystemCursorIcon::VerticalText,
|
||||
cef_cursor_type_t::CT_CELL => SystemCursorIcon::Cell,
|
||||
cef_cursor_type_t::CT_CONTEXTMENU => SystemCursorIcon::ContextMenu,
|
||||
cef_cursor_type_t::CT_ALIAS => SystemCursorIcon::Alias,
|
||||
cef_cursor_type_t::CT_PROGRESS => SystemCursorIcon::Progress,
|
||||
cef_cursor_type_t::CT_NODROP => SystemCursorIcon::NoDrop,
|
||||
cef_cursor_type_t::CT_COPY => SystemCursorIcon::Copy,
|
||||
cef_cursor_type_t::CT_NONE => SystemCursorIcon::Default,
|
||||
cef_cursor_type_t::CT_NOTALLOWED => SystemCursorIcon::NotAllowed,
|
||||
cef_cursor_type_t::CT_ZOOMIN => SystemCursorIcon::ZoomIn,
|
||||
cef_cursor_type_t::CT_ZOOMOUT => SystemCursorIcon::ZoomOut,
|
||||
cef_cursor_type_t::CT_GRAB => SystemCursorIcon::Grab,
|
||||
cef_cursor_type_t::CT_GRABBING => SystemCursorIcon::Grabbing,
|
||||
cef_cursor_type_t::CT_MIDDLE_PANNING_VERTICAL => SystemCursorIcon::AllScroll,
|
||||
cef_cursor_type_t::CT_MIDDLE_PANNING_HORIZONTAL => SystemCursorIcon::AllScroll,
|
||||
cef_cursor_type_t::CT_CUSTOM => SystemCursorIcon::Default,
|
||||
cef_cursor_type_t::CT_DND_NONE => SystemCursorIcon::Default,
|
||||
cef_cursor_type_t::CT_DND_MOVE => SystemCursorIcon::Move,
|
||||
cef_cursor_type_t::CT_DND_COPY => SystemCursorIcon::Copy,
|
||||
cef_cursor_type_t::CT_DND_LINK => SystemCursorIcon::Alias,
|
||||
cef_cursor_type_t::CT_NUM_VALUES => SystemCursorIcon::Default,
|
||||
_ => SystemCursorIcon::Default,
|
||||
}
|
||||
}
|
||||
281
crates/bevy_cef_core/src/browser_process/localhost.rs
Normal file
281
crates/bevy_cef_core/src/browser_process/localhost.rs
Normal file
@@ -0,0 +1,281 @@
|
||||
mod data_responser;
|
||||
mod headers_responser;
|
||||
|
||||
use crate::browser_process::localhost::data_responser::{DataResponser, parse_bytes_single_range};
|
||||
use crate::browser_process::localhost::headers_responser::HeadersResponser;
|
||||
use crate::prelude::IntoString;
|
||||
use async_channel::{Receiver, Sender};
|
||||
use bevy::asset::Asset;
|
||||
use bevy::prelude::*;
|
||||
use bevy::tasks::IoTaskPool;
|
||||
use cef::rc::{Rc, RcImpl};
|
||||
use cef::{
|
||||
Browser, Callback, CefString, Frame, ImplCallback, ImplRequest, ImplResourceHandler,
|
||||
ImplResponse, ImplSchemeHandlerFactory, Request, ResourceHandler, ResourceReadCallback,
|
||||
Response, SchemeHandlerFactory, WrapResourceHandler, WrapSchemeHandlerFactory, sys,
|
||||
};
|
||||
use cef_dll_sys::{_cef_resource_handler_t, cef_base_ref_counted_t};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::os::raw::c_int;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
/// `cef://` scheme response asset.
|
||||
#[derive(Asset, Reflect, Debug, Clone, Serialize, Deserialize)]
|
||||
#[reflect(Debug, Serialize, Deserialize)]
|
||||
pub struct CefResponse {
|
||||
/// The media type.
|
||||
pub mime_type: String,
|
||||
/// The status code of the response, e.g., 200 for OK, 404 for Not Found.
|
||||
pub status_code: u32,
|
||||
/// The response data, typically HTML or other content.
|
||||
pub data: Vec<u8>,
|
||||
}
|
||||
|
||||
impl Default for CefResponse {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
mime_type: "text/html".to_string(),
|
||||
status_code: 404,
|
||||
data: b"<!DOCTYPE html><html><body><h1>404 Not Found</h1></body></html>".to_vec(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Component)]
|
||||
pub struct Responser(pub Sender<CefResponse>);
|
||||
|
||||
#[derive(Resource, Debug, Clone, Deref)]
|
||||
pub struct Requester(pub Sender<CefRequest>);
|
||||
|
||||
#[derive(Resource, Debug, Clone)]
|
||||
pub struct RequesterReceiver(pub Receiver<CefRequest>);
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct CefRequest {
|
||||
pub uri: String,
|
||||
pub responser: Responser,
|
||||
}
|
||||
|
||||
/// Use to register a local schema handler for the CEF browser.
|
||||
///
|
||||
/// ## Reference
|
||||
///
|
||||
/// - [`CefSchemeHandlerFactory Class Reference`](https://cef-builds.spotifycdn.com/docs/106.1/classCefSchemeHandlerFactory.html)
|
||||
pub struct LocalSchemaHandlerBuilder {
|
||||
object: *mut RcImpl<sys::_cef_scheme_handler_factory_t, Self>,
|
||||
requester: Requester,
|
||||
}
|
||||
|
||||
impl LocalSchemaHandlerBuilder {
|
||||
pub fn build(requester: Requester) -> SchemeHandlerFactory {
|
||||
SchemeHandlerFactory::new(Self {
|
||||
object: std::ptr::null_mut(),
|
||||
requester,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Rc for LocalSchemaHandlerBuilder {
|
||||
fn as_base(&self) -> &sys::cef_base_ref_counted_t {
|
||||
unsafe {
|
||||
let base = &*self.object;
|
||||
std::mem::transmute(&base.cef_object)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl WrapSchemeHandlerFactory for LocalSchemaHandlerBuilder {
|
||||
fn wrap_rc(&mut self, object: *mut RcImpl<sys::cef_scheme_handler_factory_t, Self>) {
|
||||
self.object = object;
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for LocalSchemaHandlerBuilder {
|
||||
fn clone(&self) -> Self {
|
||||
let object = unsafe {
|
||||
let rc_impl = &mut *self.object;
|
||||
rc_impl.interface.add_ref();
|
||||
rc_impl
|
||||
};
|
||||
Self {
|
||||
object,
|
||||
requester: self.requester.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ImplSchemeHandlerFactory for LocalSchemaHandlerBuilder {
|
||||
fn create(
|
||||
&self,
|
||||
_browser: Option<&mut Browser>,
|
||||
_frame: Option<&mut Frame>,
|
||||
_scheme_name: Option<&CefString>,
|
||||
_request: Option<&mut Request>,
|
||||
) -> Option<ResourceHandler> {
|
||||
Some(LocalResourceHandlerBuilder::build(self.requester.clone()))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn get_raw(&self) -> *mut sys::_cef_scheme_handler_factory_t {
|
||||
self.object.cast()
|
||||
}
|
||||
}
|
||||
|
||||
struct LocalResourceHandlerBuilder {
|
||||
object: *mut RcImpl<_cef_resource_handler_t, Self>,
|
||||
requester: Requester,
|
||||
headers: Arc<Mutex<HeadersResponser>>,
|
||||
data: Arc<Mutex<DataResponser>>,
|
||||
}
|
||||
|
||||
impl LocalResourceHandlerBuilder {
|
||||
fn build(requester: Requester) -> ResourceHandler {
|
||||
ResourceHandler::new(Self {
|
||||
object: std::ptr::null_mut(),
|
||||
requester,
|
||||
headers: Arc::new(Mutex::new(HeadersResponser::default())),
|
||||
data: Arc::new(Mutex::new(DataResponser::default())),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl WrapResourceHandler for LocalResourceHandlerBuilder {
|
||||
fn wrap_rc(&mut self, object: *mut RcImpl<sys::_cef_resource_handler_t, Self>) {
|
||||
self.object = object;
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for LocalResourceHandlerBuilder {
|
||||
fn clone(&self) -> Self {
|
||||
let object = unsafe {
|
||||
let rc_impl = &mut *self.object;
|
||||
rc_impl.interface.add_ref();
|
||||
rc_impl
|
||||
};
|
||||
Self {
|
||||
object,
|
||||
requester: self.requester.clone(),
|
||||
headers: self.headers.clone(),
|
||||
data: self.data.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Rc for LocalResourceHandlerBuilder {
|
||||
fn as_base(&self) -> &cef_base_ref_counted_t {
|
||||
unsafe {
|
||||
let base = &*self.object;
|
||||
std::mem::transmute(&base.cef_object)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ImplResourceHandler for LocalResourceHandlerBuilder {
|
||||
fn open(
|
||||
&self,
|
||||
request: Option<&mut Request>,
|
||||
handle_request: Option<&mut c_int>,
|
||||
callback: Option<&mut Callback>,
|
||||
) -> c_int {
|
||||
let Some(request) = request else {
|
||||
// Cancel the request if no request is provided
|
||||
return 0;
|
||||
};
|
||||
let range_header_value = request.header_by_name(Some(&"Range".into())).into_string();
|
||||
let range = parse_bytes_single_range(&range_header_value);
|
||||
let Some(callback) = callback.cloned() else {
|
||||
// If no callback is provided, we cannot handle the request
|
||||
return 0;
|
||||
};
|
||||
if let Some(handle_request) = handle_request {
|
||||
*handle_request = 0;
|
||||
}
|
||||
let url = request.url().into_string();
|
||||
let requester = self.requester.clone();
|
||||
let headers_responser = self.headers.clone();
|
||||
let data_responser = self.data.clone();
|
||||
IoTaskPool::get()
|
||||
.spawn(async move {
|
||||
let (tx, rx) = async_channel::bounded(1);
|
||||
let _ = requester
|
||||
.send(CefRequest {
|
||||
uri: url
|
||||
.strip_prefix("cef://localhost/")
|
||||
.unwrap_or_default()
|
||||
.to_string(),
|
||||
responser: Responser(tx),
|
||||
})
|
||||
.await;
|
||||
let response = rx.recv().await.unwrap_or_default();
|
||||
headers_responser.lock().unwrap().prepare(&response, &range);
|
||||
data_responser
|
||||
.lock()
|
||||
.unwrap()
|
||||
.prepare(response.data, &range);
|
||||
callback.cont();
|
||||
})
|
||||
.detach();
|
||||
1
|
||||
}
|
||||
|
||||
fn response_headers(
|
||||
&self,
|
||||
response: Option<&mut Response>,
|
||||
response_length: Option<&mut i64>,
|
||||
_redirect_url: Option<&mut CefString>,
|
||||
) {
|
||||
let Ok(responser) = self.headers.lock() else {
|
||||
return;
|
||||
};
|
||||
if let Some(response) = response {
|
||||
response.set_mime_type(Some(&responser.mime_type.as_str().into()));
|
||||
response.set_status(responser.status_code as _);
|
||||
for (name, value) in &responser.headers {
|
||||
response.set_header_by_name(
|
||||
Some(&name.as_str().into()),
|
||||
Some(&value.as_str().into()),
|
||||
false as _,
|
||||
);
|
||||
}
|
||||
}
|
||||
if let Some(response_length) = response_length {
|
||||
*response_length = responser.response_length as _;
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::not_unsafe_ptr_arg_deref)]
|
||||
fn read(
|
||||
&self,
|
||||
data_out: *mut u8,
|
||||
bytes_to_read: c_int,
|
||||
bytes_read: Option<&mut c_int>,
|
||||
_: Option<&mut ResourceReadCallback>,
|
||||
) -> c_int {
|
||||
let Some(bytes_read) = bytes_read else {
|
||||
// If no bytes_read is provided, we cannot read data
|
||||
return 0;
|
||||
};
|
||||
let Ok(mut responser) = self.data.lock() else {
|
||||
return 0;
|
||||
};
|
||||
match responser.read(bytes_to_read as _) {
|
||||
Some(data) if !data.is_empty() => {
|
||||
let n = data.len();
|
||||
unsafe {
|
||||
std::ptr::copy_nonoverlapping(data.as_ptr(), data_out, n);
|
||||
}
|
||||
*bytes_read = n as i32;
|
||||
1
|
||||
}
|
||||
_ => {
|
||||
*bytes_read = 0;
|
||||
0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn get_raw(&self) -> *mut _cef_resource_handler_t {
|
||||
self.object.cast()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,236 @@
|
||||
use bevy::prelude::*;
|
||||
|
||||
#[derive(Default, Clone)]
|
||||
pub struct DataResponser {
|
||||
data: Vec<u8>,
|
||||
offset: usize,
|
||||
end_offset: usize,
|
||||
}
|
||||
|
||||
impl DataResponser {
|
||||
/// Prepares the data and headers for the response.
|
||||
///
|
||||
/// The range header values only support the `bytes` range unit type and single range.
|
||||
/// TODO: Support multiple ranges.
|
||||
pub fn prepare(&mut self, data: Vec<u8>, range: &Option<(usize, Option<usize>)>) {
|
||||
if let Some((start, end)) = range {
|
||||
self.offset = *start;
|
||||
self.end_offset = end.unwrap_or(data.len() - 1) + 1;
|
||||
self.data = data;
|
||||
} else {
|
||||
self.offset = 0;
|
||||
self.end_offset = data.len();
|
||||
self.data = data;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn read(&mut self, bytes_to_read: isize) -> Option<&[u8]> {
|
||||
if self.offset >= self.data.len() {
|
||||
return None;
|
||||
}
|
||||
let start = self.offset;
|
||||
let end = if bytes_to_read < 0 {
|
||||
self.data.len()
|
||||
} else {
|
||||
(self.offset as isize + bytes_to_read) as usize
|
||||
};
|
||||
let end = end.min(self.end_offset);
|
||||
|
||||
if start >= end || start >= self.data.len() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let slice = &self.data[start..end.min(self.data.len())];
|
||||
self.offset += slice.len();
|
||||
Some(slice)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_bytes_single_range(range_header_value: &str) -> Option<(usize, Option<usize>)> {
|
||||
let ranges = parse_bytes_range(range_header_value)?;
|
||||
ranges.first().cloned()
|
||||
}
|
||||
|
||||
/// Parses the `Range` header value from a request and returns the start of the range.
|
||||
///
|
||||
/// ## Reference
|
||||
///
|
||||
/// - [`Range_requests`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/Range_requests)
|
||||
fn parse_bytes_range(range_header_value: &str) -> Option<Vec<(usize, Option<usize>)>> {
|
||||
if !range_header_value.starts_with("bytes=") {
|
||||
return None;
|
||||
}
|
||||
let mut ranges = Vec::new();
|
||||
let value = range_header_value.trim_start_matches("bytes=");
|
||||
// bytes=100-200,300-400 => ["100-200", "300-400"]
|
||||
let byte_ranges = value.split(",");
|
||||
for range in byte_ranges {
|
||||
// 100-200 => ["100", "200"]
|
||||
let mut split = range.split("-");
|
||||
let start = split.next()?;
|
||||
let end = split.next();
|
||||
let start = start.parse::<usize>().ok()?;
|
||||
let end = end.and_then(|e| e.parse::<usize>().ok());
|
||||
ranges.push((start, end));
|
||||
}
|
||||
Some(ranges)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
#[test]
|
||||
fn range_start_is_none_if_empty() {
|
||||
assert_eq!(parse_bytes_range(""), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn range_only_start_offset() {
|
||||
assert_eq!(parse_bytes_range("bytes=100-"), Some(vec![(100, None)]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn range_one_bytes() {
|
||||
assert_eq!(
|
||||
parse_bytes_range("bytes=100-200"),
|
||||
Some(vec![(100, Some(200))])
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn range_multiple_ranges() {
|
||||
assert_eq!(
|
||||
parse_bytes_range("bytes=100-200,300-400"),
|
||||
Some(vec![(100, Some(200)), (300, Some(400))])
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn data_responser_new_with_start_and_end() {
|
||||
let data = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
|
||||
let mut responser = DataResponser::default();
|
||||
responser.prepare(data.clone(), &Some((2, Some(7))));
|
||||
assert_eq!(responser.data, data);
|
||||
assert_eq!(responser.offset, 2);
|
||||
assert_eq!(responser.end_offset, 7);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn data_responser_new_with_start_only() {
|
||||
let data = vec![1, 2, 3, 4, 5];
|
||||
let mut responser = DataResponser::default();
|
||||
responser.prepare(data.clone(), &Some((3, None)));
|
||||
|
||||
assert_eq!(responser.data, data);
|
||||
assert_eq!(responser.offset, 3);
|
||||
assert_eq!(responser.end_offset, 5);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn data_responser_new_with_zero_start() {
|
||||
let data = vec![1, 2, 3];
|
||||
let mut responser = DataResponser::default();
|
||||
responser.prepare(data.clone(), &Some((0, None)));
|
||||
|
||||
assert_eq!(responser.data, data);
|
||||
assert_eq!(responser.offset, 0);
|
||||
assert_eq!(responser.end_offset, 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn data_responser_new_with_empty_data() {
|
||||
let data = vec![];
|
||||
let mut responser = DataResponser::default();
|
||||
responser.prepare(data.clone(), &Some((0, None)));
|
||||
|
||||
assert_eq!(responser.data, data);
|
||||
assert_eq!(responser.offset, 0);
|
||||
assert_eq!(responser.end_offset, 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn data_responser_new_with_start_beyond_data_length() {
|
||||
let data = vec![1, 2, 3];
|
||||
let mut responser = DataResponser::default();
|
||||
responser.prepare(data.clone(), &Some((5, None)));
|
||||
|
||||
assert_eq!(responser.data, data);
|
||||
assert_eq!(responser.offset, 5);
|
||||
assert_eq!(responser.end_offset, 3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn data_responser_new_with_end_beyond_data_length() {
|
||||
let data = vec![1, 2, 3];
|
||||
let mut responser = DataResponser::default();
|
||||
responser.prepare(data.clone(), &Some((1, Some(10))));
|
||||
|
||||
assert_eq!(responser.data, data);
|
||||
assert_eq!(responser.offset, 1);
|
||||
assert_eq!(responser.end_offset, 10);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn data_responser_read_no_end_data_smaller_than_bytes_to_read() {
|
||||
let data = vec![1, 2, 3, 4, 5];
|
||||
let mut responser = DataResponser::default();
|
||||
responser.prepare(data, &Some((2, None)));
|
||||
|
||||
let result = responser.read(10);
|
||||
assert_eq!(result, Some(&[3, 4, 5][..]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn data_responser_read_no_end_data_larger_than_bytes_to_read() {
|
||||
let data = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
|
||||
let mut responser = DataResponser::default();
|
||||
responser.prepare(data, &Some((2, None)));
|
||||
|
||||
let result1 = responser.read(3);
|
||||
assert_eq!(result1, Some(&[3, 4, 5][..]));
|
||||
|
||||
let result2 = responser.read(3);
|
||||
assert_eq!(result2, Some(&[6, 7, 8][..]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn data_responser_read_with_end_data_smaller_than_bytes_to_read() {
|
||||
let data = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
|
||||
let mut responser = DataResponser::default();
|
||||
responser.prepare(data, &Some((2, Some(6))));
|
||||
|
||||
let result = responser.read(10);
|
||||
assert_eq!(result, Some(&[3, 4, 5, 6][..]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn data_responser_read_with_end_data_larger_than_bytes_to_read() {
|
||||
let data = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
|
||||
let mut responser = DataResponser::default();
|
||||
responser.prepare(data, &Some((1, Some(7))));
|
||||
|
||||
let result1 = responser.read(3);
|
||||
assert_eq!(result1, Some(&[2, 3, 4][..]));
|
||||
|
||||
let result2 = responser.read(3);
|
||||
assert_eq!(result2, Some(&[5, 6, 7][..]));
|
||||
}
|
||||
#[test]
|
||||
fn data_responser_read_consecutive_calls_until_end() {
|
||||
let data = vec![1, 2, 3, 4, 5, 6, 7, 8];
|
||||
let mut responser = DataResponser::default();
|
||||
responser.prepare(data, &Some((1, Some(6))));
|
||||
|
||||
let result1 = responser.read(2);
|
||||
assert_eq!(result1, Some(&[2, 3][..]));
|
||||
|
||||
let result2 = responser.read(2);
|
||||
assert_eq!(result2, Some(&[4, 5][..]));
|
||||
|
||||
let result3 = responser.read(2);
|
||||
assert_eq!(result3, Some(&[6][..]));
|
||||
|
||||
let result4 = responser.read(2);
|
||||
assert_eq!(result4, None);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,236 @@
|
||||
use crate::prelude::CefResponse;
|
||||
|
||||
#[derive(Clone, Default, Debug)]
|
||||
pub struct HeadersResponser {
|
||||
pub mime_type: String,
|
||||
pub status_code: u32,
|
||||
pub headers: Vec<(String, String)>,
|
||||
pub response_length: usize,
|
||||
}
|
||||
|
||||
impl HeadersResponser {
|
||||
pub fn prepare(&mut self, cef_response: &CefResponse, range: &Option<(usize, Option<usize>)>) {
|
||||
self.mime_type = cef_response.mime_type.clone();
|
||||
self.status_code = if range.is_some() {
|
||||
206 // Partial Content
|
||||
} else {
|
||||
cef_response.status_code
|
||||
};
|
||||
self.headers.clear();
|
||||
self.response_length = obtain_response_length(&cef_response.data, range);
|
||||
if let Some(content_range) = content_range_header_value(&cef_response.data, range) {
|
||||
self.headers
|
||||
.push(("Content-Range".to_string(), content_range));
|
||||
self.headers
|
||||
.push(("Accept-Ranges".to_string(), "bytes".to_string()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a `Content-Range` header value based on the provided data and range.
|
||||
///
|
||||
/// If the range is `None`, since the request type is not a range request, it returns `None`.
|
||||
///
|
||||
/// ## Reference
|
||||
///
|
||||
/// - [206 Partial Content](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Status/206)
|
||||
fn content_range_header_value(
|
||||
data: &[u8],
|
||||
range: &Option<(usize, Option<usize>)>,
|
||||
) -> Option<String> {
|
||||
let (start, end) = range.as_ref()?;
|
||||
Some(format!(
|
||||
"bytes {}-{}/{}",
|
||||
start,
|
||||
end.unwrap_or(data.len() - 1),
|
||||
data.len()
|
||||
))
|
||||
}
|
||||
|
||||
fn obtain_response_length(data: &[u8], range: &Option<(usize, Option<usize>)>) -> usize {
|
||||
match range {
|
||||
Some((start, end)) => end.unwrap_or(data.len() - 1) - start + 1,
|
||||
None => data.len(),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use bevy::utils::default;
|
||||
|
||||
#[test]
|
||||
fn test_obtain_response_length_no_range() {
|
||||
let data = b"Hello, World!";
|
||||
let result = obtain_response_length(data, &None);
|
||||
assert_eq!(result, 13);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_obtain_response_length_empty_data_no_range() {
|
||||
let data = b"";
|
||||
let result = obtain_response_length(data, &None);
|
||||
assert_eq!(result, 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_obtain_response_length_range_with_end() {
|
||||
let data = b"Hello, World!";
|
||||
let result = obtain_response_length(data, &Some((0, Some(5))));
|
||||
assert_eq!(result, 5);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_obtain_response_length_range_partial() {
|
||||
let data = b"Hello, World!";
|
||||
let result = obtain_response_length(data, &Some((7, Some(12))));
|
||||
assert_eq!(result, 5);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_obtain_response_length_range_without_end() {
|
||||
let data = b"Hello, World!";
|
||||
let result = obtain_response_length(data, &Some((7, None)));
|
||||
assert_eq!(result, 6);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_obtain_response_length_range_from_start() {
|
||||
let data = b"Hello, World!";
|
||||
let result = obtain_response_length(data, &Some((0, None)));
|
||||
assert_eq!(result, 13);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_obtain_response_length_range_zero_length() {
|
||||
let data = b"Hello, World!";
|
||||
let result = obtain_response_length(data, &Some((5, Some(5))));
|
||||
assert_eq!(result, 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_obtain_response_length_range_end_equals_data_len() {
|
||||
let data = b"Hello, World!";
|
||||
let result = obtain_response_length(data, &Some((0, Some(13))));
|
||||
assert_eq!(result, 13);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_obtain_response_length_empty_data_with_range() {
|
||||
let data = b"";
|
||||
let result = obtain_response_length(data, &Some((0, None)));
|
||||
assert_eq!(result, 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_obtain_response_length_large_data() {
|
||||
let data = vec![0u8; 1024];
|
||||
let result = obtain_response_length(&data, &None);
|
||||
assert_eq!(result, 1024);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_obtain_response_length_large_data_with_range() {
|
||||
let data = vec![0u8; 1024];
|
||||
let result = obtain_response_length(&data, &Some((100, Some(200))));
|
||||
assert_eq!(result, 100);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_content_range_header_value_no_range() {
|
||||
let data = b"Hello, World!";
|
||||
let result = content_range_header_value(data, &None);
|
||||
assert_eq!(result, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_content_range_header_value_range_with_end() {
|
||||
let data = b"Hello, World!";
|
||||
let result = content_range_header_value(data, &Some((0, Some(5))));
|
||||
assert_eq!(result, Some("bytes 0-4/13".to_string()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_content_range_header_value_range_without_end() {
|
||||
let data = b"Hello, World!";
|
||||
let result = content_range_header_value(data, &Some((7, None)));
|
||||
assert_eq!(result, Some("bytes 7-12/13".to_string()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_content_range_header_value_range_from_start() {
|
||||
let data = b"Hello, World!";
|
||||
let result = content_range_header_value(data, &Some((0, None)));
|
||||
assert_eq!(result, Some("bytes 0-12/13".to_string()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_content_range_header_value_range_partial() {
|
||||
let data = b"Hello, World!";
|
||||
let result = content_range_header_value(data, &Some((7, Some(12))));
|
||||
assert_eq!(result, Some("bytes 7-11/13".to_string()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_content_range_header_value_range_single_byte() {
|
||||
let data = b"Hello, World!";
|
||||
let result = content_range_header_value(data, &Some((5, Some(6))));
|
||||
assert_eq!(result, Some("bytes 5-5/13".to_string()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_content_range_header_value_range_last_byte() {
|
||||
let data = b"Hello, World!";
|
||||
let result = content_range_header_value(data, &Some((12, Some(13))));
|
||||
assert_eq!(result, Some("bytes 12-12/13".to_string()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_content_range_header_value_single_byte_data() {
|
||||
let data = b"a";
|
||||
let result = content_range_header_value(data, &Some((0, None)));
|
||||
assert_eq!(result, Some("bytes 0-0/1".to_string()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_content_range_header_value_large_data() {
|
||||
let data = vec![0u8; 1024];
|
||||
let result = content_range_header_value(&data, &Some((100, Some(200))));
|
||||
assert_eq!(result, Some("bytes 100-199/1024".to_string()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_content_range_header_value_large_data_no_end() {
|
||||
let data = vec![0u8; 1024];
|
||||
let result = content_range_header_value(&data, &Some((500, None)));
|
||||
assert_eq!(result, Some("bytes 500-1023/1024".to_string()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_content_range_header_value_zero_start() {
|
||||
let data = b"test";
|
||||
let result = content_range_header_value(data, &Some((0, Some(2))));
|
||||
assert_eq!(result, Some("bytes 0-1/4".to_string()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_content_range_header_value_range_end_equals_data_len() {
|
||||
let data = b"Hello, World!";
|
||||
let result = content_range_header_value(data, &Some((0, Some(13))));
|
||||
assert_eq!(result, Some("bytes 0-12/13".to_string()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn status_code_is_206_for_partial_content() {
|
||||
let data = b"Hello, World!";
|
||||
let mut headers_responser = HeadersResponser::default();
|
||||
headers_responser.prepare(
|
||||
&CefResponse {
|
||||
data: data.to_vec(),
|
||||
..default()
|
||||
},
|
||||
&Some((0, Some(5))),
|
||||
);
|
||||
assert_eq!(headers_responser.status_code, 206);
|
||||
}
|
||||
}
|
||||
167
crates/bevy_cef_core/src/browser_process/renderer_handler.rs
Normal file
167
crates/bevy_cef_core/src/browser_process/renderer_handler.rs
Normal file
@@ -0,0 +1,167 @@
|
||||
use async_channel::{Receiver, Sender};
|
||||
use bevy::prelude::{Entity, Event};
|
||||
use cef::rc::{Rc, RcImpl};
|
||||
use cef::{
|
||||
Browser, CefString, ImplRenderHandler, PaintElementType, Range, Rect, RenderHandler,
|
||||
WrapRenderHandler, sys,
|
||||
};
|
||||
use cef_dll_sys::cef_paint_element_type_t;
|
||||
use std::cell::Cell;
|
||||
use std::os::raw::c_int;
|
||||
|
||||
pub type TextureSender = Sender<RenderTexture>;
|
||||
|
||||
pub type TextureReceiver = Receiver<RenderTexture>;
|
||||
|
||||
/// The texture structure passed from [`CefRenderHandler::OnPaint`](https://cef-builds.spotifycdn.com/docs/106.1/classCefRenderHandler.html#a6547d5c9dd472e6b84706dc81d3f1741).
|
||||
#[derive(Debug, Clone, PartialEq, Event)]
|
||||
pub struct RenderTexture {
|
||||
/// The entity of target rendering webview.
|
||||
pub webview: Entity,
|
||||
/// The type of the paint element.
|
||||
pub ty: RenderPaintElementType,
|
||||
/// The width of the texture.
|
||||
pub width: u32,
|
||||
/// The height of the texture.
|
||||
pub height: u32,
|
||||
/// This buffer will be `width` *`height` * 4 bytes in size and represents a BGRA image with an upper-left origin
|
||||
pub buffer: Vec<u8>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub enum RenderPaintElementType {
|
||||
/// The main frame of the browser.
|
||||
View,
|
||||
/// The popup frame of the browser.
|
||||
Popup,
|
||||
}
|
||||
|
||||
pub type SharedViewSize = std::rc::Rc<Cell<bevy::prelude::Vec2>>;
|
||||
pub type SharedImeCaret = std::rc::Rc<Cell<u32>>;
|
||||
|
||||
/// ## Reference
|
||||
///
|
||||
/// - [`CefRenderHandler Class Reference`](https://cef-builds.spotifycdn.com/docs/106.1/classCefRenderHandler.html)
|
||||
pub struct RenderHandlerBuilder {
|
||||
object: *mut RcImpl<sys::cef_render_handler_t, Self>,
|
||||
webview: Entity,
|
||||
texture_sender: TextureSender,
|
||||
size: SharedViewSize,
|
||||
ime_caret: SharedImeCaret,
|
||||
}
|
||||
|
||||
impl RenderHandlerBuilder {
|
||||
pub fn build(
|
||||
webview: Entity,
|
||||
texture_sender: TextureSender,
|
||||
size: SharedViewSize,
|
||||
ime_caret: SharedImeCaret,
|
||||
) -> RenderHandler {
|
||||
RenderHandler::new(Self {
|
||||
object: std::ptr::null_mut(),
|
||||
webview,
|
||||
texture_sender,
|
||||
size,
|
||||
ime_caret,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Rc for RenderHandlerBuilder {
|
||||
fn as_base(&self) -> &sys::cef_base_ref_counted_t {
|
||||
unsafe {
|
||||
let base = &*self.object;
|
||||
std::mem::transmute(&base.cef_object)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl WrapRenderHandler for RenderHandlerBuilder {
|
||||
fn wrap_rc(&mut self, object: *mut RcImpl<sys::_cef_render_handler_t, Self>) {
|
||||
self.object = object;
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for RenderHandlerBuilder {
|
||||
fn clone(&self) -> Self {
|
||||
let object = unsafe {
|
||||
let rc_impl = &mut *self.object;
|
||||
rc_impl.interface.add_ref();
|
||||
rc_impl
|
||||
};
|
||||
Self {
|
||||
object,
|
||||
webview: self.webview,
|
||||
texture_sender: self.texture_sender.clone(),
|
||||
size: self.size.clone(),
|
||||
ime_caret: self.ime_caret.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ImplRenderHandler for RenderHandlerBuilder {
|
||||
fn view_rect(&self, _browser: Option<&mut Browser>, rect: Option<&mut Rect>) {
|
||||
if let Some(rect) = rect {
|
||||
let size = self.size.get();
|
||||
rect.width = size.x as _;
|
||||
rect.height = size.y as _;
|
||||
}
|
||||
}
|
||||
|
||||
fn on_text_selection_changed(
|
||||
&self,
|
||||
_browser: Option<&mut Browser>,
|
||||
_: Option<&CefString>,
|
||||
selected_range: Option<&Range>,
|
||||
) {
|
||||
if let Some(selected_range) = selected_range {
|
||||
self.ime_caret.set(selected_range.to);
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::not_unsafe_ptr_arg_deref)]
|
||||
fn on_paint(
|
||||
&self,
|
||||
_browser: Option<&mut Browser>,
|
||||
type_: PaintElementType,
|
||||
_dirty_rects_count: usize,
|
||||
_dirty_rects: Option<&Rect>,
|
||||
buffer: *const u8,
|
||||
width: c_int,
|
||||
height: c_int,
|
||||
) {
|
||||
let ty = match type_.as_ref() {
|
||||
cef_paint_element_type_t::PET_POPUP => RenderPaintElementType::Popup,
|
||||
_ => RenderPaintElementType::View,
|
||||
};
|
||||
let texture = RenderTexture {
|
||||
webview: self.webview,
|
||||
ty,
|
||||
width: width as u32,
|
||||
height: height as u32,
|
||||
buffer: unsafe {
|
||||
std::slice::from_raw_parts(buffer, (width * height * 4) as usize).to_vec()
|
||||
},
|
||||
};
|
||||
let _ = self.texture_sender.send_blocking(texture);
|
||||
}
|
||||
|
||||
/// MEMO: This method only supports on Windows
|
||||
///
|
||||
/// In Windows, this method is more performant than `on_paint`?
|
||||
#[cfg(target_os = "windows")]
|
||||
fn on_accelerated_paint(
|
||||
&self,
|
||||
_browser: Option<&mut Browser>,
|
||||
_type_: PaintElementType,
|
||||
_dirty_rects_count: usize,
|
||||
_dirty_rects: Option<&Rect>,
|
||||
_: Option<&AcceleratedPaintInfo>,
|
||||
) {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn get_raw(&self) -> *mut sys::_cef_render_handler_t {
|
||||
self.object.cast()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
use cef::rc::{Rc, RcImpl};
|
||||
use cef::{ImplRequestContextHandler, WrapRequestContextHandler, sys};
|
||||
|
||||
/// ## Reference
|
||||
///
|
||||
/// - [`CefRequestContextHandler Class Reference`](https://cef-builds.spotifycdn.com/docs/106.1/classCefRequestContextHandler.html)
|
||||
pub struct RequestContextHandlerBuilder {
|
||||
object: *mut RcImpl<sys::cef_request_context_handler_t, Self>,
|
||||
}
|
||||
|
||||
impl RequestContextHandlerBuilder {
|
||||
pub fn build() -> cef::RequestContextHandler {
|
||||
cef::RequestContextHandler::new(Self {
|
||||
object: core::ptr::null_mut(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl WrapRequestContextHandler for RequestContextHandlerBuilder {
|
||||
fn wrap_rc(&mut self, object: *mut RcImpl<sys::_cef_request_context_handler_t, Self>) {
|
||||
self.object = object;
|
||||
}
|
||||
}
|
||||
|
||||
impl Rc for RequestContextHandlerBuilder {
|
||||
fn as_base(&self) -> &sys::cef_base_ref_counted_t {
|
||||
unsafe {
|
||||
let base = &*self.object;
|
||||
core::mem::transmute(&base.cef_object)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for RequestContextHandlerBuilder {
|
||||
fn clone(&self) -> Self {
|
||||
let object = unsafe {
|
||||
let rc_impl = &mut *self.object;
|
||||
rc_impl.interface.add_ref();
|
||||
rc_impl
|
||||
};
|
||||
Self { object }
|
||||
}
|
||||
}
|
||||
|
||||
impl ImplRequestContextHandler for RequestContextHandlerBuilder {
|
||||
#[inline]
|
||||
fn get_raw(&self) -> *mut sys::_cef_request_context_handler_t {
|
||||
self.object.cast()
|
||||
}
|
||||
}
|
||||
52
crates/bevy_cef_core/src/debug.rs
Normal file
52
crates/bevy_cef_core/src/debug.rs
Normal file
@@ -0,0 +1,52 @@
|
||||
use cef::{load_library, unload_library};
|
||||
use std::env::home_dir;
|
||||
|
||||
/// This loader is a modified version of [LibraryLoader](cef::library_loader::LibraryLoader) that can load the framework located in the home directory.
|
||||
pub struct DebugLibraryLoader {
|
||||
path: std::path::PathBuf,
|
||||
}
|
||||
|
||||
impl Default for DebugLibraryLoader {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl DebugLibraryLoader {
|
||||
const FRAMEWORK_PATH: &'static str =
|
||||
"Chromium Embedded Framework.framework/Chromium Embedded Framework";
|
||||
|
||||
pub fn new() -> Self {
|
||||
let path = home_dir()
|
||||
.unwrap()
|
||||
.join(".local")
|
||||
.join("share")
|
||||
.join("cef")
|
||||
.join(Self::FRAMEWORK_PATH)
|
||||
.canonicalize()
|
||||
.unwrap();
|
||||
|
||||
Self { path }
|
||||
}
|
||||
|
||||
// See [cef_load_library] for more documentation.
|
||||
pub fn load(&self) -> bool {
|
||||
Self::load_library(&self.path)
|
||||
}
|
||||
|
||||
fn load_library(name: &std::path::Path) -> bool {
|
||||
use std::os::unix::ffi::OsStrExt;
|
||||
let Ok(name) = std::ffi::CString::new(name.as_os_str().as_bytes()) else {
|
||||
return false;
|
||||
};
|
||||
unsafe { load_library(Some(&*name.as_ptr().cast())) == 1 }
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for DebugLibraryLoader {
|
||||
fn drop(&mut self) {
|
||||
if unload_library() != 1 {
|
||||
eprintln!("cannot unload framework {}", self.path.display());
|
||||
}
|
||||
}
|
||||
}
|
||||
15
crates/bevy_cef_core/src/lib.rs
Normal file
15
crates/bevy_cef_core/src/lib.rs
Normal file
@@ -0,0 +1,15 @@
|
||||
mod browser_process;
|
||||
mod debug;
|
||||
mod render_process;
|
||||
mod util;
|
||||
|
||||
pub mod prelude {
|
||||
pub use crate::browser_process::*;
|
||||
pub use crate::debug::*;
|
||||
pub use crate::render_process::app::*;
|
||||
pub use crate::render_process::brp::*;
|
||||
pub use crate::render_process::emit::*;
|
||||
pub use crate::render_process::execute_render_process;
|
||||
pub use crate::render_process::render_process_handler::*;
|
||||
pub use crate::util::*;
|
||||
}
|
||||
28
crates/bevy_cef_core/src/render_process.rs
Normal file
28
crates/bevy_cef_core/src/render_process.rs
Normal file
@@ -0,0 +1,28 @@
|
||||
use crate::prelude::RenderProcessAppBuilder;
|
||||
use cef::args::Args;
|
||||
use cef::{api_hash, execute_process, sys};
|
||||
|
||||
pub mod app;
|
||||
pub mod brp;
|
||||
pub mod emit;
|
||||
pub mod listen;
|
||||
pub mod render_process_handler;
|
||||
|
||||
/// Execute the CEF render process.
|
||||
pub fn execute_render_process() {
|
||||
let args = Args::new();
|
||||
#[cfg(target_os = "macos")]
|
||||
let _loader = {
|
||||
let loader =
|
||||
cef::library_loader::LibraryLoader::new(&std::env::current_exe().unwrap(), true);
|
||||
assert!(loader.load());
|
||||
loader
|
||||
};
|
||||
let _ = api_hash(sys::CEF_API_VERSION_LAST, 0);
|
||||
let mut app = RenderProcessAppBuilder::build();
|
||||
execute_process(
|
||||
Some(args.as_main_args()),
|
||||
Some(&mut app),
|
||||
std::ptr::null_mut(),
|
||||
);
|
||||
}
|
||||
63
crates/bevy_cef_core/src/render_process/app.rs
Normal file
63
crates/bevy_cef_core/src/render_process/app.rs
Normal file
@@ -0,0 +1,63 @@
|
||||
use crate::prelude::RenderProcessHandlerBuilder;
|
||||
use crate::util::{SCHEME_CEF, cef_scheme_flags};
|
||||
use cef::rc::{Rc, RcImpl};
|
||||
use cef::{ImplApp, ImplSchemeRegistrar, RenderProcessHandler, SchemeRegistrar, WrapApp};
|
||||
use cef_dll_sys::{_cef_app_t, cef_base_ref_counted_t};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct RenderProcessAppBuilder {
|
||||
object: *mut RcImpl<_cef_app_t, Self>,
|
||||
}
|
||||
|
||||
impl RenderProcessAppBuilder {
|
||||
pub fn build() -> cef::App {
|
||||
cef::App::new(Self {
|
||||
object: core::ptr::null_mut(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for RenderProcessAppBuilder {
|
||||
fn clone(&self) -> Self {
|
||||
let object = unsafe {
|
||||
let rc_impl = &mut *self.object;
|
||||
rc_impl.interface.add_ref();
|
||||
self.object
|
||||
};
|
||||
Self { object }
|
||||
}
|
||||
}
|
||||
|
||||
impl Rc for RenderProcessAppBuilder {
|
||||
fn as_base(&self) -> &cef_base_ref_counted_t {
|
||||
unsafe {
|
||||
let base = &*self.object;
|
||||
std::mem::transmute(&base.cef_object)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ImplApp for RenderProcessAppBuilder {
|
||||
fn render_process_handler(&self) -> Option<RenderProcessHandler> {
|
||||
Some(RenderProcessHandler::new(
|
||||
RenderProcessHandlerBuilder::build(),
|
||||
))
|
||||
}
|
||||
|
||||
fn on_register_custom_schemes(&self, registrar: Option<&mut SchemeRegistrar>) {
|
||||
if let Some(registrar) = registrar {
|
||||
registrar.add_custom_scheme(Some(&SCHEME_CEF.into()), cef_scheme_flags() as _);
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn get_raw(&self) -> *mut _cef_app_t {
|
||||
self.object as *mut _cef_app_t
|
||||
}
|
||||
}
|
||||
|
||||
impl WrapApp for RenderProcessAppBuilder {
|
||||
fn wrap_rc(&mut self, object: *mut RcImpl<_cef_app_t, Self>) {
|
||||
self.object = object;
|
||||
}
|
||||
}
|
||||
181
crates/bevy_cef_core/src/render_process/brp.rs
Normal file
181
crates/bevy_cef_core/src/render_process/brp.rs
Normal file
@@ -0,0 +1,181 @@
|
||||
use crate::prelude::{BRP_PROMISES, PROCESS_MESSAGE_BRP, v8_value_to_json};
|
||||
use cef::rc::{ConvertParam, ConvertReturnValue, Rc, RcImpl};
|
||||
use cef::{
|
||||
CefString, Frame, ImplFrame, ImplListValue, ImplProcessMessage, ImplV8Handler, ImplV8Value,
|
||||
ProcessId, V8Handler, V8Value, WrapV8Handler, process_message_create, sys,
|
||||
v8_value_create_promise, v8_value_create_string,
|
||||
};
|
||||
use cef_dll_sys::{_cef_v8_handler_t, _cef_v8_value_t, cef_process_id_t, cef_string_t};
|
||||
use std::os::raw::c_int;
|
||||
use std::ptr::write;
|
||||
|
||||
/// Implements the `window.brp` function in JavaScript.
|
||||
///
|
||||
/// The function definition is `async <T>(request: BrpRequest) -> Promise<T>`.
|
||||
///
|
||||
/// The flow from the execution of the function to the return of the result to the Javascript side is as follows:
|
||||
/// 1. Send `BrpRequest` to the browser process.
|
||||
/// 2. The browser process receives `BrpResult` and sends it to the render process.
|
||||
/// 3. The render process receives the result in `on_process_message_received`.
|
||||
/// 4. The render process resolves the result to the `Promise` of `window.brp`.
|
||||
pub struct BrpBuilder {
|
||||
object: *mut RcImpl<_cef_v8_handler_t, Self>,
|
||||
frame: Frame,
|
||||
}
|
||||
|
||||
impl BrpBuilder {
|
||||
pub fn build(frame: Frame) -> V8Handler {
|
||||
V8Handler::new(Self {
|
||||
object: core::ptr::null_mut(),
|
||||
frame,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Rc for BrpBuilder {
|
||||
fn as_base(&self) -> &sys::cef_base_ref_counted_t {
|
||||
unsafe {
|
||||
let base = &*self.object;
|
||||
std::mem::transmute(&base.cef_object)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl WrapV8Handler for BrpBuilder {
|
||||
fn wrap_rc(&mut self, object: *mut RcImpl<_cef_v8_handler_t, Self>) {
|
||||
self.object = object;
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for BrpBuilder {
|
||||
fn clone(&self) -> Self {
|
||||
let object = unsafe {
|
||||
let rc_impl = &mut *self.object;
|
||||
rc_impl.interface.add_ref();
|
||||
rc_impl
|
||||
};
|
||||
Self {
|
||||
object,
|
||||
frame: self.frame.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ImplV8Handler for BrpBuilder {
|
||||
fn execute(
|
||||
&self,
|
||||
_: Option<&CefString>,
|
||||
_: Option<&mut V8Value>,
|
||||
arguments: Option<&[Option<V8Value>]>,
|
||||
ret: Option<&mut Option<V8Value>>,
|
||||
_: Option<&mut CefString>,
|
||||
) -> c_int {
|
||||
if let Some(mut process) = process_message_create(Some(&PROCESS_MESSAGE_BRP.into()))
|
||||
&& let Some(promise) = v8_value_create_promise()
|
||||
{
|
||||
if let Some(arguments_list) = process.argument_list()
|
||||
&& let Some(arguments) = arguments
|
||||
&& let Some(Some(arg)) = arguments.first()
|
||||
&& let Some(brp_request) = v8_value_to_json(arg)
|
||||
&& let Ok(brp_request) = serde_json::to_string(&brp_request)
|
||||
&& let Some(ret) = ret
|
||||
{
|
||||
let id = uuid::Uuid::new_v4().to_string();
|
||||
arguments_list.set_string(0, Some(&id.as_str().into()));
|
||||
arguments_list.set_string(1, Some(&brp_request.as_str().into()));
|
||||
self.frame.send_process_message(
|
||||
ProcessId::from(cef_process_id_t::PID_BROWSER),
|
||||
Some(&mut process),
|
||||
);
|
||||
ret.replace(promise.clone());
|
||||
let mut promises = BRP_PROMISES.lock().unwrap();
|
||||
promises.insert(id, promise);
|
||||
} else {
|
||||
let mut exception =
|
||||
v8_value_create_string(Some(&"Failed to execute BRP request".into()));
|
||||
promise.resolve_promise(exception.as_mut());
|
||||
}
|
||||
}
|
||||
1
|
||||
}
|
||||
|
||||
fn init_methods(object: &mut _cef_v8_handler_t) {
|
||||
init_methods::<Self>(object);
|
||||
}
|
||||
|
||||
fn get_raw(&self) -> *mut _cef_v8_handler_t {
|
||||
self.object.cast()
|
||||
}
|
||||
}
|
||||
|
||||
fn init_methods<I: ImplV8Handler>(object: &mut _cef_v8_handler_t) {
|
||||
object.execute = Some(execute::<I>);
|
||||
}
|
||||
|
||||
extern "C" fn execute<I: ImplV8Handler>(
|
||||
self_: *mut _cef_v8_handler_t,
|
||||
name: *const cef_string_t,
|
||||
object: *mut _cef_v8_value_t,
|
||||
arguments_count: usize,
|
||||
arguments: *const *mut _cef_v8_value_t,
|
||||
retval: *mut *mut _cef_v8_value_t,
|
||||
exception: *mut cef_string_t,
|
||||
) -> c_int {
|
||||
let (arg_self_, arg_name, arg_object, arg_arguments_count, arg_arguments, _, arg_exception) = (
|
||||
self_,
|
||||
name,
|
||||
object,
|
||||
arguments_count,
|
||||
arguments,
|
||||
retval,
|
||||
exception,
|
||||
);
|
||||
let arg_self_: &RcImpl<_, I> = RcImpl::get(arg_self_);
|
||||
let arg_name = if arg_name.is_null() {
|
||||
None
|
||||
} else {
|
||||
Some(arg_name.into())
|
||||
};
|
||||
let arg_name = arg_name.as_ref();
|
||||
let mut arg_object =
|
||||
unsafe { arg_object.as_mut() }.map(|arg| (arg as *mut _cef_v8_value_t).wrap_result());
|
||||
let arg_object = arg_object.as_mut();
|
||||
let vec_arguments = unsafe { arg_arguments.as_ref() }.map(|arg| {
|
||||
let arg =
|
||||
unsafe { std::slice::from_raw_parts(std::ptr::from_ref(arg), arg_arguments_count) };
|
||||
arg.iter()
|
||||
.map(|arg| {
|
||||
if arg.is_null() {
|
||||
None
|
||||
} else {
|
||||
Some((*arg).wrap_result())
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
});
|
||||
let arg_arguments = vec_arguments.as_deref();
|
||||
let mut arg_retval: Option<V8Value> = None;
|
||||
let arg = Some(&mut arg_retval);
|
||||
let mut arg_exception = if arg_exception.is_null() {
|
||||
None
|
||||
} else {
|
||||
Some(arg_exception.into())
|
||||
};
|
||||
let arg_exception = arg_exception.as_mut();
|
||||
let r = ImplV8Handler::execute(
|
||||
&arg_self_.interface,
|
||||
arg_name,
|
||||
arg_object,
|
||||
arg_arguments,
|
||||
arg,
|
||||
arg_exception,
|
||||
);
|
||||
if let Some(ret) = arg_retval {
|
||||
// When the result is received, the pointer should be updated here
|
||||
// and the exception should also be updated.
|
||||
unsafe {
|
||||
write(retval, ret.into_raw());
|
||||
}
|
||||
}
|
||||
r
|
||||
}
|
||||
82
crates/bevy_cef_core/src/render_process/emit.rs
Normal file
82
crates/bevy_cef_core/src/render_process/emit.rs
Normal file
@@ -0,0 +1,82 @@
|
||||
use crate::prelude::PROCESS_MESSAGE_JS_EMIT;
|
||||
use crate::util::v8_value_to_json;
|
||||
use cef::rc::{Rc, RcImpl};
|
||||
use cef::{
|
||||
CefString, Frame, ImplFrame, ImplListValue, ImplProcessMessage, ImplV8Handler, ProcessId,
|
||||
V8Handler, V8Value, WrapV8Handler, process_message_create, sys,
|
||||
};
|
||||
use cef_dll_sys::cef_process_id_t;
|
||||
use std::os::raw::c_int;
|
||||
|
||||
pub struct EmitBuilder {
|
||||
object: *mut RcImpl<sys::_cef_v8_handler_t, Self>,
|
||||
frame: Frame,
|
||||
}
|
||||
|
||||
impl EmitBuilder {
|
||||
pub fn build(frame: Frame) -> V8Handler {
|
||||
V8Handler::new(Self {
|
||||
object: core::ptr::null_mut(),
|
||||
frame,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Rc for EmitBuilder {
|
||||
fn as_base(&self) -> &sys::cef_base_ref_counted_t {
|
||||
unsafe {
|
||||
let base = &*self.object;
|
||||
std::mem::transmute(&base.cef_object)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl WrapV8Handler for EmitBuilder {
|
||||
fn wrap_rc(&mut self, object: *mut RcImpl<sys::_cef_v8_handler_t, Self>) {
|
||||
self.object = object;
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for EmitBuilder {
|
||||
fn clone(&self) -> Self {
|
||||
let object = unsafe {
|
||||
let rc_impl = &mut *self.object;
|
||||
rc_impl.interface.add_ref();
|
||||
rc_impl
|
||||
};
|
||||
Self {
|
||||
object,
|
||||
frame: self.frame.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ImplV8Handler for EmitBuilder {
|
||||
fn execute(
|
||||
&self,
|
||||
_: Option<&CefString>,
|
||||
_: Option<&mut V8Value>,
|
||||
arguments: Option<&[Option<V8Value>]>,
|
||||
_: Option<&mut Option<V8Value>>,
|
||||
_: Option<&mut CefString>,
|
||||
) -> c_int {
|
||||
if let Some(mut process) = process_message_create(Some(&PROCESS_MESSAGE_JS_EMIT.into()))
|
||||
&& let Some(arguments_list) = process.argument_list()
|
||||
&& let Some(arguments) = arguments
|
||||
&& let Some(Some(arg)) = arguments.first()
|
||||
&& let Some(arg) = v8_value_to_json(arg)
|
||||
&& let Ok(arg) = serde_json::to_string(&arg)
|
||||
{
|
||||
arguments_list.set_string(0, Some(&arg.as_str().into()));
|
||||
self.frame.send_process_message(
|
||||
ProcessId::from(cef_process_id_t::PID_BROWSER),
|
||||
Some(&mut process),
|
||||
);
|
||||
}
|
||||
1
|
||||
}
|
||||
|
||||
fn get_raw(&self) -> *mut sys::_cef_v8_handler_t {
|
||||
self.object.cast()
|
||||
}
|
||||
}
|
||||
77
crates/bevy_cef_core/src/render_process/listen.rs
Normal file
77
crates/bevy_cef_core/src/render_process/listen.rs
Normal file
@@ -0,0 +1,77 @@
|
||||
use crate::prelude::LISTEN_EVENTS;
|
||||
use crate::util::IntoString;
|
||||
use cef::rc::{Rc, RcImpl};
|
||||
use cef::{CefString, Frame, ImplV8Handler, ImplV8Value, V8Handler, V8Value, WrapV8Handler, sys};
|
||||
use std::os::raw::c_int;
|
||||
|
||||
pub struct ListenBuilder {
|
||||
object: *mut RcImpl<sys::_cef_v8_handler_t, Self>,
|
||||
frame: Frame,
|
||||
}
|
||||
|
||||
impl ListenBuilder {
|
||||
pub fn build(frame: Frame) -> V8Handler {
|
||||
V8Handler::new(Self {
|
||||
object: core::ptr::null_mut(),
|
||||
frame,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Rc for ListenBuilder {
|
||||
fn as_base(&self) -> &sys::cef_base_ref_counted_t {
|
||||
unsafe {
|
||||
let base = &*self.object;
|
||||
std::mem::transmute(&base.cef_object)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl WrapV8Handler for ListenBuilder {
|
||||
fn wrap_rc(&mut self, object: *mut RcImpl<sys::_cef_v8_handler_t, Self>) {
|
||||
self.object = object;
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for ListenBuilder {
|
||||
fn clone(&self) -> Self {
|
||||
let object = unsafe {
|
||||
let rc_impl = &mut *self.object;
|
||||
rc_impl.interface.add_ref();
|
||||
rc_impl
|
||||
};
|
||||
Self {
|
||||
object,
|
||||
frame: self.frame.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ImplV8Handler for ListenBuilder {
|
||||
fn execute(
|
||||
&self,
|
||||
_: Option<&CefString>,
|
||||
_: Option<&mut V8Value>,
|
||||
arguments: Option<&[Option<V8Value>]>,
|
||||
_: Option<&mut Option<V8Value>>,
|
||||
_: Option<&mut CefString>,
|
||||
) -> c_int {
|
||||
if let Some(arguments) = arguments
|
||||
&& let Some(Some(id)) = arguments.first()
|
||||
&& 0 < id.is_string()
|
||||
&& let Some(Some(callback)) = arguments.get(1)
|
||||
&& 0 < callback.is_function()
|
||||
{
|
||||
LISTEN_EVENTS
|
||||
.lock()
|
||||
.unwrap()
|
||||
.insert(id.string_value().into_string(), callback.clone());
|
||||
}
|
||||
1
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn get_raw(&self) -> *mut sys::_cef_v8_handler_t {
|
||||
self.object.cast()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,191 @@
|
||||
use crate::prelude::{EmitBuilder, IntoString};
|
||||
use crate::render_process::brp::BrpBuilder;
|
||||
use crate::render_process::listen::ListenBuilder;
|
||||
use crate::util::json_to_v8;
|
||||
use crate::util::v8_accessor::V8DefaultAccessorBuilder;
|
||||
use crate::util::v8_interceptor::V8DefaultInterceptorBuilder;
|
||||
use bevy::platform::collections::HashMap;
|
||||
use bevy_remote::BrpResult;
|
||||
use cef::rc::{Rc, RcImpl};
|
||||
use cef::{
|
||||
Browser, Frame, ImplFrame, ImplListValue, ImplProcessMessage, ImplRenderProcessHandler,
|
||||
ImplV8Context, ImplV8Value, ProcessId, ProcessMessage, V8Context, V8Propertyattribute, V8Value,
|
||||
WrapRenderProcessHandler, sys, v8_value_create_function, v8_value_create_object,
|
||||
};
|
||||
use std::os::raw::c_int;
|
||||
use std::sync::Mutex;
|
||||
|
||||
pub(crate) static BRP_PROMISES: Mutex<HashMap<String, V8Value>> = Mutex::new(HashMap::new());
|
||||
pub(crate) static LISTEN_EVENTS: Mutex<HashMap<String, V8Value>> = Mutex::new(HashMap::new());
|
||||
|
||||
pub const PROCESS_MESSAGE_BRP: &str = "brp";
|
||||
pub const PROCESS_MESSAGE_HOST_EMIT: &str = "host-emit";
|
||||
pub const PROCESS_MESSAGE_JS_EMIT: &str = "js-emit";
|
||||
|
||||
pub struct RenderProcessHandlerBuilder {
|
||||
object: *mut RcImpl<sys::_cef_render_process_handler_t, Self>,
|
||||
}
|
||||
|
||||
impl RenderProcessHandlerBuilder {
|
||||
pub fn build() -> RenderProcessHandlerBuilder {
|
||||
RenderProcessHandlerBuilder {
|
||||
object: core::ptr::null_mut(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl WrapRenderProcessHandler for RenderProcessHandlerBuilder {
|
||||
fn wrap_rc(&mut self, object: *mut RcImpl<sys::_cef_render_process_handler_t, Self>) {
|
||||
self.object = object;
|
||||
}
|
||||
}
|
||||
|
||||
impl Rc for RenderProcessHandlerBuilder {
|
||||
fn as_base(&self) -> &sys::cef_base_ref_counted_t {
|
||||
unsafe {
|
||||
let base = &*self.object;
|
||||
std::mem::transmute(&base.cef_object)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for RenderProcessHandlerBuilder {
|
||||
fn clone(&self) -> Self {
|
||||
let object = unsafe {
|
||||
let rc_impl = &mut *self.object;
|
||||
rc_impl.interface.add_ref();
|
||||
rc_impl
|
||||
};
|
||||
Self { object }
|
||||
}
|
||||
}
|
||||
|
||||
impl ImplRenderProcessHandler for RenderProcessHandlerBuilder {
|
||||
fn on_context_created(
|
||||
&self,
|
||||
_browser: Option<&mut Browser>,
|
||||
frame: Option<&mut Frame>,
|
||||
context: Option<&mut V8Context>,
|
||||
) {
|
||||
if let Some(g) = context.and_then(|c| c.global())
|
||||
&& let Some(frame) = frame
|
||||
&& let Some(mut cef) = v8_value_create_object(
|
||||
Some(&mut V8DefaultAccessorBuilder::build()),
|
||||
Some(&mut V8DefaultInterceptorBuilder::build()),
|
||||
)
|
||||
&& let Some(mut brp) = v8_value_create_function(
|
||||
Some(&"brp".into()),
|
||||
Some(&mut BrpBuilder::build(frame.clone())),
|
||||
)
|
||||
&& let Some(mut emit) = v8_value_create_function(
|
||||
Some(&"emit".into()),
|
||||
Some(&mut EmitBuilder::build(frame.clone())),
|
||||
)
|
||||
&& let Some(mut listen) = v8_value_create_function(
|
||||
Some(&"listen".into()),
|
||||
Some(&mut ListenBuilder::build(frame.clone())),
|
||||
)
|
||||
{
|
||||
cef.set_value_bykey(
|
||||
Some(&"brp".into()),
|
||||
Some(&mut brp),
|
||||
V8Propertyattribute::default(),
|
||||
);
|
||||
cef.set_value_bykey(
|
||||
Some(&"emit".into()),
|
||||
Some(&mut emit),
|
||||
V8Propertyattribute::default(),
|
||||
);
|
||||
cef.set_value_bykey(
|
||||
Some(&"listen".into()),
|
||||
Some(&mut listen),
|
||||
V8Propertyattribute::default(),
|
||||
);
|
||||
g.set_value_bykey(
|
||||
Some(&"cef".into()),
|
||||
Some(&mut cef),
|
||||
V8Propertyattribute::default(),
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
fn on_process_message_received(
|
||||
&self,
|
||||
_browser: Option<&mut Browser>,
|
||||
frame: Option<&mut Frame>,
|
||||
_: ProcessId,
|
||||
message: Option<&mut ProcessMessage>,
|
||||
) -> c_int {
|
||||
if let Some(message) = message
|
||||
&& let Some(frame) = frame
|
||||
&& let Some(ctx) = frame.v8_context()
|
||||
{
|
||||
match message.name().into_string().as_str() {
|
||||
PROCESS_MESSAGE_BRP => {
|
||||
handle_brp_message(message, ctx);
|
||||
}
|
||||
PROCESS_MESSAGE_HOST_EMIT => {
|
||||
handle_listen_message(message, ctx);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
};
|
||||
1
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn get_raw(&self) -> *mut sys::_cef_render_process_handler_t {
|
||||
self.object.cast()
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_brp_message(message: &ProcessMessage, ctx: V8Context) {
|
||||
let Some(argument_list) = message.argument_list() else {
|
||||
return;
|
||||
};
|
||||
let id = argument_list.string(0).into_string();
|
||||
let payload = argument_list.string(1).into_string();
|
||||
let Ok(Some(promise)) = BRP_PROMISES.lock().map(|mut p| p.remove(&id)) else {
|
||||
return;
|
||||
};
|
||||
|
||||
if let Ok(brp_result) = serde_json::from_str::<BrpResult>(&payload) {
|
||||
ctx.enter();
|
||||
match brp_result {
|
||||
Ok(v) => {
|
||||
promise.resolve_promise(json_to_v8(v).as_mut());
|
||||
}
|
||||
Err(e) => {
|
||||
promise.reject_promise(Some(&e.message.as_str().into()));
|
||||
}
|
||||
}
|
||||
ctx.exit();
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_listen_message(message: &ProcessMessage, mut ctx: V8Context) {
|
||||
let Some(argument_list) = message.argument_list() else {
|
||||
return;
|
||||
};
|
||||
let id = argument_list.string(0).into_string();
|
||||
let payload = argument_list.string(1).into_string();
|
||||
|
||||
ctx.enter();
|
||||
if let Ok(value) = serde_json::from_str::<serde_json::Value>(&payload)
|
||||
&& let Ok(events) = LISTEN_EVENTS.lock()
|
||||
{
|
||||
let mut obj = v8_value_create_object(
|
||||
Some(&mut V8DefaultAccessorBuilder::build()),
|
||||
Some(&mut V8DefaultInterceptorBuilder::build()),
|
||||
);
|
||||
let Some(callback) = events.get(&id) else {
|
||||
return;
|
||||
};
|
||||
callback.execute_function_with_context(
|
||||
Some(&mut ctx),
|
||||
obj.as_mut(),
|
||||
Some(&[json_to_v8(value)]),
|
||||
);
|
||||
}
|
||||
ctx.exit();
|
||||
}
|
||||
140
crates/bevy_cef_core/src/util.rs
Normal file
140
crates/bevy_cef_core/src/util.rs
Normal file
@@ -0,0 +1,140 @@
|
||||
pub mod v8_accessor;
|
||||
mod v8_handler_wrapper;
|
||||
pub mod v8_interceptor;
|
||||
|
||||
use crate::util::v8_accessor::V8DefaultAccessorBuilder;
|
||||
use crate::util::v8_interceptor::V8DefaultInterceptorBuilder;
|
||||
use cef::rc::ConvertParam;
|
||||
use cef::{
|
||||
CefStringList, CefStringUserfreeUtf16, CefStringUtf16, ImplV8Value, V8Propertyattribute,
|
||||
v8_value_create_array, v8_value_create_bool, v8_value_create_double, v8_value_create_int,
|
||||
v8_value_create_null, v8_value_create_object, v8_value_create_string,
|
||||
};
|
||||
use cef_dll_sys::_cef_string_utf16_t;
|
||||
use cef_dll_sys::cef_scheme_options_t::{
|
||||
CEF_SCHEME_OPTION_CORS_ENABLED, CEF_SCHEME_OPTION_LOCAL, CEF_SCHEME_OPTION_SECURE,
|
||||
CEF_SCHEME_OPTION_STANDARD,
|
||||
};
|
||||
use std::env::home_dir;
|
||||
use std::path::PathBuf;
|
||||
|
||||
pub const SCHEME_CEF: &str = "cef";
|
||||
|
||||
pub const HOST_CEF: &str = "localhost";
|
||||
|
||||
pub fn cef_scheme_flags() -> u32 {
|
||||
CEF_SCHEME_OPTION_STANDARD as u32
|
||||
| CEF_SCHEME_OPTION_SECURE as u32
|
||||
| CEF_SCHEME_OPTION_LOCAL as u32
|
||||
| CEF_SCHEME_OPTION_CORS_ENABLED as u32
|
||||
}
|
||||
|
||||
pub fn debug_chromium_libraries_path() -> PathBuf {
|
||||
debug_chromium_embedded_framework_dir_path().join("Libraries")
|
||||
}
|
||||
|
||||
pub fn debug_chromium_embedded_framework_dir_path() -> PathBuf {
|
||||
debug_cef_path().join("Chromium Embedded Framework.framework")
|
||||
}
|
||||
|
||||
pub fn debug_cef_path() -> PathBuf {
|
||||
home_dir().unwrap().join(".local").join("share").join("cef")
|
||||
}
|
||||
|
||||
pub fn debug_render_process_path() -> PathBuf {
|
||||
cargo_bin_path().join("bevy_cef_debug_render_process")
|
||||
}
|
||||
|
||||
pub fn cargo_bin_path() -> PathBuf {
|
||||
home_dir().unwrap().join(".cargo").join("bin")
|
||||
}
|
||||
|
||||
pub trait IntoString {
|
||||
fn into_string(self) -> String;
|
||||
}
|
||||
|
||||
impl IntoString for CefStringUserfreeUtf16 {
|
||||
fn into_string(self) -> String {
|
||||
let ptr: *mut _cef_string_utf16_t = self.into_raw();
|
||||
CefStringUtf16::from(ptr).to_string()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn v8_value_to_json(v8: &cef::V8Value) -> Option<serde_json::Value> {
|
||||
if v8.is_bool().is_positive() {
|
||||
Some(serde_json::Value::Bool(v8.bool_value().is_positive()))
|
||||
} else if v8.is_int().is_positive() {
|
||||
Some(serde_json::Value::Number(serde_json::Number::from(
|
||||
v8.int_value(),
|
||||
)))
|
||||
} else if v8.is_double().is_positive() {
|
||||
Some(serde_json::Value::Number(
|
||||
serde_json::Number::from_f64(v8.double_value()).unwrap(),
|
||||
))
|
||||
} else if v8.is_string().is_positive() {
|
||||
Some(serde_json::Value::String(v8.string_value().into_string()))
|
||||
} else if v8.is_null().is_positive() || v8.is_undefined().is_positive() {
|
||||
Some(serde_json::Value::Null)
|
||||
} else if v8.is_array().is_positive() {
|
||||
let mut array = Vec::new();
|
||||
let mut keys = CefStringList::new();
|
||||
v8.keys(Some(&mut keys));
|
||||
for key in keys.into_iter() {
|
||||
if let Some(v) = v8.value_bykey(Some(&key.as_str().into()))
|
||||
&& let Some(serialized) = v8_value_to_json(&v)
|
||||
{
|
||||
{
|
||||
array.push(serialized);
|
||||
}
|
||||
}
|
||||
}
|
||||
Some(serde_json::Value::Array(array))
|
||||
} else if v8.is_object().is_positive() {
|
||||
let mut object = serde_json::Map::new();
|
||||
let mut keys = CefStringList::new();
|
||||
v8.keys(Some(&mut keys));
|
||||
for key in keys.into_iter() {
|
||||
if let Some(v) = v8.value_bykey(Some(&key.as_str().into()))
|
||||
&& let Some(serialized) = v8_value_to_json(&v)
|
||||
{
|
||||
{
|
||||
object.insert(key, serialized);
|
||||
}
|
||||
}
|
||||
}
|
||||
Some(serde_json::Value::Object(object))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn json_to_v8(v: serde_json::Value) -> Option<cef::V8Value> {
|
||||
match v {
|
||||
serde_json::Value::Null => v8_value_create_null(),
|
||||
serde_json::Value::Bool(b) => v8_value_create_bool(b as _),
|
||||
serde_json::Value::Number(n) if n.is_i64() => v8_value_create_int(n.as_i64()? as i32),
|
||||
serde_json::Value::Number(n) => v8_value_create_double(n.as_f64()?),
|
||||
serde_json::Value::String(s) => v8_value_create_string(Some(&s.as_str().into())),
|
||||
serde_json::Value::Array(arr) => {
|
||||
let v8_array = v8_value_create_array(arr.len() as _)?;
|
||||
for (i, item) in arr.into_iter().enumerate() {
|
||||
v8_array.set_value_byindex(i as _, json_to_v8(item).as_mut());
|
||||
}
|
||||
Some(v8_array)
|
||||
}
|
||||
serde_json::Value::Object(obj) => {
|
||||
let v8_object = v8_value_create_object(
|
||||
Some(&mut V8DefaultAccessorBuilder::build()),
|
||||
Some(&mut V8DefaultInterceptorBuilder::build()),
|
||||
)?;
|
||||
for (key, value) in obj {
|
||||
v8_object.set_value_bykey(
|
||||
Some(&key.as_str().into()),
|
||||
json_to_v8(value).as_mut(),
|
||||
V8Propertyattribute::default(),
|
||||
);
|
||||
}
|
||||
Some(v8_object)
|
||||
}
|
||||
}
|
||||
}
|
||||
48
crates/bevy_cef_core/src/util/v8_accessor.rs
Normal file
48
crates/bevy_cef_core/src/util/v8_accessor.rs
Normal file
@@ -0,0 +1,48 @@
|
||||
use cef::rc::{Rc, RcImpl};
|
||||
use cef::{ImplV8Accessor, V8Accessor, WrapV8Accessor, sys};
|
||||
use cef_dll_sys::_cef_v8_accessor_t;
|
||||
|
||||
pub struct V8DefaultAccessorBuilder {
|
||||
object: *mut RcImpl<_cef_v8_accessor_t, Self>,
|
||||
}
|
||||
|
||||
impl V8DefaultAccessorBuilder {
|
||||
pub fn build() -> V8Accessor {
|
||||
V8Accessor::new(Self {
|
||||
object: core::ptr::null_mut(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Rc for V8DefaultAccessorBuilder {
|
||||
fn as_base(&self) -> &sys::cef_base_ref_counted_t {
|
||||
unsafe {
|
||||
let base = &*self.object;
|
||||
std::mem::transmute(&base.cef_object)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for V8DefaultAccessorBuilder {
|
||||
fn clone(&self) -> Self {
|
||||
let object = unsafe {
|
||||
let rc_impl = &mut *self.object;
|
||||
rc_impl.interface.add_ref();
|
||||
rc_impl
|
||||
};
|
||||
Self { object }
|
||||
}
|
||||
}
|
||||
|
||||
impl WrapV8Accessor for V8DefaultAccessorBuilder {
|
||||
fn wrap_rc(&mut self, object: *mut RcImpl<_cef_v8_accessor_t, Self>) {
|
||||
self.object = object;
|
||||
}
|
||||
}
|
||||
|
||||
impl ImplV8Accessor for V8DefaultAccessorBuilder {
|
||||
#[inline]
|
||||
fn get_raw(&self) -> *mut _cef_v8_accessor_t {
|
||||
self.object.cast()
|
||||
}
|
||||
}
|
||||
70
crates/bevy_cef_core/src/util/v8_handler_wrapper.rs
Normal file
70
crates/bevy_cef_core/src/util/v8_handler_wrapper.rs
Normal file
@@ -0,0 +1,70 @@
|
||||
// use std::os::raw::c_int;
|
||||
// use bevy::ecs::reflect::ReflectCommandExt;
|
||||
// use cef::rc::{Rc, RcImpl};
|
||||
// use cef::{sys, CefString, ImplV8Handler, V8Handler, V8Value, WrapV8Handler};
|
||||
// use cef_dll_sys::{_cef_v8_handler_t, _cef_v8_value_t, cef_string_t};
|
||||
//
|
||||
// pub struct V8HandlerWrapBuilder<T> {
|
||||
// base: T,
|
||||
// }
|
||||
//
|
||||
// impl<T> V8HandlerWrapBuilder<T> {
|
||||
// pub fn build(base: T) -> V8Handler {
|
||||
// V8Handler::new(Self {
|
||||
// base,
|
||||
// })
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// impl<T: Rc> Rc for V8HandlerWrapBuilder<T> {
|
||||
// fn as_base(&self) -> &sys::cef_base_ref_counted_t {
|
||||
// self.base.as_base()
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// impl<T: ImplV8Handler> ImplV8Handler for V8HandlerWrapBuilder<T> {
|
||||
// fn execute(&self, name: Option<&CefString>, object: Option<&mut V8Value>, arguments: Option<&[Option<V8Value>]>, retval: Option<&mut Option<V8Value>>, exception: Option<&mut CefString>) -> c_int {
|
||||
// self.base.execute(
|
||||
// name,
|
||||
// object,
|
||||
// arguments,
|
||||
// retval,
|
||||
// exception,
|
||||
// )
|
||||
// }
|
||||
//
|
||||
// fn get_raw(&self) -> *mut _cef_v8_handler_t {
|
||||
// self.base.get_raw()
|
||||
// }
|
||||
//
|
||||
// fn init_methods(object: &mut _cef_v8_handler_t) {
|
||||
// T::init_methods(object);
|
||||
//
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// impl<T: Clone> Clone for V8HandlerWrapBuilder<T> {
|
||||
// fn clone(&self) -> Self {
|
||||
// Self{
|
||||
// base: self.base.clone(),
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// impl <T: WrapV8Handler> cef::WrapV8Handler for V8HandlerWrapBuilder<T> {
|
||||
// fn wrap_rc(&mut self, object: *mut RcImpl<sys::_cef_v8_handler_t, Self>) {
|
||||
// self.base.wrap_rc(object);
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// // extern "C" fn execute<I: ImplV8Handler>(
|
||||
// // self_: *mut _cef_v8_handler_t,
|
||||
// // name: *const cef_string_t,
|
||||
// // object: *mut _cef_v8_value_t,
|
||||
// // arguments_count: usize,
|
||||
// // arguments: *const *mut _cef_v8_value_t,
|
||||
// // retval: *mut *mut _cef_v8_value_t,
|
||||
// // exception: *mut cef_string_t,
|
||||
// // ) -> ::std::os::raw::c_int {
|
||||
// //
|
||||
// // }
|
||||
48
crates/bevy_cef_core/src/util/v8_interceptor.rs
Normal file
48
crates/bevy_cef_core/src/util/v8_interceptor.rs
Normal file
@@ -0,0 +1,48 @@
|
||||
use cef::rc::{Rc, RcImpl};
|
||||
use cef::{ImplV8Interceptor, V8Interceptor, WrapV8Interceptor, sys};
|
||||
use cef_dll_sys::_cef_v8_interceptor_t;
|
||||
|
||||
pub struct V8DefaultInterceptorBuilder {
|
||||
object: *mut RcImpl<_cef_v8_interceptor_t, Self>,
|
||||
}
|
||||
|
||||
impl V8DefaultInterceptorBuilder {
|
||||
pub fn build() -> V8Interceptor {
|
||||
V8Interceptor::new(Self {
|
||||
object: core::ptr::null_mut(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl WrapV8Interceptor for V8DefaultInterceptorBuilder {
|
||||
fn wrap_rc(&mut self, object: *mut RcImpl<_cef_v8_interceptor_t, Self>) {
|
||||
self.object = object;
|
||||
}
|
||||
}
|
||||
|
||||
impl Rc for V8DefaultInterceptorBuilder {
|
||||
fn as_base(&self) -> &sys::cef_base_ref_counted_t {
|
||||
unsafe {
|
||||
let base = &*self.object;
|
||||
std::mem::transmute(&base.cef_object)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for V8DefaultInterceptorBuilder {
|
||||
fn clone(&self) -> Self {
|
||||
let object = unsafe {
|
||||
let rc_impl = &mut *self.object;
|
||||
rc_impl.interface.add_ref();
|
||||
rc_impl
|
||||
};
|
||||
Self { object }
|
||||
}
|
||||
}
|
||||
|
||||
impl ImplV8Interceptor for V8DefaultInterceptorBuilder {
|
||||
#[inline]
|
||||
fn get_raw(&self) -> *mut _cef_v8_interceptor_t {
|
||||
self.object.cast()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user