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

src/settings.rs (6.3K)

  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     /// Sync bookmarks with Raindrop.io's Unsorted collection
 96     /// (RAINDROP-SYNC.md). Off by default: it needs a token in the keyring,
 97     /// and it writes to an account elsewhere.
 98     pub raindrop: bool,
 99     /// Vi-style modal keys, after qutebrowser (`src/vi.rs`). Off by default:
100     /// it changes what every letter typed at a page means.
101     pub vi_mode: bool,
102     /// Window edge the utility bar floats against.
103     pub bar_position: BarPosition,
104     /// What pages are told to prefer.
105     pub color_scheme: ColorScheme,
106     /// Command used to hand the current page to another browser. Empty means
107     /// "ask XDG", which is right until cce-browser is itself the default.
108     pub external_browser: Option<String>,
109 }
110 
111 impl Default for Settings {
112     fn default() -> Self {
113         Self {
114             homepage: DEFAULT_HOMEPAGE.to_string(),
115             search_prefix: search_prefix("duckduckgo").to_string(),
116             download_dir: None,
117             history: true,
118             accounts: true,
119             raindrop: false,
120             vi_mode: false,
121             bar_position: BarPosition::Top,
122             color_scheme: ColorScheme::Dark,
123             external_browser: None,
124         }
125     }
126 }
127 
128 /// Engine keys as written by the system-interface Browser page.
129 fn search_prefix(key: &str) -> &'static str {
130     match key {
131         "google" => "https://www.google.com/search?q=",
132         "bing" => "https://www.bing.com/search?q=",
133         "wikipedia" => "https://en.wikipedia.org/wiki/Special:Search?search=",
134         _ => "https://duckduckgo.com/html/?q=",
135     }
136 }
137 
138 pub fn load() -> Settings {
139     let path = cce_ui::config::get_app_config_path("cce-browser");
140     let content = std::fs::read_to_string(path).unwrap_or_default();
141     let val = cce_ui::config::parse_kdl_to_json(&content);
142     let b = &val["browser"];
143 
144     let homepage = match b["homepage"].as_str().map(str::trim) {
145         Some(h) if !h.is_empty() => h.to_string(),
146         _ => DEFAULT_HOMEPAGE.to_string(),
147     };
148     let download_dir = match b["download-dir"].as_str().map(str::trim) {
149         Some(d) if !d.is_empty() => {
150             let home = std::env::var("HOME").unwrap_or_default();
151             Some(PathBuf::from(match d.strip_prefix("~/") {
152                 Some(rest) => format!("{home}/{rest}"),
153                 None => d.to_string(),
154             }))
155         }
156         _ => None,
157     };
158     Settings {
159         homepage,
160         search_prefix: search_prefix(b["search"].as_str().unwrap_or("duckduckgo")).to_string(),
161         download_dir,
162         history: b["history"].as_bool().unwrap_or(true),
163         accounts: b["accounts"].as_bool().unwrap_or(true),
164         raindrop: b["raindrop"].as_bool().unwrap_or(false),
165         vi_mode: b["vi-mode"].as_bool().unwrap_or(false),
166         bar_position: BarPosition::from_key(b["bar-position"].as_str().unwrap_or("top")),
167         color_scheme: ColorScheme::from_key(b["color-scheme"].as_str().unwrap_or("dark")),
168         external_browser: b["external-browser"]
169             .as_str()
170             .map(str::trim)
171             .filter(|s| !s.is_empty())
172             .map(str::to_string),
173     }
174 }