use std::{collections::HashMap, convert::TryFrom, fmt::Debug, future::ready};
use futures_util::{Stream, StreamExt};
use serde::{Deserialize, Serialize};
use zbus::zvariant::{OwnedValue, Type, Value};
use crate::{Error, desktop::Color, proxy::Proxy};
pub type Namespace = HashMap<String, OwnedValue>;
#[derive(Deserialize, Type)]
pub struct Setting(String, String, OwnedValue);
impl Setting {
pub fn namespace(&self) -> &str {
&self.0
}
pub fn key(&self) -> &str {
&self.1
}
pub fn value(&self) -> &OwnedValue {
&self.2
}
}
impl std::fmt::Debug for Setting {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Setting")
.field("namespace", &self.namespace())
.field("key", &self.key())
.field("value", self.value())
.finish()
}
}
#[cfg_attr(feature = "glib", derive(glib::Enum))]
#[cfg_attr(feature = "glib", enum_type(name = "AshpdColorScheme"))]
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default)]
pub enum ColorScheme {
#[default]
NoPreference,
PreferDark,
PreferLight,
}
impl From<ColorScheme> for OwnedValue {
fn from(value: ColorScheme) -> Self {
match value {
ColorScheme::PreferDark => 1,
ColorScheme::PreferLight => 2,
_ => 0,
}
.into()
}
}
impl TryFrom<OwnedValue> for ColorScheme {
type Error = Error;
fn try_from(value: OwnedValue) -> Result<Self, Self::Error> {
TryFrom::<Value>::try_from(value.into())
}
}
impl TryFrom<Value<'_>> for ColorScheme {
type Error = Error;
fn try_from(value: Value) -> Result<Self, Self::Error> {
Ok(match u32::try_from(value)? {
1 => Self::PreferDark,
2 => Self::PreferLight,
_ => Self::NoPreference,
})
}
}
#[cfg_attr(feature = "glib", derive(glib::Enum))]
#[cfg_attr(feature = "glib", enum_type(name = "AshpdContrast"))]
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default)]
pub enum Contrast {
#[default]
NoPreference,
High,
}
impl From<Contrast> for OwnedValue {
fn from(value: Contrast) -> Self {
match value {
Contrast::High => 1,
_ => 0,
}
.into()
}
}
impl TryFrom<OwnedValue> for Contrast {
type Error = Error;
fn try_from(value: OwnedValue) -> Result<Self, Self::Error> {
TryFrom::<Value>::try_from(value.into())
}
}
impl TryFrom<Value<'_>> for Contrast {
type Error = Error;
fn try_from(value: Value) -> Result<Self, Self::Error> {
Ok(match u32::try_from(value)? {
1 => Self::High,
_ => Self::NoPreference,
})
}
}
#[cfg_attr(feature = "glib", derive(glib::Enum))]
#[cfg_attr(feature = "glib", enum_type(name = "AshpdReducedMotion"))]
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default)]
pub enum ReducedMotion {
#[default]
NoPreference,
ReducedMotion,
}
impl From<ReducedMotion> for OwnedValue {
fn from(value: ReducedMotion) -> Self {
match value {
ReducedMotion::ReducedMotion => 1,
_ => 0,
}
.into()
}
}
impl TryFrom<OwnedValue> for ReducedMotion {
type Error = Error;
fn try_from(value: OwnedValue) -> Result<Self, Self::Error> {
TryFrom::<Value>::try_from(value.into())
}
}
impl TryFrom<Value<'_>> for ReducedMotion {
type Error = Error;
fn try_from(value: Value) -> Result<Self, Self::Error> {
Ok(match u32::try_from(value)? {
1 => Self::ReducedMotion,
_ => Self::NoPreference,
})
}
}
pub const APPEARANCE_NAMESPACE: &str = "org.freedesktop.appearance";
pub const COLOR_SCHEME_KEY: &str = "color-scheme";
pub const ACCENT_COLOR_SCHEME_KEY: &str = "accent-color";
pub const CONTRAST_KEY: &str = "contrast";
pub const REDUCED_MOTION_KEY: &str = "reduced-motion";
#[derive(Debug)]
#[doc(alias = "org.freedesktop.portal.Settings")]
pub struct Settings(Proxy<'static>);
impl Settings {
pub async fn new() -> Result<Settings, Error> {
let proxy = Proxy::new_desktop("org.freedesktop.portal.Settings").await?;
Ok(Self(proxy))
}
pub async fn with_connection(connection: zbus::Connection) -> Result<Settings, Error> {
let proxy =
Proxy::new_desktop_with_connection(connection, "org.freedesktop.portal.Settings")
.await?;
Ok(Self(proxy))
}
pub fn version(&self) -> u32 {
self.0.version()
}
#[doc(alias = "ReadAll")]
pub async fn read_all(
&self,
namespaces: &[impl AsRef<str> + Type + Serialize + Debug],
) -> Result<HashMap<String, Namespace>, Error> {
self.0.call("ReadAll", &(namespaces)).await
}
#[doc(alias = "Read")]
#[doc(alias = "ReadOne")]
pub async fn read<T>(&self, namespace: &str, key: &str) -> Result<T, Error>
where
T: TryFrom<OwnedValue>,
Error: From<<T as TryFrom<OwnedValue>>::Error>,
{
let value = self.0.call::<OwnedValue>("Read", &(namespace, key)).await?;
if let Ok(v) = value.downcast_ref::<Value>() {
T::try_from(v.try_to_owned()?).map_err(From::from)
} else {
T::try_from(value).map_err(From::from)
}
}
pub async fn accent_color(&self) -> Result<Color, Error> {
self.read::<(f64, f64, f64)>(APPEARANCE_NAMESPACE, ACCENT_COLOR_SCHEME_KEY)
.await
.map(Color::from)
}
pub async fn color_scheme(&self) -> Result<ColorScheme, Error> {
self.read::<ColorScheme>(APPEARANCE_NAMESPACE, COLOR_SCHEME_KEY)
.await
}
pub async fn contrast(&self) -> Result<Contrast, Error> {
self.read::<Contrast>(APPEARANCE_NAMESPACE, CONTRAST_KEY)
.await
}
pub async fn reduced_motion(&self) -> Result<ReducedMotion, Error> {
self.read::<ReducedMotion>(APPEARANCE_NAMESPACE, REDUCED_MOTION_KEY)
.await
}
pub async fn receive_color_scheme_changed(
&self,
) -> Result<impl Stream<Item = ColorScheme>, Error> {
Ok(self
.receive_setting_changed_with_args(APPEARANCE_NAMESPACE, COLOR_SCHEME_KEY)
.await?
.filter_map(|t| ready(t.ok())))
}
pub async fn receive_accent_color_changed(&self) -> Result<impl Stream<Item = Color>, Error> {
Ok(self
.receive_setting_changed_with_args::<(f64, f64, f64)>(
APPEARANCE_NAMESPACE,
ACCENT_COLOR_SCHEME_KEY,
)
.await?
.filter_map(|t| ready(t.ok().map(Color::from))))
}
pub async fn receive_contrast_changed(&self) -> Result<impl Stream<Item = Contrast>, Error> {
Ok(self
.receive_setting_changed_with_args(APPEARANCE_NAMESPACE, CONTRAST_KEY)
.await?
.filter_map(|t| ready(t.ok())))
}
pub async fn receive_reduced_motion_changed(
&self,
) -> Result<impl Stream<Item = ReducedMotion>, Error> {
Ok(self
.receive_setting_changed_with_args(APPEARANCE_NAMESPACE, REDUCED_MOTION_KEY)
.await?
.filter_map(|t| ready(t.ok())))
}
#[doc(alias = "SettingChanged")]
pub async fn receive_setting_changed(&self) -> Result<impl Stream<Item = Setting>, Error> {
self.0.signal("SettingChanged").await
}
pub async fn receive_setting_changed_with_args<T>(
&self,
namespace: &str,
key: &str,
) -> Result<impl Stream<Item = Result<T, Error>> + use<T>, Error>
where
T: TryFrom<OwnedValue>,
Error: From<<T as TryFrom<OwnedValue>>::Error>,
{
Ok(self
.0
.signal_with_args::<Setting>("SettingChanged", &[(0, namespace), (1, key)])
.await?
.map(|x| T::try_from(x.2).map_err(From::from)))
}
}
impl std::ops::Deref for Settings {
type Target = zbus::Proxy<'static>;
fn deref(&self) -> &Self::Target {
&self.0
}
}