ashpd/desktop/
global_shortcuts.rs

1//! Register global shortcuts
2
3use std::{collections::HashMap, fmt::Debug, time::Duration};
4
5use futures_util::{Stream, TryFutureExt};
6use serde::{Deserialize, Serialize};
7use zbus::zvariant::{
8    DeserializeDict, ObjectPath, OwnedObjectPath, OwnedValue, SerializeDict, Type,
9};
10
11use super::{session::SessionPortal, HandleToken, Request, Session};
12use crate::{
13    desktop::session::CreateSessionResponse, proxy::Proxy, ActivationToken, Error, WindowIdentifier,
14};
15
16#[derive(Clone, SerializeDict, Type, Debug, Default)]
17#[zvariant(signature = "dict")]
18struct NewShortcutInfo {
19    /// User-readable text describing what the shortcut does.
20    description: String,
21    /// The preferred shortcut trigger, defined as described by the "shortcuts"
22    /// XDG specification. Optional.
23    preferred_trigger: Option<String>,
24}
25
26/// Shortcut descriptor used to bind new shortcuts in
27/// [`GlobalShortcuts::bind_shortcuts`]
28#[derive(Clone, Serialize, Type, Debug)]
29pub struct NewShortcut(String, NewShortcutInfo);
30
31impl NewShortcut {
32    /// Construct new shortcut
33    pub fn new(id: impl Into<String>, description: impl Into<String>) -> Self {
34        Self(
35            id.into(),
36            NewShortcutInfo {
37                description: description.into(),
38                preferred_trigger: None,
39            },
40        )
41    }
42
43    /// Sets the preferred shortcut trigger, defined as described by the
44    /// "shortcuts" XDG specification.
45    #[must_use]
46    pub fn preferred_trigger<'a>(mut self, preferred_trigger: impl Into<Option<&'a str>>) -> Self {
47        self.1.preferred_trigger = preferred_trigger.into().map(ToOwned::to_owned);
48        self
49    }
50}
51
52#[derive(Clone, DeserializeDict, Type, Debug, Default)]
53#[zvariant(signature = "dict")]
54struct ShortcutInfo {
55    /// User-readable text describing what the shortcut does.
56    description: String,
57    /// User-readable text describing how to trigger the shortcut for the client
58    /// to render.
59    trigger_description: String,
60}
61
62/// Struct that contains information about existing binded shortcut.
63///
64/// If you need to create a new shortcuts, take a look at [`NewShortcut`]
65/// instead.
66#[derive(Clone, Deserialize, Type, Debug)]
67pub struct Shortcut(String, ShortcutInfo);
68
69impl Shortcut {
70    /// Shortcut id
71    pub fn id(&self) -> &str {
72        &self.0
73    }
74
75    /// User-readable text describing what the shortcut does.
76    pub fn description(&self) -> &str {
77        &self.1.description
78    }
79
80    /// User-readable text describing how to trigger the shortcut for the client
81    /// to render.
82    pub fn trigger_description(&self) -> &str {
83        &self.1.trigger_description
84    }
85}
86
87/// Specified options for a [`GlobalShortcuts::create_session`] request.
88#[derive(SerializeDict, Type, Debug, Default)]
89#[zvariant(signature = "dict")]
90struct CreateSessionOptions {
91    /// A string that will be used as the last element of the handle.
92    handle_token: HandleToken,
93    /// A string that will be used as the last element of the session handle.
94    session_handle_token: HandleToken,
95}
96
97/// Specified options for a [`GlobalShortcuts::bind_shortcuts`] request.
98#[derive(SerializeDict, Type, Debug, Default)]
99#[zvariant(signature = "dict")]
100struct BindShortcutsOptions {
101    /// A string that will be used as the last element of the handle.
102    handle_token: HandleToken,
103}
104
105/// A response to a [`GlobalShortcuts::bind_shortcuts`] request.
106#[derive(DeserializeDict, Type, Debug)]
107#[zvariant(signature = "dict")]
108pub struct BindShortcuts {
109    shortcuts: Vec<Shortcut>,
110}
111
112impl BindShortcuts {
113    /// A list of shortcuts.
114    pub fn shortcuts(&self) -> &[Shortcut] {
115        &self.shortcuts
116    }
117}
118
119#[derive(SerializeDict, Type, Debug)]
120#[zvariant(signature = "dict")]
121struct ConfigureShortcutsOptions {
122    activation_token: Option<ActivationToken>,
123}
124
125/// Specified options for a [`GlobalShortcuts::list_shortcuts`] request.
126#[derive(SerializeDict, Type, Debug, Default)]
127#[zvariant(signature = "dict")]
128struct ListShortcutsOptions {
129    /// A string that will be used as the last element of the handle.
130    handle_token: HandleToken,
131}
132
133/// A response to a [`GlobalShortcuts::list_shortcuts`] request.
134#[derive(DeserializeDict, Type, Debug)]
135#[zvariant(signature = "dict")]
136pub struct ListShortcuts {
137    /// A list of shortcuts.
138    shortcuts: Vec<Shortcut>,
139}
140
141impl ListShortcuts {
142    /// A list of shortcuts.
143    pub fn shortcuts(&self) -> &[Shortcut] {
144        &self.shortcuts
145    }
146}
147
148/// Notifies about a shortcut becoming active.
149#[derive(Debug, Deserialize, Type)]
150pub struct Activated(OwnedObjectPath, String, u64, HashMap<String, OwnedValue>);
151
152impl Activated {
153    /// Session that requested the shortcut.
154    pub fn session_handle(&self) -> ObjectPath<'_> {
155        self.0.as_ref()
156    }
157
158    /// The application-provided ID for the shortcut.
159    pub fn shortcut_id(&self) -> &str {
160        &self.1
161    }
162
163    /// The timestamp, as seconds and microseconds since the Unix epoch.
164    pub fn timestamp(&self) -> Duration {
165        Duration::from_millis(self.2)
166    }
167
168    /// Optional information
169    pub fn options(&self) -> &HashMap<String, OwnedValue> {
170        &self.3
171    }
172}
173
174/// Notifies that a shortcut is not active anymore.
175#[derive(Debug, Deserialize, Type)]
176pub struct Deactivated(OwnedObjectPath, String, u64, HashMap<String, OwnedValue>);
177
178impl Deactivated {
179    /// Session that requested the shortcut.
180    pub fn session_handle(&self) -> ObjectPath<'_> {
181        self.0.as_ref()
182    }
183
184    /// The application-provided ID for the shortcut.
185    pub fn shortcut_id(&self) -> &str {
186        &self.1
187    }
188
189    /// The timestamp, as seconds and microseconds since the Unix epoch.
190    pub fn timestamp(&self) -> Duration {
191        Duration::from_millis(self.2)
192    }
193
194    /// Optional information
195    pub fn options(&self) -> &HashMap<String, OwnedValue> {
196        &self.3
197    }
198}
199
200/// Indicates that the information associated with some of the shortcuts has
201/// changed.
202#[derive(Debug, Deserialize, Type)]
203pub struct ShortcutsChanged(OwnedObjectPath, Vec<Shortcut>);
204
205impl ShortcutsChanged {
206    /// Session that requested the shortcut.
207    pub fn session_handle(&self) -> ObjectPath<'_> {
208        self.0.as_ref()
209    }
210
211    /// Shortcuts that have been registered.
212    pub fn shortcuts(&self) -> &[Shortcut] {
213        &self.1
214    }
215}
216
217/// Wrapper of the DBus interface: [`org.freedesktop.portal.GlobalShortcuts`](https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.GlobalShortcuts.html).
218#[derive(Debug)]
219#[doc(alias = "org.freedesktop.portal.GlobalShortcuts")]
220pub struct GlobalShortcuts<'a>(Proxy<'a>);
221
222impl<'a> GlobalShortcuts<'a> {
223    /// Create a new instance of [`GlobalShortcuts`].
224    pub async fn new() -> Result<GlobalShortcuts<'a>, Error> {
225        let proxy = Proxy::new_desktop("org.freedesktop.portal.GlobalShortcuts").await?;
226        Ok(Self(proxy))
227    }
228
229    /// Create a global shortcuts session.
230    ///
231    /// # Specifications
232    ///
233    /// See also [`CreateSession`](https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.GlobalShortcuts.html#org-freedesktop-portal-globalshortcuts-createsession).
234    #[doc(alias = "CreateSession")]
235    pub async fn create_session(&self) -> Result<Session<'a, Self>, Error> {
236        let options = CreateSessionOptions::default();
237        let (request, proxy) = futures_util::try_join!(
238            self.0
239                .request::<CreateSessionResponse>(&options.handle_token, "CreateSession", &options)
240                .into_future(),
241            Session::from_unique_name(&options.session_handle_token).into_future(),
242        )?;
243        assert_eq!(proxy.path(), &request.response()?.session_handle.as_ref());
244        Ok(proxy)
245    }
246
247    /// Bind the shortcuts.
248    ///
249    /// # Specifications
250    ///
251    /// See also [`BindShortcuts`](https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.GlobalShortcuts.html#org-freedesktop-portal-globalshortcuts-bindshortcuts).
252    #[doc(alias = "BindShortcuts")]
253    pub async fn bind_shortcuts(
254        &self,
255        session: &Session<'_, Self>,
256        shortcuts: &[NewShortcut],
257        identifier: Option<&WindowIdentifier>,
258    ) -> Result<Request<BindShortcuts>, Error> {
259        let options = BindShortcutsOptions::default();
260        let identifier = identifier.map(|i| i.to_string()).unwrap_or_default();
261        self.0
262            .request(
263                &options.handle_token,
264                "BindShortcuts",
265                &(session, shortcuts, identifier, &options),
266            )
267            .await
268    }
269
270    /// Lists all shortcuts.
271    ///
272    /// # Specifications
273    ///
274    /// See also [`ListShortcuts`](https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.GlobalShortcuts.html#org-freedesktop-portal-globalshortcuts-listshortcuts).
275    #[doc(alias = "ListShortcuts")]
276    pub async fn list_shortcuts(
277        &self,
278        session: &Session<'_, Self>,
279    ) -> Result<Request<ListShortcuts>, Error> {
280        let options = ListShortcutsOptions::default();
281        self.0
282            .request(&options.handle_token, "ListShortcuts", &(session, &options))
283            .await
284    }
285
286    /// Request showing a configuration UI so the user is able to conigure all
287    /// shortcuts of this session.
288    ///
289    /// # Specifications
290    ///
291    /// See also [`ConfigureShortcuts`](https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.GlobalShortcuts.html#org-freedesktop-portal-globalshortcuts-configureshortcuts).
292    #[doc(alias = "ConfigureShortcuts")]
293    pub async fn configure_shortcuts(
294        &self,
295        session: &Session<'_, Self>,
296        identifier: Option<&WindowIdentifier>,
297        activation_token: impl Into<Option<ActivationToken>>,
298    ) -> Result<(), Error> {
299        let options = ConfigureShortcutsOptions {
300            activation_token: activation_token.into(),
301        };
302        let identifier = identifier.map(|i| i.to_string()).unwrap_or_default();
303
304        self.0
305            .call_versioned::<()>("ConfigureShortcuts", &(session, identifier, options), 2)
306            .await
307    }
308
309    /// Signal emitted when shortcut becomes active.
310    ///
311    /// # Specifications
312    ///
313    /// See also [`Activated`](https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.GlobalShortcuts.html#org-freedesktop-portal-globalshortcuts-activated).
314    #[doc(alias = "Activated")]
315    pub async fn receive_activated(&self) -> Result<impl Stream<Item = Activated>, Error> {
316        self.0.signal("Activated").await
317    }
318
319    /// Signal emitted when shortcut is not active anymore.
320    ///
321    /// # Specifications
322    ///
323    /// See also [`Deactivated`](https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.GlobalShortcuts.html#org-freedesktop-portal-globalshortcuts-deactivated).
324    #[doc(alias = "Deactivated")]
325    pub async fn receive_deactivated(&self) -> Result<impl Stream<Item = Deactivated>, Error> {
326        self.0.signal("Deactivated").await
327    }
328
329    /// Signal emitted when information associated with some of the shortcuts
330    /// has changed.
331    ///
332    /// # Specifications
333    ///
334    /// See also [`ShortcutsChanged`](https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.GlobalShortcuts.html#org-freedesktop-portal-globalshortcuts-shortcutschanged).
335    #[doc(alias = "ShortcutsChanged")]
336    pub async fn receive_shortcuts_changed(
337        &self,
338    ) -> Result<impl Stream<Item = ShortcutsChanged>, Error> {
339        self.0.signal("ShortcutsChanged").await
340    }
341}
342
343impl<'a> std::ops::Deref for GlobalShortcuts<'a> {
344    type Target = zbus::Proxy<'a>;
345
346    fn deref(&self) -> &Self::Target {
347        &self.0
348    }
349}
350
351impl crate::Sealed for GlobalShortcuts<'_> {}
352impl SessionPortal for GlobalShortcuts<'_> {}