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)", ×tamp_s, ×tamp_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