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
//! Request access to specific devices such as camera, speakers or microphone.
//!
//! **Note** This portal doesn't work for sandboxed applications.
//!
//! ### Examples
//!
//! Access a [`Device`]
//!
//! ```rust,no_run
//! use ashpd::desktop::device::{Device, DeviceProxy};
//!
//! async fn run() -> ashpd::Result<()> {
//!     let proxy = DeviceProxy::new().await?;
//!     proxy.access_device(6879, &[Device::Speakers]).await?;
//!     Ok(())
//! }
//! ```

use std::{fmt, str::FromStr};

use serde::{Deserialize, Serialize};
use zbus::zvariant::{SerializeDict, Type};

use super::{HandleToken, Request};
use crate::{proxy::Proxy, Error};

#[derive(SerializeDict, Type, Debug, Default)]
/// Specified options for a [`DeviceProxy::access_device`] request.
#[zvariant(signature = "dict")]
struct AccessDeviceOptions {
    /// A string that will be used as the last element of the handle.
    handle_token: HandleToken,
}

#[cfg_attr(feature = "glib", derive(glib::Enum))]
#[cfg_attr(feature = "glib", enum_type(name = "AshpdDevice"))]
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Type)]
#[zvariant(signature = "s")]
#[serde(rename_all = "lowercase")]
/// The possible device to request access to.
pub enum Device {
    /// A microphone.
    Microphone,
    /// Speakers.
    Speakers,
    /// A Camera.
    Camera,
}

impl fmt::Display for Device {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::Microphone => write!(f, "Microphone"),
            Self::Speakers => write!(f, "Speakers"),
            Self::Camera => write!(f, "Camera"),
        }
    }
}

impl AsRef<str> for Device {
    fn as_ref(&self) -> &str {
        match self {
            Self::Microphone => "Microphone",
            Self::Speakers => "Speakers",
            Self::Camera => "Camera",
        }
    }
}

impl From<Device> for &'static str {
    fn from(d: Device) -> Self {
        match d {
            Device::Microphone => "Microphone",
            Device::Speakers => "Speakers",
            Device::Camera => "Camera",
        }
    }
}

impl FromStr for Device {
    type Err = Error;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s {
            "Microphone" | "microphone" => Ok(Device::Microphone),
            "Speakers" | "speakers" => Ok(Device::Speakers),
            "Camera" | "camera" => Ok(Device::Camera),
            _ => Err(Error::ParseError("Failed to parse device, invalid value")),
        }
    }
}

/// The interface lets services ask if an application should get access to
/// devices such as microphones, speakers or cameras. Not a portal in the strict
/// sense, since the API is not directly accessible to applications inside the
/// sandbox.
///
/// Wrapper of the DBus interface: [`org.freedesktop.portal.Device`](https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.Device.html).
#[derive(Debug)]
#[doc(alias = "org.freedesktop.portal.Device")]
pub struct DeviceProxy<'a>(Proxy<'a>);

impl<'a> DeviceProxy<'a> {
    /// Create a new instance of [`DeviceProxy`].
    pub async fn new() -> Result<DeviceProxy<'a>, Error> {
        let proxy = Proxy::new_desktop("org.freedesktop.portal.Device").await?;
        Ok(Self(proxy))
    }

    /// Asks for access to a device.
    ///
    /// # Arguments
    ///
    /// * `pid` - The pid of the application on whose behalf the request is
    ///   made.
    /// * `devices` - A list of devices to request access to.
    ///
    /// *Note* Asking for multiple devices at the same time may or may not be
    /// supported
    ///
    /// # Specifications
    ///
    /// See also [`AccessDevice`](https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.Device.html#org-freedesktop-portal-device-accessdevice).
    #[doc(alias = "AccessDevice")]
    pub async fn access_device(&self, pid: u32, devices: &[Device]) -> Result<Request<()>, Error> {
        let options = AccessDeviceOptions::default();
        self.0
            .empty_request(
                &options.handle_token,
                "AccessDevice",
                &(pid, devices, &options),
            )
            .await
    }
}

impl<'a> std::ops::Deref for DeviceProxy<'a> {
    type Target = zbus::Proxy<'a>;

    fn deref(&self) -> &Self::Target {
        &self.0
    }
}