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 }