Skip to main content

ashpd/desktop/
screenshot.rs

1//! Take a screenshot or pick a color.
2//!
3//! Wrapper of the DBus interface: [`org.freedesktop.portal.Screenshot`](https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.Screenshot.html).
4//!
5//! # Examples
6//!
7//! ## Taking a screenshot
8//!
9//! ```rust,no_run
10//! use ashpd::desktop::screenshot::Screenshot;
11//!
12//! async fn run() -> ashpd::Result<()> {
13//!     let response = Screenshot::request()
14//!         .interactive(true)
15//!         .modal(true)
16//!         .send()
17//!         .await?
18//!         .response()?;
19//!     println!("URI: {}", response.uri());
20//!     Ok(())
21//! }
22//! ```
23//!
24//! ## Picking a color
25//!
26//! ```rust,no_run
27//! use ashpd::desktop::Color;
28//!
29//! async fn run() -> ashpd::Result<()> {
30//!     let color = Color::pick().send().await?.response()?;
31//!     println!("({}, {}, {})", color.red(), color.green(), color.blue());
32//!
33//!     Ok(())
34//! }
35//! ```
36use std::fmt::Debug;
37
38use enumflags2::{BitFlags, bitflags};
39use serde::{Deserialize, Serialize};
40use serde_repr::{Deserialize_repr, Serialize_repr};
41use zbus::zvariant::{
42    Optional, Type,
43    as_value::{self, optional},
44};
45
46use super::{HandleToken, Request};
47use crate::{Error, Uri, WindowIdentifier, desktop::Color, proxy::Proxy};
48
49#[bitflags]
50#[derive(Serialize_repr, Deserialize_repr, PartialEq, Eq, Debug, Copy, Clone, Type)]
51#[repr(u32)]
52/// Available screenshot targets
53pub enum AvailableTargets {
54    /// Screen.
55    Screen = 1,
56    /// Window.
57    Window = 2,
58    /// Area.
59    Area = 4,
60    /// Active window.
61    ActiveWindow = 8,
62}
63
64/// Options for taking a screenshot.
65#[derive(Serialize, Deserialize, Type, Debug, Default)]
66#[zvariant(signature = "dict")]
67pub struct ScreenshotOptions {
68    #[serde(with = "as_value", skip_deserializing)]
69    handle_token: HandleToken,
70    #[serde(default, with = "optional", skip_serializing_if = "Option::is_none")]
71    modal: Option<bool>,
72    #[serde(default, with = "optional", skip_serializing_if = "Option::is_none")]
73    interactive: Option<bool>,
74    #[serde(default, with = "optional", skip_serializing)]
75    #[cfg_attr(not(feature = "backend"), allow(dead_code))]
76    permission_store_checked: Option<bool>,
77    #[serde(default, with = "optional", skip_serializing_if = "Option::is_none")]
78    target: Option<AvailableTargets>,
79}
80
81impl ScreenshotOptions {
82    /// Sets whether the dialog should be modal.
83    #[must_use]
84    pub fn set_modal(mut self, modal: impl Into<Option<bool>>) -> Self {
85        self.modal = modal.into();
86        self
87    }
88
89    /// Gets whether the dialog should be modal.
90    #[cfg(feature = "backend")]
91    pub fn modal(&self) -> Option<bool> {
92        self.modal
93    }
94
95    /// Sets whether the dialog should offer customization.
96    #[must_use]
97    pub fn set_interactive(mut self, interactive: impl Into<Option<bool>>) -> Self {
98        self.interactive = interactive.into();
99        self
100    }
101
102    #[must_use]
103    /// Set the screenshot requested targets.
104    pub fn set_target(mut self, targets: impl Into<Option<AvailableTargets>>) -> Self {
105        self.target = targets.into();
106        self
107    }
108
109    /// Gets whether the dialog should offer customization.
110    #[cfg(feature = "backend")]
111    pub fn interactive(&self) -> Option<bool> {
112        self.interactive
113    }
114
115    /// The screenshot target.
116    #[cfg(feature = "backend")]
117    pub fn target(&self) -> Option<AvailableTargets> {
118        self.target
119    }
120
121    /// Gets whether the permission store has been checked.
122    #[cfg(feature = "backend")]
123    pub fn permission_store_checked(&self) -> Option<bool> {
124        self.permission_store_checked
125    }
126}
127
128#[derive(Serialize, Deserialize, Type)]
129#[zvariant(signature = "dict")]
130/// The response of a [`ScreenshotRequest`] request.
131pub struct Screenshot {
132    #[serde(with = "as_value")]
133    uri: Uri,
134}
135
136impl Screenshot {
137    #[cfg(feature = "backend")]
138    #[cfg_attr(docsrs, doc(cfg(feature = "backend")))]
139    /// Create a new instance of the screenshot.
140    pub fn new(uri: Uri) -> Self {
141        Self { uri }
142    }
143
144    /// Creates a new builder-pattern struct instance to construct
145    /// [`Screenshot`].
146    ///
147    /// This method returns an instance of [`ScreenshotRequest`].
148    pub fn request() -> ScreenshotRequest {
149        ScreenshotRequest::default()
150    }
151
152    /// The screenshot URI.
153    pub fn uri(&self) -> &Uri {
154        &self.uri
155    }
156}
157
158impl Debug for Screenshot {
159    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
160        f.write_str(self.uri.as_str())
161    }
162}
163
164/// Options for picking a color.
165#[derive(Serialize, Deserialize, Type, Debug, Default)]
166#[zvariant(signature = "dict")]
167pub struct ColorOptions {
168    #[serde(with = "as_value", skip_deserializing)]
169    handle_token: HandleToken,
170}
171
172/// Wrapper of the DBus interface: [`org.freedesktop.portal.Screenshot`](https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.Screenshot.html).
173#[derive(Debug)]
174#[doc(alias = "org.freedesktop.portal.Screenshot")]
175pub struct ScreenshotProxy(Proxy<'static>);
176
177impl ScreenshotProxy {
178    /// Create a new instance of [`ScreenshotProxy`].
179    pub async fn new() -> Result<Self, Error> {
180        let proxy = Proxy::new_desktop("org.freedesktop.portal.Screenshot").await?;
181        Ok(Self(proxy))
182    }
183
184    /// Create a new instance of [`ScreenshotProxy`].
185    pub async fn with_connection(connection: zbus::Connection) -> Result<Self, Error> {
186        let proxy =
187            Proxy::new_desktop_with_connection(connection, "org.freedesktop.portal.Screenshot")
188                .await?;
189        Ok(Self(proxy))
190    }
191
192    /// Returns the portal interface version.
193    pub fn version(&self) -> u32 {
194        self.0.version()
195    }
196
197    /// Obtains the color of a single pixel.
198    ///
199    /// # Arguments
200    ///
201    /// * `identifier` - Identifier for the application window.
202    ///
203    /// # Specifications
204    ///
205    /// See also [`PickColor`](https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.Screenshot.html#org-freedesktop-portal-screenshot-pickcolor).
206    #[doc(alias = "PickColor")]
207    #[doc(alias = "xdp_portal_pick_color")]
208    pub async fn pick_color(
209        &self,
210        identifier: Option<&WindowIdentifier>,
211        options: ColorOptions,
212    ) -> Result<Request<Color>, Error> {
213        let identifier = Optional::from(identifier);
214        self.0
215            .request(&options.handle_token, "PickColor", &(identifier, &options))
216            .await
217    }
218
219    /// Takes a screenshot.
220    ///
221    /// # Arguments
222    ///
223    /// * `identifier` - Identifier for the application window.
224    /// * `interactive` - Sets whether the dialog should offer customization
225    ///   before a screenshot or not.
226    /// * `modal` - Sets whether the dialog should be a modal.
227    ///
228    /// # Returns
229    ///
230    /// The screenshot URI.
231    ///
232    /// # Specifications
233    ///
234    /// See also [`Screenshot`](https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.Screenshot.html#org-freedesktop-portal-screenshot-screenshot).
235    #[doc(alias = "Screenshot")]
236    #[doc(alias = "xdp_portal_take_screenshot")]
237    pub async fn screenshot(
238        &self,
239        identifier: Option<&WindowIdentifier>,
240        options: ScreenshotOptions,
241    ) -> Result<Request<Screenshot>, Error> {
242        let identifier = Optional::from(identifier);
243        self.0
244            .request(&options.handle_token, "Screenshot", &(identifier, &options))
245            .await
246    }
247
248    /// Supported screenshot targets.
249    ///
250    ///  # Specifications
251    ///
252    /// See also [`AvailableTargets`](https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.Screenshot.html#org-freedesktop-portal-screenshot-availabletargets).
253    #[doc(alias = "AvailableTargets")]
254    pub async fn available_targets(&self) -> Result<BitFlags<AvailableTargets>, Error> {
255        self.0
256            .property_versioned::<BitFlags<AvailableTargets>>("AvailableTargets", 3)
257            .await
258    }
259}
260
261impl std::ops::Deref for ScreenshotProxy {
262    type Target = zbus::Proxy<'static>;
263
264    fn deref(&self) -> &Self::Target {
265        &self.0
266    }
267}
268
269#[derive(Debug, Default)]
270#[doc(alias = "xdp_portal_pick_color")]
271/// A [builder-pattern] type to construct [`Color`].
272///
273/// [builder-pattern]: https://doc.rust-lang.org/1.0.0/style/ownership/builders.html
274pub struct ColorRequest {
275    identifier: Option<WindowIdentifier>,
276    options: ColorOptions,
277    connection: Option<zbus::Connection>,
278}
279
280impl ColorRequest {
281    #[must_use]
282    /// Sets a window identifier.
283    pub fn identifier(mut self, identifier: impl Into<Option<WindowIdentifier>>) -> Self {
284        self.identifier = identifier.into();
285        self
286    }
287
288    #[must_use]
289    /// Sets a connection to use other than the internal one.
290    pub fn connection(mut self, connection: Option<zbus::Connection>) -> Self {
291        self.connection = connection;
292        self
293    }
294
295    /// Build the [`Color`].
296    pub async fn send(self) -> Result<Request<Color>, Error> {
297        let proxy = if let Some(connection) = self.connection {
298            ScreenshotProxy::with_connection(connection).await?
299        } else {
300            ScreenshotProxy::new().await?
301        };
302        proxy
303            .pick_color(self.identifier.as_ref(), self.options)
304            .await
305    }
306}
307
308impl Color {
309    /// Creates a new builder-pattern struct instance to construct
310    /// [`Color`].
311    ///
312    /// This method returns an instance of [`ColorRequest`].
313    pub fn pick() -> ColorRequest {
314        ColorRequest::default()
315    }
316}
317
318#[derive(Debug, Default)]
319#[doc(alias = "xdp_portal_take_screenshot")]
320/// A [builder-pattern] type to construct a screenshot [`Screenshot`].
321///
322/// [builder-pattern]: https://doc.rust-lang.org/1.0.0/style/ownership/builders.html
323pub struct ScreenshotRequest {
324    options: ScreenshotOptions,
325    identifier: Option<WindowIdentifier>,
326    connection: Option<zbus::Connection>,
327}
328
329impl ScreenshotRequest {
330    #[must_use]
331    /// Sets a window identifier.
332    pub fn identifier(mut self, identifier: impl Into<Option<WindowIdentifier>>) -> Self {
333        self.identifier = identifier.into();
334        self
335    }
336
337    /// Sets whether the dialog should be a modal.
338    #[must_use]
339    pub fn modal(mut self, modal: impl Into<Option<bool>>) -> Self {
340        self.options.modal = modal.into();
341        self
342    }
343
344    /// Sets whether the dialog should offer customization before a screenshot
345    /// or not.
346    #[must_use]
347    pub fn interactive(mut self, interactive: impl Into<Option<bool>>) -> Self {
348        self.options.interactive = interactive.into();
349        self
350    }
351
352    #[must_use]
353    /// Set the screenshot requested targets.
354    pub fn target(mut self, targets: impl Into<Option<AvailableTargets>>) -> Self {
355        self.options.target = targets.into();
356        self
357    }
358
359    #[must_use]
360    /// Sets a connection to use other than the internal one.
361    pub fn connection(mut self, connection: Option<zbus::Connection>) -> Self {
362        self.connection = connection;
363        self
364    }
365
366    /// Build the [`Screenshot`].
367    pub async fn send(self) -> Result<Request<Screenshot>, Error> {
368        let proxy = if let Some(connection) = self.connection {
369            ScreenshotProxy::with_connection(connection).await?
370        } else {
371            ScreenshotProxy::new().await?
372        };
373        proxy
374            .screenshot(self.identifier.as_ref(), self.options)
375            .await
376    }
377}