ashpd/desktop/
location.rs

1//! # Examples
2//!
3//! ```rust,no_run
4//! use ashpd::desktop::location::{Accuracy, LocationProxy};
5//! use futures_util::{FutureExt, StreamExt};
6//!
7//! async fn run() -> ashpd::Result<()> {
8//!     let proxy = LocationProxy::new().await?;
9//!     let session = proxy
10//!         .create_session(None, None, Some(Accuracy::Street))
11//!         .await?;
12//!     let mut stream = proxy.receive_location_updated().await?;
13//!     let (_, location) = futures_util::join!(
14//!         proxy
15//!             .start(&session, None)
16//!             .map(|e| e.expect("Couldn't start session")),
17//!         stream.next().map(|e| e.expect("Stream is exhausted"))
18//!     );
19//!     println!("{}", location.accuracy());
20//!     println!("{}", location.longitude());
21//!     println!("{}", location.latitude());
22//!     session.close().await?;
23//!     Ok(())
24//! }
25//! ```
26
27use std::fmt::Debug;
28
29use futures_util::{Stream, TryFutureExt};
30use serde::Deserialize;
31use serde_repr::Serialize_repr;
32use zbus::zvariant::{DeserializeDict, ObjectPath, OwnedObjectPath, SerializeDict, Type};
33
34use super::{session::SessionPortal, HandleToken, Request, Session};
35use crate::{proxy::Proxy, Error, WindowIdentifier};
36
37#[cfg_attr(feature = "glib", derive(glib::Enum))]
38#[cfg_attr(feature = "glib", enum_type(name = "AshpdLocationAccuracy"))]
39#[derive(Serialize_repr, PartialEq, Eq, Clone, Copy, Debug, Type)]
40#[doc(alias = "XdpLocationAccuracy")]
41#[repr(u32)]
42/// The accuracy of the location.
43pub enum Accuracy {
44    #[doc(alias = "XDP_LOCATION_ACCURACY_NONE")]
45    /// None.
46    None = 0,
47    #[doc(alias = "XDP_LOCATION_ACCURACY_COUNTRY")]
48    /// Country.
49    Country = 1,
50    #[doc(alias = "XDP_LOCATION_ACCURACY_CITY")]
51    /// City.
52    City = 2,
53    #[doc(alias = "XDP_LOCATION_ACCURACY_NEIGHBORHOOD")]
54    /// Neighborhood.
55    Neighborhood = 3,
56    #[doc(alias = "XDP_LOCATION_ACCURACY_STREET")]
57    /// Street.
58    Street = 4,
59    #[doc(alias = "XDP_LOCATION_ACCURACY_EXACT")]
60    /// The exact location.
61    Exact = 5,
62}
63
64#[derive(SerializeDict, Type, Debug, Default)]
65/// Specified options for a [`LocationProxy::create_session`] request.
66#[zvariant(signature = "dict")]
67struct CreateSessionOptions {
68    /// A string that will be used as the last element of the session handle.
69    session_handle_token: HandleToken,
70    /// Distance threshold in meters. Default is 0.
71    #[zvariant(rename = "distance-threshold")]
72    distance_threshold: Option<u32>,
73    /// Time threshold in seconds. Default is 0.
74    #[zvariant(rename = "time-threshold")]
75    time_threshold: Option<u32>,
76    /// Requested accuracy. Default is `Accuracy::Exact`.
77    accuracy: Option<Accuracy>,
78}
79
80#[derive(SerializeDict, Type, Debug, Default)]
81/// Specified options for a [`LocationProxy::start`] request.
82#[zvariant(signature = "dict")]
83struct SessionStartOptions {
84    /// A string that will be used as the last element of the handle.
85    handle_token: HandleToken,
86}
87
88#[derive(Deserialize, Type)]
89/// The response received on a `location_updated` signal.
90pub struct Location(OwnedObjectPath, LocationInner);
91
92impl Location {
93    /// The associated session.
94    pub fn session_handle(&self) -> ObjectPath<'_> {
95        self.0.as_ref()
96    }
97
98    /// The accuracy, in meters.
99    pub fn accuracy(&self) -> f64 {
100        self.1.accuracy
101    }
102
103    /// The altitude, in meters.
104    pub fn altitude(&self) -> Option<f64> {
105        if self.1.altitude == -f64::MAX {
106            None
107        } else {
108            Some(self.1.altitude)
109        }
110    }
111
112    /// The speed, in meters per second.
113    pub fn speed(&self) -> Option<f64> {
114        if self.1.speed == -1f64 {
115            None
116        } else {
117            Some(self.1.speed)
118        }
119    }
120
121    /// The heading, in degrees, going clockwise. North 0, East 90, South 180,
122    /// West 270.
123    pub fn heading(&self) -> Option<f64> {
124        if self.1.heading == -1f64 {
125            None
126        } else {
127            Some(self.1.heading)
128        }
129    }
130
131    /// The location description.
132    pub fn description(&self) -> Option<&str> {
133        if self.1.description.is_empty() {
134            None
135        } else {
136            Some(&self.1.description)
137        }
138    }
139
140    /// The latitude, in degrees.
141    pub fn latitude(&self) -> f64 {
142        self.1.latitude
143    }
144
145    /// The longitude, in degrees.
146    pub fn longitude(&self) -> f64 {
147        self.1.longitude
148    }
149
150    /// The timestamp when the location was retrieved.
151    pub fn timestamp(&self) -> std::time::Duration {
152        std::time::Duration::from_secs(self.1.timestamp.0)
153    }
154}
155
156impl Debug for Location {
157    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
158        f.debug_struct("Location")
159            .field("accuracy", &self.accuracy())
160            .field("altitude", &self.altitude())
161            .field("speed", &self.speed())
162            .field("heading", &self.heading())
163            .field("description", &self.description())
164            .field("latitude", &self.latitude())
165            .field("longitude", &self.longitude())
166            .field("timestamp", &self.timestamp())
167            .finish()
168    }
169}
170
171#[derive(Debug, SerializeDict, DeserializeDict, Type)]
172#[zvariant(signature = "dict")]
173struct LocationInner {
174    #[zvariant(rename = "Accuracy")]
175    accuracy: f64,
176    #[zvariant(rename = "Altitude")]
177    altitude: f64,
178    #[zvariant(rename = "Speed")]
179    speed: f64,
180    #[zvariant(rename = "Heading")]
181    heading: f64,
182    #[zvariant(rename = "Description")]
183    description: String,
184    #[zvariant(rename = "Latitude")]
185    latitude: f64,
186    #[zvariant(rename = "Longitude")]
187    longitude: f64,
188    #[zvariant(rename = "Timestamp")]
189    timestamp: (u64, u64),
190}
191
192/// The interface lets sandboxed applications query basic information about the
193/// location.
194///
195/// Wrapper of the DBus interface: [`org.freedesktop.portal.Location`](https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.Location.html).
196#[derive(Debug)]
197#[doc(alias = "org.freedesktop.portal.Location")]
198pub struct LocationProxy<'a>(Proxy<'a>);
199
200impl<'a> LocationProxy<'a> {
201    /// Create a new instance of [`LocationProxy`].
202    pub async fn new() -> Result<LocationProxy<'a>, Error> {
203        let proxy = Proxy::new_desktop("org.freedesktop.portal.Location").await?;
204        Ok(Self(proxy))
205    }
206
207    /// Signal emitted when the user location is updated.
208    ///
209    /// # Specifications
210    ///
211    /// See also [`LocationUpdated`](https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.Location.html#org-freedesktop-portal-location-locationupdated).
212    #[doc(alias = "LocationUpdated")]
213    #[doc(alias = "XdpPortal::location-updated")]
214    pub async fn receive_location_updated(&self) -> Result<impl Stream<Item = Location>, Error> {
215        self.0.signal("LocationUpdated").await
216    }
217
218    /// Create a location session.
219    ///
220    /// # Arguments
221    ///
222    /// * `distance_threshold` - Sets the distance threshold in meters, default
223    ///   to `0`.
224    /// * `time_threshold` - Sets the time threshold in seconds, default to `0`.
225    /// * `accuracy` - Sets the location accuracy, default to
226    ///   [`Accuracy::Exact`].
227    ///
228    /// # Specifications
229    ///
230    /// See also [`CreateSession`](https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.Location.html#org-freedesktop-portal-location-createsession).
231    #[doc(alias = "CreateSession")]
232    pub async fn create_session(
233        &self,
234        distance_threshold: Option<u32>,
235        time_threshold: Option<u32>,
236        accuracy: Option<Accuracy>,
237    ) -> Result<Session<'a, Self>, Error> {
238        let options = CreateSessionOptions {
239            distance_threshold,
240            time_threshold,
241            accuracy,
242            ..Default::default()
243        };
244        let (path, proxy) = futures_util::try_join!(
245            self.0
246                .call::<OwnedObjectPath>("CreateSession", &(options))
247                .into_future(),
248            Session::from_unique_name(&options.session_handle_token).into_future(),
249        )?;
250        assert_eq!(proxy.path(), &path.into_inner());
251        Ok(proxy)
252    }
253
254    /// Start the location session.
255    /// An application can only attempt start a session once.
256    ///
257    /// # Arguments
258    ///
259    /// * `session` - A [`Session`], created with
260    ///   [`create_session()`][`LocationProxy::create_session`].
261    /// * `identifier` - Identifier for the application window.
262    ///
263    /// # Specifications
264    ///
265    /// See also [`Start`](https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.Location.html#org-freedesktop-portal-location-start).
266    #[doc(alias = "Start")]
267    #[doc(alias = "xdp_portal_location_monitor_start")]
268    pub async fn start(
269        &self,
270        session: &Session<'_, Self>,
271        identifier: Option<&WindowIdentifier>,
272    ) -> Result<Request<()>, Error> {
273        let options = SessionStartOptions::default();
274        let identifier = identifier.map(|i| i.to_string()).unwrap_or_default();
275        self.0
276            .empty_request(
277                &options.handle_token,
278                "Start",
279                &(session, &identifier, &options),
280            )
281            .await
282    }
283}
284
285impl crate::Sealed for LocationProxy<'_> {}
286impl SessionPortal for LocationProxy<'_> {}
287
288impl<'a> std::ops::Deref for LocationProxy<'a> {
289    type Target = zbus::Proxy<'a>;
290
291    fn deref(&self) -> &Self::Target {
292        &self.0
293    }
294}