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)")]
14pub enum Icon {
18 Uri(url::Url),
20 Names(Vec<String>),
22 Bytes(Vec<u8>),
24 FileDescriptor(std::os::fd::OwnedFd),
26}
27
28impl Icon {
29 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 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 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 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}