git.lucas.co / cce-browser
web browser (Servo)
git clone https://git.lucas.co/cce-browser.git

src/settings.rs (5.8K)

  1 //! Browser settings from the per-app cce config
  2 //! (`~/.config/cce/cce-browser/config.kdl`, section `browser`) — the file
  3 //! cce-system-interface's Browser page edits. Loaded at startup and
  4 //! re-read when the window regains focus, so settings changed in
  5 //! system-interface apply on the next switch back to the browser.
  6 
  7 use std::path::PathBuf;
  8 
  9 pub const DEFAULT_HOMEPAGE: &str = "https://servo.org";
 10 
 11 /// Which edge the floating utility bar is anchored to. The page is
 12 /// full-bleed under the bar either way, so this is chrome geometry only.
 13 #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
 14 pub enum BarPosition {
 15     #[default]
 16     Top,
 17     Bottom,
 18 }
 19 
 20 impl BarPosition {
 21     /// Config keys as written by the system-interface Browser page.
 22     fn from_key(key: &str) -> Self {
 23         match key {
 24             "bottom" => Self::Bottom,
 25             _ => Self::Top,
 26         }
 27     }
 28 }
 29 
 30 /// The color scheme reported to pages as `prefers-color-scheme`. Sites that
 31 /// ship a dark stylesheet honor it; sites that don't are unaffected — this is
 32 /// a signal, not a filter over their colors.
 33 #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
 34 pub enum ColorScheme {
 35     #[default]
 36     Dark,
 37     Light,
 38     /// Dark by force: a user stylesheet inverts the page, for sites that
 39     /// ship no dark theme at all (google.com serves a hardcoded white).
 40     ForceDark,
 41 }
 42 
 43 impl ColorScheme {
 44     /// Config keys as written by the system-interface Browser page.
 45     fn from_key(key: &str) -> Self {
 46         match key {
 47             "light" => Self::Light,
 48             "force-dark" => Self::ForceDark,
 49             _ => Self::Dark,
 50         }
 51     }
 52 
 53     /// What to report for `prefers-color-scheme`, backend-neutrally.
 54     ///
 55     /// Force-dark reports **light** on purpose: the filter inverts
 56     /// unconditionally, so a site with a real dark theme would be handed an
 57     /// already-dark page and inverted back into a light one.
 58     pub fn is_dark(self) -> bool {
 59         matches!(self, Self::Dark)
 60     }
 61 
 62     /// Whether the inverting user stylesheet is installed.
 63     pub fn forces_dark(self) -> bool {
 64         matches!(self, Self::ForceDark)
 65     }
 66 }
 67 
 68 #[cfg(feature = "servo")]
 69 impl From<ColorScheme> for servo::Theme {
 70     fn from(scheme: ColorScheme) -> Self {
 71         match scheme {
 72             ColorScheme::Dark => servo::Theme::Dark,
 73             // Force-dark inverts unconditionally, so pages have to render
 74             // their LIGHT theme underneath: reporting dark to a site that
 75             // has one would hand the filter an already-dark page and invert
 76             // it back into a light one.
 77             ColorScheme::Light | ColorScheme::ForceDark => servo::Theme::Light,
 78         }
 79     }
 80 }
 81 
 82 #[derive(Debug, Clone, PartialEq)]
 83 pub struct Settings {
 84     pub homepage: String,
 85     /// Query-URL prefix for the search fallback; escaped terms are appended.
 86     pub search_prefix: String,
 87     /// Override for the download directory (None = XDG default).
 88     pub download_dir: Option<PathBuf>,
 89     /// Record page visits to cce://history.
 90     pub history: bool,
 91     /// Offer accounts from cce-secrets on login forms. On by default, and a
 92     /// single switch for the whole feature: with it off the browser injects
 93     /// no watcher script and never opens the keyring.
 94     pub accounts: bool,
 95     /// Window edge the utility bar floats against.
 96     pub bar_position: BarPosition,
 97     /// What pages are told to prefer.
 98     pub color_scheme: ColorScheme,
 99     /// Command used to hand the current page to another browser. Empty means
100     /// "ask XDG", which is right until cce-browser is itself the default.
101     pub external_browser: Option<String>,
102 }
103 
104 impl Default for Settings {
105     fn default() -> Self {
106         Self {
107             homepage: DEFAULT_HOMEPAGE.to_string(),
108             search_prefix: search_prefix("duckduckgo").to_string(),
109             download_dir: None,
110             history: true,
111             accounts: true,
112             bar_position: BarPosition::Top,
113             color_scheme: ColorScheme::Dark,
114             external_browser: None,
115         }
116     }
117 }
118 
119 /// Engine keys as written by the system-interface Browser page.
120 fn search_prefix(key: &str) -> &'static str {
121     match key {
122         "google" => "https://www.google.com/search?q=",
123         "bing" => "https://www.bing.com/search?q=",
124         "wikipedia" => "https://en.wikipedia.org/wiki/Special:Search?search=",
125         _ => "https://duckduckgo.com/html/?q=",
126     }
127 }
128 
129 pub fn load() -> Settings {
130     let path = cce_ui::config::get_app_config_path("cce-browser");
131     let content = std::fs::read_to_string(path).unwrap_or_default();
132     let val = cce_ui::config::parse_kdl_to_json(&content);
133     let b = &val["browser"];
134 
135     let homepage = match b["homepage"].as_str().map(str::trim) {
136         Some(h) if !h.is_empty() => h.to_string(),
137         _ => DEFAULT_HOMEPAGE.to_string(),
138     };
139     let download_dir = match b["download-dir"].as_str().map(str::trim) {
140         Some(d) if !d.is_empty() => {
141             let home = std::env::var("HOME").unwrap_or_default();
142             Some(PathBuf::from(match d.strip_prefix("~/") {
143                 Some(rest) => format!("{home}/{rest}"),
144                 None => d.to_string(),
145             }))
146         }
147         _ => None,
148     };
149     Settings {
150         homepage,
151         search_prefix: search_prefix(b["search"].as_str().unwrap_or("duckduckgo")).to_string(),
152         download_dir,
153         history: b["history"].as_bool().unwrap_or(true),
154         accounts: b["accounts"].as_bool().unwrap_or(true),
155         bar_position: BarPosition::from_key(b["bar-position"].as_str().unwrap_or("top")),
156         color_scheme: ColorScheme::from_key(b["color-scheme"].as_str().unwrap_or("dark")),
157         external_browser: b["external-browser"]
158             .as_str()
159             .map(str::trim)
160             .filter(|s| !s.is_empty())
161             .map(str::to_string),
162     }
163 }