1use std::{collections::HashMap, convert::TryFrom, fmt::Debug, future::ready};
31
32use futures_util::{Stream, StreamExt};
33use serde::{Deserialize, Serialize};
34use zbus::zvariant::{OwnedValue, Type, Value};
35
36use crate::{desktop::Color, proxy::Proxy, Error};
37
38pub type Namespace = HashMap<String, OwnedValue>;
40
41#[derive(Deserialize, Type)]
42pub struct Setting(String, String, OwnedValue);
44
45impl Setting {
46 pub fn namespace(&self) -> &str {
48 &self.0
49 }
50
51 pub fn key(&self) -> &str {
53 &self.1
54 }
55
56 pub fn value(&self) -> &OwnedValue {
58 &self.2
59 }
60}
61
62impl std::fmt::Debug for Setting {
63 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
64 f.debug_struct("Setting")
65 .field("namespace", &self.namespace())
66 .field("key", &self.key())
67 .field("value", self.value())
68 .finish()
69 }
70}
71
72#[cfg_attr(feature = "glib", derive(glib::Enum))]
74#[cfg_attr(feature = "glib", enum_type(name = "AshpdColorScheme"))]
75#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default)]
76pub enum ColorScheme {
77 #[default]
79 NoPreference,
80 PreferDark,
82 PreferLight,
84}
85
86impl From<ColorScheme> for OwnedValue {
87 fn from(value: ColorScheme) -> Self {
88 match value {
89 ColorScheme::PreferDark => 1,
90 ColorScheme::PreferLight => 2,
91 _ => 0,
92 }
93 .into()
94 }
95}
96
97impl TryFrom<OwnedValue> for ColorScheme {
98 type Error = Error;
99
100 fn try_from(value: OwnedValue) -> Result<Self, Self::Error> {
101 TryFrom::<Value>::try_from(value.into())
102 }
103}
104
105impl TryFrom<Value<'_>> for ColorScheme {
106 type Error = Error;
107
108 fn try_from(value: Value) -> Result<Self, Self::Error> {
109 Ok(match u32::try_from(value)? {
110 1 => Self::PreferDark,
111 2 => Self::PreferLight,
112 _ => Self::NoPreference,
113 })
114 }
115}
116
117#[cfg_attr(feature = "glib", derive(glib::Enum))]
119#[cfg_attr(feature = "glib", enum_type(name = "AshpdContrast"))]
120#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default)]
121pub enum Contrast {
122 #[default]
124 NoPreference,
125 High,
127}
128
129impl From<Contrast> for OwnedValue {
130 fn from(value: Contrast) -> Self {
131 match value {
132 Contrast::High => 1,
133 _ => 0,
134 }
135 .into()
136 }
137}
138
139impl TryFrom<OwnedValue> for Contrast {
140 type Error = Error;
141
142 fn try_from(value: OwnedValue) -> Result<Self, Self::Error> {
143 TryFrom::<Value>::try_from(value.into())
144 }
145}
146
147impl TryFrom<Value<'_>> for Contrast {
148 type Error = Error;
149
150 fn try_from(value: Value) -> Result<Self, Self::Error> {
151 Ok(match u32::try_from(value)? {
152 1 => Self::High,
153 _ => Self::NoPreference,
154 })
155 }
156}
157
158pub const APPEARANCE_NAMESPACE: &str = "org.freedesktop.appearance";
160pub const COLOR_SCHEME_KEY: &str = "color-scheme";
162pub const ACCENT_COLOR_SCHEME_KEY: &str = "accent-color";
164pub const CONTRAST_KEY: &str = "contrast";
166
167#[derive(Debug)]
173#[doc(alias = "org.freedesktop.portal.Settings")]
174pub struct Settings<'a>(Proxy<'a>);
175
176impl<'a> Settings<'a> {
177 pub async fn new() -> Result<Settings<'a>, Error> {
179 let proxy = Proxy::new_desktop("org.freedesktop.portal.Settings").await?;
180 Ok(Self(proxy))
181 }
182
183 #[doc(alias = "ReadAll")]
201 pub async fn read_all(
202 &self,
203 namespaces: &[impl AsRef<str> + Type + Serialize + Debug],
204 ) -> Result<HashMap<String, Namespace>, Error> {
205 self.0.call("ReadAll", &(namespaces)).await
206 }
207
208 #[doc(alias = "Read")]
223 #[doc(alias = "ReadOne")]
224 pub async fn read<T>(&self, namespace: &str, key: &str) -> Result<T, Error>
225 where
226 T: TryFrom<OwnedValue>,
227 Error: From<<T as TryFrom<OwnedValue>>::Error>,
228 {
229 let value = self.0.call::<OwnedValue>("Read", &(namespace, key)).await?;
230 if let Ok(v) = value.downcast_ref::<Value>() {
231 T::try_from(v.try_to_owned()?).map_err(From::from)
232 } else {
233 T::try_from(value).map_err(From::from)
234 }
235 }
236
237 pub async fn accent_color(&self) -> Result<Color, Error> {
239 self.read::<(f64, f64, f64)>(APPEARANCE_NAMESPACE, ACCENT_COLOR_SCHEME_KEY)
240 .await
241 .map(Color::from)
242 }
243
244 pub async fn color_scheme(&self) -> Result<ColorScheme, Error> {
246 self.read::<ColorScheme>(APPEARANCE_NAMESPACE, COLOR_SCHEME_KEY)
247 .await
248 }
249
250 pub async fn contrast(&self) -> Result<Contrast, Error> {
252 self.read::<Contrast>(APPEARANCE_NAMESPACE, CONTRAST_KEY)
253 .await
254 }
255
256 pub async fn receive_color_scheme_changed(
258 &self,
259 ) -> Result<impl Stream<Item = ColorScheme>, Error> {
260 Ok(self
261 .receive_setting_changed_with_args(APPEARANCE_NAMESPACE, COLOR_SCHEME_KEY)
262 .await?
263 .filter_map(|t| ready(t.ok())))
264 }
265
266 pub async fn receive_accent_color_changed(&self) -> Result<impl Stream<Item = Color>, Error> {
268 Ok(self
269 .receive_setting_changed_with_args::<(f64, f64, f64)>(
270 APPEARANCE_NAMESPACE,
271 ACCENT_COLOR_SCHEME_KEY,
272 )
273 .await?
274 .filter_map(|t| ready(t.ok().map(Color::from))))
275 }
276
277 pub async fn receive_contrast_changed(&self) -> Result<impl Stream<Item = Contrast>, Error> {
279 Ok(self
280 .receive_setting_changed_with_args(APPEARANCE_NAMESPACE, CONTRAST_KEY)
281 .await?
282 .filter_map(|t| ready(t.ok())))
283 }
284
285 #[doc(alias = "SettingChanged")]
291 pub async fn receive_setting_changed(&self) -> Result<impl Stream<Item = Setting>, Error> {
292 self.0.signal("SettingChanged").await
293 }
294
295 pub async fn receive_setting_changed_with_args<T>(
320 &self,
321 namespace: &str,
322 key: &str,
323 ) -> Result<impl Stream<Item = Result<T, Error>>, Error>
324 where
325 T: TryFrom<OwnedValue>,
326 Error: From<<T as TryFrom<OwnedValue>>::Error>,
327 {
328 Ok(self
329 .0
330 .signal_with_args::<Setting>("SettingChanged", &[(0, namespace), (1, key)])
331 .await?
332 .map(|x| T::try_from(x.2).map_err(From::from)))
333 }
334}
335
336impl<'a> std::ops::Deref for Settings<'a> {
337 type Target = zbus::Proxy<'a>;
338
339 fn deref(&self) -> &Self::Target {
340 &self.0
341 }
342}