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 }