1use std::collections::HashMap;
54
55use enumflags2::{bitflags, BitFlags};
56use serde::{Deserialize, Serialize};
57use serde_repr::{Deserialize_repr, Serialize_repr};
58use zbus::zvariant::{self, DeserializeDict, OwnedValue, SerializeDict, Type, Value};
59
60use super::{HandleToken, Icon, Request};
61use crate::{proxy::Proxy, ActivationToken, Error, WindowIdentifier};
62
63#[bitflags]
64#[derive(Default, Serialize_repr, Deserialize_repr, PartialEq, Eq, Debug, Copy, Clone, Type)]
65#[repr(u32)]
66#[doc(alias = "XdpLauncherType")]
67pub enum LauncherType {
69 #[doc(alias = "XDP_LAUNCHER_APPLICATION")]
70 #[default]
71 Application,
73 #[doc(alias = "XDP_LAUNCHER_WEBAPP")]
74 WebApplication,
76}
77
78#[cfg_attr(feature = "glib", derive(glib::Enum))]
79#[cfg_attr(feature = "glib", enum_type(name = "AshpdIconType"))]
80#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord, Type)]
81#[zvariant(signature = "s")]
82#[serde(rename_all = "lowercase")]
83pub enum IconType {
85 Png,
87 Jpeg,
89 Svg,
91}
92
93#[derive(Debug, Deserialize, Type)]
94#[zvariant(signature = "(vsu)")]
95pub struct LauncherIcon(zvariant::OwnedValue, IconType, u32);
97
98impl LauncherIcon {
99 pub fn icon(&self) -> Icon {
101 Icon::try_from(&self.0).unwrap()
102 }
103
104 pub fn type_(&self) -> IconType {
106 self.1
107 }
108
109 pub fn size(&self) -> u32 {
111 self.2
112 }
113}
114
115#[derive(Debug, Default, SerializeDict, Type)]
116#[zvariant(signature = "dict")]
117pub struct PrepareInstallOptions {
119 handle_token: HandleToken,
120 modal: Option<bool>,
121 launcher_type: LauncherType,
122 target: Option<String>,
123 editable_name: Option<bool>,
124 editable_icon: Option<bool>,
125}
126
127impl PrepareInstallOptions {
128 pub fn modal(mut self, modal: impl Into<Option<bool>>) -> Self {
130 self.modal = modal.into();
131 self
132 }
133
134 pub fn launcher_type(mut self, launcher_type: LauncherType) -> Self {
136 self.launcher_type = launcher_type;
137 self
138 }
139
140 pub fn target<'a>(mut self, target: impl Into<Option<&'a str>>) -> Self {
143 self.target = target.into().map(ToOwned::to_owned);
144 self
145 }
146
147 pub fn editable_name(mut self, editable_name: impl Into<Option<bool>>) -> Self {
149 self.editable_name = editable_name.into();
150 self
151 }
152
153 pub fn editable_icon(mut self, editable_icon: impl Into<Option<bool>>) -> Self {
155 self.editable_icon = editable_icon.into();
156 self
157 }
158}
159
160#[derive(DeserializeDict, Type)]
161#[zvariant(signature = "dict")]
162pub struct PrepareInstallResponse {
164 name: String,
165 icon: OwnedValue,
166 token: String,
167}
168
169impl PrepareInstallResponse {
170 pub fn name(&self) -> &str {
172 &self.name
173 }
174
175 pub fn token(&self) -> &str {
177 &self.token
178 }
179
180 pub fn icon(&self) -> Icon {
182 let inner = self.icon.downcast_ref::<Value>().unwrap();
183 Icon::try_from(inner).unwrap()
184 }
185}
186
187impl std::fmt::Debug for PrepareInstallResponse {
188 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
189 f.debug_struct("PrepareInstallResponse")
190 .field("name", &self.name())
191 .field("icon", &self.icon())
192 .field("token", &self.token())
193 .finish()
194 }
195}
196
197#[derive(SerializeDict, Type, Debug, Default)]
198#[zvariant(signature = "dict")]
199pub struct LaunchOptions {
201 activation_token: Option<ActivationToken>,
202}
203
204impl LaunchOptions {
205 #[must_use]
207 pub fn activation_token(
208 mut self,
209 activation_token: impl Into<Option<ActivationToken>>,
210 ) -> Self {
211 self.activation_token = activation_token.into();
212 self
213 }
214}
215
216#[derive(Debug)]
217pub struct UnexpectedIconError;
219
220impl std::error::Error for UnexpectedIconError {}
221impl std::fmt::Display for UnexpectedIconError {
222 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
223 f.write_str("Unexpected icon type. Only Icon::Bytes is supported")
224 }
225}
226
227#[derive(Debug)]
232#[doc(alias = "org.freedesktop.portal.DynamicLauncher")]
233pub struct DynamicLauncherProxy<'a>(Proxy<'a>);
234
235impl<'a> DynamicLauncherProxy<'a> {
236 pub async fn new() -> Result<DynamicLauncherProxy<'a>, Error> {
238 let proxy = Proxy::new_desktop("org.freedesktop.portal.DynamicLauncher").await?;
239 Ok(Self(proxy))
240 }
241
242 #[doc(alias = "PrepareInstall")]
248 #[doc(alias = "xdp_portal_dynamic_launcher_prepare_install")]
249 #[doc(alias = "xdp_portal_dynamic_launcher_prepare_install_finish")]
250 pub async fn prepare_install(
251 &self,
252 identifier: Option<&WindowIdentifier>,
253 name: &str,
254 icon: Icon,
255 options: PrepareInstallOptions,
256 ) -> Result<Request<PrepareInstallResponse>, Error> {
257 if !icon.is_bytes() {
258 return Err(UnexpectedIconError {}.into());
259 }
260 let identifier = identifier.map(|i| i.to_string()).unwrap_or_default();
261 self.0
262 .request(
263 &options.handle_token,
264 "PrepareInstall",
265 &(identifier, name, icon.as_value(), &options),
266 )
267 .await
268 }
269
270 #[doc(alias = "RequestInstallToken")]
276 #[doc(alias = "xdp_portal_dynamic_launcher_request_install_token")]
277 pub async fn request_install_token(&self, name: &str, icon: Icon) -> Result<String, Error> {
278 if !icon.is_bytes() {
279 return Err(UnexpectedIconError {}.into());
280 }
281
282 let options: HashMap<&str, zvariant::Value<'_>> = HashMap::new();
284 self.0
285 .call::<String>("RequestInstallToken", &(name, icon.as_value(), options))
286 .await
287 }
288
289 #[doc(alias = "Install")]
293 #[doc(alias = "xdp_portal_dynamic_launcher_install")]
294 pub async fn install(
295 &self,
296 token: &str,
297 desktop_file_id: &str,
298 desktop_entry: &str,
299 ) -> Result<(), Error> {
300 let options: HashMap<&str, zvariant::Value<'_>> = HashMap::new();
302 self.0
303 .call::<()>("Install", &(token, desktop_file_id, desktop_entry, options))
304 .await
305 }
306
307 #[doc(alias = "Uninstall")]
311 #[doc(alias = "xdp_portal_dynamic_launcher_uninstall")]
312 pub async fn uninstall(&self, desktop_file_id: &str) -> Result<(), Error> {
313 let options: HashMap<&str, zvariant::Value<'_>> = HashMap::new();
315 self.0
316 .call::<()>("Uninstall", &(desktop_file_id, options))
317 .await
318 }
319
320 #[doc(alias = "GetDesktopEntry")]
324 #[doc(alias = "xdp_portal_dynamic_launcher_get_desktop_entry")]
325 pub async fn desktop_entry(&self, desktop_file_id: &str) -> Result<String, Error> {
326 self.0.call("GetDesktopEntry", &(desktop_file_id)).await
327 }
328
329 #[doc(alias = "GetIcon")]
333 #[doc(alias = "xdp_portal_dynamic_launcher_get_icon")]
334 pub async fn icon(&self, desktop_file_id: &str) -> Result<LauncherIcon, Error> {
335 self.0.call("GetIcon", &(desktop_file_id)).await
336 }
337
338 #[doc(alias = "Launch")]
342 #[doc(alias = "xdp_portal_dynamic_launcher_launch")]
343 pub async fn launch(&self, desktop_file_id: &str, options: LaunchOptions) -> Result<(), Error> {
344 self.0.call("Launch", &(desktop_file_id, &options)).await
345 }
346
347 #[doc(alias = "SupportedLauncherTypes")]
351 pub async fn supported_launcher_types(&self) -> Result<BitFlags<LauncherType>, Error> {
352 self.0
353 .property::<BitFlags<LauncherType>>("SupportedLauncherTypes")
354 .await
355 }
356}
357
358impl<'a> std::ops::Deref for DynamicLauncherProxy<'a> {
359 type Target = zbus::Proxy<'a>;
360
361 fn deref(&self) -> &Self::Target {
362 &self.0
363 }
364}
365
366#[cfg(test)]
367mod test {
368 use super::*;
369
370 #[test]
371 fn test_icon_signature() {
372 assert_eq!(LauncherIcon::SIGNATURE, "(vsu)");
373
374 let icon = vec![IconType::Png];
375 assert_eq!(serde_json::to_string(&icon).unwrap(), "[\"png\"]");
376 }
377}