1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
use futures_lite::prelude::*;
use futures_lite::stream::once;
use zbus::zvariant;
use zbus::zvariant::OwnedValue;
use zbus::Connection;
use zbus::Error;
use zbus::ProxyBuilder;
use zbus::Result;

#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};

const NAMESPACE: &str = "org.freedesktop.appearance";
const KEY: &str = "color-scheme";

#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum SchemePreference {
    #[default]
    NoPreference = 0,
    Dark = 1,
    Light = 2,
}

impl TryFrom<zvariant::Value<'_>> for SchemePreference {
    type Error = Error;

    // https://github.com/flatpak/xdg-desktop-portal/blob/c0f0eb103effdcf3701a1bf53f12fe953fbf0b75/data/org.freedesktop.impl.portal.Settings.xml#L37
    fn try_from(value: zvariant::Value) -> Result<Self> {
        Ok(match u32::try_from(value)? {
            0 => SchemePreference::NoPreference,
            1 => SchemePreference::Dark,
            2 => SchemePreference::Light,
            _ => SchemePreference::NoPreference,
        })
    }
}

impl<'s> TryFrom<&'s zbus::Message> for SchemePreference {
    type Error = Error;
    fn try_from(message: &'s zbus::Message) -> Result<Self> {
        message
            .body::<(&str, &str, zvariant::Value<'_>)>()
            .and_then(|args| SchemePreference::try_from(args.2))
    }
}

#[derive(Debug, Clone)]
pub struct SchemeProxy<'a>(zbus::Proxy<'a>);

impl<'a> ::zbus::ProxyDefault for SchemeProxy<'a> {
    const INTERFACE: &'static str = "org.freedesktop.portal.Settings";
    const DESTINATION: &'static str = "org.freedesktop.portal.Desktop";
    const PATH: &'static str = "/org/freedesktop/portal/desktop";
}

impl<'c> From<zbus::Proxy<'c>> for SchemeProxy<'c> {
    fn from(proxy: zbus::Proxy<'c>) -> Self {
        SchemeProxy(proxy)
    }
}

impl<'a> SchemeProxy<'a> {
    pub async fn new(conn: &Connection) -> Result<SchemeProxy<'a>> {
        Self::builder(conn).build().await
    }

    pub async fn with_new_connection() -> Result<SchemeProxy<'a>> {
        Self::new(&zbus::Connection::session().await?).await
    }

    pub fn builder(conn: &::zbus::Connection) -> ProxyBuilder<'a, Self> {
        ProxyBuilder::new(conn).cache_properties(zbus::CacheProperties::No)
    }

    pub async fn read(&self) -> Result<SchemePreference> {
        let reply: OwnedValue = self.0.call("Read", &(NAMESPACE, KEY)).await?;
        reply
            .downcast_ref::<zvariant::Value>()
            .cloned()
            .ok_or(zvariant::Error::IncorrectType.into())
            .and_then(SchemePreference::try_from)
    }

    // Can contain duplicates
    async fn receive_changed(&self) -> Result<impl Stream<Item = SchemePreference>> {
        let signal = self
            .0
            .receive_signal_with_args("SettingChanged", &[(0, NAMESPACE), (1, KEY)])
            .await?
            .filter_map(|x| SchemePreference::try_from(x.as_ref()).ok());

        Ok(signal)
    }

    pub async fn init_and_receive_changed(&self) -> Result<impl Stream<Item = SchemePreference>> {
        let mut preference = self.read().await?;
        Ok(
            once(preference).chain(self.receive_changed().await?.filter_map(move |p| {
                if p == preference {
                    None
                } else {
                    preference = p;
                    Some(p)
                }
            })),
        )
    }
}