web browser (Servo)
git clone https://git.lucas.co/cce-browser.git
Read settings from the app config; reload on window focus
New settings.rs reads ~/.config/cce/cce-browser/config.kdl (browser
section, written by cce-system-interface's new Browser page): homepage
replaces the hardcoded start URL, the configured engine drives the
URL-bar search fallback, download-dir overrides the XDG default (a
global override, since downloads run on worker threads), and history
gates visit recording. Settings re-load when the window regains focus,
so edits in system-interface apply on the next switch back.
Live-verified end to end: homepage launch, Wikipedia search, history
off (no records), history re-enabled via focus reload (recorded).
Co-Authored-By: Claude Fable 5 <[email protected]>
src/downloads.rs | 15 ++++++++++--
src/main.rs | 42 +++++++++++++++++++++++++++-------
src/settings.rs | 69 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
src/webview.rs | 11 ++++++++-
4 files changed, 126 insertions(+), 11 deletions(-)
diff --git a/src/downloads.rs b/src/downloads.rs
index 08d336e..9bf942a 100644
--- a/src/downloads.rs
+++ b/src/downloads.rs
@@ -57,9 +57,20 @@ pub struct Downloads {
next_id: std::sync::atomic::AtomicU64,
}
-/// The user's download directory: XDG_DOWNLOAD_DIR from user-dirs.dirs
-/// when configured, else ~/Downloads.
+/// Settings override for the download directory (None = XDG default).
+/// A global because downloads run on worker threads.
+static DIR_OVERRIDE: Mutex<Option<PathBuf>> = Mutex::new(None);
+
+pub fn set_download_dir(dir: Option<PathBuf>) {
+ *DIR_OVERRIDE.lock().unwrap() = dir;
+}
+
+/// The user's download directory: the settings override when set, else
+/// XDG_DOWNLOAD_DIR from user-dirs.dirs, else ~/Downloads.
fn download_dir() -> PathBuf {
+ if let Some(dir) = DIR_OVERRIDE.lock().unwrap().clone() {
+ return dir;
+ }
let home = PathBuf::from(std::env::var("HOME").unwrap_or_default());
let conf = home.join(".config/user-dirs.dirs");
if let Ok(text) = std::fs::read_to_string(conf) {
diff --git a/src/main.rs b/src/main.rs
index 5caf6b0..a14e79f 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -8,6 +8,7 @@
mod downloads;
mod pages;
+mod settings;
mod webview;
use url::Url;
@@ -47,7 +48,6 @@ const URL_PAD_X: f32 = 9.0;
/// Pixels per wheel notch when the DE reports discrete line deltas.
const LINE_PX: f64 = 76.0;
-const HOME_URL: &str = "https://servo.org";
const PAGE_BG: [f32; 4] = [0.10, 0.10, 0.11, 1.0];
const FIELD_BG: [f32; 4] = [0.09, 0.09, 0.10, 0.40];
@@ -70,6 +70,8 @@ pub enum Message {
struct BrowserApp {
host: ServoHost,
+ /// Loaded from the app config; re-read when the window regains focus.
+ settings: settings::Settings,
win: (f32, f32),
scale: f64,
pointer: (f32, f32),
@@ -173,7 +175,7 @@ fn url_rect(win_w: f32) -> Rect {
/// Turn URL-bar input into something loadable: a real URL as-is, a bare
/// host gets https://, anything else becomes a search.
-fn parse_url_input(input: &str) -> Option<Url> {
+fn parse_url_input(input: &str, search_prefix: &str) -> Option<Url> {
let s = input.trim();
if s.is_empty() {
return None;
@@ -198,7 +200,7 @@ fn parse_url_input(input: &str) -> Option<Url> {
}
}
let q: String = url::form_urlencoded::byte_serialize(s.as_bytes()).collect();
- Url::parse(&format!("https://duckduckgo.com/html/?q={q}")).ok()
+ Url::parse(&format!("{search_prefix}{q}")).ok()
}
fn dom_button(button: MouseButton) -> Option<servo::MouseButton> {
@@ -283,7 +285,7 @@ impl BrowserApp {
}
fn navigate(&mut self) {
- if let Some(url) = parse_url_input(&self.url_input) {
+ if let Some(url) = parse_url_input(&self.url_input, &self.settings.search_prefix) {
self.host.load(url);
self.url_focused = false;
self.loading = true;
@@ -291,6 +293,18 @@ impl BrowserApp {
}
/// New blank tab with the URL bar focused for typing.
+ /// Pick up settings edits (system-interface, cce-data-editor) when the
+ /// window regains focus.
+ fn reload_settings(&mut self) {
+ let new = settings::load();
+ if new == self.settings {
+ return;
+ }
+ downloads::set_download_dir(new.download_dir.clone());
+ self.host.set_history_enabled(new.history);
+ self.settings = new;
+ }
+
fn new_tab(&mut self) {
let url = Url::parse("about:blank").expect("about:blank");
self.host.open_tab(url);
@@ -417,16 +431,22 @@ impl Application for BrowserApp {
type Message = Message;
fn new(_qh: &QueueHandle<EngineState<Self>>, sender: calloop::channel::Sender<Self::Message>) -> Self {
- // Optional CLI arg: the start URL (same parsing as the URL bar).
+ let settings = settings::load();
+ downloads::set_download_dir(settings.download_dir.clone());
+ // Optional CLI arg: the start URL (same parsing as the URL bar);
+ // otherwise the configured homepage.
let url = std::env::args()
.nth(1)
- .and_then(|arg| parse_url_input(&arg))
- .unwrap_or_else(|| Url::parse(HOME_URL).expect("home url"));
+ .and_then(|arg| parse_url_input(&arg, &settings.search_prefix))
+ .or_else(|| parse_url_input(&settings.homepage, &settings.search_prefix))
+ .unwrap_or_else(|| Url::parse(settings::DEFAULT_HOMEPAGE).expect("home url"));
let url_input = url.to_string();
let cursor = url_input.len();
- let host = ServoHost::new(sender, url, (1200, 800));
+ let mut host = ServoHost::new(sender, url, (1200, 800));
+ host.set_history_enabled(settings.history);
Self {
host,
+ settings,
win: (1200.0, 800.0),
scale: 1.0,
pointer: (0.0, 0.0),
@@ -469,6 +489,12 @@ impl Application for BrowserApp {
fn tick(&mut self, _dt: f32, _needs_rebuild: &mut bool) {}
+ fn handle_focus_change(&mut self, focused: bool, _needs_rebuild: &mut bool) {
+ if focused {
+ self.reload_settings();
+ }
+ }
+
fn handle_resize(&mut self, width: f32, height: f32, scale: f64) {
self.win = (width, height);
self.scale = scale;
diff --git a/src/settings.rs b/src/settings.rs
new file mode 100644
index 0000000..b788c45
--- /dev/null
+++ b/src/settings.rs
@@ -0,0 +1,69 @@
+//! Browser settings from the per-app cce config
+//! (`~/.config/cce/cce-browser/config.kdl`, section `browser`) — the file
+//! cce-system-interface's Browser page edits. Loaded at startup and
+//! re-read when the window regains focus, so settings changed in
+//! system-interface apply on the next switch back to the browser.
+
+use std::path::PathBuf;
+
+pub const DEFAULT_HOMEPAGE: &str = "https://servo.org";
+
+#[derive(Debug, Clone, PartialEq)]
+pub struct Settings {
+ pub homepage: String,
+ /// Query-URL prefix for the search fallback; escaped terms are appended.
+ pub search_prefix: String,
+ /// Override for the download directory (None = XDG default).
+ pub download_dir: Option<PathBuf>,
+ /// Record page visits to cce://history.
+ pub history: bool,
+}
+
+impl Default for Settings {
+ fn default() -> Self {
+ Self {
+ homepage: DEFAULT_HOMEPAGE.to_string(),
+ search_prefix: search_prefix("duckduckgo").to_string(),
+ download_dir: None,
+ history: true,
+ }
+ }
+}
+
+/// Engine keys as written by the system-interface Browser page.
+fn search_prefix(key: &str) -> &'static str {
+ match key {
+ "google" => "https://www.google.com/search?q=",
+ "bing" => "https://www.bing.com/search?q=",
+ "wikipedia" => "https://en.wikipedia.org/wiki/Special:Search?search=",
+ _ => "https://duckduckgo.com/html/?q=",
+ }
+}
+
+pub fn load() -> Settings {
+ let path = cce_ui::config::get_app_config_path("cce-browser");
+ let content = std::fs::read_to_string(path).unwrap_or_default();
+ let val = cce_ui::config::parse_kdl_to_json(&content);
+ let b = &val["browser"];
+
+ let homepage = match b["homepage"].as_str().map(str::trim) {
+ Some(h) if !h.is_empty() => h.to_string(),
+ _ => DEFAULT_HOMEPAGE.to_string(),
+ };
+ let download_dir = match b["download-dir"].as_str().map(str::trim) {
+ Some(d) if !d.is_empty() => {
+ let home = std::env::var("HOME").unwrap_or_default();
+ Some(PathBuf::from(match d.strip_prefix("~/") {
+ Some(rest) => format!("{home}/{rest}"),
+ None => d.to_string(),
+ }))
+ }
+ _ => None,
+ };
+ Settings {
+ homepage,
+ search_prefix: search_prefix(b["search"].as_str().unwrap_or("duckduckgo")).to_string(),
+ download_dir,
+ history: b["history"].as_bool().unwrap_or(true),
+ }
+}
diff --git a/src/webview.rs b/src/webview.rs
index 4031f99..51fe1bc 100644
--- a/src/webview.rs
+++ b/src/webview.rs
@@ -154,6 +154,14 @@ pub struct ServoHost {
active: usize,
size_px: (u32, u32),
scale: f32,
+ /// Settings gate for cce://history recording.
+ history_enabled: bool,
+}
+
+impl ServoHost {
+ pub fn set_history_enabled(&mut self, on: bool) {
+ self.history_enabled = on;
+ }
}
impl ServoHost {
@@ -210,6 +218,7 @@ impl ServoHost {
active: usize::MAX,
size_px,
scale: 1.0,
+ history_enabled: true,
};
host.open_tab(url);
host
@@ -347,7 +356,7 @@ impl ServoHost {
let was_loading = tab.loading;
tab.loading = loading;
// Load-complete transition: log the visit.
- if was_loading && !loading {
+ if was_loading && !loading && self.history_enabled {
if let Some(url) = &tab.url {
self.history
.record(url.as_str(), tab.title.as_deref().unwrap_or(""));