zng_view/config/
dconf.rs
1use std::{io::BufRead as _, time::Duration};
2
3use zng_unit::{Rgba, TimeUnits as _};
4use zng_view_api::{
5 Event,
6 config::{
7 AnimationsConfig, ChromeConfig, ColorScheme, ColorsConfig, FontAntiAliasing, KeyRepeatConfig, LocaleConfig, MultiClickConfig,
8 TouchConfig,
9 },
10};
11
12use crate::AppEvent;
13
14pub fn font_aa() -> FontAntiAliasing {
15 super::other::font_aa()
16}
17
18pub fn multi_click_config() -> MultiClickConfig {
19 let mut cfg = MultiClickConfig::default();
20 if let Some(d) = dconf_uint("/org/gnome/desktop/peripherals/mouse/double-click") {
21 cfg.time = d.ms();
22 }
23 cfg
24}
25
26pub fn animations_config() -> AnimationsConfig {
27 let mut cfg = AnimationsConfig::default();
28 if let Some(e) = dconf_bool("/org/gnome/desktop/interface/enable-animations") {
29 cfg.enabled = e;
30 }
31 if let Some(d) = dconf_uint("/org/gnome/desktop/interface/cursor-blink-time") {
32 cfg.caret_blink_interval = (d / 2).ms();
33 }
34 if let Some(e) = dconf_bool("/org/gnome/desktop/interface/cursor-blink") {
35 if !e {
36 cfg.caret_blink_interval = Duration::MAX;
37 }
38 }
39 cfg
40}
41
42pub fn key_repeat_config() -> KeyRepeatConfig {
43 let mut cfg = KeyRepeatConfig::default();
44 if let Some(d) = dconf_uint("/org/gnome/desktop/peripherals/keyboard/delay") {
45 cfg.start_delay = d.ms();
46 }
47 if let Some(d) = dconf_uint("/org/gnome/desktop/peripherals/keyboard/repeat-interval") {
48 cfg.interval = d.ms();
49 }
50 cfg
51}
52
53pub fn touch_config() -> TouchConfig {
54 super::other::touch_config()
55}
56
57pub fn colors_config() -> ColorsConfig {
58 let scheme = match dconf("/org/gnome/desktop/interface/color-scheme") {
59 Some(cs) => {
60 if cs.contains("dark") {
61 ColorScheme::Dark
62 } else {
63 ColorScheme::Light
64 }
65 }
66 None => ColorScheme::Light,
67 };
68
69 let theme = dconf("/org/gnome/desktop/interface/gtk-theme");
71 let theme = match theme.as_ref() {
72 Some(n) => n.strip_prefix("Yaru-").unwrap_or("").split('-').next().unwrap_or(""),
73 None => "?",
74 };
75 let accent = match theme {
77 "" => Rgba::new(233, 84, 32, 255), "bark" => Rgba::new(120, 120, 89, 255), "sage" => Rgba::new(101, 123, 105, 255), "olive" => Rgba::new(75, 133, 1, 255), "viridian" => Rgba::new(3, 135, 91, 255), "prussiangreen" => Rgba::new(48, 130, 128, 255), "blue" => Rgba::new(0, 115, 229, 255), "purple" => Rgba::new(119, 100, 216, 255), "magenta" => Rgba::new(179, 76, 179, 255), "red" => Rgba::new(218, 52, 80, 255), _ => ColorsConfig::default().accent,
88 };
89
90 ColorsConfig { scheme, accent }
91}
92
93pub fn locale_config() -> LocaleConfig {
94 super::other::locale_config()
96}
97
98pub fn chrome_config() -> ChromeConfig {
99 let is_wayland = std::env::var("WAYLAND_DISPLAY").is_ok();
100 let is_gnome = std::env::var("ORIGINAL_XDG_CURRENT_DESKTOP")
103 .or_else(|_| std::env::var("XDG_CURRENT_DESKTOP"))
104 .is_ok_and(|val| val.contains("GNOME"));
105 ChromeConfig {
106 prefer_custom: is_gnome,
107 provided: !(is_wayland && is_gnome),
108 }
109}
110
111fn on_change(key: &str, s: &crate::AppEventSender) {
112 match key {
115 "/org/gnome/desktop/interface/color-scheme" | "/org/gnome/desktop/interface/gtk-theme" => {
116 let _ = s.send(AppEvent::Notify(Event::ColorsConfigChanged(colors_config())));
117 }
118 "/org/gnome/desktop/peripherals/keyboard/delay" | "/org/gnome/desktop/peripherals/keyboard/repeat-interval" => {
119 let _ = s.send(AppEvent::Notify(Event::KeyRepeatConfigChanged(key_repeat_config())));
120 }
121 "/org/gnome/desktop/peripherals/mouse/double-click" => {
122 let _ = s.send(AppEvent::Notify(Event::MultiClickConfigChanged(multi_click_config())));
123 }
124 "/org/gnome/desktop/interface/enable-animations"
125 | "/org/gnome/desktop/interface/cursor-blink-time"
126 | "/org/gnome/desktop/interface/cursor-blink" => {
127 let _ = s.send(AppEvent::Notify(Event::AnimationsConfigChanged(animations_config())));
128 }
129 _ => {}
130 }
131}
132
133fn dconf_bool(key: &str) -> Option<bool> {
134 let s = dconf(key)?;
135 match s.parse::<bool>() {
136 Ok(b) => Some(b),
137 Err(e) => {
138 tracing::error!("unexpected value for {key} '{s}', parse error: {e}");
139 None
140 }
141 }
142}
143
144fn dconf_uint(key: &str) -> Option<u64> {
145 let s = dconf(key)?;
146 let s = if let Some((t, i)) = s.rsplit_once(' ') {
147 if !t.starts_with("uint") {
148 tracing::error!("unexpected value for {key} '{s}'");
149 return None;
150 }
151 i
152 } else {
153 s.as_str()
154 };
155 match s.parse::<u64>() {
156 Ok(i) => Some(i),
157 Err(e) => {
158 tracing::error!("unexpected value for {key} '{s}', parse error: {e}");
159 None
160 }
161 }
162}
163
164fn dconf(key: &str) -> Option<String> {
165 let out = std::process::Command::new("dconf").arg("read").arg(key).output();
166 match out {
167 Ok(s) => {
168 if s.status.success() {
169 Some(String::from_utf8_lossy(&s.stdout).trim().to_owned())
170 } else {
171 let e = String::from_utf8_lossy(&s.stderr);
172 tracing::error!("dconf read {key} error, {}", e.lines().next().unwrap_or_default());
173 None
174 }
175 }
176 Err(e) => {
177 tracing::error!("cannot run dconf, {e}");
178 None
179 }
180 }
181}
182
183pub fn spawn_listener(event_loop: crate::AppEventSender) -> Option<Box<dyn FnOnce()>> {
184 let mut w = std::process::Command::new("dconf");
185 w.arg("watch")
186 .arg("/")
187 .stdin(std::process::Stdio::null())
188 .stdout(std::process::Stdio::piped())
189 .stderr(std::process::Stdio::null());
190
191 let mut w = match w.spawn() {
192 Ok(w) => w,
193 Err(e) => {
194 tracing::error!("cannot monitor config, dconf did not spawn, {e}");
195 return None;
196 }
197 };
198 let stdout = w.stdout.take().unwrap();
199 std::thread::spawn(move || {
200 for line in std::io::BufReader::new(stdout).lines() {
201 match line {
202 Ok(l) => {
203 if l.starts_with('/') {
204 on_change(&l, &event_loop);
205 }
206 }
207 Err(_) => {
208 break;
209 }
210 }
211 }
212 });
213
214 Some(Box::new(move || {
215 let _ = w.kill();
216 let _ = w.wait();
217 }))
218}