1 /* GIO - GLib Input, Output and Streaming Library
2 *
3 * Copyright 2016 Red Hat, Inc.
4 *
5 * SPDX-License-Identifier: LGPL-2.1-or-later
6 *
7 * This library is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Lesser General Public
9 * License as published by the Free Software Foundation; either
10 * version 2.1 of the License, or (at your option) any later version.
11 *
12 * This library is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Lesser General Public License for more details.
16 *
17 * You should have received a copy of the GNU Lesser General
18 * Public License along with this library; if not, see <http://www.gnu.org/licenses/>.
19 */
20
21 #include "config.h"
22
23 #include "gnetworkmonitorportal.h"
24 #include "ginitable.h"
25 #include "giomodule-priv.h"
26 #include "xdp-dbus.h"
27 #include "gportalsupport.h"
28
29 static GInitableIface *initable_parent_iface;
30 static void g_network_monitor_portal_iface_init (GNetworkMonitorInterface *iface);
31 static void g_network_monitor_portal_initable_iface_init (GInitableIface *iface);
32
33 enum
34 {
35 PROP_0,
36 PROP_NETWORK_AVAILABLE,
37 PROP_NETWORK_METERED,
38 PROP_CONNECTIVITY
39 };
40
41 struct _GNetworkMonitorPortalPrivate
42 {
43 GDBusProxy *proxy;
44 gboolean has_network;
45
46 gboolean available;
47 gboolean metered;
48 GNetworkConnectivity connectivity;
49 };
50
51 G_DEFINE_TYPE_WITH_CODE (GNetworkMonitorPortal, g_network_monitor_portal, G_TYPE_NETWORK_MONITOR_BASE,
52 G_ADD_PRIVATE (GNetworkMonitorPortal)
53 G_IMPLEMENT_INTERFACE (G_TYPE_NETWORK_MONITOR,
54 g_network_monitor_portal_iface_init)
55 G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE,
56 g_network_monitor_portal_initable_iface_init)
57 _g_io_modules_ensure_extension_points_registered ();
58 g_io_extension_point_implement (G_NETWORK_MONITOR_EXTENSION_POINT_NAME,
59 g_define_type_id,
60 "portal",
61 40))
62
63 static void
64 g_network_monitor_portal_init (GNetworkMonitorPortal *nm)
65 {
66 nm->priv = g_network_monitor_portal_get_instance_private (nm);
67 }
68
69 static void
70 g_network_monitor_portal_get_property (GObject *object,
71 guint prop_id,
72 GValue *value,
73 GParamSpec *pspec)
74 {
75 GNetworkMonitorPortal *nm = G_NETWORK_MONITOR_PORTAL (object);
76
77 switch (prop_id)
78 {
79 case PROP_NETWORK_AVAILABLE:
80 g_value_set_boolean (value, nm->priv->available);
81 break;
82
83 case PROP_NETWORK_METERED:
84 g_value_set_boolean (value, nm->priv->metered);
85 break;
86
87 case PROP_CONNECTIVITY:
88 g_value_set_enum (value, nm->priv->connectivity);
89 break;
90
91 default:
92 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
93 break;
94 }
95 }
96
97 static gboolean
98 is_valid_connectivity (guint32 value)
99 {
100 GEnumValue *enum_value;
101 GEnumClass *enum_klass;
102
103 enum_klass = g_type_class_ref (G_TYPE_NETWORK_CONNECTIVITY);
104 enum_value = g_enum_get_value (enum_klass, value);
105
106 g_type_class_unref (enum_klass);
107
108 return enum_value != NULL;
109 }
110
111 static void
112 got_available (GObject *source,
113 GAsyncResult *res,
114 gpointer data)
115 {
116 GDBusProxy *proxy = G_DBUS_PROXY (source);
117 GNetworkMonitorPortal *nm = G_NETWORK_MONITOR_PORTAL (data);
118 GError *error = NULL;
119 GVariant *ret;
120 gboolean available;
121
122 ret = g_dbus_proxy_call_finish (proxy, res, &error);
123 if (ret == NULL)
124 {
125 if (!g_error_matches (error, G_DBUS_ERROR, G_DBUS_ERROR_UNKNOWN_METHOD))
126 {
127 g_warning ("%s", error->message);
128 g_clear_error (&error);
129 return;
130 }
131
132 g_clear_error (&error);
133
134 /* Fall back to version 1 */
135 ret = g_dbus_proxy_get_cached_property (nm->priv->proxy, "available");
136 if (ret == NULL)
137 {
138 g_warning ("Failed to get the '%s' property", "available");
139 return;
140 }
141
142 available = g_variant_get_boolean (ret);
143 g_variant_unref (ret);
144 }
145 else
146 {
147 g_variant_get (ret, "(b)", &available);
148 g_variant_unref (ret);
149 }
150
151 if (nm->priv->available != available)
152 {
153 nm->priv->available = available;
154 g_object_notify (G_OBJECT (nm), "network-available");
155 g_signal_emit_by_name (nm, "network-changed", available);
156 }
157 }
158
159 static void
160 got_metered (GObject *source,
161 GAsyncResult *res,
162 gpointer data)
163 {
164 GDBusProxy *proxy = G_DBUS_PROXY (source);
165 GNetworkMonitorPortal *nm = G_NETWORK_MONITOR_PORTAL (data);
166 GError *error = NULL;
167 GVariant *ret;
168 gboolean metered;
169
170 ret = g_dbus_proxy_call_finish (proxy, res, &error);
171 if (ret == NULL)
172 {
173 if (!g_error_matches (error, G_DBUS_ERROR, G_DBUS_ERROR_UNKNOWN_METHOD))
174 {
175 g_warning ("%s", error->message);
176 g_clear_error (&error);
177 return;
178 }
179
180 g_clear_error (&error);
181
182 /* Fall back to version 1 */
183 ret = g_dbus_proxy_get_cached_property (nm->priv->proxy, "metered");
184 if (ret == NULL)
185 {
186 g_warning ("Failed to get the '%s' property", "metered");
187 return;
188 }
189
190 metered = g_variant_get_boolean (ret);
191 g_variant_unref (ret);
192 }
193 else
194 {
195 g_variant_get (ret, "(b)", &metered);
196 g_variant_unref (ret);
197 }
198
199 if (nm->priv->metered != metered)
200 {
201 nm->priv->metered = metered;
202 g_object_notify (G_OBJECT (nm), "network-metered");
203 g_signal_emit_by_name (nm, "network-changed", nm->priv->available);
204 }
205 }
206
207 static void
208 got_connectivity (GObject *source,
209 GAsyncResult *res,
210 gpointer data)
211 {
212 GDBusProxy *proxy = G_DBUS_PROXY (source);
213 GNetworkMonitorPortal *nm = G_NETWORK_MONITOR_PORTAL (data);
214 GError *error = NULL;
215 GVariant *ret;
216 GNetworkConnectivity connectivity;
217
218 ret = g_dbus_proxy_call_finish (proxy, res, &error);
219 if (ret == NULL)
220 {
221 if (!g_error_matches (error, G_DBUS_ERROR, G_DBUS_ERROR_UNKNOWN_METHOD))
222 {
223 g_warning ("%s", error->message);
224 g_clear_error (&error);
225 return;
226 }
227
228 g_clear_error (&error);
229
230 /* Fall back to version 1 */
231 ret = g_dbus_proxy_get_cached_property (nm->priv->proxy, "connectivity");
232 if (ret == NULL)
233 {
234 g_warning ("Failed to get the '%s' property", "connectivity");
235 return;
236 }
237
238 connectivity = g_variant_get_uint32 (ret);
239 g_variant_unref (ret);
240 }
241 else
242 {
243 g_variant_get (ret, "(u)", &connectivity);
244 g_variant_unref (ret);
245 }
246
247 if (nm->priv->connectivity != connectivity &&
248 is_valid_connectivity (connectivity))
249 {
250 nm->priv->connectivity = connectivity;
251 g_object_notify (G_OBJECT (nm), "connectivity");
252 g_signal_emit_by_name (nm, "network-changed", nm->priv->available);
253 }
254 }
255
256 static void
257 got_status (GObject *source,
258 GAsyncResult *res,
259 gpointer data)
260 {
261 GDBusProxy *proxy = G_DBUS_PROXY (source);
262 GNetworkMonitorPortal *nm = G_NETWORK_MONITOR_PORTAL (data);
263 GError *error = NULL;
264 GVariant *ret;
265 GVariant *status;
266 gboolean available;
267 gboolean metered;
268 GNetworkConnectivity connectivity;
269
270 ret = g_dbus_proxy_call_finish (proxy, res, &error);
271 if (ret == NULL)
272 {
273 if (g_error_matches (error, G_DBUS_ERROR, G_DBUS_ERROR_UNKNOWN_METHOD))
274 {
275 /* Fall back to version 2 */
276 g_dbus_proxy_call (proxy, "GetConnectivity", NULL, 0, -1, NULL, got_connectivity, nm);
277 g_dbus_proxy_call (proxy, "GetMetered", NULL, 0, -1, NULL, got_metered, nm);
278 g_dbus_proxy_call (proxy, "GetAvailable", NULL, 0, -1, NULL, got_available, nm);
279 }
280 else
281 g_warning ("%s", error->message);
282
283 g_clear_error (&error);
284 return;
285 }
286
287 g_variant_get (ret, "(@a{sv})", &status);
288 g_variant_unref (ret);
289
290 g_variant_lookup (status, "available", "b", &available);
291 g_variant_lookup (status, "metered", "b", &metered);
292 g_variant_lookup (status, "connectivity", "u", &connectivity);
293 g_variant_unref (status);
294
295 g_object_freeze_notify (G_OBJECT (nm));
296
297 if (nm->priv->available != available)
298 {
299 nm->priv->available = available;
300 g_object_notify (G_OBJECT (nm), "network-available");
301 }
302
303 if (nm->priv->metered != metered)
304 {
305 nm->priv->metered = metered;
306 g_object_notify (G_OBJECT (nm), "network-metered");
307 }
308
309 if (nm->priv->connectivity != connectivity &&
310 is_valid_connectivity (connectivity))
311 {
312 nm->priv->connectivity = connectivity;
313 g_object_notify (G_OBJECT (nm), "connectivity");
314 }
315
316 g_object_thaw_notify (G_OBJECT (nm));
317
318 g_signal_emit_by_name (nm, "network-changed", available);
319 }
320
321 static void
322 update_properties (GDBusProxy *proxy,
323 GNetworkMonitorPortal *nm)
324 {
325 /* Try version 3 first */
326 g_dbus_proxy_call (proxy, "GetStatus", NULL, 0, -1, NULL, got_status, nm);
327 }
328
329 static void
330 proxy_signal (GDBusProxy *proxy,
331 const char *sender,
332 const char *signal,
333 GVariant *parameters,
334 GNetworkMonitorPortal *nm)
335 {
336 if (!nm->priv->has_network)
337 return;
338
339 if (strcmp (signal, "changed") != 0)
340 return;
341
342 /* Version 1 updates "available" with the "changed" signal */
343 if (g_variant_is_of_type (parameters, G_VARIANT_TYPE ("(b)")))
344 {
345 gboolean available;
346
347 g_variant_get (parameters, "(b)", &available);
348 if (nm->priv->available != available)
349 {
350 nm->priv->available = available;
351 g_object_notify (G_OBJECT (nm), "available");
352 }
353 g_signal_emit_by_name (nm, "network-changed", available);
354 }
355 else
356 {
357 update_properties (proxy, nm);
358 }
359 }
360
361 static void
362 proxy_properties_changed (GDBusProxy *proxy,
363 GVariant *changed,
364 GVariant *invalidated,
365 GNetworkMonitorPortal *nm)
366 {
367 gboolean should_emit_changed = FALSE;
368 GVariant *ret;
369
370 if (!nm->priv->has_network)
371 return;
372
373 ret = g_dbus_proxy_get_cached_property (proxy, "connectivity");
374 if (ret)
375 {
376 GNetworkConnectivity connectivity = g_variant_get_uint32 (ret);
377 if (nm->priv->connectivity != connectivity &&
378 is_valid_connectivity (connectivity))
379 {
380 nm->priv->connectivity = connectivity;
381 g_object_notify (G_OBJECT (nm), "connectivity");
382 should_emit_changed = TRUE;
383 }
384 g_variant_unref (ret);
385 }
386
387 ret = g_dbus_proxy_get_cached_property (proxy, "metered");
388 if (ret)
389 {
390 gboolean metered = g_variant_get_boolean (ret);
391 if (nm->priv->metered != metered)
392 {
393 nm->priv->metered = metered;
394 g_object_notify (G_OBJECT (nm), "network-metered");
395 should_emit_changed = TRUE;
396 }
397 g_variant_unref (ret);
398 }
399
400 ret = g_dbus_proxy_get_cached_property (proxy, "available");
401 if (ret)
402 {
403 gboolean available = g_variant_get_boolean (ret);
404 if (nm->priv->available != available)
405 {
406 nm->priv->available = available;
407 g_object_notify (G_OBJECT (nm), "network-available");
408 should_emit_changed = TRUE;
409 }
410 g_variant_unref (ret);
411 }
412
413 if (should_emit_changed)
414 g_signal_emit_by_name (nm, "network-changed", nm->priv->available);
415 }
416
417 static gboolean
418 g_network_monitor_portal_initable_init (GInitable *initable,
419 GCancellable *cancellable,
420 GError **error)
421 {
422 GNetworkMonitorPortal *nm = G_NETWORK_MONITOR_PORTAL (initable);
423 GDBusProxy *proxy;
424 gchar *name_owner = NULL;
425
426 nm->priv->available = FALSE;
427 nm->priv->metered = FALSE;
428 nm->priv->connectivity = G_NETWORK_CONNECTIVITY_LOCAL;
429
430 if (!glib_should_use_portal ())
431 {
432 g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Not using portals");
433 return FALSE;
434 }
435
436 proxy = g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SESSION,
437 G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES,
438 NULL,
439 "org.freedesktop.portal.Desktop",
440 "/org/freedesktop/portal/desktop",
441 "org.freedesktop.portal.NetworkMonitor",
442 cancellable,
443 error);
444 if (!proxy)
445 return FALSE;
446
447 name_owner = g_dbus_proxy_get_name_owner (proxy);
448
449 if (!name_owner)
450 {
451 g_object_unref (proxy);
452 g_set_error (error,
453 G_DBUS_ERROR,
454 G_DBUS_ERROR_NAME_HAS_NO_OWNER,
455 "Desktop portal not found");
456 return FALSE;
457 }
458
459 g_free (name_owner);
460
461 g_signal_connect (proxy, "g-signal", G_CALLBACK (proxy_signal), nm);
462 g_signal_connect (proxy, "g-properties-changed", G_CALLBACK (proxy_properties_changed), nm);
463
464 nm->priv->proxy = proxy;
465 nm->priv->has_network = glib_network_available_in_sandbox ();
466
467 if (!initable_parent_iface->init (initable, cancellable, error))
468 return FALSE;
469
470 if (nm->priv->has_network)
471 update_properties (proxy, nm);
472
473 return TRUE;
474 }
475
476 static void
477 g_network_monitor_portal_finalize (GObject *object)
478 {
479 GNetworkMonitorPortal *nm = G_NETWORK_MONITOR_PORTAL (object);
480
481 g_clear_object (&nm->priv->proxy);
482
483 G_OBJECT_CLASS (g_network_monitor_portal_parent_class)->finalize (object);
484 }
485
486 static void
487 g_network_monitor_portal_class_init (GNetworkMonitorPortalClass *class)
488 {
489 GObjectClass *gobject_class = G_OBJECT_CLASS (class);
490
491 gobject_class->finalize = g_network_monitor_portal_finalize;
492 gobject_class->get_property = g_network_monitor_portal_get_property;
493
494 g_object_class_override_property (gobject_class, PROP_NETWORK_AVAILABLE, "network-available");
495 g_object_class_override_property (gobject_class, PROP_NETWORK_METERED, "network-metered");
496 g_object_class_override_property (gobject_class, PROP_CONNECTIVITY, "connectivity");
497 }
498
499 static gboolean
500 g_network_monitor_portal_can_reach (GNetworkMonitor *monitor,
501 GSocketConnectable *connectable,
502 GCancellable *cancellable,
503 GError **error)
504 {
505 GNetworkMonitorPortal *nm = G_NETWORK_MONITOR_PORTAL (monitor);
506 GVariant *ret;
507 GNetworkAddress *address;
508 gboolean reachable = FALSE;
509
510 if (!G_IS_NETWORK_ADDRESS (connectable))
511 {
512 g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
513 "Can't handle this kind of GSocketConnectable (%s)",
514 G_OBJECT_TYPE_NAME (connectable));
515 return FALSE;
516 }
517
518 address = G_NETWORK_ADDRESS (connectable);
519
520 ret = g_dbus_proxy_call_sync (nm->priv->proxy,
521 "CanReach",
522 g_variant_new ("(su)",
523 g_network_address_get_hostname (address),
524 g_network_address_get_port (address)),
525 G_DBUS_CALL_FLAGS_NONE,
526 -1,
527 cancellable,
528 error);
529
530 if (ret)
531 {
532 g_variant_get (ret, "(b)", &reachable);
533 g_variant_unref (ret);
534 }
535
536 return reachable;
537 }
538
539 static void
540 can_reach_done (GObject *source,
541 GAsyncResult *result,
542 gpointer data)
543 {
544 GTask *task = data;
545 GNetworkMonitorPortal *nm = G_NETWORK_MONITOR_PORTAL (g_task_get_source_object (task));
546 GError *error = NULL;
547 GVariant *ret;
548 gboolean reachable;
549
550 ret = g_dbus_proxy_call_finish (nm->priv->proxy, result, &error);
551 if (ret == NULL)
552 {
553 g_task_return_error (task, error);
554 g_object_unref (task);
555 return;
556 }
557
558 g_variant_get (ret, "(b)", &reachable);
559 g_variant_unref (ret);
560
561 if (reachable)
562 g_task_return_boolean (task, TRUE);
563 else
564 g_task_return_new_error_literal (task,
565 G_IO_ERROR, G_IO_ERROR_HOST_UNREACHABLE,
566 "Can't reach host");
567
568 g_object_unref (task);
569 }
570
571 static void
572 g_network_monitor_portal_can_reach_async (GNetworkMonitor *monitor,
573 GSocketConnectable *connectable,
574 GCancellable *cancellable,
575 GAsyncReadyCallback callback,
576 gpointer data)
577 {
578 GNetworkMonitorPortal *nm = G_NETWORK_MONITOR_PORTAL (monitor);
579 GTask *task;
580 GNetworkAddress *address;
581
582 task = g_task_new (monitor, cancellable, callback, data);
583
584 if (!G_IS_NETWORK_ADDRESS (connectable))
585 {
586 g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
587 "Can't handle this kind of GSocketConnectable (%s)",
588 G_OBJECT_TYPE_NAME (connectable));
589 g_object_unref (task);
590 return;
591 }
592
593 address = G_NETWORK_ADDRESS (connectable);
594
595 g_dbus_proxy_call (nm->priv->proxy,
596 "CanReach",
597 g_variant_new ("(su)",
598 g_network_address_get_hostname (address),
599 g_network_address_get_port (address)),
600 G_DBUS_CALL_FLAGS_NONE,
601 -1,
602 cancellable,
603 can_reach_done,
604 task);
605 }
606
607 static gboolean
608 g_network_monitor_portal_can_reach_finish (GNetworkMonitor *monitor,
609 GAsyncResult *result,
610 GError **error)
611 {
612 return g_task_propagate_boolean (G_TASK (result), error);
613 }
614
615 static void
616 g_network_monitor_portal_iface_init (GNetworkMonitorInterface *monitor_iface)
617 {
618 monitor_iface->can_reach = g_network_monitor_portal_can_reach;
619 monitor_iface->can_reach_async = g_network_monitor_portal_can_reach_async;
620 monitor_iface->can_reach_finish = g_network_monitor_portal_can_reach_finish;
621 }
622
623 static void
624 g_network_monitor_portal_initable_iface_init (GInitableIface *iface)
625 {
626 initable_parent_iface = g_type_interface_peek_parent (iface);
627
628 iface->init = g_network_monitor_portal_initable_init;
629 }