ashpd/desktop/
session.rs

1use std::{collections::HashMap, fmt::Debug, marker::PhantomData};
2
3use futures_util::Stream;
4use serde::{Deserialize, Serialize, Serializer};
5use zbus::zvariant::{ObjectPath, OwnedObjectPath, OwnedValue, Type};
6
7use crate::{desktop::HandleToken, proxy::Proxy, Error};
8
9/// Shared by all portal interfaces that involve long lived sessions.
10///
11/// When a method that creates a session is called, if successful, the reply
12/// will include a session handle (i.e. object path) for a Session object, which
13/// will stay alive for the duration of the session.
14///
15/// The duration of the session is defined by the interface that creates it.
16/// For convenience, the interface contains a method [`Session::close`],
17/// and a signal [`Session::receive_closed`]. Whether it is allowed to
18/// directly call [`Session::close`] depends on the interface.
19///
20/// Wrapper of the DBus interface: [`org.freedesktop.portal.Session`](https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.Session.html).
21#[derive(Type)]
22#[doc(alias = "org.freedesktop.portal.Session")]
23#[zvariant(signature = "o")]
24pub struct Session<'a, T>(Proxy<'a>, PhantomData<T>)
25where
26    T: SessionPortal;
27
28impl<'a, T> Session<'a, T>
29where
30    T: SessionPortal,
31{
32    /// Create a new instance of [`Session`].
33    ///
34    /// **Note** A [`Session`] is not supposed to be created manually.
35    pub(crate) async fn new<P>(path: P) -> Result<Session<'a, T>, Error>
36    where
37        P: TryInto<ObjectPath<'a>>,
38        P::Error: Into<zbus::Error>,
39    {
40        let proxy = Proxy::new_desktop_with_path("org.freedesktop.portal.Session", path).await?;
41        Ok(Self(proxy, PhantomData))
42    }
43
44    pub(crate) async fn from_unique_name(
45        handle_token: &HandleToken,
46    ) -> Result<Session<'a, T>, crate::Error> {
47        let path =
48            Proxy::unique_name("/org/freedesktop/portal/desktop/session", handle_token).await?;
49        #[cfg(feature = "tracing")]
50        tracing::info!("Creating a org.freedesktop.portal.Session {}", path);
51        Self::new(path).await
52    }
53
54    /// Emitted when a session is closed.
55    ///
56    /// # Specifications
57    ///
58    /// See also [`Closed`](https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.Session.html#org-freedesktop-portal-session-closed).
59    #[doc(alias = "Closed")]
60    pub async fn receive_closed(&self) -> Result<impl Stream<Item = ()>, Error> {
61        self.0.signal("Closed").await
62    }
63
64    /// Closes the portal session to which this object refers and ends all
65    /// related user interaction (dialogs, etc).
66    ///
67    /// # Specifications
68    ///
69    /// See also [`Close`](https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.Session.html#org-freedesktop-portal-session-close).
70    #[doc(alias = "Close")]
71    pub async fn close(&self) -> Result<(), Error> {
72        self.0.call("Close", &()).await
73    }
74
75    pub(crate) fn path(&self) -> &ObjectPath<'_> {
76        self.0.path()
77    }
78}
79
80impl<T> Serialize for Session<'_, T>
81where
82    T: SessionPortal,
83{
84    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
85    where
86        S: Serializer,
87    {
88        ObjectPath::serialize(self.path(), serializer)
89    }
90}
91
92impl<T> Debug for Session<'_, T>
93where
94    T: SessionPortal,
95{
96    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
97        f.debug_tuple("Session")
98            .field(&self.path().as_str())
99            .finish()
100    }
101}
102
103/// Portals that have a long-lived interaction
104pub trait SessionPortal: crate::Sealed {}
105
106/// A response to a `create_session` request.
107#[derive(Type, Debug)]
108#[zvariant(signature = "dict")]
109pub(crate) struct CreateSessionResponse {
110    pub(crate) session_handle: OwnedObjectPath,
111}
112
113// Context: Various portal were expected to actually return an OwnedObjectPath
114// but unfortunately this wasn't the case when the portals were implemented in
115// xdp. Fixing that would be an API break as well...
116// See <https://github.com/flatpak/xdg-desktop-portal/pull/609>
117// The Location, ScreenCast, Remote Desktop, Global Shortcuts and Inhibit
118// portals `CreateSession` calls are all affected.
119//
120// So in order to be future proof, we try to deserialize the `session_handle`
121// key as a string and fallback to an object path in case the situation gets
122// resolved in the future.
123impl<'de> Deserialize<'de> for CreateSessionResponse {
124    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
125    where
126        D: serde::Deserializer<'de>,
127    {
128        let map: HashMap<String, OwnedValue> = HashMap::deserialize(deserializer)?;
129        let session_handle = map.get("session_handle").ok_or_else(|| {
130            serde::de::Error::custom(
131                "CreateSessionResponse failed to deserialize. Couldn't find a session_handle",
132            )
133        })?;
134
135        let path = if let Ok(object_path_str) = session_handle.downcast_ref::<&str>() {
136            ObjectPath::try_from(object_path_str).unwrap()
137        } else if let Ok(object_path) = session_handle.downcast_ref::<ObjectPath<'_>>() {
138            object_path
139        } else {
140            return Err(serde::de::Error::custom(
141                "Wrong session_handle type. Expected `s` or `o`.",
142            ));
143        };
144
145        Ok(Self {
146            session_handle: path.into(),
147        })
148    }
149}