ashpd/desktop/
icon.rs

1use std::os::fd::AsFd;
2
3use serde::{
4    de,
5    ser::{Serialize, SerializeTuple},
6    Deserialize,
7};
8use zbus::zvariant::{self, OwnedValue, Type, Value};
9
10use crate::Error;
11
12#[derive(Debug, Type)]
13#[zvariant(signature = "(sv)")]
14/// A representation of an icon.
15///
16/// Used by both the Notification & Dynamic launcher portals.
17pub enum Icon {
18    /// An icon URI.
19    Uri(url::Url),
20    /// A list of icon names.
21    Names(Vec<String>),
22    /// Icon bytes.
23    Bytes(Vec<u8>),
24    /// A file descriptor.
25    FileDescriptor(std::os::fd::OwnedFd),
26}
27
28impl Icon {
29    /// Create an icon from a list of names.
30    pub fn with_names<N>(names: impl IntoIterator<Item = N>) -> Self
31    where
32        N: ToString,
33    {
34        Self::Names(names.into_iter().map(|name| name.to_string()).collect())
35    }
36
37    pub(crate) fn is_bytes(&self) -> bool {
38        matches!(self, Self::Bytes(_))
39    }
40
41    pub(crate) fn inner_bytes(&self) -> Value {
42        match self {
43            Self::Bytes(bytes) => {
44                let mut array = zvariant::Array::new(u8::SIGNATURE);
45                for byte in bytes.iter() {
46                    // Safe to unwrap because we are sure it is of the correct type
47                    array.append(Value::from(*byte)).unwrap();
48                }
49                Value::from(array)
50            }
51            _ => panic!("Only bytes icons can be converted to a bytes variant"),
52        }
53    }
54
55    pub(crate) fn as_value(&self) -> Value {
56        let tuple = match self {
57            Self::Uri(uri) => ("file", Value::from(uri.as_str())),
58            Self::Names(names) => {
59                let mut array = zvariant::Array::new(String::SIGNATURE);
60                for name in names.iter() {
61                    // Safe to unwrap because we are sure it is of the correct type
62                    array.append(Value::from(name)).unwrap();
63                }
64
65                ("themed", Value::from(array))
66            }
67            Self::Bytes(_) => ("bytes", self.inner_bytes()),
68            Self::FileDescriptor(fd) => ("file-descriptor", zvariant::Fd::from(fd).into()),
69        };
70        Value::new(tuple)
71    }
72}
73
74impl Serialize for Icon {
75    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
76    where
77        S: serde::Serializer,
78    {
79        let mut tuple = serializer.serialize_tuple(2)?;
80        match self {
81            Self::Uri(uri) => {
82                tuple.serialize_element("file")?;
83                tuple.serialize_element(&Value::from(uri.as_str()))?;
84            }
85            Self::Names(names) => {
86                tuple.serialize_element("themed")?;
87                let mut array = zvariant::Array::new(String::SIGNATURE);
88                for name in names.iter() {
89                    // Safe to unwrap because we are sure it is of the correct type
90                    array.append(Value::from(name)).unwrap();
91                }
92                tuple.serialize_element(&Value::from(array))?;
93            }
94            Self::Bytes(_) => {
95                tuple.serialize_element("bytes")?;
96                tuple.serialize_element(&self.inner_bytes())?;
97            }
98            Self::FileDescriptor(fd) => {
99                tuple.serialize_element("file-descriptor")?;
100                tuple.serialize_element(&Value::from(zvariant::Fd::from(fd)))?;
101            }
102        }
103        tuple.end()
104    }
105}
106
107impl<'de> Deserialize<'de> for Icon {
108    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
109    where
110        D: serde::Deserializer<'de>,
111    {
112        let (type_, data) = <(String, OwnedValue)>::deserialize(deserializer)?;
113        match type_.as_str() {
114            "file" => {
115                let uri_str = data.downcast_ref::<zvariant::Str>().unwrap();
116                let uri = url::Url::parse(uri_str.as_str())
117                    .map_err(|_| de::Error::custom("Couldn't deserialize Icon of type 'file'"))?;
118                Ok(Self::Uri(uri))
119            }
120            "bytes" => {
121                let array = data.downcast_ref::<zvariant::Array>().unwrap();
122                let mut bytes = Vec::with_capacity(array.len());
123                for byte in array.inner() {
124                    bytes.push(byte.downcast_ref::<u8>().unwrap());
125                }
126                Ok(Self::Bytes(bytes))
127            }
128            "themed" => {
129                let array = data.downcast_ref::<zvariant::Array>().unwrap();
130                let mut names = Vec::with_capacity(array.len());
131                for value in array.inner() {
132                    let name = value.downcast_ref::<zvariant::Str>().unwrap();
133                    names.push(name.as_str().to_owned());
134                }
135                Ok(Self::Names(names))
136            }
137            "file-descriptor" => {
138                let fd = data.downcast_ref::<zvariant::Fd>().unwrap();
139                Ok(Self::FileDescriptor(
140                    fd.as_fd()
141                        .try_clone_to_owned()
142                        .expect("Failed to clone file descriptor"),
143                ))
144            }
145            _ => Err(de::Error::custom("Invalid Icon type")),
146        }
147    }
148}
149
150impl TryFrom<&OwnedValue> for Icon {
151    type Error = crate::Error;
152    fn try_from(value: &OwnedValue) -> Result<Self, Self::Error> {
153        let structure = value.downcast_ref::<zvariant::Structure>().unwrap();
154        let fields = structure.fields();
155        let type_ = fields[0].downcast_ref::<zvariant::Str>().unwrap();
156        match type_.as_str() {
157            "file" => {
158                let uri_str = fields[1]
159                    .downcast_ref::<zvariant::Str>()
160                    .unwrap()
161                    .to_owned();
162                let uri = url::Url::parse(uri_str.as_str())
163                    .map_err(|_| crate::Error::ParseError("Failed to parse uri"))?;
164                Ok(Self::Uri(uri))
165            }
166            "bytes" => {
167                let array = fields[1].downcast_ref::<zvariant::Array>().unwrap();
168                let mut bytes = Vec::with_capacity(array.len());
169                for byte in array.inner() {
170                    bytes.push(byte.downcast_ref::<u8>().unwrap());
171                }
172                Ok(Self::Bytes(bytes))
173            }
174            "themed" => {
175                let array = fields[1].downcast_ref::<zvariant::Array>().unwrap();
176                let mut names = Vec::with_capacity(array.len());
177                for value in array.inner() {
178                    let name = value.downcast_ref::<zvariant::Str>().unwrap();
179                    names.push(name.as_str().to_owned());
180                }
181                Ok(Self::Names(names))
182            }
183            "file-descriptor" => {
184                let fd = fields[1].downcast_ref::<zvariant::Fd>().unwrap();
185                Ok(Self::FileDescriptor(
186                    fd.as_fd()
187                        .try_clone_to_owned()
188                        .expect("Failed to clone file descriptor"),
189                ))
190            }
191            _ => Err(Error::ParseError("Invalid Icon type")),
192        }
193    }
194}
195
196impl TryFrom<OwnedValue> for Icon {
197    type Error = crate::Error;
198    fn try_from(value: OwnedValue) -> Result<Self, Self::Error> {
199        Self::try_from(&value)
200    }
201}
202
203impl TryFrom<Value<'_>> for Icon {
204    type Error = crate::Error;
205    fn try_from(value: Value<'_>) -> Result<Self, Self::Error> {
206        Self::try_from(&value)
207    }
208}
209impl TryFrom<&Value<'_>> for Icon {
210    type Error = crate::Error;
211    fn try_from(value: &Value<'_>) -> Result<Self, Self::Error> {
212        Self::try_from(value.try_to_owned()?)
213    }
214}
215
216#[cfg(test)]
217mod test {
218    use zbus::zvariant::{serialized::Context, to_bytes, Endian};
219
220    use super::*;
221
222    #[test]
223    fn check_icon_signature() {
224        assert_eq!(Icon::SIGNATURE, "(sv)");
225    }
226
227    #[test]
228    fn serialize_deserialize() {
229        let ctxt = Context::new_dbus(Endian::Little, 0);
230
231        let icon = Icon::with_names(["dialog-symbolic"]);
232
233        let encoded = to_bytes(ctxt, &icon).unwrap();
234        let decoded: Icon = encoded.deserialize().unwrap().0;
235        assert!(matches!(decoded, Icon::Names(_)));
236
237        let icon = Icon::Uri(url::Url::parse("file://some/icon.png").unwrap());
238        let encoded = to_bytes(ctxt, &icon).unwrap();
239        let decoded: Icon = encoded.deserialize().unwrap().0;
240        assert!(matches!(decoded, Icon::Uri(_)));
241
242        let icon = Icon::Bytes(vec![1, 0, 1, 0]);
243        let encoded = to_bytes(ctxt, &icon).unwrap();
244        let decoded: Icon = encoded.deserialize().unwrap().0;
245        assert!(matches!(decoded, Icon::Bytes(_)));
246
247        let fd = std::fs::File::open("/tmp").unwrap();
248        let icon = Icon::FileDescriptor(fd.as_fd().try_clone_to_owned().unwrap());
249        let encoded = to_bytes(ctxt, &icon).unwrap();
250        let decoded: Icon = encoded.deserialize().unwrap().0;
251        assert!(matches!(decoded, Icon::FileDescriptor(_)));
252    }
253}