From 8a5d7a09064e6f17c973e3ec1e37535c7ae13e0c Mon Sep 17 00:00:00 2001 From: Louis Date: Thu, 9 Oct 2025 22:44:12 +0700 Subject: [PATCH] feat: egui + cef + osr --- browser/Cargo.toml | 46 + browser/README.md | 74 + browser/app/Cargo.toml | 38 + browser/app/src/mac/bundle_app.rs | 182 + browser/app/src/mac/helper.rs | 25 + browser/app/src/main.rs | 782 + browser/app/src/win/app.exe.manifest | 40 + browser/cef/CHANGELOG.md | 215 + browser/cef/Cargo.toml | 26 + browser/cef/README.md | 3 + browser/cef/src/args.rs | 83 + .../cef/src/bindings/aarch64_apple_darwin.rs | 48028 +++++++++++++++ .../src/bindings/aarch64_pc_windows_msvc.rs | 48016 +++++++++++++++ .../src/bindings/aarch64_unknown_linux_gnu.rs | 48062 ++++++++++++++++ .../src/bindings/arm_unknown_linux_gnueabi.rs | 48062 ++++++++++++++++ .../cef/src/bindings/i686_pc_windows_msvc.rs | 18389 ++++++ browser/cef/src/bindings/mod.rs | 177 + .../cef/src/bindings/x86_64_apple_darwin.rs | 48028 +++++++++++++++ .../src/bindings/x86_64_pc_windows_msvc.rs | 48016 +++++++++++++++ .../src/bindings/x86_64_unknown_linux_gnu.rs | 48062 ++++++++++++++++ browser/cef/src/lib.rs | 17 + browser/cef/src/library_loader.rs | 46 + browser/cef/src/rc.rs | 380 + browser/cef/src/sandbox.rs | 53 + browser/cef/src/string.rs | 1415 + browser/download-cef/CHANGELOG.md | 49 + browser/download-cef/Cargo.toml | 23 + browser/download-cef/README.md | 4 + browser/download-cef/src/lib.rs | 644 + browser/export-cef-dir/CHANGELOG.md | 208 + browser/export-cef-dir/Cargo.toml | 16 + browser/export-cef-dir/README.md | 28 + browser/export-cef-dir/src/main.rs | 136 + browser/get-latest/Cargo.toml | 21 + browser/get-latest/README.md | 5 + browser/get-latest/cliff.toml | 112 + browser/get-latest/src/main.rs | 235 + browser/sys/CHANGELOG.md | 202 + browser/sys/Cargo.toml | 26 + browser/sys/README.md | 4 + browser/sys/build.rs | 131 + .../sys/src/bindings/aarch64_apple_darwin.rs | 15978 +++++ .../src/bindings/aarch64_pc_windows_msvc.rs | 16054 ++++++ .../src/bindings/aarch64_unknown_linux_gnu.rs | 16012 +++++ .../src/bindings/arm_unknown_linux_gnueabi.rs | 15999 +++++ .../sys/src/bindings/i686_pc_windows_msvc.rs | 16274 ++++++ browser/sys/src/bindings/mod.rs | 39 + .../sys/src/bindings/x86_64_apple_darwin.rs | 15978 +++++ .../src/bindings/x86_64_pc_windows_msvc.rs | 16054 ++++++ .../src/bindings/x86_64_unknown_linux_gnu.rs | 16012 +++++ browser/sys/src/lib.rs | 91 + browser/sys/wrapper.h | 33 + browser/update-bindings/Cargo.toml | 22 + browser/update-bindings/README.md | 8 + browser/update-bindings/build.rs | 2 + browser/update-bindings/src/dirs.rs | 39 + browser/update-bindings/src/main.rs | 102 + browser/update-bindings/src/parse_tree.rs | 3576 ++ browser/update-bindings/src/upgrade.rs | 102 + 59 files changed, 492484 insertions(+) create mode 100644 browser/Cargo.toml create mode 100644 browser/README.md create mode 100644 browser/app/Cargo.toml create mode 100644 browser/app/src/mac/bundle_app.rs create mode 100644 browser/app/src/mac/helper.rs create mode 100644 browser/app/src/main.rs create mode 100644 browser/app/src/win/app.exe.manifest create mode 100644 browser/cef/CHANGELOG.md create mode 100644 browser/cef/Cargo.toml create mode 100644 browser/cef/README.md create mode 100644 browser/cef/src/args.rs create mode 100644 browser/cef/src/bindings/aarch64_apple_darwin.rs create mode 100644 browser/cef/src/bindings/aarch64_pc_windows_msvc.rs create mode 100644 browser/cef/src/bindings/aarch64_unknown_linux_gnu.rs create mode 100644 browser/cef/src/bindings/arm_unknown_linux_gnueabi.rs create mode 100644 browser/cef/src/bindings/i686_pc_windows_msvc.rs create mode 100644 browser/cef/src/bindings/mod.rs create mode 100644 browser/cef/src/bindings/x86_64_apple_darwin.rs create mode 100644 browser/cef/src/bindings/x86_64_pc_windows_msvc.rs create mode 100644 browser/cef/src/bindings/x86_64_unknown_linux_gnu.rs create mode 100644 browser/cef/src/lib.rs create mode 100644 browser/cef/src/library_loader.rs create mode 100644 browser/cef/src/rc.rs create mode 100644 browser/cef/src/sandbox.rs create mode 100644 browser/cef/src/string.rs create mode 100644 browser/download-cef/CHANGELOG.md create mode 100644 browser/download-cef/Cargo.toml create mode 100644 browser/download-cef/README.md create mode 100644 browser/download-cef/src/lib.rs create mode 100644 browser/export-cef-dir/CHANGELOG.md create mode 100644 browser/export-cef-dir/Cargo.toml create mode 100644 browser/export-cef-dir/README.md create mode 100644 browser/export-cef-dir/src/main.rs create mode 100644 browser/get-latest/Cargo.toml create mode 100644 browser/get-latest/README.md create mode 100644 browser/get-latest/cliff.toml create mode 100644 browser/get-latest/src/main.rs create mode 100644 browser/sys/CHANGELOG.md create mode 100644 browser/sys/Cargo.toml create mode 100644 browser/sys/README.md create mode 100644 browser/sys/build.rs create mode 100644 browser/sys/src/bindings/aarch64_apple_darwin.rs create mode 100644 browser/sys/src/bindings/aarch64_pc_windows_msvc.rs create mode 100644 browser/sys/src/bindings/aarch64_unknown_linux_gnu.rs create mode 100644 browser/sys/src/bindings/arm_unknown_linux_gnueabi.rs create mode 100644 browser/sys/src/bindings/i686_pc_windows_msvc.rs create mode 100644 browser/sys/src/bindings/mod.rs create mode 100644 browser/sys/src/bindings/x86_64_apple_darwin.rs create mode 100644 browser/sys/src/bindings/x86_64_pc_windows_msvc.rs create mode 100644 browser/sys/src/bindings/x86_64_unknown_linux_gnu.rs create mode 100644 browser/sys/src/lib.rs create mode 100644 browser/sys/wrapper.h create mode 100644 browser/update-bindings/Cargo.toml create mode 100644 browser/update-bindings/README.md create mode 100644 browser/update-bindings/build.rs create mode 100644 browser/update-bindings/src/dirs.rs create mode 100644 browser/update-bindings/src/main.rs create mode 100644 browser/update-bindings/src/parse_tree.rs create mode 100755 browser/update-bindings/src/upgrade.rs diff --git a/browser/Cargo.toml b/browser/Cargo.toml new file mode 100644 index 000000000..a138ce22c --- /dev/null +++ b/browser/Cargo.toml @@ -0,0 +1,46 @@ +[workspace] +resolver = "2" + +members = [ + "download-cef", + "get-latest", + "update-bindings", + "export-cef-dir", + "sys", + "cef", + "app", +] + +[workspace.package] +version = "140.3.1+140.1.14" +edition = "2021" +license = "Apache-2.0 OR MIT" +authors = [] +repository = "https://github.com/menloresearch/jan" + +[workspace.dependencies] +cef = { path = "cef" } +cef-dll-sys = { version = "140.3.1", path = "sys" } +download-cef = { version = "2.2", path = "download-cef" } + +anyhow = "1" +bindgen = "0.72" +clap = { version = "4", features = ["derive"] } +cmake = "0.1.52" +convert_case = "0.8" +git-cliff = "2" +git-cliff-core = "2" +plist = "1" +proc-macro2 = "1" +quote = "1" +regex = "1" +semver = "1" +serde = { version = "1", features = ["derive"] } +serde_json = "1" +syn = { version = "2", features = ["full"] } +thiserror = "2" +toml_edit = "0.23" + +[workspace.dependencies.windows-sys] +version = "0.61" +features = ["Win32_System_Environment", "Win32_System_LibraryLoader"] diff --git a/browser/README.md b/browser/README.md new file mode 100644 index 000000000..355fcfe8a --- /dev/null +++ b/browser/README.md @@ -0,0 +1,74 @@ +# cef-rs + +Use CEF in Rust. + +## Supported Targets + +| Target | Linux | macOS | Windows | +| ------ | ----- | ----- | ------- | +| x86_64 | ✅ | ✅ | ✅ | +| ARM64 | ✅ | ✅ | ✅ | + +## Usage + +### Install Shared CEF Binaries + +This step is optional, but it will make all other builds of the `cef` crate much faster. If you don't do this, the `cef-dll-sys` crate `build.rs` script will download and extract the same files under its `OUT_DIR` directory. You should repeat this step each time you upgrade to a new version of the `cef` crate. + +#### Linux or macOS: + +```sh +cargo run -p export-cef-dir -- --force $HOME/.local/share/cef +``` + +#### Windows (using PowerShell) + +```pwsh +cargo run -p export-cef-dir -- --force $env:USERPROFILE/.local/share/cef +``` + +### Set Environment Variables + +#### Linux + +```sh +export CEF_PATH="$HOME/.local/share/cef" +export LD_LIBRARY_PATH="$LD_LIBRARY_PATH:$CEF_PATH" +``` + +#### macOS + +```sh +export CEF_PATH="$HOME/.local/share/cef" +export DYLD_FALLBACK_LIBRARY_PATH="$DYLD_FALLBACK_LIBRARY_PATH:$CEF_PATH:$CEF_PATH/Chromium Embedded Framework.framework/Libraries" +``` + +#### Windows (using PowerShell) + +```pwsh +$env:CEF_PATH="$env:USERPROFILE/.local/share/cef" +$env:PATH="$env:PATH;$env:CEF_PATH" +``` + +### Run the `app` + +#### Linux + +```sh +cargo run --bin app +``` + +#### macOS + +```sh +cargo run --bin bundle_app +open target/debug/app.app +``` + +#### Windows (using PowerShell) + +```pwsh +cp ./app/win/jan.exe.manifest ./target/debug/ +cargo run --bin app +``` + diff --git a/browser/app/Cargo.toml b/browser/app/Cargo.toml new file mode 100644 index 000000000..1a4ddf4a4 --- /dev/null +++ b/browser/app/Cargo.toml @@ -0,0 +1,38 @@ +[package] +name = "app" +edition = "2024" +publish = false + +[[bin]] +name = "app" + +[[bin]] +name = "app_helper" +path = "src/mac/helper.rs" + +[[bin]] +name = "bundle_app" +path = "src/mac/bundle_app.rs" + +[features] +default = ["sandbox"] +sandbox = ["cef/sandbox"] + +[dependencies] +cef.workspace = true +cef-dll-sys.workspace = true +parking_lot = "0.12" +once_cell = "1.19" +tokio = { version = "1", features = ["time"] } + +egui = "0.29" +egui-wgpu = "0.29" +egui-winit = "0.29" +wgpu = "22" +winit = { version = "0.30", features = [] } +pollster = "0.4" +env_logger = "0.11" + +[target.'cfg(target_os = "macos")'.dependencies] +plist.workspace = true +serde.workspace = true diff --git a/browser/app/src/mac/bundle_app.rs b/browser/app/src/mac/bundle_app.rs new file mode 100644 index 000000000..659deaad3 --- /dev/null +++ b/browser/app/src/mac/bundle_app.rs @@ -0,0 +1,182 @@ +#[cfg(target_os = "macos")] +mod mac { + use serde::Serialize; + use std::collections::HashMap; + use std::fs; + use std::path::{Path, PathBuf}; + use std::process::{Command, Stdio}; + + #[derive(Serialize)] + struct InfoPlist { + #[serde(rename = "CFBundleDevelopmentRegion")] + cf_bundle_development_region: String, + #[serde(rename = "CFBundleDisplayName")] + cf_bundle_display_name: String, + #[serde(rename = "CFBundleExecutable")] + cf_bundle_executable: String, + #[serde(rename = "CFBundleIdentifier")] + cf_bundle_identifier: String, + #[serde(rename = "CFBundleInfoDictionaryVersion")] + cf_bundle_info_dictionary_version: String, + #[serde(rename = "CFBundleName")] + cf_bundle_name: String, + #[serde(rename = "CFBundlePackageType")] + cf_bundle_package_type: String, + #[serde(rename = "CFBundleSignature")] + cf_bundle_signature: String, + #[serde(rename = "CFBundleVersion")] + cf_bundle_version: String, + #[serde(rename = "CFBundleShortVersionString")] + cf_bundle_short_version_string: String, + #[serde(rename = "LSEnvironment")] + ls_environment: HashMap, + #[serde(rename = "LSFileQuarantineEnabled")] + ls_file_quarantine_enabled: bool, + #[serde(rename = "LSMinimumSystemVersion")] + ls_minimum_system_version: String, + #[serde(rename = "LSUIElement")] + ls_ui_element: Option, + #[serde(rename = "NSBluetoothAlwaysUsageDescription")] + ns_bluetooth_always_usage_description: String, + #[serde(rename = "NSSupportsAutomaticGraphicsSwitching")] + ns_supports_automatic_graphics_switching: bool, + #[serde(rename = "NSWebBrowserPublicKeyCredentialUsageDescription")] + ns_web_browser_publickey_credential_usage_description: String, + #[serde(rename = "NSCameraUsageDescription")] + ns_camera_usage_description: String, + #[serde(rename = "NSMicrophoneUsageDescription")] + ns_microphone_usage_description: String, + } + + const EXEC_PATH: &str = "Contents/MacOS"; + const FRAMEWORKS_PATH: &str = "Contents/Frameworks"; + const RESOURCES_PATH: &str = "Contents/Resources"; + const FRAMEWORK: &str = "Chromium Embedded Framework.framework"; + const HELPERS: &[&str] = &[ + "app Helper (GPU)", + "app Helper (Renderer)", + "app Helper (Plugin)", + "app Helper (Alerts)", + "app Helper", + ]; + + fn create_app_layout(app_path: &Path) -> PathBuf { + [EXEC_PATH, RESOURCES_PATH, FRAMEWORKS_PATH] + .iter() + .for_each(|p| fs::create_dir_all(app_path.join(p)).unwrap()); + app_path.join("Contents") + } + + fn create_app(app_path: &Path, exec_name: &str, bin: &Path, is_helper: bool) -> PathBuf { + let app_path = app_path.join(exec_name).with_extension("app"); + let contents_path = create_app_layout(&app_path); + create_info_plist(&contents_path, exec_name, is_helper).unwrap(); + fs::copy(bin, app_path.join(EXEC_PATH).join(exec_name)).unwrap(); + app_path + } + + // See https://bitbucket.org/chromiumembedded/cef/wiki/GeneralUsage.md#markdown-header-macos + fn bundle(app_path: &Path) { + let example_path = PathBuf::from(app_path); + let main_app_path = create_app( + app_path, + "app", + &example_path.join("app"), + false, + ); + let cef_path = cef_dll_sys::get_cef_dir().unwrap(); + let to = main_app_path.join(FRAMEWORKS_PATH).join(FRAMEWORK); + if to.exists() { + fs::remove_dir_all(&to).unwrap(); + } + copy_directory(&cef_path.join(FRAMEWORK), &to); + HELPERS.iter().for_each(|helper| { + create_app( + &main_app_path.join(FRAMEWORKS_PATH), + helper, + &example_path.join("app_helper"), + true, + ); + }); + } + + fn create_info_plist( + contents_path: &Path, + exec_name: &str, + is_helper: bool, + ) -> Result<(), Box> { + let info_plist = InfoPlist { + cf_bundle_development_region: "en".to_string(), + cf_bundle_display_name: exec_name.to_string(), + cf_bundle_executable: exec_name.to_string(), + cf_bundle_identifier: "app.jan.ai.helper".to_string(), + cf_bundle_info_dictionary_version: "6.0".to_string(), + cf_bundle_name: "cef-rs".to_string(), + cf_bundle_package_type: "APPL".to_string(), + cf_bundle_signature: "????".to_string(), + cf_bundle_version: "1.0.0".to_string(), + cf_bundle_short_version_string: "1.0".to_string(), + ls_environment: [("MallocNanoZone".to_string(), "0".to_string())] + .iter() + .cloned() + .collect(), + ls_file_quarantine_enabled: true, + ls_minimum_system_version: "11.0".to_string(), + ls_ui_element: if is_helper { + Some("1".to_string()) + } else { + None + }, + ns_bluetooth_always_usage_description: exec_name.to_string(), + ns_supports_automatic_graphics_switching: true, + ns_web_browser_publickey_credential_usage_description: exec_name.to_string(), + ns_camera_usage_description: exec_name.to_string(), + ns_microphone_usage_description: exec_name.to_string(), + }; + + plist::to_file_xml(contents_path.join("Info.plist"), &info_plist)?; + Ok(()) + } + + fn copy_directory(src: &Path, dst: &Path) { + fs::create_dir_all(dst).unwrap(); + for entry in fs::read_dir(src).unwrap() { + let entry = entry.unwrap(); + let dst_path = dst.join(entry.file_name()); + if entry.file_type().unwrap().is_dir() { + copy_directory(&entry.path(), &dst_path); + } else { + fs::copy(&entry.path(), &dst_path).unwrap(); + } + } + } + + fn run_command(args: &[&str]) -> Result<(), Box> { + let status = Command::new("cargo") + .args(args) + .stdout(Stdio::inherit()) + .stderr(Stdio::inherit()) + .status()?; + + if !status.success() { + std::process::exit(1); + } + Ok(()) + } + + pub fn main() -> Result<(), Box> { + let app_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../target/debug"); + run_command(&["build", "--bin", "app"])?; + run_command(&["build", "--bin", "app_helper"])?; + bundle(&app_path); + Ok(()) + } +} + +#[cfg(target_os = "macos")] +fn main() -> Result<(), Box> { + mac::main() +} + +#[cfg(not(target_os = "macos"))] +fn main() {} diff --git a/browser/app/src/mac/helper.rs b/browser/app/src/mac/helper.rs new file mode 100644 index 000000000..5dbab0ad7 --- /dev/null +++ b/browser/app/src/mac/helper.rs @@ -0,0 +1,25 @@ +use cef::{args::Args, *}; + +fn main() { + let args = Args::new(); + + #[cfg(all(target_os = "macos", feature = "sandbox"))] + let _sandbox = { + let mut sandbox = cef::sandbox::Sandbox::new(); + sandbox.initialize(args.as_main_args()); + sandbox + }; + + #[cfg(target_os = "macos")] + let _loader = { + let loader = library_loader::LibraryLoader::new(&std::env::current_exe().unwrap(), true); + assert!(loader.load()); + loader + }; + + execute_process( + Some(args.as_main_args()), + None::<&mut App>, + std::ptr::null_mut(), + ); +} diff --git a/browser/app/src/main.rs b/browser/app/src/main.rs new file mode 100644 index 000000000..6c460b388 --- /dev/null +++ b/browser/app/src/main.rs @@ -0,0 +1,782 @@ +use cef::{args::Args, rc::*, *}; +use parking_lot::RwLock; +use std::sync::Arc; +use std::time::{Duration, Instant}; +use winit::{ + application::ApplicationHandler, + event::WindowEvent, + event_loop::{ActiveEventLoop, ControlFlow, EventLoop}, + platform::pump_events::{EventLoopExtPumpEvents, PumpStatus}, + window::{Window, WindowAttributes, WindowId}, +}; + +// Shared state between CEF and rendering +static TEXTURE_BUFFER: once_cell::sync::Lazy>>> = + once_cell::sync::Lazy::new(|| Arc::new(RwLock::new(Vec::new()))); +static TEXTURE_SIZE: once_cell::sync::Lazy>> = + once_cell::sync::Lazy::new(|| Arc::new(RwLock::new((1024, 768)))); +static CEF_INITIALIZED: once_cell::sync::Lazy>> = + once_cell::sync::Lazy::new(|| Arc::new(RwLock::new(false))); +static BROWSER_INSTANCE: once_cell::sync::Lazy>>> = + once_cell::sync::Lazy::new(|| Arc::new(RwLock::new(None))); + +// CEF App implementation +struct DemoApp { + object: *mut RcImpl, +} + +impl DemoApp { + fn new_app() -> App { + App::new(Self { + object: std::ptr::null_mut(), + }) + } +} + +impl WrapApp for DemoApp { + fn wrap_rc(&mut self, object: *mut RcImpl) { + self.object = object; + } +} + +impl Clone for DemoApp { + fn clone(&self) -> Self { + unsafe { + let rc_impl = &mut *self.object; + rc_impl.interface.add_ref(); + } + Self { + object: self.object, + } + } +} + +impl Rc for DemoApp { + fn as_base(&self) -> &cef_dll_sys::cef_base_ref_counted_t { + unsafe { + let base = &*self.object; + std::mem::transmute(&base.cef_object) + } + } +} + +impl ImplApp for DemoApp { + fn get_raw(&self) -> *mut cef_dll_sys::_cef_app_t { + self.object.cast() + } + + fn browser_process_handler(&self) -> Option { + Some(DemoBrowserProcessHandler::new_browser_process_handler()) + } +} + +// Browser Process Handler +struct DemoBrowserProcessHandler { + object: *mut RcImpl, +} + +impl DemoBrowserProcessHandler { + fn new_browser_process_handler() -> BrowserProcessHandler { + BrowserProcessHandler::new(Self { + object: std::ptr::null_mut(), + }) + } +} + +impl Rc for DemoBrowserProcessHandler { + 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 DemoBrowserProcessHandler { + fn wrap_rc( + &mut self, + object: *mut RcImpl, + ) { + self.object = object; + } +} + +impl Clone for DemoBrowserProcessHandler { + fn clone(&self) -> Self { + unsafe { + let rc_impl = &mut *self.object; + rc_impl.interface.add_ref(); + } + Self { + object: self.object, + } + } +} + +impl ImplBrowserProcessHandler for DemoBrowserProcessHandler { + fn get_raw(&self) -> *mut cef_dll_sys::_cef_browser_process_handler_t { + self.object.cast() + } + + fn on_context_initialized(&self) { + let _ = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { + println!("CEF context initialized"); + + *CEF_INITIALIZED.write() = true; + + let mut client = DemoClient::new_client(); + let url = CefString::from("https://www.google.com"); + + let window_info = WindowInfo { + windowless_rendering_enabled: 1, + ..Default::default() + }; + + let settings = BrowserSettings::default(); + + let _browser = browser_host_create_browser( + Some(&window_info), + Some(&mut client), + Some(&url), + Some(&settings), + Option::<&mut DictionaryValue>::None, + Option::<&mut RequestContext>::None, + ); + })); + } +} + +// Client +struct DemoClient { + object: *mut RcImpl, +} + +impl DemoClient { + fn new_client() -> Client { + Client::new(Self { + object: std::ptr::null_mut(), + }) + } +} + +impl WrapClient for DemoClient { + fn wrap_rc(&mut self, object: *mut RcImpl) { + self.object = object; + } +} + +impl Clone for DemoClient { + fn clone(&self) -> Self { + unsafe { + let rc_impl = &mut *self.object; + rc_impl.interface.add_ref(); + } + Self { + object: self.object, + } + } +} + +impl Rc for DemoClient { + fn as_base(&self) -> &cef_dll_sys::cef_base_ref_counted_t { + unsafe { + let base = &*self.object; + std::mem::transmute(&base.cef_object) + } + } +} + +impl ImplClient for DemoClient { + fn get_raw(&self) -> *mut cef_dll_sys::_cef_client_t { + self.object.cast() + } + + fn render_handler(&self) -> Option { + Some(DemoRenderHandler::new_render_handler()) + } + + fn life_span_handler(&self) -> Option { + Some(DemoLifeSpanHandler::new_life_span_handler()) + } +} + +// LifeSpan Handler to capture browser instance +struct DemoLifeSpanHandler { + object: *mut RcImpl, +} + +impl DemoLifeSpanHandler { + fn new_life_span_handler() -> LifeSpanHandler { + LifeSpanHandler::new(Self { + object: std::ptr::null_mut(), + }) + } +} + +impl WrapLifeSpanHandler for DemoLifeSpanHandler { + fn wrap_rc(&mut self, object: *mut RcImpl) { + self.object = object; + } +} + +impl Clone for DemoLifeSpanHandler { + fn clone(&self) -> Self { + unsafe { + let rc_impl = &mut *self.object; + rc_impl.interface.add_ref(); + } + Self { + object: self.object, + } + } +} + +impl Rc for DemoLifeSpanHandler { + fn as_base(&self) -> &cef_dll_sys::cef_base_ref_counted_t { + unsafe { + let base = &*self.object; + std::mem::transmute(&base.cef_object) + } + } +} + +impl ImplLifeSpanHandler for DemoLifeSpanHandler { + fn get_raw(&self) -> *mut cef_dll_sys::_cef_life_span_handler_t { + self.object.cast() + } + + fn on_after_created(&self, browser: Option<&mut Browser>) { + let _ = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { + if let Some(browser) = browser { + // Store the browser instance for later use + *BROWSER_INSTANCE.write() = Some(browser.clone()); + println!("Browser created and stored!"); + } + })); + } +} + +// Render Handler +struct DemoRenderHandler { + object: *mut RcImpl, +} + +impl DemoRenderHandler { + fn new_render_handler() -> RenderHandler { + RenderHandler::new(Self { + object: std::ptr::null_mut(), + }) + } +} + +impl WrapRenderHandler for DemoRenderHandler { + fn wrap_rc(&mut self, object: *mut RcImpl) { + self.object = object; + } +} + +impl Clone for DemoRenderHandler { + fn clone(&self) -> Self { + unsafe { + let rc_impl = &mut *self.object; + rc_impl.interface.add_ref(); + } + Self { + object: self.object, + } + } +} + +impl Rc for DemoRenderHandler { + fn as_base(&self) -> &cef_dll_sys::cef_base_ref_counted_t { + unsafe { + let base = &*self.object; + std::mem::transmute(&base.cef_object) + } + } +} + +impl ImplRenderHandler for DemoRenderHandler { + fn get_raw(&self) -> *mut cef_dll_sys::_cef_render_handler_t { + self.object.cast() + } + + fn view_rect(&self, _browser: Option<&mut Browser>, rect: Option<&mut Rect>) { + let _ = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { + if let Some(rect) = rect { + let size = TEXTURE_SIZE.read(); + rect.x = 0; + rect.y = 0; + rect.width = size.0 as i32; + rect.height = size.1 as i32; + } + })); + } + + fn on_paint( + &self, + _browser: Option<&mut Browser>, + _type_: PaintElementType, + _dirty_rects_count: usize, + _dirty_rects: Option<&Rect>, + buffer: *const u8, + width: ::std::os::raw::c_int, + height: ::std::os::raw::c_int, + ) { + let _ = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { + if buffer.is_null() || width <= 0 || height <= 0 { + return; + } + + let size = (width * height * 4) as usize; + let slice = unsafe { std::slice::from_raw_parts(buffer, size) }; + + let mut texture_buffer = TEXTURE_BUFFER.write(); + texture_buffer.clear(); + texture_buffer.extend_from_slice(slice); + + let mut texture_size = TEXTURE_SIZE.write(); + *texture_size = (width as u32, height as u32); + })); + } +} + +// egui + wgpu Application +struct EguiCefApp { + window: Option>, + egui_state: Option, + url_input: String, + frame_count: u64, + last_update: Instant, +} + +struct EguiState { + device: wgpu::Device, + queue: wgpu::Queue, + surface: wgpu::Surface<'static>, + surface_config: wgpu::SurfaceConfiguration, + egui_renderer: egui_wgpu::Renderer, + egui_state: egui_winit::State, + cef_texture: Option, +} + +impl EguiCefApp { + fn new() -> Self { + Self { + window: None, + egui_state: None, + url_input: String::from("https://www.google.com"), + frame_count: 0, + last_update: Instant::now(), + } + } +} + +impl ApplicationHandler for EguiCefApp { + fn resumed(&mut self, event_loop: &ActiveEventLoop) { + if self.window.is_some() { + return; + } + + let window_attrs = WindowAttributes::default() + .with_title("egui + CEF Browser") + .with_inner_size(winit::dpi::LogicalSize::new(1024, 768)); + + let window = Arc::new(event_loop.create_window(window_attrs).unwrap()); + + // Initialize wgpu and egui + let egui_state = pollster::block_on(async { + let instance = wgpu::Instance::new(wgpu::InstanceDescriptor { + backends: wgpu::Backends::all(), + ..Default::default() + }); + + let surface = instance.create_surface(window.clone()).unwrap(); + + let adapter = instance + .request_adapter(&wgpu::RequestAdapterOptions { + power_preference: wgpu::PowerPreference::default(), + compatible_surface: Some(&surface), + force_fallback_adapter: false, + }) + .await + .unwrap(); + + let (device, queue) = adapter + .request_device( + &wgpu::DeviceDescriptor { + label: Some("Device"), + required_features: wgpu::Features::empty(), + required_limits: wgpu::Limits::default(), + memory_hints: Default::default(), + }, + None, + ) + .await + .unwrap(); + + let surface_caps = surface.get_capabilities(&adapter); + let surface_format = surface_caps + .formats + .iter() + .find(|f| f.is_srgb()) + .copied() + .unwrap_or(surface_caps.formats[0]); + + let size = window.inner_size(); + let surface_config = wgpu::SurfaceConfiguration { + usage: wgpu::TextureUsages::RENDER_ATTACHMENT, + format: surface_format, + width: size.width, + height: size.height, + present_mode: wgpu::PresentMode::AutoVsync, + alpha_mode: surface_caps.alpha_modes[0], + view_formats: vec![], + desired_maximum_frame_latency: 2, + }; + + surface.configure(&device, &surface_config); + + let egui_ctx = egui::Context::default(); + let viewport_id = egui_ctx.viewport_id(); + let max_texture_side = Some(device.limits().max_texture_dimension_2d as usize); + let egui_state = egui_winit::State::new( + egui_ctx, + viewport_id, + &window, + Some(window.scale_factor() as f32), + Some(winit::window::Theme::Dark), + max_texture_side, + ); + + let egui_renderer = egui_wgpu::Renderer::new(&device, surface_format, None, 1, false); + + EguiState { + device, + queue, + surface, + surface_config, + egui_renderer, + egui_state, + cef_texture: None, + } + }); + + self.egui_state = Some(egui_state); + self.window = Some(window); + } + + fn window_event(&mut self, event_loop: &ActiveEventLoop, _id: WindowId, event: WindowEvent) { + let Some(window) = &self.window else { + return; + }; + let Some(state) = &mut self.egui_state else { + return; + }; + + // Let egui handle the event first + let response = state.egui_state.on_window_event(window, &event); + + if response.repaint { + window.request_redraw(); + } + + if response.consumed { + return; + } + + match event { + WindowEvent::CloseRequested => { + event_loop.exit(); + } + WindowEvent::Resized(new_size) => { + if new_size.width > 0 && new_size.height > 0 { + state.surface_config.width = new_size.width; + state.surface_config.height = new_size.height; + state.surface.configure(&state.device, &state.surface_config); + window.request_redraw(); + } + } + WindowEvent::RedrawRequested => { + self.render(); + } + _ => {} + } + } +} + +impl EguiCefApp { + fn render(&mut self) { + let Some(window) = &self.window else { + return; + }; + let Some(state) = &mut self.egui_state else { + return; + }; + + self.frame_count += 1; + + // Build egui UI + let raw_input = state.egui_state.take_egui_input(window); + let egui_output = state.egui_state.egui_ctx().run(raw_input, |ctx| { + egui::CentralPanel::default().show(ctx, |ui| { + ui.heading("🦀 egui + CEF Browser Integration"); + ui.separator(); + + ui.horizontal(|ui| { + ui.label("URL:"); + ui.text_edit_singleline(&mut self.url_input); + if ui.button("Navigate").clicked() { + // Navigate to the URL + let url = self.url_input.clone(); + if let Some(browser) = BROWSER_INSTANCE.read().as_ref() { + if let Some(main_frame) = browser.main_frame() { + let cef_url = CefString::from(url.as_str()); + main_frame.load_url(Some(&cef_url)); + } + } + } + }); + + ui.separator(); + + let cef_ready = *CEF_INITIALIZED.read(); + ui.label(format!("CEF Status: {}", if cef_ready { "✅ Ready" } else { "⏳ Initializing..." })); + ui.label(format!("Frame: {}", self.frame_count)); + ui.label(format!("FPS: {:.1}", 1.0 / self.last_update.elapsed().as_secs_f32())); + + ui.separator(); + + // Display CEF texture + let texture_buffer = TEXTURE_BUFFER.read(); + let size = TEXTURE_SIZE.read(); + + if !texture_buffer.is_empty() && size.0 > 0 && size.1 > 0 { + // Update or create wgpu texture from CEF buffer + if state.cef_texture.is_none() + || state.cef_texture.as_ref().unwrap().width() != size.0 + || state.cef_texture.as_ref().unwrap().height() != size.1 + { + state.cef_texture = Some(state.device.create_texture(&wgpu::TextureDescriptor { + label: Some("CEF Texture"), + size: wgpu::Extent3d { + width: size.0, + height: size.1, + depth_or_array_layers: 1, + }, + mip_level_count: 1, + sample_count: 1, + dimension: wgpu::TextureDimension::D2, + format: wgpu::TextureFormat::Rgba8UnormSrgb, + usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST, + view_formats: &[], + })); + } + + // Convert BGRA to RGBA + let mut rgba_buffer = Vec::with_capacity(texture_buffer.len()); + for chunk in texture_buffer.chunks_exact(4) { + rgba_buffer.push(chunk[2]); // R + rgba_buffer.push(chunk[1]); // G + rgba_buffer.push(chunk[0]); // B + rgba_buffer.push(chunk[3]); // A + } + + if let Some(texture) = &state.cef_texture { + state.queue.write_texture( + wgpu::ImageCopyTexture { + texture, + mip_level: 0, + origin: wgpu::Origin3d::ZERO, + aspect: wgpu::TextureAspect::All, + }, + &rgba_buffer, + wgpu::ImageDataLayout { + offset: 0, + bytes_per_row: Some(4 * size.0), + rows_per_image: Some(size.1), + }, + wgpu::Extent3d { + width: size.0, + height: size.1, + depth_or_array_layers: 1, + }, + ); + + // Register texture with egui + let texture_id = state.egui_renderer.register_native_texture( + &state.device, + &texture.create_view(&wgpu::TextureViewDescriptor::default()), + wgpu::FilterMode::Linear, + ); + + ui.image(egui::ImageSource::Texture(egui::load::SizedTexture::new( + texture_id, + [size.0 as f32, size.1 as f32], + ))); + } + } else { + ui.label("⏳ Waiting for CEF to render..."); + } + }); + }); + + state.egui_state.handle_platform_output(window, egui_output.platform_output); + + // Render + let output = state.surface.get_current_texture().unwrap(); + let view = output.texture.create_view(&wgpu::TextureViewDescriptor::default()); + + let mut encoder = state.device.create_command_encoder(&wgpu::CommandEncoderDescriptor { + label: Some("Render Encoder"), + }); + + let screen_descriptor = egui_wgpu::ScreenDescriptor { + size_in_pixels: [state.surface_config.width, state.surface_config.height], + pixels_per_point: window.scale_factor() as f32, + }; + + let primitives = state.egui_state.egui_ctx().tessellate(egui_output.shapes, egui_output.pixels_per_point); + + // Update textures + for (id, image_delta) in &egui_output.textures_delta.set { + state.egui_renderer.update_texture(&state.device, &state.queue, *id, image_delta); + } + + // Update buffers + state.egui_renderer.update_buffers( + &state.device, + &state.queue, + &mut encoder, + &primitives, + &screen_descriptor, + ); + + // Render + { + let render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + label: Some("Render Pass"), + color_attachments: &[Some(wgpu::RenderPassColorAttachment { + view: &view, + resolve_target: None, + ops: wgpu::Operations { + load: wgpu::LoadOp::Clear(wgpu::Color { + r: 0.1, + g: 0.1, + b: 0.1, + a: 1.0, + }), + store: wgpu::StoreOp::Store, + }, + })], + depth_stencil_attachment: None, + timestamp_writes: None, + occlusion_query_set: None, + }); + + // Render egui + state.egui_renderer.render( + &mut render_pass.forget_lifetime(), + &primitives, + &screen_descriptor, + ); + } // render_pass dropped here automatically + + for id in &egui_output.textures_delta.free { + state.egui_renderer.free_texture(id); + } + + state.queue.submit(std::iter::once(encoder.finish())); + output.present(); + + self.last_update = Instant::now(); + window.request_redraw(); + } +} + +fn main() { + env_logger::init(); + + #[cfg(target_os = "macos")] + let _loader = { + let loader = library_loader::LibraryLoader::new(&std::env::current_exe().unwrap(), false); + if !loader.load() { + eprintln!("Failed to load CEF library"); + return; + } + loader + }; + + let _ = api_hash(sys::CEF_API_VERSION_LAST, 0); + + let args = Args::new(); + let cmd = args.as_cmd_line().unwrap(); + + let switch = CefString::from("type"); + let is_browser_process = cmd.has_switch(Some(&switch)) != 1; + + let mut app = DemoApp::new_app(); + + let ret = execute_process( + Some(args.as_main_args()), + Some(&mut app), + std::ptr::null_mut(), + ); + + // Handle non-browser processes (renderer, GPU, etc.) + if !is_browser_process { + let process_type = CefString::from(&cmd.switch_value(Some(&switch))); + println!("launch process {process_type}"); + assert!(ret >= 0, "cannot execute non-browser process"); + return; + } + + // Browser process - initialize CEF with external message pump + println!("launch browser process"); + assert!(ret == -1, "cannot execute browser process"); + + let mut settings = Settings { + no_sandbox: !cfg!(feature = "sandbox") as _, + windowless_rendering_enabled: 1, + external_message_pump: 1, // Key: allows manual event loop control + ..Default::default() + }; + + let init_result = initialize( + Some(args.as_main_args()), + Some(&mut settings), + Some(&mut app), + std::ptr::null_mut(), + ); + + if init_result != 1 { + eprintln!("Failed to initialize CEF: {}", init_result); + return; + } + + println!("CEF initialized successfully with external message pump"); + + // Run event loop with manual pumping + let mut event_loop = EventLoop::new().unwrap(); + event_loop.set_control_flow(ControlFlow::Poll); + + let mut egui_app = EguiCefApp::new(); + + loop { + // Pump CEF messages + do_message_loop_work(); + + // Pump winit events + let status = event_loop.pump_app_events(Some(Duration::ZERO), &mut egui_app); + + if let PumpStatus::Exit(code) = status { + println!("Exiting with code: {}", code); + break; + } + + // Small sleep to prevent busy-waiting + std::thread::sleep(Duration::from_millis(1)); + } + + shutdown(); +} diff --git a/browser/app/src/win/app.exe.manifest b/browser/app/src/win/app.exe.manifest new file mode 100644 index 000000000..bc54019f4 --- /dev/null +++ b/browser/app/src/win/app.exe.manifest @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/browser/cef/CHANGELOG.md b/browser/cef/CHANGELOG.md new file mode 100644 index 000000000..ccfabd916 --- /dev/null +++ b/browser/cef/CHANGELOG.md @@ -0,0 +1,215 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +## [140.3.1+140.1.14](https://github.com/tauri-apps/cef-rs/compare/cef-v140.3.0+140.1.14...cef-v140.3.1+140.1.14) - 2025-10-03 + +### Fixed + +- copy wrapped out-params back to pointers ([#224](https://github.com/tauri-apps/cef-rs/pull/224)) + +### Other + +- update bindings +- *(test)* test out-params + +## [140.2.0+140.1.14](https://github.com/tauri-apps/cef-rs/compare/cef-v140.1.0+140.1.13...cef-v140.2.0+140.1.14) - 2025-09-21 + +### Other + +- *(release)* update CEF version to 140.1.14 + +## [140.1.0+140.1.13](https://github.com/tauri-apps/cef-rs/compare/cef-v140.0.0+140.1.13...cef-v140.1.0+140.1.13) - 2025-09-19 + +### Other + +- release + +## [140.0.0+140.1.13](https://github.com/tauri-apps/cef-rs/compare/cef-v139.8.0+139.0.40...cef-v140.0.0+140.1.13) - 2025-09-19 + +### Other + +- update bindings +- *(release)* update CEF version to 140.1.13 + +## [139.8.0+139.0.40](https://github.com/tauri-apps/cef-rs/compare/cef-v139.7.2+139.0.38...cef-v139.8.0+139.0.40) - 2025-09-12 + +### Other + +- *(release)* update CEF version to 139.0.40 + +## [139.7.2+139.0.38](https://github.com/tauri-apps/cef-rs/compare/cef-v139.7.1+139.0.38...cef-v139.7.2+139.0.38) - 2025-09-08 + +### Fixed + +- cleanup logic for copying back out-params +- handle out-params ([#173](https://github.com/tauri-apps/cef-rs/issues/173)) + +### Other + +- release v139.7.2+139.0.38 +- update bindings + +## [139.7.1+139.0.38](https://github.com/tauri-apps/cef-rs/compare/cef-v139.7.0+139.0.38...cef-v139.7.1+139.0.38) - 2025-09-07 + +### Other + +- release v139.7.1+139.0.38 +- *(deps)* update rust crate windows-sys to 0.61 + +## [139.7.0+139.0.38](https://github.com/tauri-apps/cef-rs/compare/cef-v139.6.0+139.0.37...cef-v139.7.0+139.0.38) - 2025-08-31 + +### Other + +- *(release)* update CEF version to 139.0.38 + +## [139.6.0+139.0.37](https://github.com/tauri-apps/cef-rs/compare/cef-v139.5.0+139.0.30...cef-v139.6.0+139.0.37) - 2025-08-29 + +### Other + +- *(release)* update CEF version to 139.0.37 + +## [139.5.0+139.0.30](https://github.com/tauri-apps/cef-rs/compare/cef-v139.4.0+139.0.28...cef-v139.5.0+139.0.30) - 2025-08-28 + +### Other + +- *(release)* update CEF version to 139.0.30 + +## [139.4.0+139.0.28](https://github.com/tauri-apps/cef-rs/compare/cef-v139.3.0+139.0.26...cef-v139.4.0+139.0.28) - 2025-08-23 + +### Other + +- *(release)* update CEF version to 139.0.28 + +## [139.3.0+139.0.26](https://github.com/tauri-apps/cef-rs/compare/cef-v139.2.1+139.0.23...cef-v139.3.0+139.0.26) - 2025-08-22 + +### Other + +- *(release)* update CEF version to 139.0.26 + +## [139.2.1+139.0.23](https://github.com/tauri-apps/cef-rs/compare/cef-v139.2.0+139.0.23...cef-v139.2.1+139.0.23) - 2025-08-16 + +### Fixed + +- warnings about usize < 0 comparisons + +### Other + +- release + +## [139.2.0+139.0.23](https://github.com/tauri-apps/cef-rs/compare/cef-v139.1.0+139.0.20...cef-v139.2.0+139.0.23) - 2025-08-16 + +### Other + +- *(release)* update CEF version to 139.0.23 + +## [139.1.0+139.0.20](https://github.com/tauri-apps/cef-rs/compare/cef-v139.0.1+139.0.17...cef-v139.1.0+139.0.20) - 2025-08-15 + +### Other + +- *(release)* update CEF version to 139.0.20 + +## [139.0.1+139.0.17](https://github.com/tauri-apps/cef-rs/compare/cef-v139.0.0+139.0.17...cef-v139.0.1+139.0.17) - 2025-08-08 + +### Other + +- release v139.0.1+139.0.17 + +## [139.0.0+139.0.17](https://github.com/tauri-apps/cef-rs/compare/cef-v138.9.0+138.0.36...cef-v139.0.0+139.0.17) - 2025-08-08 + +### Other + +- *(release)* update CEF version to 139.0.17 + +## [138.9.0+138.0.36](https://github.com/tauri-apps/cef-rs/compare/cef-v138.8.0+138.0.34...cef-v138.9.0+138.0.36) - 2025-08-07 + +### Other + +- *(release)* update CEF version to 138.0.36 + +## [138.8.0+138.0.34](https://github.com/tauri-apps/cef-rs/compare/cef-v138.7.1+138.0.33...cef-v138.8.0+138.0.34) - 2025-08-02 + +### Fixed + +- remove cef version from example dependencies + +### Other + +- *(release)* update CEF version to 138.0.34 + +## [138.7.1+138.0.33](https://github.com/tauri-apps/cef-rs/compare/cef-v138.7.0+138.0.33...cef-v138.7.1+138.0.33) - 2025-07-29 + +### Other + +- release v138.7.1+138.0.33 +- move examples into separate crates + +## [138.7.0+138.0.33](https://github.com/tauri-apps/cef-rs/compare/cef-v138.6.1+138.0.27...cef-v138.7.0+138.0.33) - 2025-07-29 + +### Other + +- *(release)* update CEF version to 138.0.33 + +## [138.6.1+138.0.27](https://github.com/tauri-apps/cef-rs/compare/cef-v138.6.0+138.0.27...cef-v138.6.1+138.0.27) - 2025-07-28 + +### Fixed + +- embed git-cliff as a library in get-latest + +### Other + +- *(release)* bump version for get-latest updates + +## [138.6.0+138.0.27](https://github.com/tauri-apps/cef-rs/compare/cef-v138.5.1+138.0.26...cef-v138.6.0+138.0.27) - 2025-07-28 + +### Added + +- update CEF version to 138.0.27 + +### Fixed + +- bump version for release + +## [138.5.1+138.0.26](https://github.com/tauri-apps/cef-rs/compare/cef-v138.5.0+138.0.26...cef-v138.5.1+138.0.26) - 2025-07-22 + +### Other + +- release +- *(doc)* regenerate CHANGELOG.md + +## [138.5.0+138.0.26](https://github.com/tauri-apps/cef-rs/compare/cef-v138.4.0+138.0.25...cef-v138.5.0+138.0.26) - 2025-07-19 + +### Other + +- update CEF version + +## [138.4.0+138.0.25](https://github.com/tauri-apps/cef-rs/compare/cef-v138.3.0+138.0.23...cef-v138.4.0+138.0.25) - 2025-07-18 + +### Other + +- update CEF version + +## [138.3.0+138.0.23](https://github.com/tauri-apps/cef-rs/compare/cef-v138.2.2+138.0.21...cef-v138.3.0+138.0.23) - 2025-07-17 + +### Other + +- update CEF version + +## [138.2.2+138.0.21](https://github.com/tauri-apps/cef-rs/compare/cef-v138.2.1+138.0.21...cef-v138.2.2+138.0.21) - 2025-07-14 + +### Other + +- release +- seed CHANGELOG.md files + +## [138.2.1+138.0.21](https://github.com/tauri-apps/cef-rs/compare/cef-v138.2.0+138.0.21...cef-v138.2.1+138.0.21) - 2025-07-14 + +### Fixed + +- bump major version of download-cef [#145](https://github.com/tauri-apps/cef-rs/issues/145) + diff --git a/browser/cef/Cargo.toml b/browser/cef/Cargo.toml new file mode 100644 index 000000000..a872941de --- /dev/null +++ b/browser/cef/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "cef" +description = "Use cef in Rust" + +version.workspace = true +edition.workspace = true +license.workspace = true +authors.workspace = true +repository.workspace = true + +[features] +default = ["sandbox"] +dox = ["cef-dll-sys/dox"] +sandbox = ["cef-dll-sys/sandbox"] + +[package.metadata.docs.rs] +features = ["dox"] + +[dependencies] +cef-dll-sys.workspace = true + +[target.'cfg(target_os = "windows")'.dependencies] +windows-sys.workspace = true + +[target.'cfg(target_os = "macos")'.dependencies] +libloading = "0.8" diff --git a/browser/cef/README.md b/browser/cef/README.md new file mode 100644 index 000000000..2c6e4dad1 --- /dev/null +++ b/browser/cef/README.md @@ -0,0 +1,3 @@ +# cef + +Use the [Chromium Embedded Framework](https://github.com/chromiumembedded/cef) in Rust. diff --git a/browser/cef/src/args.rs b/browser/cef/src/args.rs new file mode 100644 index 000000000..4f8bf613d --- /dev/null +++ b/browser/cef/src/args.rs @@ -0,0 +1,83 @@ +#[cfg(not(target_os = "windows"))] +use std::ffi::{c_char, CString}; + +use crate::*; + +#[derive(Clone, Default)] +pub struct Args { + #[cfg(not(target_os = "windows"))] + _source: Vec, + #[cfg(not(target_os = "windows"))] + _argv: Vec<*const c_char>, + main_args: MainArgs, +} + +impl Args { + #[cfg(target_os = "windows")] + pub fn new() -> Self { + let main_args = MainArgs { + instance: cef_dll_sys::HINSTANCE( + unsafe { + windows_sys::Win32::System::LibraryLoader::GetModuleHandleW(std::ptr::null()) + } + .cast(), + ), + }; + + Self { main_args } + } + + #[cfg(not(target_os = "windows"))] + pub fn new() -> Self { + let args = std::env::args(); + let _source = args + .into_iter() + .map(|arg| CString::new(arg).unwrap()) + .collect::>(); + let _argv = _source + .iter() + .map(|arg| arg.as_ptr()) + .collect::>(); + let main_args = MainArgs { + argc: _argv.len() as i32, + argv: _argv.as_ptr() as *mut *mut _, + }; + + Self { + _source, + _argv, + main_args, + } + } + + pub fn as_main_args(&self) -> &MainArgs { + &self.main_args + } + + #[cfg(any(target_os = "macos", target_os = "linux"))] + pub fn as_cmd_line(&self) -> Option { + let Some(cmd_line) = command_line_create() else { + return None; + }; + cmd_line.init_from_argv(self.as_main_args().argc, self.as_main_args().argv.cast()); + Some(cmd_line) + } + + #[cfg(target_os = "windows")] + pub fn as_cmd_line(&self) -> Option { + let cmd_line = command_line_create().and_then(|cmd_line| { + unsafe { + std::ffi::CStr::from_ptr( + windows_sys::Win32::System::Environment::GetCommandLineA().cast(), + ) + } + .to_str() + .ok() + .map(|args| { + cmd_line.init_from_string(Some(&CefString::from(args))); + cmd_line + }) + }); + cmd_line + } +} diff --git a/browser/cef/src/bindings/aarch64_apple_darwin.rs b/browser/cef/src/bindings/aarch64_apple_darwin.rs new file mode 100644 index 000000000..0347c398f --- /dev/null +++ b/browser/cef/src/bindings/aarch64_apple_darwin.rs @@ -0,0 +1,48028 @@ +#![allow( + dead_code, + improper_ctypes_definitions, + non_camel_case_types, + unused_variables, + clippy::not_unsafe_ptr_arg_deref, + clippy::too_many_arguments +)] +use crate::rc::{ConvertParam, ConvertReturnValue, Rc, RcImpl, RefGuard, WrapParamRef}; +use cef_dll_sys::*; + +/// Perform the conversion between CEF and Rust types in field initializers. +fn init_array_field(mut value: [U; N]) -> [T; N] +where + T: Sized, + U: Sized + Into, +{ + std::array::from_fn(move |i| { + let mut elem = unsafe { std::mem::zeroed() }; + std::mem::swap(&mut value[i], &mut elem); + elem.into() + }) +} + +/// See [`cef_string_wide_t`] for more documentation. +pub use crate::string::CefStringUserfreeWide; + +/// See [`cef_string_utf8_t`] for more documentation. +pub use crate::string::CefStringUserfreeUtf8; + +/// See [`cef_string_utf16_t`] for more documentation. +pub use crate::string::CefStringUserfreeUtf16; + +/// See [`char16_t`] for more documentation. +pub type Char = char16_t; + +/// See [`cef_string_userfree_utf16_t`] for more documentation. +pub type CefStringUserfree = CefStringUserfreeUtf16; + +/// See [`cef_string_utf16_t`] for more documentation. +pub type CefString = CefStringUtf16; + +/// See [`u32`] for more documentation. +pub type Color = u32; + +/// See [`_cef_string_wide_t`] for more documentation. +pub use crate::string::CefStringWide; + +/// See [`_cef_string_utf8_t`] for more documentation. +pub use crate::string::CefStringUtf8; + +/// See [`_cef_string_utf16_t`] for more documentation. +pub use crate::string::CefStringUtf16; + +/// See [`_cef_string_list_t`] for more documentation. +pub use crate::string::CefStringList; + +/// See [`_cef_string_map_t`] for more documentation. +pub use crate::string::CefStringMap; + +/// See [`_cef_string_multimap_t`] for more documentation. +pub use crate::string::CefStringMultimap; + +/// See [`_cef_basetime_t`] for more documentation. +#[derive(Clone)] +pub struct Basetime { + pub val: i64, +} +impl From<_cef_basetime_t> for Basetime { + fn from(value: _cef_basetime_t) -> Self { + Self { val: value.val } + } +} +impl From for _cef_basetime_t { + fn from(value: Basetime) -> Self { + Self { val: value.val } + } +} +impl Default for Basetime { + fn default() -> Self { + unsafe { std::mem::zeroed() } + } +} + +/// See [`_cef_time_t`] for more documentation. +#[derive(Clone)] +pub struct Time { + pub year: ::std::os::raw::c_int, + pub month: ::std::os::raw::c_int, + pub day_of_week: ::std::os::raw::c_int, + pub day_of_month: ::std::os::raw::c_int, + pub hour: ::std::os::raw::c_int, + pub minute: ::std::os::raw::c_int, + pub second: ::std::os::raw::c_int, + pub millisecond: ::std::os::raw::c_int, +} +impl From<_cef_time_t> for Time { + fn from(value: _cef_time_t) -> Self { + Self { + year: value.year, + month: value.month, + day_of_week: value.day_of_week, + day_of_month: value.day_of_month, + hour: value.hour, + minute: value.minute, + second: value.second, + millisecond: value.millisecond, + } + } +} +impl From