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

source code of gtk/subprojects/glib/gio/gnetworkmonitorportal.c