use enumflags2::{BitFlags, bitflags};
use serde::{Deserialize, Serialize};
use serde_repr::{Deserialize_repr, Serialize_repr};
use zbus::zvariant::{
self, Optional, OwnedValue, Type, Value,
as_value::{self, optional},
};
use super::{HandleToken, Icon, Request};
use crate::{ActivationToken, Error, WindowIdentifier, proxy::Proxy};
#[bitflags]
#[derive(Default, Serialize_repr, Deserialize_repr, PartialEq, Eq, Debug, Copy, Clone, Type)]
#[repr(u32)]
#[doc(alias = "XdpLauncherType")]
pub enum LauncherType {
#[doc(alias = "XDP_LAUNCHER_APPLICATION")]
#[default]
Application,
#[doc(alias = "XDP_LAUNCHER_WEBAPP")]
WebApplication,
}
#[cfg_attr(feature = "glib", derive(glib::Enum))]
#[cfg_attr(feature = "glib", enum_type(name = "AshpdIconType"))]
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord, Type)]
#[zvariant(signature = "s")]
#[serde(rename_all = "lowercase")]
pub enum IconType {
Png,
Jpeg,
Svg,
}
#[derive(Debug, Deserialize, Type)]
#[zvariant(signature = "(vsu)")]
pub struct LauncherIcon(zvariant::OwnedValue, IconType, u32);
impl LauncherIcon {
pub fn icon(&self) -> Icon {
Icon::try_from(&self.0).unwrap()
}
pub fn type_(&self) -> IconType {
self.1
}
pub fn size(&self) -> u32 {
self.2
}
}
#[derive(Serialize, Type, Debug, Default)]
#[zvariant(signature = "dict")]
pub struct PrepareInstallOptions {
#[serde(with = "as_value")]
handle_token: HandleToken,
#[serde(with = "optional", skip_serializing_if = "Option::is_none")]
modal: Option<bool>,
#[serde(with = "as_value")]
launcher_type: LauncherType,
#[serde(with = "optional", skip_serializing_if = "Option::is_none")]
target: Option<String>,
#[serde(with = "optional", skip_serializing_if = "Option::is_none")]
editable_name: Option<bool>,
#[serde(with = "optional", skip_serializing_if = "Option::is_none")]
editable_icon: Option<bool>,
}
impl PrepareInstallOptions {
pub fn set_modal(mut self, modal: impl Into<Option<bool>>) -> Self {
self.modal = modal.into();
self
}
pub fn set_launcher_type(mut self, launcher_type: LauncherType) -> Self {
self.launcher_type = launcher_type;
self
}
pub fn set_target<'a>(mut self, target: impl Into<Option<&'a str>>) -> Self {
self.target = target.into().map(ToOwned::to_owned);
self
}
pub fn set_editable_name(mut self, editable_name: impl Into<Option<bool>>) -> Self {
self.editable_name = editable_name.into();
self
}
pub fn set_editable_icon(mut self, editable_icon: impl Into<Option<bool>>) -> Self {
self.editable_icon = editable_icon.into();
self
}
}
#[derive(Deserialize, Type)]
#[zvariant(signature = "dict")]
pub struct PrepareInstallResponse {
#[serde(with = "as_value")]
name: String,
#[serde(with = "as_value")]
icon: OwnedValue,
#[serde(with = "as_value")]
token: String,
}
impl PrepareInstallResponse {
pub fn name(&self) -> &str {
&self.name
}
pub fn token(&self) -> &str {
&self.token
}
pub fn icon(&self) -> Icon {
let inner = self.icon.downcast_ref::<Value>().unwrap();
Icon::try_from(inner).unwrap()
}
}
impl std::fmt::Debug for PrepareInstallResponse {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("PrepareInstallResponse")
.field("name", &self.name())
.field("icon", &self.icon())
.field("token", &self.token())
.finish()
}
}
#[derive(Serialize, Type, Debug, Default)]
#[zvariant(signature = "dict")]
pub struct LaunchOptions {
#[serde(with = "optional", skip_serializing_if = "Option::is_none")]
activation_token: Option<ActivationToken>,
}
impl LaunchOptions {
#[must_use]
pub fn activation_token(
mut self,
activation_token: impl Into<Option<ActivationToken>>,
) -> Self {
self.activation_token = activation_token.into();
self
}
}
#[derive(Serialize, Type, Debug, Default)]
#[zvariant(signature = "dict")]
pub struct InstallOptions {}
#[derive(Serialize, Type, Debug, Default)]
#[zvariant(signature = "dict")]
pub struct UninstallOptions {}
#[derive(Serialize, Type, Debug, Default)]
#[zvariant(signature = "dict")]
pub struct RequestInstallTokenOptions {}
#[derive(Debug)]
pub struct UnexpectedIconError;
impl std::error::Error for UnexpectedIconError {}
impl std::fmt::Display for UnexpectedIconError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str("Unexpected icon type. Only Icon::Bytes is supported")
}
}
#[derive(Debug)]
#[doc(alias = "org.freedesktop.portal.DynamicLauncher")]
pub struct DynamicLauncherProxy(Proxy<'static>);
impl DynamicLauncherProxy {
pub async fn new() -> Result<Self, Error> {
let proxy = Proxy::new_desktop("org.freedesktop.portal.DynamicLauncher").await?;
Ok(Self(proxy))
}
pub async fn with_connection(connection: zbus::Connection) -> Result<Self, Error> {
let proxy = Proxy::new_desktop_with_connection(
connection,
"org.freedesktop.portal.DynamicLauncher",
)
.await?;
Ok(Self(proxy))
}
pub fn version(&self) -> u32 {
self.0.version()
}
#[doc(alias = "PrepareInstall")]
#[doc(alias = "xdp_portal_dynamic_launcher_prepare_install")]
#[doc(alias = "xdp_portal_dynamic_launcher_prepare_install_finish")]
pub async fn prepare_install(
&self,
identifier: Option<&WindowIdentifier>,
name: &str,
icon: Icon,
options: PrepareInstallOptions,
) -> Result<Request<PrepareInstallResponse>, Error> {
if !icon.is_bytes() {
return Err(UnexpectedIconError {}.into());
}
let identifier = Optional::from(identifier);
self.0
.request(
&options.handle_token,
"PrepareInstall",
&(identifier, name, icon.as_value(), &options),
)
.await
}
#[doc(alias = "RequestInstallToken")]
#[doc(alias = "xdp_portal_dynamic_launcher_request_install_token")]
pub async fn request_install_token(
&self,
name: &str,
icon: Icon,
options: RequestInstallTokenOptions,
) -> Result<String, Error> {
if !icon.is_bytes() {
return Err(UnexpectedIconError {}.into());
}
self.0
.call::<String>("RequestInstallToken", &(name, icon.as_value(), options))
.await
}
#[doc(alias = "Install")]
#[doc(alias = "xdp_portal_dynamic_launcher_install")]
pub async fn install(
&self,
token: &str,
desktop_file_id: &str,
desktop_entry: &str,
options: InstallOptions,
) -> Result<(), Error> {
self.0
.call::<()>("Install", &(token, desktop_file_id, desktop_entry, options))
.await
}
#[doc(alias = "Uninstall")]
#[doc(alias = "xdp_portal_dynamic_launcher_uninstall")]
pub async fn uninstall(
&self,
desktop_file_id: &str,
options: UninstallOptions,
) -> Result<(), Error> {
self.0
.call::<()>("Uninstall", &(desktop_file_id, options))
.await
}
#[doc(alias = "GetDesktopEntry")]
#[doc(alias = "xdp_portal_dynamic_launcher_get_desktop_entry")]
pub async fn desktop_entry(&self, desktop_file_id: &str) -> Result<String, Error> {
self.0.call("GetDesktopEntry", &(desktop_file_id)).await
}
#[doc(alias = "GetIcon")]
#[doc(alias = "xdp_portal_dynamic_launcher_get_icon")]
pub async fn icon(&self, desktop_file_id: &str) -> Result<LauncherIcon, Error> {
self.0.call("GetIcon", &(desktop_file_id)).await
}
#[doc(alias = "Launch")]
#[doc(alias = "xdp_portal_dynamic_launcher_launch")]
pub async fn launch(&self, desktop_file_id: &str, options: LaunchOptions) -> Result<(), Error> {
self.0.call("Launch", &(desktop_file_id, &options)).await
}
#[doc(alias = "SupportedLauncherTypes")]
pub async fn supported_launcher_types(&self) -> Result<BitFlags<LauncherType>, Error> {
self.0
.property::<BitFlags<LauncherType>>("SupportedLauncherTypes")
.await
}
}
impl std::ops::Deref for DynamicLauncherProxy {
type Target = zbus::Proxy<'static>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_icon_signature() {
assert_eq!(LauncherIcon::SIGNATURE, "(vsu)");
let icon = vec![IconType::Png];
assert_eq!(serde_json::to_string(&icon).unwrap(), "[\"png\"]");
}
}