feat: egui + cef + osr
This commit is contained in:
parent
01050f3103
commit
8a5d7a0906
46
browser/Cargo.toml
Normal file
46
browser/Cargo.toml
Normal file
@ -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"]
|
||||
74
browser/README.md
Normal file
74
browser/README.md
Normal file
@ -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
|
||||
```
|
||||
|
||||
38
browser/app/Cargo.toml
Normal file
38
browser/app/Cargo.toml
Normal file
@ -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
|
||||
182
browser/app/src/mac/bundle_app.rs
Normal file
182
browser/app/src/mac/bundle_app.rs
Normal file
@ -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<String, String>,
|
||||
#[serde(rename = "LSFileQuarantineEnabled")]
|
||||
ls_file_quarantine_enabled: bool,
|
||||
#[serde(rename = "LSMinimumSystemVersion")]
|
||||
ls_minimum_system_version: String,
|
||||
#[serde(rename = "LSUIElement")]
|
||||
ls_ui_element: Option<String>,
|
||||
#[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<dyn std::error::Error>> {
|
||||
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<dyn std::error::Error>> {
|
||||
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<dyn std::error::Error>> {
|
||||
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<dyn std::error::Error>> {
|
||||
mac::main()
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
fn main() {}
|
||||
25
browser/app/src/mac/helper.rs
Normal file
25
browser/app/src/mac/helper.rs
Normal file
@ -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(),
|
||||
);
|
||||
}
|
||||
782
browser/app/src/main.rs
Normal file
782
browser/app/src/main.rs
Normal file
@ -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<Arc<RwLock<Vec<u8>>>> =
|
||||
once_cell::sync::Lazy::new(|| Arc::new(RwLock::new(Vec::new())));
|
||||
static TEXTURE_SIZE: once_cell::sync::Lazy<Arc<RwLock<(u32, u32)>>> =
|
||||
once_cell::sync::Lazy::new(|| Arc::new(RwLock::new((1024, 768))));
|
||||
static CEF_INITIALIZED: once_cell::sync::Lazy<Arc<RwLock<bool>>> =
|
||||
once_cell::sync::Lazy::new(|| Arc::new(RwLock::new(false)));
|
||||
static BROWSER_INSTANCE: once_cell::sync::Lazy<Arc<RwLock<Option<Browser>>>> =
|
||||
once_cell::sync::Lazy::new(|| Arc::new(RwLock::new(None)));
|
||||
|
||||
// CEF App implementation
|
||||
struct DemoApp {
|
||||
object: *mut RcImpl<cef_dll_sys::_cef_app_t, Self>,
|
||||
}
|
||||
|
||||
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<cef_dll_sys::_cef_app_t, Self>) {
|
||||
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<BrowserProcessHandler> {
|
||||
Some(DemoBrowserProcessHandler::new_browser_process_handler())
|
||||
}
|
||||
}
|
||||
|
||||
// Browser Process Handler
|
||||
struct DemoBrowserProcessHandler {
|
||||
object: *mut RcImpl<cef_dll_sys::cef_browser_process_handler_t, Self>,
|
||||
}
|
||||
|
||||
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<cef_dll_sys::_cef_browser_process_handler_t, Self>,
|
||||
) {
|
||||
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<cef_dll_sys::_cef_client_t, Self>,
|
||||
}
|
||||
|
||||
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<cef_dll_sys::_cef_client_t, Self>) {
|
||||
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<RenderHandler> {
|
||||
Some(DemoRenderHandler::new_render_handler())
|
||||
}
|
||||
|
||||
fn life_span_handler(&self) -> Option<LifeSpanHandler> {
|
||||
Some(DemoLifeSpanHandler::new_life_span_handler())
|
||||
}
|
||||
}
|
||||
|
||||
// LifeSpan Handler to capture browser instance
|
||||
struct DemoLifeSpanHandler {
|
||||
object: *mut RcImpl<cef_dll_sys::_cef_life_span_handler_t, Self>,
|
||||
}
|
||||
|
||||
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<cef_dll_sys::_cef_life_span_handler_t, Self>) {
|
||||
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<cef_dll_sys::_cef_render_handler_t, Self>,
|
||||
}
|
||||
|
||||
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<cef_dll_sys::_cef_render_handler_t, Self>) {
|
||||
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<Arc<Window>>,
|
||||
egui_state: Option<EguiState>,
|
||||
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<wgpu::Texture>,
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
40
browser/app/src/win/app.exe.manifest
Normal file
40
browser/app/src/win/app.exe.manifest
Normal file
@ -0,0 +1,40 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
|
||||
|
||||
<!-- https://cef-builds.spotifycdn.com/cef_binary_132.3.2+g4997b2f+chromium-132.0.6834.161_windows64.tar.bz2 -->
|
||||
<!-- This compatibility section is from the standar pre - built binary package, specifically from %CEF_ROOT%/tests/app/win/compatibility.manifest -->
|
||||
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
|
||||
<application>
|
||||
<!--The ID below indicates application support for Windows Vista -->
|
||||
<supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}"/>
|
||||
<!--The ID below indicates application support for Windows 7 -->
|
||||
<supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/>
|
||||
<!--The ID below indicates application support for Windows 8 -->
|
||||
<supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/>
|
||||
<!--The ID below indicates application support for Windows 8.1 -->
|
||||
<supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/>
|
||||
<!--The ID below indicates application support for Windows 10 -->
|
||||
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>
|
||||
<!-- This tag is required for XAML islands usage in the process for media scenarios. -->
|
||||
<!-- This version corresponds to the Windows 10 May 2019 Update. -->
|
||||
<maxversiontested Id="10.0.18362.0"/>
|
||||
</application>
|
||||
</compatibility>
|
||||
|
||||
<!--The compatibility section will be merged from build/win/compatibility.manifest -->
|
||||
|
||||
<dependency>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity type="Win32" name="Microsoft.Windows.Common-Controls" version="6.0.0.0" processorArchitecture="*" publicKeyToken="6595b64144ccf1df" language="*"></assemblyIdentity>
|
||||
</dependentAssembly>
|
||||
</dependency>
|
||||
|
||||
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||
<security>
|
||||
<requestedPrivileges>
|
||||
<requestedExecutionLevel level="asInvoker" />
|
||||
</requestedPrivileges>
|
||||
</security>
|
||||
</trustInfo>
|
||||
|
||||
</assembly>
|
||||
215
browser/cef/CHANGELOG.md
Normal file
215
browser/cef/CHANGELOG.md
Normal file
@ -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)
|
||||
|
||||
26
browser/cef/Cargo.toml
Normal file
26
browser/cef/Cargo.toml
Normal file
@ -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"
|
||||
3
browser/cef/README.md
Normal file
3
browser/cef/README.md
Normal file
@ -0,0 +1,3 @@
|
||||
# cef
|
||||
|
||||
Use the [Chromium Embedded Framework](https://github.com/chromiumembedded/cef) in Rust.
|
||||
83
browser/cef/src/args.rs
Normal file
83
browser/cef/src/args.rs
Normal file
@ -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<CString>,
|
||||
#[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::<Vec<CString>>();
|
||||
let _argv = _source
|
||||
.iter()
|
||||
.map(|arg| arg.as_ptr())
|
||||
.collect::<Vec<*const c_char>>();
|
||||
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<CommandLine> {
|
||||
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<CommandLine> {
|
||||
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
|
||||
}
|
||||
}
|
||||
48028
browser/cef/src/bindings/aarch64_apple_darwin.rs
Normal file
48028
browser/cef/src/bindings/aarch64_apple_darwin.rs
Normal file
File diff suppressed because it is too large
Load Diff
48016
browser/cef/src/bindings/aarch64_pc_windows_msvc.rs
Normal file
48016
browser/cef/src/bindings/aarch64_pc_windows_msvc.rs
Normal file
File diff suppressed because it is too large
Load Diff
48062
browser/cef/src/bindings/aarch64_unknown_linux_gnu.rs
Normal file
48062
browser/cef/src/bindings/aarch64_unknown_linux_gnu.rs
Normal file
File diff suppressed because it is too large
Load Diff
48062
browser/cef/src/bindings/arm_unknown_linux_gnueabi.rs
Normal file
48062
browser/cef/src/bindings/arm_unknown_linux_gnueabi.rs
Normal file
File diff suppressed because it is too large
Load Diff
18389
browser/cef/src/bindings/i686_pc_windows_msvc.rs
Normal file
18389
browser/cef/src/bindings/i686_pc_windows_msvc.rs
Normal file
File diff suppressed because it is too large
Load Diff
177
browser/cef/src/bindings/mod.rs
Normal file
177
browser/cef/src/bindings/mod.rs
Normal file
@ -0,0 +1,177 @@
|
||||
#[cfg(all(target_os = "linux", target_arch = "x86_64"))]
|
||||
mod x86_64_unknown_linux_gnu;
|
||||
#[cfg(all(target_os = "linux", target_arch = "x86_64"))]
|
||||
pub use x86_64_unknown_linux_gnu::*;
|
||||
|
||||
#[cfg(all(target_os = "linux", target_arch = "aarch64"))]
|
||||
mod aarch64_unknown_linux_gnu;
|
||||
#[cfg(all(target_os = "linux", target_arch = "aarch64"))]
|
||||
pub use aarch64_unknown_linux_gnu::*;
|
||||
|
||||
#[cfg(all(target_os = "linux", target_arch = "arm"))]
|
||||
mod arm_unknown_linux_gnueabi;
|
||||
#[cfg(all(target_os = "linux", target_arch = "arm"))]
|
||||
pub use arm_unknown_linux_gnueabi::*;
|
||||
|
||||
#[cfg(all(target_os = "windows", target_arch = "x86_64"))]
|
||||
mod x86_64_pc_windows_msvc;
|
||||
#[cfg(all(target_os = "windows", target_arch = "x86_64"))]
|
||||
pub use x86_64_pc_windows_msvc::*;
|
||||
|
||||
#[cfg(all(target_os = "windows", target_arch = "x86"))]
|
||||
mod i686_pc_windows_msvc;
|
||||
#[cfg(all(target_os = "windows", target_arch = "x86"))]
|
||||
pub use i686_pc_windows_msvc::*;
|
||||
|
||||
#[cfg(all(target_os = "windows", target_arch = "aarch64"))]
|
||||
mod aarch64_pc_windows_msvc;
|
||||
#[cfg(all(target_os = "windows", target_arch = "aarch64"))]
|
||||
pub use aarch64_pc_windows_msvc::*;
|
||||
|
||||
#[cfg(all(target_os = "macos", target_arch = "x86_64"))]
|
||||
mod x86_64_apple_darwin;
|
||||
#[cfg(all(target_os = "macos", target_arch = "x86_64"))]
|
||||
pub use x86_64_apple_darwin::*;
|
||||
|
||||
#[cfg(all(target_os = "macos", target_arch = "aarch64"))]
|
||||
mod aarch64_apple_darwin;
|
||||
#[cfg(all(target_os = "macos", target_arch = "aarch64"))]
|
||||
pub use aarch64_apple_darwin::*;
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use crate::{rc::*, sys};
|
||||
use std::{cell::RefCell, ptr};
|
||||
|
||||
#[derive(Default)]
|
||||
struct CallInfo {
|
||||
extra_info: RefCell<Option<DictionaryValue>>,
|
||||
}
|
||||
|
||||
struct TestLifeSpanHandler {
|
||||
object: *mut RcImpl<sys::_cef_life_span_handler_t, Self>,
|
||||
call_info: std::rc::Rc<CallInfo>,
|
||||
}
|
||||
|
||||
impl ImplLifeSpanHandler for TestLifeSpanHandler {
|
||||
fn get_raw(&self) -> *mut sys::_cef_life_span_handler_t {
|
||||
self.object.cast()
|
||||
}
|
||||
|
||||
fn on_before_popup(
|
||||
&self,
|
||||
_browser: Option<&mut Browser>,
|
||||
_frame: Option<&mut Frame>,
|
||||
_popup_id: ::std::os::raw::c_int,
|
||||
_target_url: Option<&CefString>,
|
||||
_target_frame_name: Option<&CefString>,
|
||||
_target_disposition: WindowOpenDisposition,
|
||||
_user_gesture: ::std::os::raw::c_int,
|
||||
_popup_features: Option<&PopupFeatures>,
|
||||
_window_info: Option<&mut WindowInfo>,
|
||||
_client: Option<&mut Option<Client>>,
|
||||
_settings: Option<&mut BrowserSettings>,
|
||||
extra_info: Option<&mut Option<DictionaryValue>>,
|
||||
_no_javascript_access: Option<&mut ::std::os::raw::c_int>,
|
||||
) -> ::std::os::raw::c_int {
|
||||
let extra_info = extra_info.expect("extra_info is required");
|
||||
*extra_info = self.call_info.extra_info.take();
|
||||
1
|
||||
}
|
||||
}
|
||||
|
||||
impl WrapLifeSpanHandler for TestLifeSpanHandler {
|
||||
fn wrap_rc(&mut self, object: *mut RcImpl<sys::_cef_life_span_handler_t, Self>) {
|
||||
self.object = object;
|
||||
}
|
||||
}
|
||||
|
||||
impl TestLifeSpanHandler {
|
||||
fn new(call_info: std::rc::Rc<CallInfo>) -> LifeSpanHandler {
|
||||
LifeSpanHandler::new(Self {
|
||||
object: std::ptr::null_mut(),
|
||||
call_info,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for TestLifeSpanHandler {
|
||||
fn clone(&self) -> Self {
|
||||
let object = unsafe {
|
||||
let rc_impl = &mut *self.object;
|
||||
rc_impl.interface.add_ref();
|
||||
self.object
|
||||
};
|
||||
|
||||
Self {
|
||||
object,
|
||||
call_info: self.call_info.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Rc for TestLifeSpanHandler {
|
||||
fn as_base(&self) -> &sys::cef_base_ref_counted_t {
|
||||
unsafe {
|
||||
let base = &*self.object;
|
||||
std::mem::transmute(&base.cef_object)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dictionary_value_out_param() {
|
||||
#[cfg(target_os = "macos")]
|
||||
unsafe {
|
||||
use std::{ffi::CString, os::unix::ffi::OsStrExt};
|
||||
|
||||
let cef_dir = sys::get_cef_dir().expect("CEF not found");
|
||||
let framework_dir = cef_dir
|
||||
.join(sys::FRAMEWORK_PATH)
|
||||
.canonicalize()
|
||||
.expect("failed to get framework path");
|
||||
let framework_dir =
|
||||
CString::new(framework_dir.as_os_str().as_bytes()).expect("invalid path");
|
||||
|
||||
assert_eq!(sys::cef_load_library(framework_dir.as_ptr().cast()), 1);
|
||||
}
|
||||
|
||||
assert_eq!(initialize(None, None, None, ptr::null_mut()), 0);
|
||||
|
||||
let _ = api_hash(sys::CEF_API_VERSION_LAST, 0);
|
||||
|
||||
let call_info = std::rc::Rc::new(CallInfo::default());
|
||||
let extra_info = dictionary_value_create().expect("failed to create dictionary");
|
||||
let test_key = CefString::from("testKey");
|
||||
let test_value = CefString::from("testValue");
|
||||
extra_info.set_string(Some(&test_key), Some(&test_value));
|
||||
*call_info.extra_info.borrow_mut() = Some(extra_info);
|
||||
let mut extra_info = None;
|
||||
|
||||
let handler = TestLifeSpanHandler::new(call_info);
|
||||
assert_eq!(
|
||||
1,
|
||||
handler.on_before_popup(
|
||||
None,
|
||||
None,
|
||||
1,
|
||||
None,
|
||||
None,
|
||||
sys::cef_window_open_disposition_t::CEF_WOD_CURRENT_TAB.into(),
|
||||
0,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
Some(&mut extra_info),
|
||||
None,
|
||||
)
|
||||
);
|
||||
let extra_info = extra_info.as_ref().unwrap();
|
||||
assert_eq!(
|
||||
"testValue",
|
||||
CefString::from(&extra_info.string(Some(&test_key))).to_string()
|
||||
);
|
||||
}
|
||||
}
|
||||
48028
browser/cef/src/bindings/x86_64_apple_darwin.rs
Normal file
48028
browser/cef/src/bindings/x86_64_apple_darwin.rs
Normal file
File diff suppressed because it is too large
Load Diff
48016
browser/cef/src/bindings/x86_64_pc_windows_msvc.rs
Normal file
48016
browser/cef/src/bindings/x86_64_pc_windows_msvc.rs
Normal file
File diff suppressed because it is too large
Load Diff
48062
browser/cef/src/bindings/x86_64_unknown_linux_gnu.rs
Normal file
48062
browser/cef/src/bindings/x86_64_unknown_linux_gnu.rs
Normal file
File diff suppressed because it is too large
Load Diff
17
browser/cef/src/lib.rs
Normal file
17
browser/cef/src/lib.rs
Normal file
@ -0,0 +1,17 @@
|
||||
#![doc = include_str!("../README.md")]
|
||||
|
||||
pub mod args;
|
||||
pub mod rc;
|
||||
pub mod string;
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
pub mod library_loader;
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
pub mod sandbox;
|
||||
|
||||
#[rustfmt::skip]
|
||||
mod bindings;
|
||||
pub use bindings::*;
|
||||
|
||||
pub use cef_dll_sys as sys;
|
||||
46
browser/cef/src/library_loader.rs
Normal file
46
browser/cef/src/library_loader.rs
Normal file
@ -0,0 +1,46 @@
|
||||
use crate::{load_library, unload_library};
|
||||
|
||||
pub struct LibraryLoader {
|
||||
path: std::path::PathBuf,
|
||||
}
|
||||
|
||||
impl LibraryLoader {
|
||||
const FRAMEWORK_PATH: &str =
|
||||
"Chromium Embedded Framework.framework/Chromium Embedded Framework";
|
||||
|
||||
pub fn new(path: &std::path::Path, helper: bool) -> Self {
|
||||
let resolver = if helper {
|
||||
"../../../.."
|
||||
} else {
|
||||
"../../Frameworks"
|
||||
};
|
||||
let path = path
|
||||
.join(resolver)
|
||||
.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 LibraryLoader {
|
||||
fn drop(&mut self) {
|
||||
if unload_library() != 1 {
|
||||
eprintln!("cannot unload framework {}", self.path.display());
|
||||
}
|
||||
}
|
||||
}
|
||||
380
browser/cef/src/rc.rs
Normal file
380
browser/cef/src/rc.rs
Normal file
@ -0,0 +1,380 @@
|
||||
//! Reference counted module
|
||||
//!
|
||||
//! Many cef types are reference counted, this module is the building block to create them. Users
|
||||
//! typically don't need to uses these types, the `update-bindings` tool generates all the code
|
||||
//! which should ever call them.
|
||||
|
||||
use std::{
|
||||
fmt::Debug,
|
||||
mem,
|
||||
ops::Deref,
|
||||
ptr::{self, NonNull},
|
||||
sync::atomic::{fence, AtomicUsize, Ordering},
|
||||
};
|
||||
|
||||
use cef_dll_sys::cef_base_ref_counted_t;
|
||||
|
||||
/// Reference counted trait for types has [`cef_base_ref_counted_t`].
|
||||
pub trait Rc {
|
||||
/// Increase the reference count by 1.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// Calling this method when you need to manually handle the reference count.
|
||||
/// Otherwise, these methods shouldn't be called externally in most cases.
|
||||
unsafe fn add_ref(&self) {
|
||||
self.as_base().add_ref();
|
||||
}
|
||||
|
||||
/// Decrease reference count by 1 and release the value if the count meets 0.
|
||||
/// Reuturn `True` if it is released.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// Calling this method when you need to manually handle the reference count.
|
||||
/// Otherwise, these methods shouldn't be called externally in most cases.
|
||||
unsafe fn release(&self) -> bool {
|
||||
self.as_base().release()
|
||||
}
|
||||
|
||||
/// `True` if the reference count is exactly 1.
|
||||
fn has_one_ref(&self) -> bool {
|
||||
self.as_base().has_one_ref()
|
||||
}
|
||||
|
||||
/// `True` if the reference count is larger than 0.
|
||||
fn has_at_least_one_ref(&self) -> bool {
|
||||
self.as_base().has_at_least_one_ref()
|
||||
}
|
||||
|
||||
/// Get the reference of [cef_base_ref_counted_t].
|
||||
fn as_base(&self) -> &cef_base_ref_counted_t;
|
||||
}
|
||||
|
||||
impl Rc for cef_base_ref_counted_t {
|
||||
unsafe fn add_ref(&self) {
|
||||
if let Some(add_ref) = self.add_ref {
|
||||
add_ref(ptr::from_ref(self) as *mut _);
|
||||
}
|
||||
}
|
||||
|
||||
fn has_one_ref(&self) -> bool {
|
||||
if let Some(has_one_ref) = self.has_one_ref {
|
||||
let result = unsafe { has_one_ref(ptr::from_ref(self) as *mut _) };
|
||||
return result == 1;
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
fn has_at_least_one_ref(&self) -> bool {
|
||||
if let Some(has_at_least_one_ref) = self.has_at_least_one_ref {
|
||||
let result = unsafe { has_at_least_one_ref(ptr::from_ref(self) as *mut _) };
|
||||
return result == 1;
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
unsafe fn release(&self) -> bool {
|
||||
if let Some(release) = self.release {
|
||||
return release(ptr::from_ref(self) as *mut _) == 1;
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
fn as_base(&self) -> &Self {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
pub trait ConvertParam<T: Sized> {
|
||||
fn into_raw(self) -> T;
|
||||
}
|
||||
|
||||
impl<T, U> ConvertParam<U> for T
|
||||
where
|
||||
T: Sized + Into<U>,
|
||||
U: Sized,
|
||||
{
|
||||
fn into_raw(self) -> U {
|
||||
self.into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> ConvertParam<*mut T> for &RefGuard<T>
|
||||
where
|
||||
T: Sized + Rc,
|
||||
{
|
||||
/// Access the [RefGuard] and return the raw pointer without decreasing the reference count.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// This should be used when you need to pass wrapper type to the FFI function as **parameter**, and it is **not**
|
||||
/// the `self` type (usually the first parameter). This means we pass the ownership of the
|
||||
/// value to the function call. Using this method elsewehre may cause incorrect reference count
|
||||
/// and memory safety issues.
|
||||
fn into_raw(self) -> *mut T {
|
||||
unsafe { self.into_raw() }
|
||||
}
|
||||
}
|
||||
|
||||
pub struct WrapParamRef<T, P>
|
||||
where
|
||||
T: Sized + Into<P>,
|
||||
P: Sized + Copy + Into<T>,
|
||||
{
|
||||
value: T,
|
||||
output: Option<NonNull<P>>,
|
||||
}
|
||||
|
||||
impl<T, P> Drop for WrapParamRef<T, P>
|
||||
where
|
||||
T: Sized + Into<P>,
|
||||
P: Sized + Copy + Into<T>,
|
||||
{
|
||||
fn drop(&mut self) {
|
||||
if let Some(output) = &mut self.output {
|
||||
let output = unsafe { output.as_mut() };
|
||||
let mut value = unsafe { mem::zeroed() };
|
||||
mem::swap(&mut self.value, &mut value);
|
||||
*output = value.into();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, P> From<*mut P> for WrapParamRef<T, P>
|
||||
where
|
||||
T: Sized + Into<P>,
|
||||
P: Sized + Copy + Into<T>,
|
||||
{
|
||||
fn from(value: *mut P) -> Self {
|
||||
let mut output = NonNull::new(value);
|
||||
let value = output
|
||||
.as_mut()
|
||||
.map(|p| {
|
||||
let mut value = unsafe { mem::zeroed() };
|
||||
mem::swap(unsafe { p.as_mut() }, &mut value);
|
||||
value.into()
|
||||
})
|
||||
.unwrap_or_else(|| unsafe { mem::zeroed() });
|
||||
|
||||
Self { value, output }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, P> From<*const P> for WrapParamRef<T, P>
|
||||
where
|
||||
T: Sized + Into<P>,
|
||||
P: Sized + Copy + Into<T>,
|
||||
{
|
||||
#[allow(clippy::not_unsafe_ptr_arg_deref)]
|
||||
fn from(value: *const P) -> Self {
|
||||
let value = unsafe { value.as_ref() }
|
||||
.map(|value| (*value).into())
|
||||
.unwrap_or_else(|| unsafe { mem::zeroed() });
|
||||
|
||||
Self {
|
||||
value,
|
||||
output: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, P> AsMut<T> for WrapParamRef<T, P>
|
||||
where
|
||||
T: Sized + Into<P>,
|
||||
P: Sized + Copy + Into<T>,
|
||||
{
|
||||
fn as_mut(&mut self) -> &mut T {
|
||||
&mut self.value
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, P> AsRef<T> for WrapParamRef<T, P>
|
||||
where
|
||||
T: Sized + Into<P>,
|
||||
P: Sized + Copy + Into<T>,
|
||||
{
|
||||
fn as_ref(&self) -> &T {
|
||||
&self.value
|
||||
}
|
||||
}
|
||||
|
||||
pub trait ConvertReturnValue<T: Sized> {
|
||||
fn wrap_result(self) -> T;
|
||||
}
|
||||
|
||||
impl<T, U> ConvertReturnValue<U> for T
|
||||
where
|
||||
T: Sized + Into<U>,
|
||||
U: Sized,
|
||||
{
|
||||
fn wrap_result(self) -> U {
|
||||
self.into()
|
||||
}
|
||||
}
|
||||
|
||||
/// A smart pointer for types from cef library.
|
||||
pub struct RefGuard<T: Rc> {
|
||||
object: *mut T,
|
||||
}
|
||||
|
||||
impl<T: Debug + Rc> Debug for RefGuard<T> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let object_ref = unsafe { self.object.as_ref() };
|
||||
write!(f, "RefGuard({object_ref:#?})")
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Rc> RefGuard<T> {
|
||||
/// Create [RefGuard] from a raw C pointer.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// This should be used to get the **return value** of the FFI function. This means we get the
|
||||
/// ownership of the value. The reference count of the return value is already increased when
|
||||
/// you get it. So we don't need to increase it again manually. Using this method elsewhere may
|
||||
/// cause incorrect reference count and memory safety issues.
|
||||
pub unsafe fn from_raw(ptr: *mut T) -> RefGuard<T> {
|
||||
RefGuard { object: ptr }
|
||||
}
|
||||
|
||||
/// Create [RefGuard] from a raw C pointer and increase a reference count. This should be used
|
||||
/// when you want to copy the value and create another wrapper type.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// THis should be used when you want to manually increase the reference count upon getting the
|
||||
/// raw pointer. Using this method elsewhere may cause incorrect reference count and memory
|
||||
/// safety issues.
|
||||
pub unsafe fn from_raw_add_ref(ptr: *mut T) -> RefGuard<T> {
|
||||
let guard = RefGuard { object: ptr };
|
||||
|
||||
guard.add_ref();
|
||||
|
||||
guard
|
||||
}
|
||||
|
||||
// Get the raw pointer of [RefGuard].
|
||||
//
|
||||
/// # Safety
|
||||
///
|
||||
/// This should be used when you need to pass wrapper type to the FFI function as **parameter**, and it **is**
|
||||
/// the `self` type (usually the first parameter). This means we pass the ownership of the
|
||||
/// value to the function call. Using this method elsewhere may cause incorrect reference count
|
||||
/// and memory safety issues.
|
||||
pub unsafe fn into_raw(&self) -> *mut T {
|
||||
self.object
|
||||
}
|
||||
|
||||
/// Convert the value to another value that is also reference counted.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// This should be used when the type has type `U` as its base type. Using this method
|
||||
/// elsewhere may cause memory safety issues.
|
||||
pub unsafe fn convert<U: Rc>(&self) -> RefGuard<U> {
|
||||
RefGuard::from_raw_add_ref(self.into_raw().cast())
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl<T: Rc> Send for RefGuard<T> {}
|
||||
unsafe impl<T: Rc> Sync for RefGuard<T> {}
|
||||
|
||||
impl<T: Rc> Clone for RefGuard<T> {
|
||||
fn clone(&self) -> RefGuard<T> {
|
||||
unsafe { self.add_ref() };
|
||||
|
||||
RefGuard {
|
||||
object: self.object,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Rc> Deref for RefGuard<T> {
|
||||
type Target = T;
|
||||
|
||||
fn deref(&self) -> &T {
|
||||
unsafe { &*self.object }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Rc> Drop for RefGuard<T> {
|
||||
fn drop(&mut self) {
|
||||
unsafe { self.release() };
|
||||
}
|
||||
}
|
||||
|
||||
/// There are some types require users to implement one their own in Rust and then create a raw type around it to
|
||||
/// pass to sys level api. This is the wrapper type for it.
|
||||
#[repr(C)]
|
||||
pub struct RcImpl<T, I> {
|
||||
/// Raw cef types
|
||||
pub cef_object: T,
|
||||
/// Rust interface of such type
|
||||
pub interface: I,
|
||||
ref_count: AtomicUsize,
|
||||
}
|
||||
|
||||
impl<T, I> RcImpl<T, I> {
|
||||
pub fn new(mut cef_object: T, interface: I) -> *mut RcImpl<T, I> {
|
||||
let base: &mut cef_base_ref_counted_t =
|
||||
unsafe { &mut *(ptr::from_mut(&mut cef_object).cast()) };
|
||||
|
||||
base.size = std::mem::size_of::<T>();
|
||||
base.add_ref = Some(add_ref::<T, I>);
|
||||
base.has_one_ref = Some(has_one_ref::<T, I>);
|
||||
base.has_at_least_one_ref = Some(has_at_least_one_ref::<T, I>);
|
||||
base.release = Some(release::<T, I>);
|
||||
|
||||
Box::into_raw(Box::new(RcImpl {
|
||||
cef_object,
|
||||
interface,
|
||||
ref_count: AtomicUsize::new(1),
|
||||
}))
|
||||
}
|
||||
|
||||
pub fn get<'a>(ptr: *mut T) -> &'a mut RcImpl<T, I> {
|
||||
unsafe { &mut *(ptr.cast()) }
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" fn add_ref<T, I>(this: *mut cef_base_ref_counted_t) {
|
||||
let obj = RcImpl::<T, I>::get(this.cast());
|
||||
|
||||
obj.ref_count.fetch_add(1, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
extern "C" fn has_one_ref<T, I>(this: *mut cef_base_ref_counted_t) -> i32 {
|
||||
let obj = RcImpl::<T, I>::get(this.cast());
|
||||
|
||||
if obj.ref_count.load(Ordering::Relaxed) == 1 {
|
||||
1
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" fn has_at_least_one_ref<T, I>(this: *mut cef_base_ref_counted_t) -> i32 {
|
||||
let obj = RcImpl::<T, I>::get(this.cast());
|
||||
|
||||
if obj.ref_count.load(Ordering::Relaxed) >= 1 {
|
||||
1
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
pub extern "C" fn release<T, I>(this: *mut cef_base_ref_counted_t) -> i32 {
|
||||
let obj = RcImpl::<T, I>::get(this.cast());
|
||||
|
||||
if obj.ref_count.fetch_sub(1, Ordering::Release) != 1 {
|
||||
0
|
||||
} else {
|
||||
fence(Ordering::Acquire);
|
||||
let _: Box<RcImpl<T, I>> = unsafe { Box::from_raw(this.cast()) };
|
||||
1
|
||||
}
|
||||
}
|
||||
53
browser/cef/src/sandbox.rs
Normal file
53
browser/cef/src/sandbox.rs
Normal file
@ -0,0 +1,53 @@
|
||||
use libloading::Library;
|
||||
use std::{ffi::c_void, ptr::NonNull};
|
||||
|
||||
use crate::MainArgs;
|
||||
|
||||
pub struct Sandbox {
|
||||
lib: Library,
|
||||
context: Option<NonNull<c_void>>,
|
||||
}
|
||||
|
||||
impl Sandbox {
|
||||
const LIBCEF_SANDBOX_PATH: &str =
|
||||
"../../../../Chromium Embedded Framework.framework/Libraries/libcef_sandbox.dylib";
|
||||
|
||||
pub fn new() -> Self {
|
||||
unsafe {
|
||||
let lib = Library::new(
|
||||
std::env::current_exe()
|
||||
.unwrap()
|
||||
.join(Self::LIBCEF_SANDBOX_PATH)
|
||||
.canonicalize()
|
||||
.unwrap(),
|
||||
)
|
||||
.unwrap();
|
||||
Self { lib, context: None }
|
||||
}
|
||||
}
|
||||
|
||||
pub fn initialize(&mut self, args: &MainArgs) {
|
||||
let inner = unsafe {
|
||||
self.lib
|
||||
.get::<extern "C" fn(
|
||||
argc: std::os::raw::c_int,
|
||||
argv: *mut *mut ::std::os::raw::c_char,
|
||||
) -> *mut c_void>(b"cef_sandbox_initialize")
|
||||
.unwrap()(args.argc, args.argv)
|
||||
};
|
||||
self.context = NonNull::new(inner);
|
||||
assert!(self.context.is_some());
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Sandbox {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
if let Some(inner) = self.context {
|
||||
self.lib
|
||||
.get::<extern "C" fn(context: *mut c_void)>(b"cef_sandbox_destroy")
|
||||
.unwrap()(inner.as_ptr());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
1415
browser/cef/src/string.rs
Normal file
1415
browser/cef/src/string.rs
Normal file
File diff suppressed because it is too large
Load Diff
49
browser/download-cef/CHANGELOG.md
Normal file
49
browser/download-cef/CHANGELOG.md
Normal file
@ -0,0 +1,49 @@
|
||||
# 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]
|
||||
|
||||
## [2.2.0](https://github.com/tauri-apps/cef-rs/compare/download-cef-v2.1.0...download-cef-v2.2.0) - 2025-09-23
|
||||
|
||||
### Added
|
||||
|
||||
- add --mirror-url cli args
|
||||
- Allow to download CEF binaries with custom base url set in env variable
|
||||
|
||||
## [2.1.0](https://github.com/tauri-apps/cef-rs/compare/download-cef-v2.0.3...download-cef-v2.1.0) - 2025-09-19
|
||||
|
||||
### Added
|
||||
|
||||
- support pre-downloaded archive ([#197](https://github.com/tauri-apps/cef-rs/pull/197))
|
||||
|
||||
## [2.0.3](https://github.com/tauri-apps/cef-rs/compare/download-cef-v2.0.2...download-cef-v2.0.3) - 2025-08-16
|
||||
|
||||
### Fixed
|
||||
|
||||
- #183
|
||||
|
||||
## [2.0.2](https://github.com/tauri-apps/cef-rs/compare/download-cef-v2.0.1...download-cef-v2.0.2) - 2025-07-22
|
||||
|
||||
### Other
|
||||
|
||||
- *(doc)* regenerate CHANGELOG.md
|
||||
|
||||
## [2.0.1](https://github.com/tauri-apps/cef-rs/compare/download-cef-v2.0.0...download-cef-v2.0.1) - 2025-07-14
|
||||
|
||||
### Other
|
||||
|
||||
- release
|
||||
|
||||
## [2.0.0](https://github.com/tauri-apps/cef-rs/compare/download-cef-v1.6.0...download-cef-v2.0.0) - 2025-07-14
|
||||
|
||||
### Fixed
|
||||
|
||||
- bump major version of download-cef [#145](https://github.com/tauri-apps/cef-rs/issues/145)
|
||||
|
||||
### Other
|
||||
|
||||
- update CEF version
|
||||
23
browser/download-cef/Cargo.toml
Normal file
23
browser/download-cef/Cargo.toml
Normal file
@ -0,0 +1,23 @@
|
||||
[package]
|
||||
name = "download-cef"
|
||||
description = "Download and extract pre-built CEF (Chromium Embedded Framework) archives."
|
||||
version = "2.2.0"
|
||||
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
authors.workspace = true
|
||||
repository.workspace = true
|
||||
|
||||
[dependencies]
|
||||
clap.workspace = true
|
||||
regex.workspace = true
|
||||
semver.workspace = true
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
thiserror.workspace = true
|
||||
|
||||
bzip2 = "0.6"
|
||||
indicatif = "0.18"
|
||||
sha1_smol = "1"
|
||||
tar = "0.4"
|
||||
ureq = { version = "3", features = ["json", "socks-proxy"] }
|
||||
4
browser/download-cef/README.md
Normal file
4
browser/download-cef/README.md
Normal file
@ -0,0 +1,4 @@
|
||||
# download-cef
|
||||
|
||||
Utility functions to download and extract prebuilt [Chromium Embedded Framework](https://github.com/chromiumembedded/cef)
|
||||
archives on any supported platform.
|
||||
644
browser/download-cef/src/lib.rs
Normal file
644
browser/download-cef/src/lib.rs
Normal file
@ -0,0 +1,644 @@
|
||||
#![doc = include_str!("../README.md")]
|
||||
|
||||
use bzip2::bufread::BzDecoder;
|
||||
use clap::ValueEnum;
|
||||
use regex::Regex;
|
||||
use semver::Version;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sha1_smol::Sha1;
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
env,
|
||||
fmt::{self, Display},
|
||||
fs::{self, File},
|
||||
io::{self, BufReader, IsTerminal, Write},
|
||||
path::{Path, PathBuf},
|
||||
sync::{Mutex, OnceLock},
|
||||
thread,
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
#[macro_use]
|
||||
extern crate thiserror;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum Error {
|
||||
#[error("Unsupported target triplet: {0}")]
|
||||
UnsupportedTarget(String),
|
||||
#[error("HTTP request error: {0}")]
|
||||
Request(#[from] ureq::Error),
|
||||
#[error("Invalid version: {0}")]
|
||||
InvalidVersion(#[from] semver::Error),
|
||||
#[error("Version not found: {0}")]
|
||||
VersionNotFound(String),
|
||||
#[error("Missing Content-Length header")]
|
||||
MissingContentLength,
|
||||
#[error("Opaque Content-Length header: {0}")]
|
||||
OpaqueContentLength(#[from] ureq::http::header::ToStrError),
|
||||
#[error("Invalid Content-Length header: {0}")]
|
||||
InvalidContentLength(String),
|
||||
#[error("File I/O error: {0}")]
|
||||
Io(#[from] std::io::Error),
|
||||
#[error("Unexpected file size: downloaded {downloaded} expected {expected}")]
|
||||
UnexpectedFileSize { downloaded: u64, expected: u64 },
|
||||
#[error("Bad SHA1 file hash: {0}")]
|
||||
CorruptedFile(String),
|
||||
#[error("Invalid archive file path: {0}")]
|
||||
InvalidArchiveFile(String),
|
||||
#[error("JSON serialization error: {0}")]
|
||||
Json(#[from] serde_json::Error),
|
||||
#[error(
|
||||
"Undexpected archive version: location: {location} archive {archive} expected {expected}"
|
||||
)]
|
||||
VersionMismatch {
|
||||
location: String,
|
||||
archive: String,
|
||||
expected: String,
|
||||
},
|
||||
#[error("Invalid regex pattern: {0}")]
|
||||
InvalidRegexPattern(#[from] regex::Error),
|
||||
}
|
||||
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
pub const LINUX_TARGETS: &[&str] = &[
|
||||
"x86_64-unknown-linux-gnu",
|
||||
"arm-unknown-linux-gnueabi",
|
||||
"aarch64-unknown-linux-gnu",
|
||||
];
|
||||
|
||||
pub const MACOS_TARGETS: &[&str] = &["aarch64-apple-darwin", "x86_64-apple-darwin"];
|
||||
|
||||
pub const WINDOWS_TARGETS: &[&str] = &[
|
||||
"x86_64-pc-windows-msvc",
|
||||
"aarch64-pc-windows-msvc",
|
||||
"i686-pc-windows-msvc",
|
||||
];
|
||||
|
||||
pub fn default_version(version: &str) -> String {
|
||||
unwrap_cef_version(version).unwrap_or_else(|_| version.to_string())
|
||||
}
|
||||
|
||||
fn unwrap_cef_version(version: &str) -> Result<String> {
|
||||
static VERSIONS: OnceLock<Mutex<HashMap<Version, String>>> = OnceLock::new();
|
||||
let mut versions = VERSIONS
|
||||
.get_or_init(Default::default)
|
||||
.lock()
|
||||
.expect("Lock error");
|
||||
Ok(versions
|
||||
.entry(Version::parse(version)?)
|
||||
.or_insert_with_key(|v| {
|
||||
if v.build.is_empty() {
|
||||
version.to_string()
|
||||
} else {
|
||||
v.build.to_string()
|
||||
}
|
||||
})
|
||||
.clone())
|
||||
}
|
||||
|
||||
pub fn check_archive_json(version: &str, location: &str) -> Result<()> {
|
||||
let expected = Version::parse(&unwrap_cef_version(version)?)?;
|
||||
|
||||
static PATTERN: OnceLock<core::result::Result<Regex, regex::Error>> = OnceLock::new();
|
||||
let pattern = PATTERN
|
||||
.get_or_init(|| Regex::new(r"^cef_binary_([^+]+)(:?\+.+)?$"))
|
||||
.as_ref()
|
||||
.map_err(Clone::clone)?;
|
||||
let archive_json: CefFile = serde_json::from_reader(File::open(archive_json_path(location))?)?;
|
||||
let archive_version = pattern.replace(&archive_json.name, "$1");
|
||||
let archive = Version::parse(&archive_version)?;
|
||||
|
||||
if archive <= expected {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(Error::VersionMismatch {
|
||||
location: location.to_string(),
|
||||
expected: expected.to_string(),
|
||||
archive: archive.to_string(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn archive_json_path<P>(location: P) -> PathBuf
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
location.as_ref().join("archive.json")
|
||||
}
|
||||
|
||||
pub const DEFAULT_CDN_URL: &str = "https://cef-builds.spotifycdn.com";
|
||||
|
||||
pub fn default_download_url() -> String {
|
||||
env::var("CEF_DOWNLOAD_URL").unwrap_or(DEFAULT_CDN_URL.to_owned())
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Deserialize, Serialize, ValueEnum)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum Channel {
|
||||
Stable,
|
||||
Beta,
|
||||
}
|
||||
|
||||
impl Display for Channel {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Channel::Stable => write!(f, "stable"),
|
||||
Channel::Beta => write!(f, "beta"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Default)]
|
||||
pub struct CefIndex {
|
||||
pub macosarm64: CefPlatform,
|
||||
pub macosx64: CefPlatform,
|
||||
pub windows64: CefPlatform,
|
||||
pub windowsarm64: CefPlatform,
|
||||
pub windows32: CefPlatform,
|
||||
pub linux64: CefPlatform,
|
||||
pub linuxarm64: CefPlatform,
|
||||
pub linuxarm: CefPlatform,
|
||||
}
|
||||
|
||||
impl CefIndex {
|
||||
pub fn download() -> Result<Self> {
|
||||
Self::download_from(DEFAULT_CDN_URL)
|
||||
}
|
||||
|
||||
pub fn download_from(url: &str) -> Result<Self> {
|
||||
Ok(ureq::get(&format!("{url}/index.json"))
|
||||
.call()?
|
||||
.into_body()
|
||||
.read_json()?)
|
||||
}
|
||||
|
||||
pub fn platform(&self, target: &str) -> Result<&CefPlatform> {
|
||||
match target {
|
||||
"aarch64-apple-darwin" => Ok(&self.macosarm64),
|
||||
"x86_64-apple-darwin" => Ok(&self.macosx64),
|
||||
"x86_64-pc-windows-msvc" => Ok(&self.windows64),
|
||||
"aarch64-pc-windows-msvc" => Ok(&self.windowsarm64),
|
||||
"i686-pc-windows-msvc" => Ok(&self.windows32),
|
||||
"x86_64-unknown-linux-gnu" => Ok(&self.linux64),
|
||||
"aarch64-unknown-linux-gnu" => Ok(&self.linuxarm64),
|
||||
"arm-unknown-linux-gnueabi" => Ok(&self.linuxarm),
|
||||
v => Err(Error::UnsupportedTarget(v.to_string())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Default)]
|
||||
pub struct CefPlatform {
|
||||
pub versions: Vec<CefVersion>,
|
||||
}
|
||||
|
||||
impl CefPlatform {
|
||||
pub fn version(&self, cef_version: &str) -> Result<&CefVersion> {
|
||||
let version_prefix = format!("{cef_version}+");
|
||||
self.versions
|
||||
.iter()
|
||||
.find(|v| v.cef_version.starts_with(&version_prefix))
|
||||
.ok_or_else(|| Error::VersionNotFound(cef_version.to_string()))
|
||||
}
|
||||
|
||||
pub fn latest(&self, channel: Channel) -> Result<&CefVersion> {
|
||||
static PATTERN: OnceLock<core::result::Result<Regex, regex::Error>> = OnceLock::new();
|
||||
let pattern = PATTERN
|
||||
.get_or_init(|| Regex::new(r"^([^+]+)(:?\+.+)?$"))
|
||||
.as_ref()
|
||||
.map_err(Clone::clone)?;
|
||||
|
||||
self.versions
|
||||
.iter()
|
||||
.filter_map(|value| {
|
||||
if value.channel == channel {
|
||||
let key = Version::parse(&pattern.replace(&value.cef_version, "$1")).ok()?;
|
||||
Some((key, value))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.max_by(|(a, _), (b, _)| a.cmp(b))
|
||||
.map(|(_, v)| v)
|
||||
.ok_or_else(|| Error::VersionNotFound("latest".to_string()))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize)]
|
||||
pub struct CefVersion {
|
||||
pub channel: Channel,
|
||||
pub cef_version: String,
|
||||
pub files: Vec<CefFile>,
|
||||
}
|
||||
|
||||
impl CefVersion {
|
||||
pub fn download_archive<P>(&self, location: P, show_progress: bool) -> Result<PathBuf>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
self.download_archive_from(DEFAULT_CDN_URL, location, show_progress)
|
||||
}
|
||||
|
||||
pub fn download_archive_from<P>(
|
||||
&self,
|
||||
url: &str,
|
||||
location: P,
|
||||
show_progress: bool,
|
||||
) -> Result<PathBuf>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
let file = self.minimal()?;
|
||||
let (file, sha) = (file.name.as_str(), file.sha1.as_str());
|
||||
|
||||
fs::create_dir_all(&location)?;
|
||||
let download_file = location.as_ref().join(file);
|
||||
|
||||
if download_file.exists() {
|
||||
if calculate_file_sha1(&download_file) == sha {
|
||||
if show_progress {
|
||||
println!("Verified archive: {}", download_file.display());
|
||||
}
|
||||
return Ok(download_file);
|
||||
}
|
||||
|
||||
if show_progress {
|
||||
println!("Cleaning corrupted archive: {}", download_file.display());
|
||||
}
|
||||
let corrupted_file = location.as_ref().join(format!("corrupted_{file}"));
|
||||
fs::rename(&download_file, &corrupted_file)?;
|
||||
fs::remove_file(&corrupted_file)?;
|
||||
}
|
||||
|
||||
let cef_url = format!("{url}/{file}");
|
||||
if show_progress {
|
||||
println!("Using archive url: {cef_url}");
|
||||
}
|
||||
|
||||
let mut file = File::create(&download_file)?;
|
||||
|
||||
let resp = ureq::get(&cef_url).call()?;
|
||||
let expected = resp
|
||||
.headers()
|
||||
.get("Content-Length")
|
||||
.ok_or(Error::MissingContentLength)?;
|
||||
let expected = expected.to_str()?;
|
||||
let expected = expected
|
||||
.parse::<u64>()
|
||||
.map_err(|_| Error::InvalidContentLength(expected.to_owned()))?;
|
||||
|
||||
let downloaded = if show_progress && io::stdout().is_terminal() {
|
||||
const DOWNLOAD_TEMPLATE: &str = "{msg} {spinner:.green} [{elapsed_precise}] [{wide_bar:.cyan/blue}] {bytes}/{total_bytes} ({eta})";
|
||||
|
||||
let bar = indicatif::ProgressBar::new(expected);
|
||||
bar.set_style(
|
||||
indicatif::ProgressStyle::with_template(DOWNLOAD_TEMPLATE)
|
||||
.expect("invalid template")
|
||||
.progress_chars("##-"),
|
||||
);
|
||||
bar.set_message("Downloading");
|
||||
std::io::copy(
|
||||
&mut bar.wrap_read(resp.into_body().into_reader()),
|
||||
&mut file,
|
||||
)
|
||||
} else {
|
||||
let mut reader = resp.into_body().into_reader();
|
||||
std::io::copy(&mut reader, &mut file)
|
||||
}?;
|
||||
|
||||
if downloaded != expected {
|
||||
return Err(Error::UnexpectedFileSize {
|
||||
downloaded,
|
||||
expected,
|
||||
});
|
||||
}
|
||||
|
||||
if show_progress {
|
||||
println!("Verifying SHA1 hash: {sha}...");
|
||||
}
|
||||
if calculate_file_sha1(&download_file) != sha {
|
||||
return Err(Error::CorruptedFile(download_file.display().to_string()));
|
||||
}
|
||||
|
||||
if show_progress {
|
||||
println!("Downloaded archive: {}", download_file.display());
|
||||
}
|
||||
Ok(download_file)
|
||||
}
|
||||
|
||||
pub fn download_archive_with_retry<P>(
|
||||
&self,
|
||||
location: P,
|
||||
show_progress: bool,
|
||||
retry_delay: Duration,
|
||||
max_retries: u32,
|
||||
) -> Result<PathBuf>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
self.download_archive_with_retry_from(
|
||||
DEFAULT_CDN_URL,
|
||||
location,
|
||||
show_progress,
|
||||
retry_delay,
|
||||
max_retries,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn download_archive_with_retry_from<P>(
|
||||
&self,
|
||||
url: &str,
|
||||
location: P,
|
||||
show_progress: bool,
|
||||
retry_delay: Duration,
|
||||
max_retries: u32,
|
||||
) -> Result<PathBuf>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
let mut result = self.download_archive_from(&url, &location, show_progress);
|
||||
|
||||
let mut retry = 0;
|
||||
while let Err(Error::Io(_)) = &result {
|
||||
if retry >= max_retries {
|
||||
break;
|
||||
}
|
||||
|
||||
retry += 1;
|
||||
thread::sleep(retry_delay * retry);
|
||||
|
||||
result = self.download_archive_from(&url, &location, show_progress);
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
pub fn minimal(&self) -> Result<&CefFile> {
|
||||
self.files
|
||||
.iter()
|
||||
.find(|f| f.file_type == "minimal")
|
||||
.ok_or_else(|| Error::VersionNotFound(self.cef_version.clone()))
|
||||
}
|
||||
|
||||
pub fn write_archive_json<P>(&self, location: P) -> Result<()>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
self.minimal()?.write_archive_json(location)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Deserialize, Serialize)]
|
||||
pub struct CefFile {
|
||||
#[serde(rename = "type")]
|
||||
pub file_type: String,
|
||||
pub name: String,
|
||||
pub sha1: String,
|
||||
}
|
||||
|
||||
impl CefFile {
|
||||
pub fn write_archive_json<P>(&self, location: P) -> Result<()>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
let archive_version = serde_json::to_string_pretty(self)?;
|
||||
let mut archive_json = File::create(archive_json_path(location))?;
|
||||
archive_json.write_all(archive_version.as_bytes())?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&Path> for CefFile {
|
||||
type Error = Error;
|
||||
|
||||
fn try_from(location: &Path) -> Result<Self> {
|
||||
let file_type = "minimal".to_string();
|
||||
let name = location
|
||||
.file_name()
|
||||
.map(|f| f.display().to_string())
|
||||
.ok_or_else(|| Error::InvalidArchiveFile(location.display().to_string()))?;
|
||||
let sha1 = calculate_file_sha1(location);
|
||||
Ok(Self {
|
||||
file_type,
|
||||
name,
|
||||
sha1,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn download_target_archive<P>(
|
||||
target: &str,
|
||||
cef_version: &str,
|
||||
location: P,
|
||||
show_progress: bool,
|
||||
) -> Result<PathBuf>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
download_target_archive_from(
|
||||
DEFAULT_CDN_URL,
|
||||
target,
|
||||
cef_version,
|
||||
location,
|
||||
show_progress,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn download_target_archive_from<P>(
|
||||
url: &str,
|
||||
target: &str,
|
||||
cef_version: &str,
|
||||
location: P,
|
||||
show_progress: bool,
|
||||
) -> Result<PathBuf>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
if show_progress {
|
||||
println!("Downloading CEF archive for {target}...");
|
||||
}
|
||||
|
||||
let index = CefIndex::download_from(&url)?;
|
||||
let platform = index.platform(target)?;
|
||||
let version = platform.version(cef_version)?;
|
||||
|
||||
version.download_archive_with_retry_from(
|
||||
url,
|
||||
location,
|
||||
show_progress,
|
||||
Duration::from_secs(15),
|
||||
3,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn extract_target_archive<P, Q>(
|
||||
target: &str,
|
||||
archive: P,
|
||||
location: Q,
|
||||
show_progress: bool,
|
||||
) -> Result<PathBuf>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
Q: AsRef<Path>,
|
||||
{
|
||||
if show_progress {
|
||||
println!("Extracting archive: {}", archive.as_ref().display());
|
||||
}
|
||||
let decoder = BzDecoder::new(BufReader::new(File::open(&archive)?));
|
||||
tar::Archive::new(decoder).unpack(&location)?;
|
||||
|
||||
let extracted_dir = archive.as_ref().display().to_string();
|
||||
let extracted_dir = extracted_dir
|
||||
.strip_suffix(".tar.bz2")
|
||||
.map(PathBuf::from)
|
||||
.ok_or(Error::InvalidArchiveFile(extracted_dir))?;
|
||||
|
||||
let os_and_arch = OsAndArch::try_from(target)?;
|
||||
let OsAndArch { os, arch } = os_and_arch;
|
||||
let cef_dir = os_and_arch.to_string();
|
||||
let cef_dir = location.as_ref().join(cef_dir);
|
||||
|
||||
if cef_dir.exists() {
|
||||
let old_dir = location.as_ref().join(format!("old_{os}_{arch}"));
|
||||
if show_progress {
|
||||
println!("Cleaning up: {}", old_dir.display());
|
||||
}
|
||||
fs::rename(&cef_dir, &old_dir)?;
|
||||
fs::remove_dir_all(old_dir)?;
|
||||
}
|
||||
const RELEASE_DIR: &str = "Release";
|
||||
fs::rename(extracted_dir.join(RELEASE_DIR), &cef_dir)?;
|
||||
|
||||
if os != "macos" {
|
||||
let resources = extracted_dir.join("Resources");
|
||||
|
||||
for entry in fs::read_dir(&resources)? {
|
||||
let entry = entry?;
|
||||
fs::rename(entry.path(), cef_dir.join(entry.file_name()))?;
|
||||
}
|
||||
}
|
||||
|
||||
const CMAKE_LISTS_TXT: &str = "CMakeLists.txt";
|
||||
fs::rename(
|
||||
extracted_dir.join(CMAKE_LISTS_TXT),
|
||||
cef_dir.join(CMAKE_LISTS_TXT),
|
||||
)?;
|
||||
const CMAKE_DIR: &str = "cmake";
|
||||
fs::rename(extracted_dir.join(CMAKE_DIR), cef_dir.join(CMAKE_DIR))?;
|
||||
const INCLUDE_DIR: &str = "include";
|
||||
fs::rename(extracted_dir.join(INCLUDE_DIR), cef_dir.join(INCLUDE_DIR))?;
|
||||
const LIBCEF_DLL_DIR: &str = "libcef_dll";
|
||||
fs::rename(
|
||||
extracted_dir.join(LIBCEF_DLL_DIR),
|
||||
cef_dir.join(LIBCEF_DLL_DIR),
|
||||
)?;
|
||||
|
||||
if show_progress {
|
||||
println!("Moved contents to: {}", cef_dir.display());
|
||||
}
|
||||
|
||||
// Cleanup whatever is left in the extracted directory.
|
||||
let old_dir = extracted_dir
|
||||
.parent()
|
||||
.map(|parent| parent.join(format!("extracted_{os}_{arch}")))
|
||||
.ok_or_else(|| Error::InvalidArchiveFile(extracted_dir.display().to_string()))?;
|
||||
if show_progress {
|
||||
println!("Cleaning up: {}", old_dir.display());
|
||||
}
|
||||
fs::rename(&extracted_dir, &old_dir)?;
|
||||
fs::remove_dir_all(old_dir)?;
|
||||
|
||||
Ok(cef_dir)
|
||||
}
|
||||
|
||||
fn calculate_file_sha1(path: &Path) -> String {
|
||||
use std::io::Read;
|
||||
let mut file = BufReader::new(File::open(path).unwrap());
|
||||
let mut sha1 = Sha1::new();
|
||||
let mut buffer = [0; 8192];
|
||||
|
||||
loop {
|
||||
let count = file.read(&mut buffer).unwrap();
|
||||
if count == 0 {
|
||||
break;
|
||||
}
|
||||
sha1.update(&buffer[..count]);
|
||||
}
|
||||
|
||||
sha1.digest().to_string()
|
||||
}
|
||||
|
||||
pub struct OsAndArch {
|
||||
pub os: &'static str,
|
||||
pub arch: &'static str,
|
||||
}
|
||||
|
||||
impl Display for OsAndArch {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let os = self.os;
|
||||
let arch = self.arch;
|
||||
write!(f, "cef_{os}_{arch}")
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&str> for OsAndArch {
|
||||
type Error = Error;
|
||||
|
||||
fn try_from(target: &str) -> Result<Self> {
|
||||
match target {
|
||||
"aarch64-apple-darwin" => Ok(OsAndArch {
|
||||
os: "macos",
|
||||
arch: "aarch64",
|
||||
}),
|
||||
"x86_64-apple-darwin" => Ok(OsAndArch {
|
||||
os: "macos",
|
||||
arch: "x86_64",
|
||||
}),
|
||||
"x86_64-pc-windows-msvc" => Ok(OsAndArch {
|
||||
os: "windows",
|
||||
arch: "x86_64",
|
||||
}),
|
||||
"aarch64-pc-windows-msvc" => Ok(OsAndArch {
|
||||
os: "windows",
|
||||
arch: "aarch64",
|
||||
}),
|
||||
"i686-pc-windows-msvc" => Ok(OsAndArch {
|
||||
os: "windows",
|
||||
arch: "x86",
|
||||
}),
|
||||
"x86_64-unknown-linux-gnu" => Ok(OsAndArch {
|
||||
os: "linux",
|
||||
arch: "x86_64",
|
||||
}),
|
||||
"aarch64-unknown-linux-gnu" => Ok(OsAndArch {
|
||||
os: "linux",
|
||||
arch: "aarch64",
|
||||
}),
|
||||
"arm-unknown-linux-gnueabi" => Ok(OsAndArch {
|
||||
os: "linux",
|
||||
arch: "arm",
|
||||
}),
|
||||
v => Err(Error::UnsupportedTarget(v.to_string())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(target_os = "linux", target_arch = "x86_64"))]
|
||||
pub const DEFAULT_TARGET: &str = "x86_64-unknown-linux-gnu";
|
||||
#[cfg(all(target_os = "linux", target_arch = "aarch64"))]
|
||||
pub const DEFAULT_TARGET: &str = "aarch64-unknown-linux-gnu";
|
||||
#[cfg(all(target_os = "linux", target_arch = "arm"))]
|
||||
pub const DEFAULT_TARGET: &str = "arm-unknown-linux-gnueabi";
|
||||
|
||||
#[cfg(all(target_os = "windows", target_arch = "x86_64"))]
|
||||
pub const DEFAULT_TARGET: &str = "x86_64-pc-windows-msvc";
|
||||
#[cfg(all(target_os = "windows", target_arch = "x86"))]
|
||||
pub const DEFAULT_TARGET: &str = "i686-pc-windows-msvc";
|
||||
#[cfg(all(target_os = "windows", target_arch = "aarch64"))]
|
||||
pub const DEFAULT_TARGET: &str = "aarch64-pc-windows-msvc";
|
||||
|
||||
#[cfg(all(target_os = "macos", target_arch = "x86_64"))]
|
||||
pub const DEFAULT_TARGET: &str = "x86_64-apple-darwin";
|
||||
#[cfg(all(target_os = "macos", target_arch = "aarch64"))]
|
||||
pub const DEFAULT_TARGET: &str = "aarch64-apple-darwin";
|
||||
208
browser/export-cef-dir/CHANGELOG.md
Normal file
208
browser/export-cef-dir/CHANGELOG.md
Normal file
@ -0,0 +1,208 @@
|
||||
# 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.0+140.1.14](https://github.com/tauri-apps/cef-rs/compare/export-cef-dir-v140.2.0+140.1.14...export-cef-dir-v140.3.0+140.1.14) - 2025-09-23
|
||||
|
||||
### Added
|
||||
|
||||
- add --mirror-url cli args
|
||||
- Allow to download CEF binaries with custom base url set in env variable
|
||||
|
||||
## [140.2.0+140.1.14](https://github.com/tauri-apps/cef-rs/compare/export-cef-dir-v140.1.0+140.1.13...export-cef-dir-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/export-cef-dir-v140.0.0+140.1.13...export-cef-dir-v140.1.0+140.1.13) - 2025-09-19
|
||||
|
||||
### Added
|
||||
|
||||
- support pre-downloaded archive ([#197](https://github.com/tauri-apps/cef-rs/issues/197))
|
||||
|
||||
### Other
|
||||
|
||||
- release
|
||||
|
||||
## [140.0.0+140.1.13](https://github.com/tauri-apps/cef-rs/compare/export-cef-dir-v139.8.0+139.0.40...export-cef-dir-v140.0.0+140.1.13) - 2025-09-19
|
||||
|
||||
### Other
|
||||
|
||||
- *(release)* update CEF version to 140.1.13
|
||||
|
||||
## [139.8.0+139.0.40](https://github.com/tauri-apps/cef-rs/compare/export-cef-dir-v139.7.2+139.0.38...export-cef-dir-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/export-cef-dir-v139.7.1+139.0.38...export-cef-dir-v139.7.2+139.0.38) - 2025-09-08
|
||||
|
||||
### Other
|
||||
|
||||
- release v139.7.2+139.0.38
|
||||
|
||||
## [139.7.1+139.0.38](https://github.com/tauri-apps/cef-rs/compare/export-cef-dir-v139.7.0+139.0.38...export-cef-dir-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/export-cef-dir-v139.6.0+139.0.37...export-cef-dir-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/export-cef-dir-v139.5.0+139.0.30...export-cef-dir-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/export-cef-dir-v139.4.0+139.0.28...export-cef-dir-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/export-cef-dir-v139.3.0+139.0.26...export-cef-dir-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/export-cef-dir-v139.2.1+139.0.23...export-cef-dir-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/export-cef-dir-v139.2.0+139.0.23...export-cef-dir-v139.2.1+139.0.23) - 2025-08-16
|
||||
|
||||
### Fixed
|
||||
|
||||
- [#183](https://github.com/tauri-apps/cef-rs/issues/183)
|
||||
|
||||
### Other
|
||||
|
||||
- release
|
||||
|
||||
## [139.2.0+139.0.23](https://github.com/tauri-apps/cef-rs/compare/export-cef-dir-v139.1.0+139.0.20...export-cef-dir-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/export-cef-dir-v139.0.1+139.0.17...export-cef-dir-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/export-cef-dir-v139.0.0+139.0.17...export-cef-dir-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/export-cef-dir-v138.9.0+138.0.36...export-cef-dir-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/export-cef-dir-v138.8.0+138.0.34...export-cef-dir-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/export-cef-dir-v138.7.1+138.0.33...export-cef-dir-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/export-cef-dir-v138.7.0+138.0.33...export-cef-dir-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/export-cef-dir-v138.6.1+138.0.27...export-cef-dir-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/export-cef-dir-v138.6.0+138.0.27...export-cef-dir-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/export-cef-dir-v138.5.1+138.0.26...export-cef-dir-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/export-cef-dir-v138.5.0+138.0.26...export-cef-dir-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/export-cef-dir-v138.4.0+138.0.25...export-cef-dir-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/export-cef-dir-v138.3.0+138.0.23...export-cef-dir-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/export-cef-dir-v138.2.2+138.0.21...export-cef-dir-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/export-cef-dir-v138.2.1+138.0.21...export-cef-dir-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/export-cef-dir-v138.2.0+138.0.21...export-cef-dir-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)
|
||||
|
||||
16
browser/export-cef-dir/Cargo.toml
Normal file
16
browser/export-cef-dir/Cargo.toml
Normal file
@ -0,0 +1,16 @@
|
||||
[package]
|
||||
name = "export-cef-dir"
|
||||
description = "Export pre-built CEF (Chromium Embedded Framework) archives."
|
||||
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
authors.workspace = true
|
||||
repository.workspace = true
|
||||
|
||||
[dependencies]
|
||||
anyhow.workspace = true
|
||||
clap.workspace = true
|
||||
download-cef.workspace = true
|
||||
semver.workspace = true
|
||||
serde_json.workspace = true
|
||||
28
browser/export-cef-dir/README.md
Normal file
28
browser/export-cef-dir/README.md
Normal file
@ -0,0 +1,28 @@
|
||||
# export-cef-dir
|
||||
|
||||
Export files from the prebuilt [Chromium Embedded Framework](https://github.com/chromiumembedded/cef)
|
||||
archive on any supported platform. The structure of the exported directory matches the way that
|
||||
the `cef-dll-sys` crate expects to see them.
|
||||
|
||||
To use the target directory when building, set the `CEF_PATH` environment variable to the path of the
|
||||
exported directory, e.g., `~/.local/share/cef`.
|
||||
|
||||
To use the DLLs in this directory at runtime, the library loader path varies by platform:
|
||||
|
||||
- Linux
|
||||
|
||||
```sh
|
||||
export LD_LIBRARY_PATH="$LD_LIBRARY_PATH:$CEF_PATH"
|
||||
```
|
||||
|
||||
- macOS
|
||||
|
||||
```sh
|
||||
export DYLD_FALLBACK_LIBRARY_PATH="$DYLD_FALLBACK_LIBRARY_PATH:$CEF_PATH"
|
||||
```
|
||||
|
||||
- Windows (using PowerShell)
|
||||
|
||||
```pwsh
|
||||
$env:PATH = "$env:PATH;$env:CEF_PATH"
|
||||
```
|
||||
136
browser/export-cef-dir/src/main.rs
Normal file
136
browser/export-cef-dir/src/main.rs
Normal file
@ -0,0 +1,136 @@
|
||||
#![doc = include_str!("../README.md")]
|
||||
|
||||
use clap::Parser;
|
||||
use download_cef::{CefFile, CefIndex, OsAndArch, DEFAULT_TARGET};
|
||||
use std::{
|
||||
fs,
|
||||
path::{Path, PathBuf},
|
||||
sync::OnceLock,
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
fn default_version() -> &'static str {
|
||||
static DEFAULT_VERSION: OnceLock<String> = OnceLock::new();
|
||||
DEFAULT_VERSION
|
||||
.get_or_init(|| download_cef::default_version(env!("CARGO_PKG_VERSION")))
|
||||
.as_str()
|
||||
}
|
||||
|
||||
fn default_download_url() -> &'static str {
|
||||
static DEFAULT_DOWNLOAD_URL: OnceLock<String> = OnceLock::new();
|
||||
DEFAULT_DOWNLOAD_URL
|
||||
.get_or_init(|| download_cef::default_download_url())
|
||||
.as_str()
|
||||
}
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
#[command(about, long_about = None)]
|
||||
struct Args {
|
||||
#[arg(short, long)]
|
||||
force: bool,
|
||||
#[arg(short, long)]
|
||||
save_archive: bool,
|
||||
#[arg(short, long, default_value = DEFAULT_TARGET)]
|
||||
target: String,
|
||||
#[arg(short, long, default_value = default_version())]
|
||||
version: String,
|
||||
#[arg(short, long, default_value = default_download_url())]
|
||||
mirror_url: String,
|
||||
#[arg(short, long)]
|
||||
archive: Option<String>,
|
||||
output: String,
|
||||
}
|
||||
|
||||
fn main() -> anyhow::Result<()> {
|
||||
let args = Args::parse();
|
||||
let output = PathBuf::from(args.output);
|
||||
let url = args.mirror_url.as_str();
|
||||
|
||||
let parent = PathBuf::from(
|
||||
output
|
||||
.parent()
|
||||
.ok_or_else(|| anyhow::anyhow!("invalid target directory: {}", output.display()))?,
|
||||
);
|
||||
|
||||
if fs::exists(&output)? {
|
||||
if !args.force {
|
||||
return Err(anyhow::anyhow!(
|
||||
"target directory already exists: {}",
|
||||
output.display()
|
||||
));
|
||||
}
|
||||
|
||||
let dir = output
|
||||
.file_name()
|
||||
.and_then(|dir| dir.to_str())
|
||||
.ok_or_else(|| anyhow::anyhow!("invalid target directory: {}", output.display()))?;
|
||||
let old_output = parent.join(format!("old_{dir}"));
|
||||
fs::rename(&output, &old_output)?;
|
||||
println!("Cleaning up: {}", old_output.display());
|
||||
fs::remove_dir_all(old_output)?
|
||||
}
|
||||
|
||||
let target = args.target.as_str();
|
||||
let os_arch = OsAndArch::try_from(target)?;
|
||||
let cef_dir = os_arch.to_string();
|
||||
let cef_dir = parent.join(&cef_dir);
|
||||
|
||||
if fs::exists(&cef_dir)? {
|
||||
let dir = cef_dir
|
||||
.file_name()
|
||||
.and_then(|dir| dir.to_str())
|
||||
.ok_or_else(|| anyhow::anyhow!("invalid target directory: {}", output.display()))?;
|
||||
let old_cef_dir = parent.join(format!("old_{dir}"));
|
||||
fs::rename(&cef_dir, &old_cef_dir)?;
|
||||
println!("Cleaning up: {}", old_cef_dir.display());
|
||||
fs::remove_dir_all(old_cef_dir)?
|
||||
}
|
||||
|
||||
let (archive, extracted_dir) = match args.archive {
|
||||
Some(archive) => {
|
||||
let extracted_dir =
|
||||
download_cef::extract_target_archive(target, &archive, &parent, true)?;
|
||||
let archive = CefFile::try_from(Path::new(&archive))?;
|
||||
(archive, extracted_dir)
|
||||
}
|
||||
None => {
|
||||
let cef_version = args.version.as_str();
|
||||
let index = CefIndex::download_from(url)?;
|
||||
let platform = index.platform(target)?;
|
||||
let version = platform.version(cef_version)?;
|
||||
|
||||
let archive = version.download_archive_with_retry_from(
|
||||
url,
|
||||
&parent,
|
||||
true,
|
||||
Duration::from_secs(15),
|
||||
3,
|
||||
)?;
|
||||
let extracted_dir =
|
||||
download_cef::extract_target_archive(target, &archive, &parent, true)?;
|
||||
|
||||
if !args.save_archive {
|
||||
println!("Cleaning up: {}", archive.display());
|
||||
fs::remove_file(archive)?;
|
||||
}
|
||||
|
||||
let archive = version.minimal()?.clone();
|
||||
(archive, extracted_dir)
|
||||
}
|
||||
};
|
||||
|
||||
if extracted_dir != cef_dir {
|
||||
return Err(anyhow::anyhow!(
|
||||
"extracted dir {extracted_dir:?} does not match cef_dir {cef_dir:?}",
|
||||
));
|
||||
}
|
||||
|
||||
archive.write_archive_json(extracted_dir)?;
|
||||
|
||||
if output != cef_dir {
|
||||
println!("Renaming: {}", output.display());
|
||||
fs::rename(cef_dir, output)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
21
browser/get-latest/Cargo.toml
Normal file
21
browser/get-latest/Cargo.toml
Normal file
@ -0,0 +1,21 @@
|
||||
[package]
|
||||
name = "get-latest"
|
||||
publish = false
|
||||
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
authors.workspace = true
|
||||
repository.workspace = true
|
||||
|
||||
[dependencies]
|
||||
download-cef.workspace = true
|
||||
|
||||
clap.workspace = true
|
||||
git-cliff.workspace = true
|
||||
git-cliff-core.workspace = true
|
||||
regex.workspace = true
|
||||
semver.workspace = true
|
||||
serde.workspace = true
|
||||
thiserror.workspace = true
|
||||
toml_edit.workspace = true
|
||||
5
browser/get-latest/README.md
Normal file
5
browser/get-latest/README.md
Normal file
@ -0,0 +1,5 @@
|
||||
# get-latest
|
||||
|
||||
Download the list of versions available from the [Chromium Embedded Framework (CEF) Automated
|
||||
Builds](https://cef-builds.spotifycdn.com/index.html) page and determine the latest version that is
|
||||
available for every platform we support.
|
||||
112
browser/get-latest/cliff.toml
Normal file
112
browser/get-latest/cliff.toml
Normal file
@ -0,0 +1,112 @@
|
||||
# git-cliff ~ configuration file
|
||||
# https://git-cliff.org/docs/configuration
|
||||
|
||||
|
||||
[changelog]
|
||||
# A Tera template to be rendered as the changelog's footer.
|
||||
# See https://keats.github.io/tera/docs/#introduction
|
||||
header = """# 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]
|
||||
"""
|
||||
# A Tera template to be rendered for each release in the changelog.
|
||||
# See https://keats.github.io/tera/docs/#introduction
|
||||
body = """
|
||||
|
||||
{%- if version %}
|
||||
## [{{ version }}]\
|
||||
{%- if previous.version == version -%}\
|
||||
(<REPO>/releases/tag/{{ version }})\
|
||||
{%- else -%}\
|
||||
(<REPO>/compare/{{ previous.version }}...{{ version }})\
|
||||
{% endif %} \
|
||||
- {{ timestamp | date(format="%Y-%m-%d") }}
|
||||
{% for group, commits in commits | group_by(attribute="group") %}
|
||||
### {{ group | upper_first }}
|
||||
|
||||
{% for commit in commits %}
|
||||
{%- if commit.scope -%}
|
||||
- *({{commit.scope}})* {% if commit.breaking %}[**breaking**] {% endif %}\
|
||||
{{ commit.message }}\
|
||||
{%- if commit.links %} \
|
||||
({% for link in commit.links %}[{{link.text}}]({{link.href}}) {% endfor -%})\
|
||||
{% endif %}
|
||||
{% else -%}
|
||||
- {% if commit.breaking %}[**breaking**] {% endif %}{{ commit.message }}
|
||||
{% endif -%}
|
||||
{% endfor -%}
|
||||
{% endfor %}\
|
||||
{%- else -%}
|
||||
{% endif -%}
|
||||
"""
|
||||
# A Tera template to be rendered as the changelog's footer.
|
||||
# See https://keats.github.io/tera/docs/#introduction
|
||||
footer = ""
|
||||
# Remove leading and trailing whitespaces from the changelog's body.
|
||||
trim = true
|
||||
# Render body even when there are no releases to process.
|
||||
render_always = true
|
||||
# An array of regex based postprocessors to modify the changelog.
|
||||
postprocessors = [
|
||||
# Replace the placeholder <REPO> with a URL.
|
||||
{ pattern = '<REPO>', replace = "https://github.com/tauri-apps/cef-rs" },
|
||||
{ pattern = '\[[\w\-]+-v([0-9.+]+)\]', replace = "[${1}]" },
|
||||
]
|
||||
# render body even when there are no releases to process
|
||||
# render_always = true
|
||||
# output file path
|
||||
# output = "test.md"
|
||||
|
||||
[git]
|
||||
# Parse commits according to the conventional commits specification.
|
||||
# See https://www.conventionalcommits.org
|
||||
conventional_commits = true
|
||||
# Exclude commits that do not match the conventional commits specification.
|
||||
filter_unconventional = true
|
||||
# Require all commits to be conventional.
|
||||
# Takes precedence over filter_unconventional.
|
||||
require_conventional = false
|
||||
# Split commits on newlines, treating each line as an individual commit.
|
||||
split_commits = false
|
||||
# An array of regex based parsers to modify commit messages prior to further processing.
|
||||
commit_preprocessors = [
|
||||
# Replace issue numbers with link templates to be updated in `changelog.postprocessors`.
|
||||
{ pattern = '(\w+\s)?#([0-9]+)', replace = "${1}[#${2}](<REPO>/issues/${2})" },
|
||||
{ pattern = '\((\w+\s)?#([0-9]+)\)', replace = "([#${2}](<REPO>/issues/${2}))" },
|
||||
# Check spelling of the commit message using https://github.com/crate-ci/typos.
|
||||
# If the spelling is incorrect, it will be fixed automatically.
|
||||
#{ pattern = '.*', replace_command = 'typos --write-changes -' },
|
||||
]
|
||||
# Prevent commits that are breaking from being excluded by commit parsers.
|
||||
protect_breaking_commits = false
|
||||
# An array of regex based parsers for extracting data from the commit message.
|
||||
# Assigns commits to groups.
|
||||
# Optionally sets the commit's scope and can decide to exclude commits from further processing.
|
||||
commit_parsers = [
|
||||
{ message = "^feat", group = "added" },
|
||||
{ message = "^changed", group = "changed" },
|
||||
{ message = "^deprecated", group = "deprecated" },
|
||||
{ message = "^fix", group = "fixed" },
|
||||
{ message = "^security", group = "security" },
|
||||
{ message = "^.*", group = "other" },
|
||||
]
|
||||
# Exclude commits that are not matched by any commit parser.
|
||||
filter_commits = false
|
||||
# An array of link parsers for extracting external references, and turning them into URLs, using regex.
|
||||
link_parsers = []
|
||||
# Include only the tags that belong to the current branch.
|
||||
use_branch_tags = false
|
||||
# Order releases topologically instead of chronologically.
|
||||
topo_order = false
|
||||
# Order releases topologically instead of chronologically.
|
||||
topo_order_commits = true
|
||||
# Order of commits in each group/release within the changelog.
|
||||
# Allowed values: newest, oldest
|
||||
sort_commits = "newest"
|
||||
# Process submodules commits
|
||||
recurse_submodules = false
|
||||
235
browser/get-latest/src/main.rs
Normal file
235
browser/get-latest/src/main.rs
Normal file
@ -0,0 +1,235 @@
|
||||
#![doc = include_str!("../README.md")]
|
||||
|
||||
#[macro_use]
|
||||
extern crate thiserror;
|
||||
|
||||
use clap::Parser;
|
||||
use download_cef::{CefIndex, Channel, LINUX_TARGETS, MACOS_TARGETS, WINDOWS_TARGETS};
|
||||
use git_cliff::args::*;
|
||||
use regex::Regex;
|
||||
use semver::{BuildMetadata, Version};
|
||||
use std::{
|
||||
env, fs,
|
||||
io::Write,
|
||||
path::PathBuf,
|
||||
process::{Command, ExitStatus},
|
||||
sync::OnceLock,
|
||||
};
|
||||
use toml_edit::{value, DocumentMut};
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
enum Error {
|
||||
#[error("Download error: {0}")]
|
||||
Download(#[from] download_cef::Error),
|
||||
#[error("Invalid regex pattern: {0}")]
|
||||
InvalidRegexPattern(#[from] regex::Error),
|
||||
#[error("Invalid version: {0}")]
|
||||
InvalidVersion(#[from] semver::Error),
|
||||
#[error("No versions found")]
|
||||
NoVersionsFound,
|
||||
#[error("I/O error: {0}")]
|
||||
Io(#[from] std::io::Error),
|
||||
#[error("Invalid manifest file: {0}")]
|
||||
InvalidManifest(#[from] toml_edit::TomlError),
|
||||
#[error("Error invoking git: {0:?}")]
|
||||
GitInvocation(ExitStatus),
|
||||
#[error("Error running git-cliff: {0:?}")]
|
||||
InvalidGitCliffArgs(#[from] clap::Error),
|
||||
#[error("Error updating change log: {0:?}")]
|
||||
UpdateChangeLog(#[from] git_cliff_core::error::Error),
|
||||
}
|
||||
|
||||
type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
fn default_download_url() -> &'static str {
|
||||
static DEFAULT_DOWNLOAD_URL: OnceLock<String> = OnceLock::new();
|
||||
DEFAULT_DOWNLOAD_URL
|
||||
.get_or_init(|| download_cef::default_download_url())
|
||||
.as_str()
|
||||
}
|
||||
|
||||
#[derive(Parser)]
|
||||
#[command(about, long_about = None)]
|
||||
struct Args {
|
||||
#[arg(short, long, default_value = default_download_url())]
|
||||
mirror_url: String,
|
||||
#[arg(short, long, default_value = "stable")]
|
||||
channel: Channel,
|
||||
#[arg(short, long)]
|
||||
update_version: bool,
|
||||
}
|
||||
|
||||
fn main() -> Result<()> {
|
||||
let pattern = Regex::new(r"^([^+]+)(:?\+.+)?$")?;
|
||||
|
||||
let args = Args::parse();
|
||||
let channel = args.channel;
|
||||
let url = args.mirror_url.as_str();
|
||||
|
||||
let index = CefIndex::download_from(url)?;
|
||||
let latest_versions: Vec<_> = LINUX_TARGETS
|
||||
.iter()
|
||||
.chain(MACOS_TARGETS.iter())
|
||||
.chain(WINDOWS_TARGETS.iter())
|
||||
.map(|target| {
|
||||
index
|
||||
.platform(target)
|
||||
.and_then(|platform| platform.latest(channel.clone()))
|
||||
.map(|version| pattern.replace(&version.cef_version, "$1"))
|
||||
})
|
||||
.collect::<download_cef::Result<Vec<_>>>()?;
|
||||
let latest_versions = latest_versions
|
||||
.into_iter()
|
||||
.map(|version| Ok(Version::parse(&version)?))
|
||||
.collect::<Result<Vec<_>>>()?;
|
||||
let latest_version = latest_versions
|
||||
.into_iter()
|
||||
.min()
|
||||
.ok_or(Error::NoVersionsFound)?;
|
||||
|
||||
println!("Latest available {channel} version: {latest_version}");
|
||||
|
||||
if args.update_version {
|
||||
let current_version =
|
||||
Version::parse(&download_cef::default_version(env!("CARGO_PKG_VERSION")))?;
|
||||
if current_version < latest_version {
|
||||
let latest_build = BuildMetadata::new(&latest_version.to_string())?;
|
||||
let mut next_version = Version::parse(env!("CARGO_PKG_VERSION"))?;
|
||||
if next_version.major < latest_version.major {
|
||||
next_version.major = latest_version.major;
|
||||
next_version.minor = 0;
|
||||
} else {
|
||||
next_version.minor += 1;
|
||||
}
|
||||
next_version.patch = 0;
|
||||
next_version.build = latest_build.clone();
|
||||
|
||||
let mut manifest = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
|
||||
manifest.pop();
|
||||
let manifest = manifest.join("Cargo.toml");
|
||||
let mut doc = fs::read_to_string(&manifest)?.parse::<DocumentMut>()?;
|
||||
doc["workspace"]["package"]["version"] = value(next_version.to_string());
|
||||
let workspace_version = Version {
|
||||
build: BuildMetadata::EMPTY,
|
||||
..next_version.clone()
|
||||
};
|
||||
doc["workspace"]["dependencies"]["cef-dll-sys"]["version"] =
|
||||
value(workspace_version.to_string());
|
||||
fs::write(&manifest, doc.to_string().as_bytes())?;
|
||||
|
||||
if let Ok(output) = env::var("GITHUB_OUTPUT") {
|
||||
let mut output = fs::OpenOptions::new()
|
||||
.create(true)
|
||||
.append(true)
|
||||
.open(output)?;
|
||||
|
||||
let commit_message =
|
||||
format!("chore(release): update CEF version to {latest_version}");
|
||||
writeln!(output, "commit-message={commit_message}",)?;
|
||||
|
||||
let output = Command::new("git")
|
||||
.args(["commit", "-a", "-m", commit_message.as_str()])
|
||||
.output()?;
|
||||
std::io::stdout().write_all(&output.stdout)?;
|
||||
std::io::stderr().write_all(&output.stderr)?;
|
||||
if !output.status.success() {
|
||||
return Err(Error::GitInvocation(output.status));
|
||||
}
|
||||
|
||||
let export_cef_dir_tag = format!("export-cef-dir-v{next_version}");
|
||||
let output = Command::new("git")
|
||||
.args(["tag", "--no-sign", "-f", export_cef_dir_tag.as_str()])
|
||||
.output()?;
|
||||
std::io::stdout().write_all(&output.stdout)?;
|
||||
std::io::stderr().write_all(&output.stderr)?;
|
||||
if !output.status.success() {
|
||||
return Err(Error::GitInvocation(output.status));
|
||||
}
|
||||
|
||||
let cef_dll_sys_tag = format!("cef-dll-sys-v{next_version}");
|
||||
let output = Command::new("git")
|
||||
.args(["tag", "--no-sign", "-f", cef_dll_sys_tag.as_str()])
|
||||
.output()?;
|
||||
std::io::stdout().write_all(&output.stdout)?;
|
||||
std::io::stderr().write_all(&output.stderr)?;
|
||||
if !output.status.success() {
|
||||
return Err(Error::GitInvocation(output.status));
|
||||
}
|
||||
|
||||
let cef_tag = format!("cef-v{next_version}");
|
||||
let output = Command::new("git")
|
||||
.args(["tag", "--no-sign", "-f", cef_tag.as_str()])
|
||||
.output()?;
|
||||
std::io::stdout().write_all(&output.stdout)?;
|
||||
std::io::stderr().write_all(&output.stderr)?;
|
||||
if !output.status.success() {
|
||||
return Err(Error::GitInvocation(output.status));
|
||||
}
|
||||
|
||||
let mut config_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
|
||||
config_path.push("cliff.toml");
|
||||
|
||||
let common_opts = ["--strip", "footer", "--include-path", "Cargo.toml"];
|
||||
|
||||
let export_cef_dir_opts = Opt {
|
||||
config: config_path.clone(),
|
||||
range: Some("export-cef-dir-v138.2.0+138.0.21..".to_string()),
|
||||
..Opt::try_parse_from(
|
||||
common_opts.iter().chain(
|
||||
[
|
||||
"--include-path",
|
||||
"export-cef-dir/*",
|
||||
"--tag-pattern",
|
||||
"^export-cef-dir-v",
|
||||
"--output",
|
||||
"export-cef-dir/CHANGELOG.md",
|
||||
]
|
||||
.iter(),
|
||||
),
|
||||
)?
|
||||
};
|
||||
git_cliff::run(export_cef_dir_opts)?;
|
||||
|
||||
let cef_dll_sys_opts = Opt {
|
||||
config: config_path.clone(),
|
||||
range: Some("cef-dll-sys-v138.2.0+138.0.21..".to_string()),
|
||||
..Opt::try_parse_from(
|
||||
common_opts.iter().chain(
|
||||
[
|
||||
"--include-path",
|
||||
"sys/*",
|
||||
"--tag-pattern",
|
||||
"^cef-dll-sys-v",
|
||||
"--output",
|
||||
"sys/CHANGELOG.md",
|
||||
]
|
||||
.iter(),
|
||||
),
|
||||
)?
|
||||
};
|
||||
git_cliff::run(cef_dll_sys_opts)?;
|
||||
|
||||
let cef_opts = Opt {
|
||||
config: config_path,
|
||||
range: Some("cef-v138.2.0+138.0.21..".to_string()),
|
||||
..Opt::try_parse_from(
|
||||
common_opts.iter().chain(
|
||||
[
|
||||
"--include-path",
|
||||
"cef/*",
|
||||
"--tag-pattern",
|
||||
"^cef-v",
|
||||
"--output",
|
||||
"cef/CHANGELOG.md",
|
||||
]
|
||||
.iter(),
|
||||
),
|
||||
)?
|
||||
};
|
||||
git_cliff::run(cef_opts)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
202
browser/sys/CHANGELOG.md
Normal file
202
browser/sys/CHANGELOG.md
Normal file
@ -0,0 +1,202 @@
|
||||
# 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.2.0+140.1.14](https://github.com/tauri-apps/cef-rs/compare/cef-dll-sys-v140.1.0+140.1.13...cef-dll-sys-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-dll-sys-v140.0.0+140.1.13...cef-dll-sys-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-dll-sys-v139.8.0+139.0.40...cef-dll-sys-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-dll-sys-v139.7.2+139.0.38...cef-dll-sys-v139.8.0+139.0.40) - 2025-09-12
|
||||
|
||||
### Other
|
||||
|
||||
- update bindings
|
||||
- *(release)* update CEF version to 139.0.40
|
||||
|
||||
## [139.7.2+139.0.38](https://github.com/tauri-apps/cef-rs/compare/cef-dll-sys-v139.7.1+139.0.38...cef-dll-sys-v139.7.2+139.0.38) - 2025-09-08
|
||||
|
||||
### Fixed
|
||||
|
||||
- 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-dll-sys-v139.7.0+139.0.38...cef-dll-sys-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-dll-sys-v139.6.0+139.0.37...cef-dll-sys-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-dll-sys-v139.5.0+139.0.30...cef-dll-sys-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-dll-sys-v139.4.0+139.0.28...cef-dll-sys-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-dll-sys-v139.3.0+139.0.26...cef-dll-sys-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-dll-sys-v139.2.1+139.0.23...cef-dll-sys-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-dll-sys-v139.2.0+139.0.23...cef-dll-sys-v139.2.1+139.0.23) - 2025-08-16
|
||||
|
||||
### Other
|
||||
|
||||
- release
|
||||
|
||||
## [139.2.0+139.0.23](https://github.com/tauri-apps/cef-rs/compare/cef-dll-sys-v139.1.0+139.0.20...cef-dll-sys-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-dll-sys-v139.0.1+139.0.17...cef-dll-sys-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-dll-sys-v139.0.0+139.0.17...cef-dll-sys-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-dll-sys-v138.9.0+138.0.36...cef-dll-sys-v139.0.0+139.0.17) - 2025-08-08
|
||||
|
||||
### Other
|
||||
|
||||
- update bindings
|
||||
- *(release)* update CEF version to 139.0.17
|
||||
|
||||
## [138.9.0+138.0.36](https://github.com/tauri-apps/cef-rs/compare/cef-dll-sys-v138.8.0+138.0.34...cef-dll-sys-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-dll-sys-v138.7.1+138.0.33...cef-dll-sys-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-dll-sys-v138.7.0+138.0.33...cef-dll-sys-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-dll-sys-v138.6.1+138.0.27...cef-dll-sys-v138.7.0+138.0.33) - 2025-07-29
|
||||
|
||||
### Other
|
||||
|
||||
- update bindings
|
||||
- *(release)* update CEF version to 138.0.33
|
||||
|
||||
## [138.6.1+138.0.27](https://github.com/tauri-apps/cef-rs/compare/cef-dll-sys-v138.6.0+138.0.27...cef-dll-sys-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-dll-sys-v138.5.1+138.0.26...cef-dll-sys-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-dll-sys-v138.5.0+138.0.26...cef-dll-sys-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-dll-sys-v138.4.0+138.0.25...cef-dll-sys-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-dll-sys-v138.3.0+138.0.23...cef-dll-sys-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-dll-sys-v138.2.2+138.0.21...cef-dll-sys-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-dll-sys-v138.2.1+138.0.21...cef-dll-sys-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-dll-sys-v138.2.0+138.0.21...cef-dll-sys-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)
|
||||
|
||||
26
browser/sys/Cargo.toml
Normal file
26
browser/sys/Cargo.toml
Normal file
@ -0,0 +1,26 @@
|
||||
[package]
|
||||
name = "cef-dll-sys"
|
||||
description = "cef-rs sys crate"
|
||||
links = "cef_dll_wrapper"
|
||||
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
authors.workspace = true
|
||||
repository.workspace = true
|
||||
|
||||
[lib]
|
||||
doctest = false
|
||||
|
||||
[features]
|
||||
dox = []
|
||||
sandbox = []
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
features = ["dox"]
|
||||
|
||||
[build-dependencies]
|
||||
anyhow.workspace = true
|
||||
cmake.workspace = true
|
||||
download-cef.workspace = true
|
||||
serde_json.workspace = true
|
||||
4
browser/sys/README.md
Normal file
4
browser/sys/README.md
Normal file
@ -0,0 +1,4 @@
|
||||
# cef-dll-sys
|
||||
|
||||
Generated bindings for the prebuilt [Chromium Embedded Framework](https://github.com/chromiumembedded/cef)
|
||||
C API on any supported platform.
|
||||
131
browser/sys/build.rs
Normal file
131
browser/sys/build.rs
Normal file
@ -0,0 +1,131 @@
|
||||
#[cfg(not(feature = "dox"))]
|
||||
fn main() -> anyhow::Result<()> {
|
||||
use download_cef::{CefIndex, OsAndArch};
|
||||
use std::{env, fs, path::PathBuf};
|
||||
|
||||
println!("cargo::rerun-if-changed=build.rs");
|
||||
|
||||
let target = env::var("TARGET")?;
|
||||
let os_arch = OsAndArch::try_from(target.as_str())?;
|
||||
|
||||
println!("cargo::rerun-if-env-changed=FLATPAK");
|
||||
println!("cargo::rerun-if-env-changed=CEF_PATH");
|
||||
let cef_path_env = env::var("FLATPAK")
|
||||
.map(|_| String::from("/usr/lib"))
|
||||
.or_else(|_| env::var("CEF_PATH"));
|
||||
|
||||
let cef_dir = match cef_path_env {
|
||||
Ok(cef_path) => {
|
||||
// Allow overriding the CEF path with environment variables.
|
||||
println!("Using CEF path from environment: {cef_path}");
|
||||
download_cef::check_archive_json(&env::var("CARGO_PKG_VERSION")?, &cef_path)?;
|
||||
PathBuf::from(cef_path)
|
||||
}
|
||||
Err(_) => {
|
||||
let out_dir = PathBuf::from(env::var("OUT_DIR")?);
|
||||
let cef_dir = os_arch.to_string();
|
||||
let cef_dir = out_dir.join(&cef_dir);
|
||||
|
||||
if !fs::exists(&cef_dir)? {
|
||||
let cef_version = download_cef::default_version(&env::var("CARGO_PKG_VERSION")?);
|
||||
let index = CefIndex::download()?;
|
||||
let platform = index.platform(&target)?;
|
||||
let version = platform.version(&cef_version)?;
|
||||
|
||||
let archive = version.download_archive(&out_dir, false)?;
|
||||
let extracted_dir =
|
||||
download_cef::extract_target_archive(&target, &archive, &out_dir, false)?;
|
||||
if extracted_dir != cef_dir {
|
||||
return Err(anyhow::anyhow!(
|
||||
"extracted dir {extracted_dir:?} does not match cef_dir {cef_dir:?}",
|
||||
));
|
||||
}
|
||||
|
||||
version.write_archive_json(extracted_dir)?;
|
||||
}
|
||||
|
||||
cef_dir
|
||||
}
|
||||
};
|
||||
|
||||
let cef_dir = cef_dir.display().to_string();
|
||||
|
||||
println!("cargo::metadata=CEF_DIR={cef_dir}");
|
||||
println!("cargo::rustc-link-search=native={cef_dir}");
|
||||
|
||||
let mut cef_dll_wrapper = cmake::Config::new(&cef_dir);
|
||||
cef_dll_wrapper
|
||||
.generator("Ninja")
|
||||
.profile("RelWithDebInfo")
|
||||
.build_target("libcef_dll_wrapper");
|
||||
|
||||
let project_arch = match os_arch.arch {
|
||||
"aarch64" => "arm64",
|
||||
arch => arch,
|
||||
};
|
||||
|
||||
let sandbox = if cfg!(feature = "sandbox") {
|
||||
"ON"
|
||||
} else {
|
||||
"OFF"
|
||||
};
|
||||
|
||||
match os_arch.os {
|
||||
"linux" => {
|
||||
println!("cargo::rustc-link-lib=dylib=cef");
|
||||
}
|
||||
"windows" => {
|
||||
let sdk_libs = [
|
||||
"comctl32.lib",
|
||||
"delayimp.lib",
|
||||
"mincore.lib",
|
||||
"powrprof.lib",
|
||||
"propsys.lib",
|
||||
"runtimeobject.lib",
|
||||
"setupapi.lib",
|
||||
"shcore.lib",
|
||||
"shell32.lib",
|
||||
"shlwapi.lib",
|
||||
"user32.lib",
|
||||
"version.lib",
|
||||
"wbemuuid.lib",
|
||||
"winmm.lib",
|
||||
]
|
||||
.join(" ");
|
||||
|
||||
let build_dir = cef_dll_wrapper
|
||||
.define("CMAKE_MSVC_RUNTIME_LIBRARY", "MultiThreaded")
|
||||
.define("CMAKE_OBJECT_PATH_MAX", "500")
|
||||
.define("CMAKE_STATIC_LINKER_FLAGS", &sdk_libs)
|
||||
.define("PROJECT_ARCH", project_arch)
|
||||
.define("USE_SANDBOX", sandbox)
|
||||
.build()
|
||||
.display()
|
||||
.to_string();
|
||||
|
||||
println!("cargo::rustc-link-search=native={build_dir}/build/libcef_dll_wrapper");
|
||||
println!("cargo::rustc-link-lib=static=libcef_dll_wrapper");
|
||||
|
||||
println!("cargo::rustc-link-lib=dylib=libcef");
|
||||
}
|
||||
"macos" => {
|
||||
println!("cargo::rustc-link-lib=framework=AppKit");
|
||||
|
||||
let build_dir = cef_dll_wrapper
|
||||
.no_default_flags(true)
|
||||
.define("PROJECT_ARCH", project_arch)
|
||||
.define("USE_SANDBOX", sandbox)
|
||||
.build()
|
||||
.display()
|
||||
.to_string();
|
||||
println!("cargo::rustc-link-search=native={build_dir}/build/libcef_dll_wrapper");
|
||||
println!("cargo::rustc-link-lib=static=cef_dll_wrapper");
|
||||
}
|
||||
os => unimplemented!("unknown target {os}"),
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(feature = "dox")]
|
||||
fn main() {}
|
||||
15978
browser/sys/src/bindings/aarch64_apple_darwin.rs
Normal file
15978
browser/sys/src/bindings/aarch64_apple_darwin.rs
Normal file
File diff suppressed because one or more lines are too long
16054
browser/sys/src/bindings/aarch64_pc_windows_msvc.rs
Normal file
16054
browser/sys/src/bindings/aarch64_pc_windows_msvc.rs
Normal file
File diff suppressed because one or more lines are too long
16012
browser/sys/src/bindings/aarch64_unknown_linux_gnu.rs
Normal file
16012
browser/sys/src/bindings/aarch64_unknown_linux_gnu.rs
Normal file
File diff suppressed because one or more lines are too long
15999
browser/sys/src/bindings/arm_unknown_linux_gnueabi.rs
Normal file
15999
browser/sys/src/bindings/arm_unknown_linux_gnueabi.rs
Normal file
File diff suppressed because one or more lines are too long
16274
browser/sys/src/bindings/i686_pc_windows_msvc.rs
Normal file
16274
browser/sys/src/bindings/i686_pc_windows_msvc.rs
Normal file
File diff suppressed because one or more lines are too long
39
browser/sys/src/bindings/mod.rs
Normal file
39
browser/sys/src/bindings/mod.rs
Normal file
@ -0,0 +1,39 @@
|
||||
#[cfg(all(target_os = "linux", target_arch = "x86_64"))]
|
||||
mod x86_64_unknown_linux_gnu;
|
||||
#[cfg(all(target_os = "linux", target_arch = "x86_64"))]
|
||||
pub use x86_64_unknown_linux_gnu::*;
|
||||
|
||||
#[cfg(all(target_os = "linux", target_arch = "aarch64"))]
|
||||
mod aarch64_unknown_linux_gnu;
|
||||
#[cfg(all(target_os = "linux", target_arch = "aarch64"))]
|
||||
pub use aarch64_unknown_linux_gnu::*;
|
||||
|
||||
#[cfg(all(target_os = "linux", target_arch = "arm"))]
|
||||
mod arm_unknown_linux_gnueabi;
|
||||
#[cfg(all(target_os = "linux", target_arch = "arm"))]
|
||||
pub use arm_unknown_linux_gnueabi::*;
|
||||
|
||||
#[cfg(all(target_os = "windows", target_arch = "x86_64"))]
|
||||
mod x86_64_pc_windows_msvc;
|
||||
#[cfg(all(target_os = "windows", target_arch = "x86_64"))]
|
||||
pub use x86_64_pc_windows_msvc::*;
|
||||
|
||||
#[cfg(all(target_os = "windows", target_arch = "x86"))]
|
||||
mod i686_pc_windows_msvc;
|
||||
#[cfg(all(target_os = "windows", target_arch = "x86"))]
|
||||
pub use i686_pc_windows_msvc::*;
|
||||
|
||||
#[cfg(all(target_os = "windows", target_arch = "aarch64"))]
|
||||
mod aarch64_pc_windows_msvc;
|
||||
#[cfg(all(target_os = "windows", target_arch = "aarch64"))]
|
||||
pub use aarch64_pc_windows_msvc::*;
|
||||
|
||||
#[cfg(all(target_os = "macos", target_arch = "x86_64"))]
|
||||
mod x86_64_apple_darwin;
|
||||
#[cfg(all(target_os = "macos", target_arch = "x86_64"))]
|
||||
pub use x86_64_apple_darwin::*;
|
||||
|
||||
#[cfg(all(target_os = "macos", target_arch = "aarch64"))]
|
||||
mod aarch64_apple_darwin;
|
||||
#[cfg(all(target_os = "macos", target_arch = "aarch64"))]
|
||||
pub use aarch64_apple_darwin::*;
|
||||
15978
browser/sys/src/bindings/x86_64_apple_darwin.rs
Normal file
15978
browser/sys/src/bindings/x86_64_apple_darwin.rs
Normal file
File diff suppressed because one or more lines are too long
16054
browser/sys/src/bindings/x86_64_pc_windows_msvc.rs
Normal file
16054
browser/sys/src/bindings/x86_64_pc_windows_msvc.rs
Normal file
File diff suppressed because one or more lines are too long
16012
browser/sys/src/bindings/x86_64_unknown_linux_gnu.rs
Normal file
16012
browser/sys/src/bindings/x86_64_unknown_linux_gnu.rs
Normal file
File diff suppressed because one or more lines are too long
91
browser/sys/src/lib.rs
Normal file
91
browser/sys/src/lib.rs
Normal file
@ -0,0 +1,91 @@
|
||||
#![doc = include_str!("../README.md")]
|
||||
|
||||
#[allow(
|
||||
non_snake_case,
|
||||
non_camel_case_types,
|
||||
non_upper_case_globals,
|
||||
dead_code,
|
||||
clippy::all
|
||||
)]
|
||||
mod bindings;
|
||||
pub use bindings::*;
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
impl Default for HWND {
|
||||
fn default() -> Self {
|
||||
Self(std::ptr::null_mut())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
impl Default for HINSTANCE {
|
||||
fn default() -> Self {
|
||||
Self(std::ptr::null_mut())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
pub const FRAMEWORK_PATH: &str =
|
||||
"Chromium Embedded Framework.framework/Chromium Embedded Framework";
|
||||
|
||||
use std::{
|
||||
env::{
|
||||
self,
|
||||
consts::{ARCH, OS},
|
||||
},
|
||||
fs,
|
||||
path::PathBuf,
|
||||
};
|
||||
|
||||
pub fn get_cef_dir() -> Option<PathBuf> {
|
||||
let cef_path_env = env::var("FLATPAK")
|
||||
.map(|_| String::from("/usr/lib"))
|
||||
.or_else(|_| env::var("CEF_PATH"));
|
||||
|
||||
match cef_path_env {
|
||||
Ok(cef_path) => {
|
||||
// Allow overriding the CEF path with environment variables.
|
||||
PathBuf::from(cef_path).canonicalize().ok()
|
||||
}
|
||||
Err(_) => {
|
||||
let out_dir = PathBuf::from(env::var("OUT_DIR").ok()?);
|
||||
let cef_dir = format!("cef_{OS}_{ARCH}");
|
||||
let cef_dir = out_dir.join(&cef_dir).canonicalize().ok()?;
|
||||
fs::exists(&cef_dir).ok()?.then_some(cef_dir)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_cef_dir() {
|
||||
let _ = get_cef_dir().expect("CEF not found");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_init() {
|
||||
use std::ptr::*;
|
||||
|
||||
unsafe {
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
use std::os::unix::ffi::OsStrExt;
|
||||
|
||||
let cef_dir = get_cef_dir().expect("CEF not found");
|
||||
let framework_dir = cef_dir
|
||||
.join(FRAMEWORK_PATH)
|
||||
.canonicalize()
|
||||
.expect("failed to get framework path");
|
||||
let framework_dir = std::ffi::CString::new(framework_dir.as_os_str().as_bytes())
|
||||
.expect("invalid path");
|
||||
|
||||
assert_eq!(cef_load_library(framework_dir.as_ptr().cast()), 1);
|
||||
}
|
||||
|
||||
assert_eq!(cef_initialize(null(), null(), null_mut(), null_mut()), 0);
|
||||
};
|
||||
}
|
||||
}
|
||||
33
browser/sys/wrapper.h
Normal file
33
browser/sys/wrapper.h
Normal file
@ -0,0 +1,33 @@
|
||||
#ifndef CEF_RUST_SYS_WRAPPER_H
|
||||
#define CEF_RUST_SYS_WRAPPER_H
|
||||
|
||||
#ifdef __APPLE__
|
||||
#include "include/wrapper/cef_library_loader.h"
|
||||
#include "include/cef_sandbox_mac.h"
|
||||
#endif
|
||||
|
||||
#include "include/cef_api_hash.h"
|
||||
#include "include/cef_version.h"
|
||||
|
||||
#include "include/capi/cef_base_capi.h"
|
||||
|
||||
#include "include/capi/cef_app_capi.h"
|
||||
#include "include/capi/cef_client_capi.h"
|
||||
#include "include/capi/cef_urlrequest_capi.h"
|
||||
|
||||
#include "include/capi/views/cef_layout_capi.h"
|
||||
#include "include/capi/views/cef_box_layout_capi.h"
|
||||
#include "include/capi/views/cef_fill_layout_capi.h"
|
||||
|
||||
#include "include/capi/views/cef_button_capi.h"
|
||||
#include "include/capi/views/cef_label_button_capi.h"
|
||||
#include "include/capi/views/cef_menu_button_capi.h"
|
||||
|
||||
#include "include/capi/views/cef_textfield_capi.h"
|
||||
|
||||
#include "include/capi/views/cef_browser_view_capi.h"
|
||||
#include "include/capi/views/cef_scroll_view_capi.h"
|
||||
|
||||
#include "include/capi/views/cef_window_capi.h"
|
||||
|
||||
#endif
|
||||
22
browser/update-bindings/Cargo.toml
Normal file
22
browser/update-bindings/Cargo.toml
Normal file
@ -0,0 +1,22 @@
|
||||
[package]
|
||||
name = "update-bindings"
|
||||
publish = false
|
||||
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
authors.workspace = true
|
||||
repository.workspace = true
|
||||
|
||||
[dependencies]
|
||||
download-cef.workspace = true
|
||||
|
||||
bindgen.workspace = true
|
||||
clap.workspace = true
|
||||
convert_case.workspace = true
|
||||
proc-macro2.workspace = true
|
||||
quote.workspace = true
|
||||
regex.workspace = true
|
||||
semver.workspace = true
|
||||
syn.workspace = true
|
||||
thiserror.workspace = true
|
||||
8
browser/update-bindings/README.md
Normal file
8
browser/update-bindings/README.md
Normal file
@ -0,0 +1,8 @@
|
||||
# update-bindings
|
||||
|
||||
Download the prebuilt [Chromium Embedded Framework](https://github.com/chromiumembedded/cef)
|
||||
archive on any supported platform and run `bindgen` on the C API for the `cef-dll-sys` crate,
|
||||
then regenerate the safe bindings in the `cef` crate.
|
||||
|
||||
You can find the latest version of the prebuilt CEF archives on the [Chromium Embedded Framework
|
||||
(CEF) Automated Builds](https://cef-builds.spotifycdn.com/index.html).
|
||||
2
browser/update-bindings/build.rs
Normal file
2
browser/update-bindings/build.rs
Normal file
@ -0,0 +1,2 @@
|
||||
// Empty build.rs file to set OUT_DIR environment variable
|
||||
fn main() {}
|
||||
39
browser/update-bindings/src/dirs.rs
Normal file
39
browser/update-bindings/src/dirs.rs
Normal file
@ -0,0 +1,39 @@
|
||||
use std::{convert::From, env, path::PathBuf};
|
||||
|
||||
pub fn get_manifest_dir() -> PathBuf {
|
||||
PathBuf::from(env!("CARGO_MANIFEST_DIR"))
|
||||
}
|
||||
|
||||
pub fn get_out_dir() -> PathBuf {
|
||||
PathBuf::from(env!("OUT_DIR"))
|
||||
}
|
||||
|
||||
pub fn get_cef_root(os: &str, arch: &str) -> PathBuf {
|
||||
env::var(format!("CEF_PATH_{os}_{arch}"))
|
||||
.map(PathBuf::from)
|
||||
.unwrap_or_else(|_| {
|
||||
let mut out_dir = get_out_dir();
|
||||
out_dir.push(format!("cef_{os}_{arch}"));
|
||||
out_dir
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get_sys_dir() -> crate::Result<PathBuf> {
|
||||
let manifest_dir = get_manifest_dir();
|
||||
let mut bindings_dir = get_manifest_dir().parent().map_or_else(
|
||||
|| Err(crate::Error::MissingParent(manifest_dir)),
|
||||
|parent| Ok(PathBuf::from(parent)),
|
||||
)?;
|
||||
bindings_dir.push("sys");
|
||||
Ok(bindings_dir)
|
||||
}
|
||||
|
||||
pub fn get_cef_dir() -> crate::Result<PathBuf> {
|
||||
let manifest_dir = get_manifest_dir();
|
||||
let mut webview2_com_dir = get_manifest_dir().parent().map_or_else(
|
||||
|| Err(crate::Error::MissingParent(manifest_dir)),
|
||||
|parent| Ok(PathBuf::from(parent)),
|
||||
)?;
|
||||
webview2_com_dir.push("cef");
|
||||
Ok(webview2_com_dir)
|
||||
}
|
||||
102
browser/update-bindings/src/main.rs
Normal file
102
browser/update-bindings/src/main.rs
Normal file
@ -0,0 +1,102 @@
|
||||
#![doc = include_str!("../README.md")]
|
||||
|
||||
#[macro_use]
|
||||
extern crate thiserror;
|
||||
|
||||
use clap::Parser;
|
||||
use download_cef::DEFAULT_TARGET;
|
||||
use std::{fs, io::Read, path::Path, sync::OnceLock};
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum Error {
|
||||
#[error("Missing Parent")]
|
||||
MissingParent(std::path::PathBuf),
|
||||
#[error(transparent)]
|
||||
Io(#[from] std::io::Error),
|
||||
#[error(transparent)]
|
||||
Bindgen(#[from] bindgen::BindgenError),
|
||||
#[error(transparent)]
|
||||
Regex(#[from] regex::Error),
|
||||
#[error(transparent)]
|
||||
Syn(#[from] syn::Error),
|
||||
#[error("Parsing bindgen output failed")]
|
||||
Parse(#[from] parse_tree::Unrecognized),
|
||||
#[error("Missing Path")]
|
||||
MissingPath(std::path::PathBuf),
|
||||
}
|
||||
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
mod dirs;
|
||||
mod parse_tree;
|
||||
mod upgrade;
|
||||
|
||||
fn default_version() -> &'static str {
|
||||
static DEFAULT_VERSION: OnceLock<String> = OnceLock::new();
|
||||
DEFAULT_VERSION
|
||||
.get_or_init(|| download_cef::default_version(env!("CARGO_PKG_VERSION")))
|
||||
.as_str()
|
||||
}
|
||||
|
||||
fn default_download_url() -> &'static str {
|
||||
static DEFAULT_DOWNLOAD_URL: OnceLock<String> = OnceLock::new();
|
||||
DEFAULT_DOWNLOAD_URL
|
||||
.get_or_init(|| download_cef::default_download_url())
|
||||
.as_str()
|
||||
}
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
#[command(about, long_about = None)]
|
||||
struct Args {
|
||||
#[arg(short, long)]
|
||||
download: bool,
|
||||
#[arg(short, long)]
|
||||
bindgen: bool,
|
||||
#[arg(short, long, default_value = DEFAULT_TARGET)]
|
||||
target: String,
|
||||
#[arg(short, long, default_value = default_version())]
|
||||
version: String,
|
||||
#[arg(short, long, default_value = default_download_url())]
|
||||
mirror_url: String,
|
||||
}
|
||||
|
||||
fn main() -> Result<()> {
|
||||
let args = Args::parse();
|
||||
let target = args.target.as_str();
|
||||
|
||||
if args.bindgen {
|
||||
if args.download {
|
||||
let _ = upgrade::download(args.mirror_url.as_str(), target, args.version.as_str());
|
||||
}
|
||||
|
||||
upgrade::sys_bindgen(target)?;
|
||||
}
|
||||
|
||||
let bindings_file = upgrade::get_target_bindings(target);
|
||||
let mut sys_bindings = dirs::get_sys_dir()?;
|
||||
sys_bindings.push("src");
|
||||
sys_bindings.push("bindings");
|
||||
sys_bindings.push(&bindings_file);
|
||||
let mut cef_bindings = dirs::get_cef_dir()?;
|
||||
cef_bindings.push("src");
|
||||
cef_bindings.push("bindings");
|
||||
cef_bindings.push(&bindings_file);
|
||||
|
||||
let bindings = parse_tree::generate_bindings(&sys_bindings)?;
|
||||
let source = read_bindings(&bindings)?;
|
||||
let dest = read_bindings(&cef_bindings).unwrap_or_default();
|
||||
|
||||
if source != dest {
|
||||
fs::copy(&bindings, &cef_bindings)?;
|
||||
println!("Updated: {}", cef_bindings.display());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn read_bindings(source_path: &Path) -> crate::Result<String> {
|
||||
let mut source_file = fs::File::open(source_path)?;
|
||||
let mut updated = String::default();
|
||||
source_file.read_to_string(&mut updated)?;
|
||||
Ok(updated)
|
||||
}
|
||||
3576
browser/update-bindings/src/parse_tree.rs
Normal file
3576
browser/update-bindings/src/parse_tree.rs
Normal file
File diff suppressed because it is too large
Load Diff
102
browser/update-bindings/src/upgrade.rs
Executable file
102
browser/update-bindings/src/upgrade.rs
Executable file
@ -0,0 +1,102 @@
|
||||
use crate::dirs;
|
||||
use std::{
|
||||
path::{Path, PathBuf},
|
||||
process::Command,
|
||||
};
|
||||
|
||||
const TARGETS: &[&str] = &[
|
||||
// macos
|
||||
"aarch64-apple-darwin",
|
||||
"x86_64-apple-darwin",
|
||||
// windows
|
||||
"x86_64-pc-windows-msvc",
|
||||
"aarch64-pc-windows-msvc",
|
||||
"i686-pc-windows-msvc",
|
||||
// linux
|
||||
"x86_64-unknown-linux-gnu",
|
||||
"aarch64-unknown-linux-gnu",
|
||||
"arm-unknown-linux-gnueabi",
|
||||
];
|
||||
|
||||
pub fn download(url: &str, target: &str, version: &str) -> PathBuf {
|
||||
assert!(TARGETS.contains(&target), "unsupported target {target}");
|
||||
|
||||
let archive =
|
||||
download_cef::download_target_archive_from(url, target, version, dirs::get_out_dir(), true)
|
||||
.expect("download failed");
|
||||
|
||||
download_cef::extract_target_archive(target, &archive, dirs::get_out_dir(), true)
|
||||
.expect("extraction failed")
|
||||
}
|
||||
|
||||
pub fn sys_bindgen(target: &str) -> crate::Result<()> {
|
||||
assert!(TARGETS.contains(&target), "unsupported target {target}");
|
||||
let (os, arch) = target_to_os_arch(target);
|
||||
let cef_path = dirs::get_cef_root(os, arch);
|
||||
bindgen(target, &cef_path)
|
||||
}
|
||||
|
||||
pub fn get_target_bindings(target: &str) -> String {
|
||||
assert!(TARGETS.contains(&target), "unsupported target {target}");
|
||||
format!("{}.rs", target.replace('-', "_"))
|
||||
}
|
||||
|
||||
fn bindgen(target: &str, cef_path: &Path) -> crate::Result<()> {
|
||||
let mut sys_bindings = dirs::get_sys_dir()?;
|
||||
let mut wrapper = sys_bindings.clone();
|
||||
sys_bindings.push("src");
|
||||
sys_bindings.push("bindings");
|
||||
sys_bindings.push(format!("{}.rs", target.replace('-', "_")));
|
||||
wrapper.push("wrapper.h");
|
||||
|
||||
let mut bindings = bindgen::Builder::default()
|
||||
.header(wrapper.display().to_string())
|
||||
.default_enum_style(bindgen::EnumVariation::Rust {
|
||||
non_exhaustive: true,
|
||||
})
|
||||
.allowlist_type("cef_.*")
|
||||
.allowlist_function("cef_.*")
|
||||
.allowlist_item("CEF_API_VERSION(_.+)?")
|
||||
.allowlist_item("CEF_VERSION(_.+)?")
|
||||
.allowlist_item("CHROME_VERSION(_.+)?")
|
||||
.default_macro_constant_type(bindgen::MacroTypeVariation::Signed)
|
||||
.bitfield_enum(".*_mask_t")
|
||||
.clang_args([
|
||||
format!("-I{}", cef_path.display()),
|
||||
format!("--target={target}"),
|
||||
]);
|
||||
|
||||
if target.contains("windows") {
|
||||
bindings = bindings.new_type_alias("HINSTANCE").new_type_alias("HWND");
|
||||
} else if target.contains("apple") {
|
||||
let sdk_path = Command::new("xcrun")
|
||||
.args(["--sdk", "macosx", "--show-sdk-path"])
|
||||
.output()
|
||||
.unwrap()
|
||||
.stdout;
|
||||
|
||||
bindings = bindings.clang_arg(format!(
|
||||
"--sysroot={}",
|
||||
String::from_utf8_lossy(&sdk_path).trim()
|
||||
));
|
||||
}
|
||||
|
||||
let bindings = bindings.generate()?;
|
||||
|
||||
bindings.write_to_file(&sys_bindings)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn target_to_os_arch(target: &str) -> (&str, &str) {
|
||||
match target {
|
||||
"aarch64-apple-darwin" => ("macos", "aarch64"),
|
||||
"x86_64-apple-darwin" => ("macos", "x86_64"),
|
||||
"x86_64-pc-windows-msvc" => ("windows", "x86_64"),
|
||||
"aarch64-pc-windows-msvc" => ("windows", "aarch64"),
|
||||
"i686-pc-windows-msvc" => ("windows", "x86"),
|
||||
"x86_64-unknown-linux-gnu" => ("linux", "x86_64"),
|
||||
"aarch64-unknown-linux-gnu" => ("linux", "aarch64"),
|
||||
"arm-unknown-linux-gnueabi" => ("linux", "arm"),
|
||||
v => panic!("unsupported {v:?}"),
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user