Seregon/StratoSDK

StratoSDK is a framework with a declarative approach similar to Flutter/React, written and designed entirely for Rust.

Rust/27.3 KB/No license
crates/strato-ui-renderer/src/windowing/winit/linux/zbus/desktop_settings.rs
StratoSDK / crates / strato-ui-renderer / src / windowing / winit / linux / zbus / desktop_settings.rs
1//! Provides an application-agnostic D-Bus client for retrieving the
2//! desktop environment's appearance settings.
3 
4use std::ops::Deref as _;
5use std::time::Duration;
6 
7use futures::StreamExt as _;
8use winit::event_loop::EventLoopProxy;
9use zbus::{proxy, zvariant};
10 
11use crate::{
12 platform::SystemTheme,
13 r#async::{block_on, executor::Background, FutureExt as _},
14 windowing::winit::app::CustomEvent,
15};
16 
17const COLOR_SCHEME_SETTINGS_NAMESPACE: &str = "org.freedesktop.appearance";
18const COLOR_SCHEME_SETTINGS_KEY: &str = "color-scheme";
19 
20/// Values used by the desktop environment to encode the user's
21/// system color scheme preference.
22#[derive(Debug, Default, serde::Deserialize, serde::Serialize, zbus::zvariant::Type, PartialEq)]
23enum SystemColorScheme {
24 #[default]
25 NoPreference = 0,
26 Dark = 1,
27 Light = 2,
28}
29 
30impl From<&u32> for SystemColorScheme {
31 fn from(value: &u32) -> SystemColorScheme {
32 match value {
33 0 => SystemColorScheme::NoPreference,
34 1 => SystemColorScheme::Dark,
35 2 => SystemColorScheme::Light,
36 _ => SystemColorScheme::NoPreference,
37 }
38 }
39}
40 
41impl From<&str> for SystemColorScheme {
42 fn from(value: &str) -> SystemColorScheme {
43 match value {
44 "prefer-dark" => SystemColorScheme::Dark,
45 "prefer-light" => SystemColorScheme::Light,
46 _ => SystemColorScheme::NoPreference,
47 }
48 }
49}
50 
51impl From<&zvariant::Str<'_>> for SystemColorScheme {
52 fn from(value: &zvariant::Str) -> SystemColorScheme {
53 SystemColorScheme::from(value.as_str())
54 }
55}
56 
57impl From<&zvariant::Value<'_>> for SystemColorScheme {
58 fn from(value: &zvariant::Value) -> SystemColorScheme {
59 match value {
60 zvariant::Value::U32(u) => SystemColorScheme::from(u),
61 zvariant::Value::Str(s) => SystemColorScheme::from(s),
62 zvariant::Value::Value(boxed_v) => match boxed_v.downcast_ref::<u32>() {
63 Ok(v) => SystemColorScheme::from(&v),
64 Err(err) => {
65 log::error!(
66 "D-Bus inner variant type {:#?}: {:#?} could not be converted to SystemThemePreference: {err:#}",
67 value.value_signature(),
68 value
69 );
70 SystemColorScheme::NoPreference
71 }
72 },
73 _ => {
74 log::error!(
75 "D-Bus outer variant type {:#?}: {:#?} could not be converted to SystemThemePreference",
76 value.value_signature(),
77 value
78 );
79 SystemColorScheme::NoPreference
80 }
81 }
82 }
83}
84 
85impl From<&zvariant::OwnedValue> for SystemColorScheme {
86 fn from(owned_value: &zvariant::OwnedValue) -> SystemColorScheme {
87 SystemColorScheme::from(owned_value.deref())
88 }
89}
90 
91impl From<SystemColorScheme> for SystemTheme {
92 fn from(os_value: SystemColorScheme) -> SystemTheme {
93 match os_value {
94 SystemColorScheme::Dark => SystemTheme::Dark,
95 SystemColorScheme::Light => SystemTheme::Light,
96 SystemColorScheme::NoPreference => SystemTheme::default(),
97 }
98 }
99}
100 
101/// A D-Bus client for connecting to the desktop settings.
102#[proxy(
103 interface = "org.freedesktop.portal.Settings",
104 default_service = "org.freedesktop.portal.Desktop",
105 default_path = "/org/freedesktop/portal/desktop"
106)]
107trait DesktopSettings {
108 fn read(&self, namespace: &str, key: &str) -> zbus::fdo::Result<zvariant::OwnedValue>;
109 
110 #[zbus(signal)]
111 fn setting_changed(
112 &self,
113 interface_name: &str,
114 setting_name: &str,
115 new_setting_value: zvariant::Value<'_>,
116 ) -> zbus::fdo::Result<()>;
117}
118 
119/// Sets up a background task to listen to desktop settings change events sent
120/// over dbus and inject events into the winit EventLoop accordingly.
121pub fn watch_desktop_settings_changes(
122 event_proxy: EventLoopProxy<CustomEvent>,
123 background: &Background,
124) {
125 background
126 .spawn(async move {
127 if let Err(err) = watch_desktop_settings_changes_internal(event_proxy).await {
128 log::warn!(
129 "Encountered error while watching for desktop settings change events: {err:#}"
130 );
131 }
132 })
133 .detach();
134}
135 
136async fn watch_desktop_settings_changes_internal(
137 event_proxy: EventLoopProxy<CustomEvent>,
138) -> zbus::Result<()> {
139 let connection = zbus::Connection::session().await?;
140 let desktop_settings_proxy = DesktopSettingsProxy::new(&connection).await?;
141 let mut stream = desktop_settings_proxy.receive_setting_changed().await?;
142 while let Some(msg) = stream.next().await {
143 let Ok(args) = msg.args() else {
144 log::warn!("appearance settings signal should have arguments");
145 continue;
146 };
147 // As of now, we are only interested in system color scheme changes.
148 // In the future, we may check for other types of signals.
149 if let (&COLOR_SCHEME_SETTINGS_NAMESPACE, &COLOR_SCHEME_SETTINGS_KEY) =
150 (args.interface_name(), args.setting_name())
151 {
152 let _ = event_proxy.send_event(CustomEvent::SystemThemeChanged);
153 }
154 }
155 Ok(())
156}
157 
158/// Retrieves the system color scheme, blocking for up to 200ms to get the
159/// value via dbus.
160pub fn get_system_theme() -> Result<SystemTheme, zbus::Error> {
161 block_on(async {
162 query_system_theme_from_dbus()
163 .with_timeout(Duration::from_millis(200))
164 .await
165 .unwrap_or_else(|_| {
166 Err(zbus::Error::from(zbus::fdo::Error::TimedOut(
167 "Failed to get a response within 200ms".to_owned(),
168 )))
169 })
170 })
171}
172 
173/// Queries the current D-Bus session bus to get the system color scheme.
174async fn query_system_theme_from_dbus() -> Result<SystemTheme, zbus::Error> {
175 let client_conn = zbus::Connection::session().await?;
176 let settings_proxy = DesktopSettingsProxy::new(&client_conn).await?;
177 let owned_val = settings_proxy
178 .read(COLOR_SCHEME_SETTINGS_NAMESPACE, COLOR_SCHEME_SETTINGS_KEY)
179 .await?;
180 Ok(SystemColorScheme::from(&owned_val).into())
181}
182