use cef::*;
use std::sync::Arc;
use std::sync::atomic::{AtomicUsize, Ordering};
pub use buffr_engine::newtab::{
NEW_TAB_HTML_TEMPLATE, NEW_TAB_KEYBINDS_MARKER, NEW_TAB_SPLASH_ART_MARKER, NEW_TAB_URL,
SETTINGS_URL,
};
pub type NewTabHtmlProvider = Arc<dyn Fn() -> Vec<u8> + Send + Sync>;
pub type SettingsHtmlProvider = Arc<dyn Fn() -> Vec<u8> + Send + Sync>;
pub fn settings_html(engines: &[&str], rules: &[&str]) -> Vec<u8> {
let engine_rows: String = if engines.is_empty() {
"<li><em>(none registered)</em></li>".to_string()
} else {
engines
.iter()
.map(|id| format!("<li><code>{}</code></li>", html_escape(id)))
.collect::<Vec<_>>()
.join("\n")
};
let rule_rows: String = if rules.is_empty() {
"<li><em>(no routing rules — all URLs use the default engine)</em></li>".to_string()
} else {
rules
.iter()
.map(|r| format!("<li><code>{}</code></li>", html_escape(r)))
.collect::<Vec<_>>()
.join("\n")
};
let html = format!(
r#"<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>buffr settings</title>
<style>
body {{ font-family: system-ui, sans-serif; margin: 2rem; color: #e0e0e0; background: #1a1a1a; }}
h1 {{ font-size: 1.6rem; margin-bottom: 0.25rem; }}
h2 {{ font-size: 1.1rem; margin-top: 1.5rem; color: #aaa; }}
ul {{ list-style: disc; padding-left: 1.5rem; }}
code {{ background: #2a2a2a; padding: 0.1em 0.4em; border-radius: 3px; font-size: 0.9em; }}
.note {{ margin-top: 1.5rem; font-size: 0.85rem; color: #888; border-left: 3px solid #444; padding-left: 0.75rem; }}
</style>
</head>
<body>
<h1>buffr settings</h1>
<h2>Engine routing</h2>
<p>Registered engines:</p>
<ul>
{engine_rows}
</ul>
<p>Active routing rules (matched top-to-bottom against URL host):</p>
<ul>
{rule_rows}
</ul>
<p class="note">
Editing rules in the UI is not yet implemented — edit <code>config.toml</code> directly
and restart buffr to apply changes.
</p>
</body>
</html>"#,
);
html.into_bytes()
}
fn html_escape(s: &str) -> String {
s.replace('&', "&")
.replace('<', "<")
.replace('>', ">")
}
fn static_settings_provider() -> SettingsHtmlProvider {
Arc::new(|| settings_html(&[], &[]))
}
fn static_provider() -> NewTabHtmlProvider {
Arc::new(|| NEW_TAB_HTML_TEMPLATE.as_bytes().to_vec())
}
pub fn register_buffr_scheme(registrar: &mut cef::SchemeRegistrar) {
let scheme = CefString::from("buffr");
let opts = (SchemeOptions::STANDARD.get_raw()
| SchemeOptions::SECURE.get_raw()
| SchemeOptions::CORS_ENABLED.get_raw()
| SchemeOptions::FETCH_ENABLED.get_raw()) as i32;
registrar.add_custom_scheme(Some(&scheme), opts);
}
pub fn register_buffr_handler_factory(provider: NewTabHtmlProvider) {
register_buffr_handler_factory_with_settings(provider, None);
}
pub fn register_buffr_handler_factory_with_settings(
provider: NewTabHtmlProvider,
settings_provider: Option<SettingsHtmlProvider>,
) {
let settings = settings_provider.unwrap_or_else(static_settings_provider);
let scheme = CefString::from("buffr");
let mut factory = BuffrSchemeHandlerFactory::new(provider, settings);
cef::register_scheme_handler_factory(Some(&scheme), None, Some(&mut factory));
}
pub fn register_buffr_handler_factory_static() {
register_buffr_handler_factory(static_provider());
}
wrap_scheme_handler_factory! {
pub struct BuffrSchemeHandlerFactory {
provider: NewTabHtmlProvider,
settings_provider: SettingsHtmlProvider,
}
impl SchemeHandlerFactory {
fn create(
&self,
_browser: Option<&mut cef::Browser>,
_frame: Option<&mut cef::Frame>,
_scheme_name: Option<&CefString>,
request: Option<&mut cef::Request>,
) -> Option<cef::ResourceHandler> {
let url = request
.map(|r| CefStringUtf16::from(&r.url()).to_string())
.unwrap_or_default();
let bytes = if url.starts_with("buffr://settings") {
(self.settings_provider)()
} else {
(self.provider)()
};
Some(BuffrResourceHandler::new(
Arc::new(bytes),
Arc::new(AtomicUsize::new(0)),
))
}
}
}
wrap_resource_handler! {
pub struct BuffrResourceHandler {
bytes: Arc<Vec<u8>>,
cursor: Arc<AtomicUsize>,
}
impl ResourceHandler {
fn open(
&self,
_request: Option<&mut cef::Request>,
handle_request: Option<&mut ::std::os::raw::c_int>,
_callback: Option<&mut cef::Callback>,
) -> ::std::os::raw::c_int {
if let Some(hr) = handle_request {
*hr = 1;
}
1
}
fn response_headers(
&self,
response: Option<&mut Response>,
response_length: Option<&mut i64>,
_redirect_url: Option<&mut CefString>,
) {
if let Some(r) = response {
r.set_status(200);
let mime = CefString::from("text/html");
r.set_mime_type(Some(&mime));
}
if let Some(len) = response_length {
*len = self.bytes.len() as i64;
}
}
#[allow(clippy::not_unsafe_ptr_arg_deref)]
fn read(
&self,
data_out: *mut u8,
bytes_to_read: ::std::os::raw::c_int,
bytes_read: Option<&mut ::std::os::raw::c_int>,
_callback: Option<&mut cef::ResourceReadCallback>,
) -> ::std::os::raw::c_int {
let len = self.bytes.len();
let pos = self.cursor.load(Ordering::SeqCst);
if pos >= len || bytes_to_read <= 0 {
if let Some(br) = bytes_read {
*br = 0;
}
return 0;
}
let remaining = len - pos;
let to_copy = remaining.min(bytes_to_read as usize);
unsafe {
std::ptr::copy_nonoverlapping(
self.bytes.as_ptr().add(pos),
data_out,
to_copy,
);
}
self.cursor.store(pos + to_copy, Ordering::SeqCst);
if let Some(br) = bytes_read {
*br = to_copy as i32;
}
1
}
}
}