dark_light/platforms/
freedesktop.rs1use std::sync::mpsc;
2use std::thread::JoinHandle;
3
4use crate::{Error, Mode, Watcher};
5use zbus::proxy;
6use zbus::zvariant::OwnedValue;
7
8const APPEARANCE: &str = "org.freedesktop.appearance";
9const COLOR_SCHEME: &str = "color-scheme";
10const PORTAL_DESTINATION: &str = "org.freedesktop.portal.Desktop";
11
12#[proxy(
13 interface = "org.freedesktop.portal.Settings",
14 default_path = "/org/freedesktop/portal/desktop"
15)]
16pub trait XdgPortalSettings {
17 fn read_one(&self, namespace: &str, key: &str) -> zbus::Result<OwnedValue>;
18
19 #[zbus(signal)]
20 fn setting_changed(&self, namespace: &str, key: &str, value: OwnedValue) -> zbus::Result<()>;
21}
22
23pub fn detect() -> Result<Mode, Error> {
24 let conn = zbus::blocking::Connection::session()
25 .map_err(|e| Error::XdgDesktopPortal(e.to_string()))?;
26 let portal = XdgPortalSettingsProxyBlocking::new(&conn, PORTAL_DESTINATION)
27 .map_err(|e| Error::XdgDesktopPortal(e.to_string()))?;
28 let mode_value: u32 = portal
29 .read_one(APPEARANCE, COLOR_SCHEME)
30 .map_err(|e| Error::XdgDesktopPortal(e.to_string()))?
31 .try_into()
32 .map_err(|_| Error::XdgDesktopPortal("type convert failed".to_string()))?;
33 Ok(match mode_value {
34 1 => Mode::Dark,
35 2 => Mode::Light,
36 _ => Mode::Unspecified,
37 })
38}
39
40pub struct WatchGuard(#[allow(dead_code)] Option<JoinHandle<()>>);
46
47pub fn subscribe() -> Result<Watcher, Error> {
48 let (sender, receiver) = mpsc::channel();
49
50 let conn = zbus::blocking::Connection::session()
51 .map_err(|e| Error::XdgDesktopPortal(e.to_string()))?;
52 let portal = XdgPortalSettingsProxyBlocking::new(&conn, PORTAL_DESTINATION)
53 .map_err(|e| Error::XdgDesktopPortal(e.to_string()))?;
54 let changes = portal
55 .receive_setting_changed()
56 .map_err(|e| Error::XdgDesktopPortal(e.to_string()))?;
57
58 let handle = std::thread::spawn(move || {
59 let mut last = None;
60 for signal in changes {
61 let Ok(args) = signal.args() else { continue };
62 if args.namespace != APPEARANCE || args.key != COLOR_SCHEME {
63 continue;
64 }
65 let Ok(value): Result<u32, _> = args.value.try_into() else {
66 continue;
67 };
68 let mode = match value {
69 1 => Mode::Dark,
70 2 => Mode::Light,
71 _ => Mode::Unspecified,
72 };
73 if Some(mode) == last {
74 continue;
75 }
76 last = Some(mode);
77 if sender.send(mode).is_err() {
78 break;
79 }
80 }
81 });
82
83 Ok(Watcher {
84 receiver,
85 guard: WatchGuard(Some(handle)),
86 })
87}