1 /*
2  * Copyright (C) 2019, Matthias Clasen
3  *
4  * This file is free software; you can redistribute it and/or modify it
5  * under the terms of the GNU Lesser General Public License as
6  * published by the Free Software Foundation, version 3.0 of the
7  * License.
8  *
9  * This file is distributed in the hope that it will be useful, but
10  * WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public
15  * License along with this program.  If not, see <http://www.gnu.org/licenses/>.
16  *
17  * SPDX-License-Identifier: LGPL-3.0-only
18  */
19 
20 #include "config.h"
21 
22 #include "location.h"
23 #include "portal-private.h"
24 
25 
26 typedef struct {
27   XdpPortal *portal;
28   XdpParent *parent;
29   char *parent_handle;
30   char *id;
31   guint signal_id;
32   GTask *task;
33   char *request_path;
34   guint cancelled_id;
35   int distance;
36   int time;
37   XdpLocationAccuracy accuracy;
38 } CreateCall;
39 
40 static void
create_call_free(CreateCall * call)41 create_call_free (CreateCall *call)
42 {
43   if (call->parent)
44     {
45       call->parent->parent_unexport (call->parent);
46       xdp_parent_free (call->parent);
47     }
48  g_free (call->parent_handle);
49 
50   if (call->signal_id)
51     g_dbus_connection_signal_unsubscribe (call->portal->bus, call->signal_id);
52 
53   if (call->cancelled_id)
54     g_signal_handler_disconnect (g_task_get_cancellable (call->task), call->cancelled_id);
55 
56   g_free (call->request_path);
57 
58   g_object_unref (call->portal);
59   g_object_unref (call->task);
60 
61   g_free (call->id);
62 
63   g_free (call);
64 }
65 
66 static void
session_started(GDBusConnection * bus,const char * sender_name,const char * object_path,const char * interface_name,const char * signal_name,GVariant * parameters,gpointer data)67 session_started (GDBusConnection *bus,
68                  const char *sender_name,
69                  const char *object_path,
70                  const char *interface_name,
71                  const char *signal_name,
72                  GVariant *parameters,
73                  gpointer data)
74 {
75   CreateCall *call = data;
76   guint32 response;
77   g_autoptr(GVariant) ret = NULL;
78 
79   g_variant_get (parameters, "(u@a{sv})", &response, &ret);
80 
81   if (response == 0)
82     g_task_return_boolean (call->task, TRUE);
83   else
84     {
85       if (call->portal->location_updated_signal != 0)
86         {
87           g_dbus_connection_signal_unsubscribe (call->portal->bus, call->portal->location_updated_signal);
88           call->portal->location_updated_signal = 0;
89         }
90       g_clear_pointer (&call->portal->location_monitor_handle, g_free);
91 
92       if (response == 1)
93         g_task_return_new_error (call->task, G_IO_ERROR, G_IO_ERROR_CANCELLED, "StartLocation canceled");
94       else if (response == 2)
95         g_task_return_new_error (call->task, G_IO_ERROR, G_IO_ERROR_FAILED, "StartLocation failed");
96     }
97 
98   create_call_free (call);
99 }
100 
101 static void
location_updated(GDBusConnection * bus,const char * sender_name,const char * object_path,const char * interface_name,const char * signal_name,GVariant * parameters,gpointer data)102 location_updated (GDBusConnection *bus,
103                   const char *sender_name,
104                   const char *object_path,
105                   const char *interface_name,
106                   const char *signal_name,
107                   GVariant *parameters,
108                   gpointer data)
109 {
110   XdpPortal *portal = data;
111   g_autoptr(GVariant) variant = NULL;
112   const char *handle = NULL;
113   double latitude, longitude, altitude;
114   double accuracy, speed, heading;
115   const char *description = NULL;
116   gint64 timestamp_s, timestamp_ms;
117 
118   g_variant_get (parameters, "(o@a{sv})", &handle, &variant);
119   g_variant_lookup (variant, "Latitude", "d", &latitude);
120   g_variant_lookup (variant, "Longitude", "d", &longitude);
121   g_variant_lookup (variant, "Accuracy", "d", &accuracy);
122   g_variant_lookup (variant, "Altitude", "d", &altitude);
123   g_variant_lookup (variant, "Speed", "d", &speed);
124   g_variant_lookup (variant, "Heading", "d", &heading);
125   g_variant_lookup (variant, "Description", "&s", &description);
126   g_variant_lookup (variant, "Timestamp", "(tt)", &timestamp_s, &timestamp_ms);
127 
128   g_signal_emit_by_name (portal, "location-updated",
129                          latitude, longitude, altitude,
130                          accuracy, speed, heading,
131                          description, timestamp_s, timestamp_ms);
132 }
133 
134 static void
ensure_location_updated_connected(XdpPortal * portal)135 ensure_location_updated_connected (XdpPortal *portal)
136 {
137   if (portal->location_updated_signal == 0)
138     portal->location_updated_signal =
139         g_dbus_connection_signal_subscribe (portal->bus,
140                                             PORTAL_BUS_NAME,
141                                             "org.freedesktop.portal.Location",
142                                             "LocationUpdated",
143                                             PORTAL_OBJECT_PATH,
144                                             NULL,
145                                             G_DBUS_SIGNAL_FLAGS_NO_MATCH_RULE,
146                                             location_updated,
147                                             portal,
148                                             NULL);
149 }
150 
151 static void
cancelled_cb(GCancellable * cancellable,gpointer data)152 cancelled_cb (GCancellable *cancellable,
153               gpointer data)
154 {
155   CreateCall *call = data;
156 
157   g_dbus_connection_call (call->portal->bus,
158                           PORTAL_BUS_NAME,
159                           call->request_path,
160                           REQUEST_INTERFACE,
161                           "Close",
162                           NULL,
163                           NULL,
164                           G_DBUS_CALL_FLAGS_NONE,
165                           -1,
166                           NULL, NULL, NULL);
167 
168   g_task_return_new_error (call->task, G_IO_ERROR, G_IO_ERROR_CANCELLED, "Location call canceled by caller");
169 
170   create_call_free (call);
171 }
172 
173 static void
call_returned(GObject * object,GAsyncResult * result,gpointer data)174 call_returned (GObject *object,
175                GAsyncResult *result,
176                gpointer data)
177 {
178   CreateCall *call = data;
179   GError *error = NULL;
180   g_autoptr(GVariant) ret = NULL;
181 
182   ret = g_dbus_connection_call_finish (G_DBUS_CONNECTION (object), result, &error);
183   if (error)
184     {
185       g_task_return_error (call->task, error);
186       create_call_free (call);
187     }
188 }
189 
190 static void
session_created(GObject * object,GAsyncResult * result,gpointer data)191 session_created (GObject *object,
192                  GAsyncResult *result,
193                  gpointer data)
194 {
195   CreateCall *call = data;
196   GVariantBuilder options;
197   g_autofree char *token = NULL;
198   g_autoptr(GVariant) ret = NULL;
199   GError *error = NULL;
200   GCancellable *cancellable;
201 
202   ret = g_dbus_connection_call_finish (G_DBUS_CONNECTION (object), result, &error);
203 
204   if (error)
205     {
206       g_task_return_error (call->task, error);
207 
208       create_call_free (call);
209       return;
210     }
211 
212   token = g_strdup_printf ("portal%d", g_random_int_range (0, G_MAXINT));
213   call->request_path = g_strconcat (REQUEST_PATH_PREFIX, call->portal->sender, "/", token, NULL);
214   call->signal_id = g_dbus_connection_signal_subscribe (call->portal->bus,
215                                                         PORTAL_BUS_NAME,
216                                                         REQUEST_INTERFACE,
217                                                         "Response",
218                                                         call->request_path,
219                                                         NULL,
220                                                         G_DBUS_SIGNAL_FLAGS_NO_MATCH_RULE,
221                                                         session_started,
222                                                         call,
223                                                         NULL);
224 
225   g_variant_get (ret, "(o)", &call->portal->location_monitor_handle);
226   ensure_location_updated_connected (call->portal);
227 
228   cancellable = g_task_get_cancellable (call->task);
229 
230   if (cancellable)
231     call->cancelled_id = g_signal_connect (cancellable, "cancelled", G_CALLBACK (cancelled_cb), call);
232 
233   g_variant_builder_init (&options, G_VARIANT_TYPE_VARDICT);
234   g_variant_builder_add (&options, "{sv}", "handle_token", g_variant_new_string (token));
235   g_dbus_connection_call (call->portal->bus,
236                           PORTAL_BUS_NAME,
237                           PORTAL_OBJECT_PATH,
238                           "org.freedesktop.portal.Location",
239                           "Start",
240                           g_variant_new ("(osa{sv})",
241                                          call->id,
242                                          call->parent_handle,
243                                          &options),
244                           NULL,
245                           G_DBUS_CALL_FLAGS_NONE,
246                           -1,
247                           NULL,
248                           call_returned,
249                           call);
250 }
251 
252 static void create_session (CreateCall *call);
253 
254 static void
parent_exported(XdpParent * parent,const char * handle,gpointer data)255 parent_exported (XdpParent *parent,
256                  const char *handle,
257                  gpointer data)
258 {
259   CreateCall *call = data;
260   call->parent_handle = g_strdup (handle);
261   create_session (call);
262 }
263 
264 static void
create_session(CreateCall * call)265 create_session (CreateCall *call)
266 {
267   GVariantBuilder options;
268   g_autofree char *session_token = NULL;
269   GCancellable *cancellable;
270 
271   if (call->portal->location_monitor_handle)
272     {
273       g_task_return_boolean (call->task, TRUE);
274       create_call_free (call);
275       return;
276     }
277 
278   if (call->parent_handle == NULL)
279     {
280       call->parent->parent_export (call->parent, parent_exported, call);
281       return;
282     }
283 
284   session_token = g_strdup_printf ("portal%d", g_random_int_range (0, G_MAXINT));
285   call->id = g_strconcat (SESSION_PATH_PREFIX, call->portal->sender, "/", session_token, NULL);
286 
287   cancellable = g_task_get_cancellable (call->task);
288 
289   g_variant_builder_init (&options, G_VARIANT_TYPE_VARDICT);
290   g_variant_builder_add (&options, "{sv}", "session_handle_token", g_variant_new_string (session_token));
291   g_variant_builder_add (&options, "{sv}", "distance-threshold", g_variant_new_uint32 (call->distance));
292   g_variant_builder_add (&options, "{sv}", "time-threshold", g_variant_new_uint32 (call->time));
293   g_variant_builder_add (&options, "{sv}", "accuracy", g_variant_new_uint32 (call->accuracy));
294   g_dbus_connection_call (call->portal->bus,
295                           PORTAL_BUS_NAME,
296                           PORTAL_OBJECT_PATH,
297                           "org.freedesktop.portal.Location",
298                           "CreateSession",
299                           g_variant_new ("(a{sv})", &options),
300                           NULL,
301                           G_DBUS_CALL_FLAGS_NONE,
302                           -1,
303                           cancellable,
304                           session_created,
305                           call);
306 }
307 
308 /**
309  * xdp_portal_location_monitor_start:
310  * @portal: a [class@Portal]
311  * @parent: (nullable): a [struct@Parent], or `NULL`
312  * @distance_threshold: distance threshold, in meters
313  * @time_threshold: time threshold, in seconds
314  * @accuracy: desired accuracy
315  * @flags: options for this call
316  * @cancellable: (nullable): optional [class@Gio.Cancellable]
317  * @callback: (scope async): a callback to call when the request is done
318  * @data: (closure): data to pass to @callback
319  *
320  * Makes XdpPortal start monitoring location changes.
321  *
322  * When the location changes, the [signal@Portal::location-updated].
323  * signal is emitted.
324  *
325  * Use [method@Portal.location_monitor_stop] to stop monitoring.
326  *
327  * Note that [class@Portal] only maintains a single location monitor
328  * at a time. If you want to change the @distance_threshold,
329  * @time_threshold or @accuracy of the current monitor, you
330  * first have to call [method@Portal.location_monitor_stop] to
331  * stop monitoring.
332  */
333 void
xdp_portal_location_monitor_start(XdpPortal * portal,XdpParent * parent,guint distance_threshold,guint time_threshold,XdpLocationAccuracy accuracy,XdpLocationMonitorFlags flags,GCancellable * cancellable,GAsyncReadyCallback callback,gpointer data)334 xdp_portal_location_monitor_start (XdpPortal *portal,
335                                    XdpParent *parent,
336                                    guint distance_threshold,
337                                    guint time_threshold,
338                                    XdpLocationAccuracy accuracy,
339                                    XdpLocationMonitorFlags flags,
340                                    GCancellable *cancellable,
341                                    GAsyncReadyCallback callback,
342                                    gpointer data)
343 {
344   CreateCall *call;
345 
346   g_return_if_fail (XDP_IS_PORTAL (portal));
347   g_return_if_fail (flags == XDP_LOCATION_MONITOR_FLAG_NONE);
348 
349   call = g_new0 (CreateCall, 1);
350   call->portal = g_object_ref (portal);
351   if (parent)
352     call->parent = xdp_parent_copy (parent);
353   else
354     call->parent_handle = g_strdup ("");
355   call->distance = distance_threshold;
356   call->time = time_threshold;
357   call->accuracy = accuracy;
358   call->task = g_task_new (portal, cancellable, callback, data);
359   g_task_set_source_tag (call->task, xdp_portal_location_monitor_start);
360 
361   create_session (call);
362 }
363 
364 /**
365  * xdp_portal_location_monitor_start_finish:
366  * @portal: a [class@Portal]
367  * @result: a [iface@Gio.AsyncResult]
368  * @error: return location for an error
369  *
370  * Finishes a location-monitor request, and returns
371  * the result in the form of boolean.
372  *
373  * Returns: `TRUE` if the request succeeded
374  */
375 gboolean
xdp_portal_location_monitor_start_finish(XdpPortal * portal,GAsyncResult * result,GError ** error)376 xdp_portal_location_monitor_start_finish (XdpPortal *portal,
377                                           GAsyncResult *result,
378                                           GError **error)
379 {
380   g_return_val_if_fail (XDP_IS_PORTAL (portal), FALSE);
381   g_return_val_if_fail (g_task_is_valid (result, portal), FALSE);
382   g_return_val_if_fail (g_task_get_source_tag (G_TASK (result)) == xdp_portal_location_monitor_start, FALSE);
383 
384   return g_task_propagate_boolean (G_TASK (result), error);
385 }
386 
387 /**
388  * xdp_portal_location_monitor_stop:
389  * @portal: a [class@Portal]
390  *
391  * Stops location monitoring that was started with
392  * [method@Portal.location_monitor_start].
393  */
394 void
xdp_portal_location_monitor_stop(XdpPortal * portal)395 xdp_portal_location_monitor_stop (XdpPortal *portal)
396 {
397   g_return_if_fail (XDP_IS_PORTAL (portal));
398 
399   if (portal->location_monitor_handle != NULL)
400     {
401       g_dbus_connection_call (portal->bus,
402                               PORTAL_BUS_NAME,
403                               portal->location_monitor_handle,
404                               SESSION_INTERFACE,
405                               "Close",
406                               NULL,
407                               NULL, 0, -1, NULL, NULL, NULL);
408       g_clear_pointer (&portal->location_monitor_handle, g_free);
409     }
410 
411   if (portal->location_updated_signal)
412     {
413       g_dbus_connection_signal_unsubscribe (portal->bus, portal->location_updated_signal);
414       portal->location_updated_signal = 0;
415     }
416 }
417