1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
use std::{collections::HashMap, fmt::Debug, marker::PhantomData};

use futures_util::Stream;
use serde::{Deserialize, Serialize, Serializer};
use zbus::zvariant::{ObjectPath, OwnedObjectPath, OwnedValue, Type};

use crate::{desktop::HandleToken, proxy::Proxy, Error};

pub type SessionDetails = HashMap<String, OwnedValue>;

/// Shared by all portal interfaces that involve long lived sessions.
///
/// When a method that creates a session is called, if successful, the reply
/// will include a session handle (i.e. object path) for a Session object, which
/// will stay alive for the duration of the session.
///
/// The duration of the session is defined by the interface that creates it.
/// For convenience, the interface contains a method [`Session::close`],
/// and a signal [`Session::receive_closed`]. Whether it is allowed to
/// directly call [`Session::close`] depends on the interface.
///
/// Wrapper of the DBus interface: [`org.freedesktop.portal.Session`](https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.Session.html).
#[derive(Type)]
#[doc(alias = "org.freedesktop.portal.Session")]
#[zvariant(signature = "o")]
pub struct Session<'a, T>(Proxy<'a>, PhantomData<T>)
where
    T: SessionPortal;

impl<'a, T> Session<'a, T>
where
    T: SessionPortal,
{
    /// Create a new instance of [`Session`].
    ///
    /// **Note** A [`Session`] is not supposed to be created manually.
    pub(crate) async fn new<P>(path: P) -> Result<Session<'a, T>, Error>
    where
        P: TryInto<ObjectPath<'a>>,
        P::Error: Into<zbus::Error>,
    {
        let proxy = Proxy::new_desktop_with_path("org.freedesktop.portal.Session", path).await?;
        Ok(Self(proxy, PhantomData))
    }

    pub(crate) async fn from_unique_name(
        handle_token: &HandleToken,
    ) -> Result<Session<'a, T>, crate::Error> {
        let path =
            Proxy::unique_name("/org/freedesktop/portal/desktop/session", handle_token).await?;
        #[cfg(feature = "tracing")]
        tracing::info!("Creating a org.freedesktop.portal.Session {}", path);
        Self::new(path).await
    }

    /// Emitted when a session is closed.
    ///
    /// # Specifications
    ///
    /// See also [`Closed`](https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.Session.html#org-freedesktop-portal-session-closed).
    #[doc(alias = "Closed")]
    pub async fn receive_closed(&self) -> Result<impl Stream<Item = SessionDetails>, Error> {
        self.0.signal("Closed").await
    }

    /// Closes the portal session to which this object refers and ends all
    /// related user interaction (dialogs, etc).
    ///
    /// # Specifications
    ///
    /// See also [`Close`](https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.Session.html#org-freedesktop-portal-session-close).
    #[doc(alias = "Close")]
    pub async fn close(&self) -> Result<(), Error> {
        self.0.call("Close", &()).await
    }

    pub(crate) fn path(&self) -> &ObjectPath<'_> {
        self.0.path()
    }
}

impl<'a, T> Serialize for Session<'a, T>
where
    T: SessionPortal,
{
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        ObjectPath::serialize(self.path(), serializer)
    }
}

impl<'a, T> Debug for Session<'a, T>
where
    T: SessionPortal,
{
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_tuple("Session")
            .field(&self.path().as_str())
            .finish()
    }
}

/// Portals that have a long-lived interaction
pub trait SessionPortal {}

/// A response to a `create_session` request.
#[derive(Type, Debug)]
#[zvariant(signature = "dict")]
pub(crate) struct CreateSessionResponse {
    pub(crate) session_handle: OwnedObjectPath,
}

// Context: Various portal were expected to actually return an OwnedObjectPath
// but unfortunately this wasn't the case when the portals were implemented in
// xdp. Fixing that would be an API break as well...
// See <https://github.com/flatpak/xdg-desktop-portal/pull/609>
// The Location, ScreenCast, Remote Desktop, Global Shortcuts and Inhibit
// portals `CreateSession` calls are all affected.
//
// So in order to be future proof, we try to deserialize the `session_handle`
// key as a string and fallback to an object path in case the situation gets
// resolved in the future.
impl<'de> Deserialize<'de> for CreateSessionResponse {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: serde::Deserializer<'de>,
    {
        let map: HashMap<String, OwnedValue> = HashMap::deserialize(deserializer)?;
        let session_handle = map.get("session_handle").ok_or_else(|| {
            serde::de::Error::custom(
                "CreateSessionResponse failed to deserialize. Couldn't find a session_handle",
            )
        })?;

        let path = if let Ok(object_path_str) = session_handle.downcast_ref::<&str>() {
            ObjectPath::try_from(object_path_str).unwrap()
        } else if let Ok(object_path) = session_handle.downcast_ref::<ObjectPath<'_>>() {
            object_path
        } else {
            return Err(serde::de::Error::custom(
                "Wrong session_handle type. Expected `s` or `o`.",
            ));
        };

        Ok(Self {
            session_handle: path.into(),
        })
    }
}