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
27 static GInitableIface *initable_parent_iface;
28 static void g_network_monitor_portal_iface_init (GNetworkMonitorInterface *iface);
29 static void g_network_monitor_portal_initable_iface_init (GInitableIface *iface);
30
31 enum
32 {
33 PROP_0,
34 PROP_NETWORK_AVAILABLE,
35 PROP_NETWORK_METERED,
36 PROP_CONNECTIVITY
37 };
38
39 struct _GNetworkMonitorPortalPrivate
40 {
41 GDBusProxy *proxy;
42 gboolean has_network;
43
44 gboolean available;
45 gboolean metered;
46 GNetworkConnectivity connectivity;
47 };
48
49 G_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
61 static void
g_network_monitor_portal_init(GNetworkMonitorPortal * nm)62 g_network_monitor_portal_init (GNetworkMonitorPortal *nm)
63 {
64 nm->priv = g_network_monitor_portal_get_instance_private (nm);
65 }
66
67 static void
g_network_monitor_portal_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)68 g_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, nm->priv->available);
79 break;
80
81 case PROP_NETWORK_METERED:
82 g_value_set_boolean (value, nm->priv->metered);
83 break;
84
85 case PROP_CONNECTIVITY:
86 g_value_set_enum (value, nm->priv->connectivity);
87 break;
88
89 default:
90 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
91 break;
92 }
93 }
94
95 static gboolean
is_valid_connectivity(guint32 value)96 is_valid_connectivity (guint32 value)
97 {
98 GEnumValue *enum_value;
99 GEnumClass *enum_klass;
100
101 enum_klass = g_type_class_ref (G_TYPE_NETWORK_CONNECTIVITY);
102 enum_value = g_enum_get_value (enum_klass, value);
103
104 g_type_class_unref (enum_klass);
105
106 return enum_value != NULL;
107 }
108
109 static void
got_available(GObject * source,GAsyncResult * res,gpointer data)110 got_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, G_DBUS_ERROR, G_DBUS_ERROR_UNKNOWN_METHOD))
124 {
125 g_warning ("%s", error->message);
126 g_clear_error (&error);
127 return;
128 }
129
130 g_clear_error (&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 (ret);
141 g_variant_unref (ret);
142 }
143 else
144 {
145 g_variant_get (ret, "(b)", &available);
146 g_variant_unref (ret);
147 }
148
149 if (nm->priv->available != available)
150 {
151 nm->priv->available = available;
152 g_object_notify (G_OBJECT (nm), "network-available");
153 g_signal_emit_by_name (nm, "network-changed", available);
154 }
155 }
156
157 static void
got_metered(GObject * source,GAsyncResult * res,gpointer data)158 got_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, G_DBUS_ERROR, G_DBUS_ERROR_UNKNOWN_METHOD))
172 {
173 g_warning ("%s", error->message);
174 g_clear_error (&error);
175 return;
176 }
177
178 g_clear_error (&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 (ret);
189 g_variant_unref (ret);
190 }
191 else
192 {
193 g_variant_get (ret, "(b)", &metered);
194 g_variant_unref (ret);
195 }
196
197 if (nm->priv->metered != metered)
198 {
199 nm->priv->metered = metered;
200 g_object_notify (G_OBJECT (nm), "network-metered");
201 g_signal_emit_by_name (nm, "network-changed", nm->priv->available);
202 }
203 }
204
205 static void
got_connectivity(GObject * source,GAsyncResult * res,gpointer data)206 got_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, G_DBUS_ERROR, G_DBUS_ERROR_UNKNOWN_METHOD))
220 {
221 g_warning ("%s", error->message);
222 g_clear_error (&error);
223 return;
224 }
225
226 g_clear_error (&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 (ret);
237 g_variant_unref (ret);
238 }
239 else
240 {
241 g_variant_get (ret, "(u)", &connectivity);
242 g_variant_unref (ret);
243 }
244
245 if (nm->priv->connectivity != connectivity &&
246 is_valid_connectivity (connectivity))
247 {
248 nm->priv->connectivity = connectivity;
249 g_object_notify (G_OBJECT (nm), "connectivity");
250 g_signal_emit_by_name (nm, "network-changed", nm->priv->available);
251 }
252 }
253
254 static void
got_status(GObject * source,GAsyncResult * res,gpointer data)255 got_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, G_DBUS_ERROR, 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 (&error);
282 return;
283 }
284
285 g_variant_get (ret, "(@a{sv})", &status);
286 g_variant_unref (ret);
287
288 g_variant_lookup (status, "available", "b", &available);
289 g_variant_lookup (status, "metered", "b", &metered);
290 g_variant_lookup (status, "connectivity", "u", &connectivity);
291 g_variant_unref (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), "network-available");
299 }
300
301 if (nm->priv->metered != metered)
302 {
303 nm->priv->metered = metered;
304 g_object_notify (G_OBJECT (nm), "network-metered");
305 }
306
307 if (nm->priv->connectivity != connectivity &&
308 is_valid_connectivity (connectivity))
309 {
310 nm->priv->connectivity = connectivity;
311 g_object_notify (G_OBJECT (nm), "connectivity");
312 }
313
314 g_object_thaw_notify (G_OBJECT (nm));
315
316 g_signal_emit_by_name (nm, "network-changed", available);
317 }
318
319 static void
update_properties(GDBusProxy * proxy,GNetworkMonitorPortal * nm)320 update_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
327 static void
proxy_signal(GDBusProxy * proxy,const char * sender,const char * signal,GVariant * parameters,GNetworkMonitorPortal * nm)328 proxy_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 (signal, "changed") != 0)
338 return;
339
340 /* Version 1 updates "available" with the "changed" signal */
341 if (g_variant_is_of_type (parameters, G_VARIANT_TYPE ("(b)")))
342 {
343 gboolean available;
344
345 g_variant_get (parameters, "(b)", &available);
346 if (nm->priv->available != available)
347 {
348 nm->priv->available = available;
349 g_object_notify (G_OBJECT (nm), "available");
350 }
351 g_signal_emit_by_name (nm, "network-changed", available);
352 }
353 else
354 {
355 update_properties (proxy, nm);
356 }
357 }
358
359 static void
proxy_properties_changed(GDBusProxy * proxy,GVariant * changed,GVariant * invalidated,GNetworkMonitorPortal * nm)360 proxy_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 (ret);
375 if (nm->priv->connectivity != connectivity &&
376 is_valid_connectivity (connectivity))
377 {
378 nm->priv->connectivity = connectivity;
379 g_object_notify (G_OBJECT (nm), "connectivity");
380 should_emit_changed = TRUE;
381 }
382 g_variant_unref (ret);
383 }
384
385 ret = g_dbus_proxy_get_cached_property (proxy, "metered");
386 if (ret)
387 {
388 gboolean metered = g_variant_get_boolean (ret);
389 if (nm->priv->metered != metered)
390 {
391 nm->priv->metered = metered;
392 g_object_notify (G_OBJECT (nm), "network-metered");
393 should_emit_changed = TRUE;
394 }
395 g_variant_unref (ret);
396 }
397
398 ret = g_dbus_proxy_get_cached_property (proxy, "available");
399 if (ret)
400 {
401 gboolean available = g_variant_get_boolean (ret);
402 if (nm->priv->available != available)
403 {
404 nm->priv->available = available;
405 g_object_notify (G_OBJECT (nm), "network-available");
406 should_emit_changed = TRUE;
407 }
408 g_variant_unref (ret);
409 }
410
411 if (should_emit_changed)
412 g_signal_emit_by_name (nm, "network-changed", nm->priv->available);
413 }
414
415 static gboolean
g_network_monitor_portal_initable_init(GInitable * initable,GCancellable * cancellable,GError ** error)416 g_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 (error, G_IO_ERROR, G_IO_ERROR_FAILED, "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 (proxy);
450 g_set_error (error,
451 G_DBUS_ERROR,
452 G_DBUS_ERROR_NAME_HAS_NO_OWNER,
453 "Desktop portal not found");
454 return FALSE;
455 }
456
457 g_free (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
474 static void
g_network_monitor_portal_finalize(GObject * object)475 g_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
484 static void
g_network_monitor_portal_class_init(GNetworkMonitorPortalClass * class)485 g_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 (gobject_class, PROP_NETWORK_AVAILABLE, "network-available");
493 g_object_class_override_property (gobject_class, PROP_NETWORK_METERED, "network-metered");
494 g_object_class_override_property (gobject_class, PROP_CONNECTIVITY, "connectivity");
495 }
496
497 static gboolean
g_network_monitor_portal_can_reach(GNetworkMonitor * monitor,GSocketConnectable * connectable,GCancellable * cancellable,GError ** error)498 g_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 (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
511 "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 ("(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 (ret, "(b)", &reachable);
531 g_variant_unref (ret);
532 }
533
534 return reachable;
535 }
536
537 static void
can_reach_done(GObject * source,GAsyncResult * result,gpointer data)538 can_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 (task);
553 return;
554 }
555
556 g_variant_get (ret, "(b)", &reachable);
557 g_variant_unref (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 (task);
567 }
568
569 static void
g_network_monitor_portal_can_reach_async(GNetworkMonitor * monitor,GSocketConnectable * connectable,GCancellable * cancellable,GAsyncReadyCallback callback,gpointer data)570 g_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 (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 ("(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
605 static gboolean
g_network_monitor_portal_can_reach_finish(GNetworkMonitor * monitor,GAsyncResult * result,GError ** error)606 g_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
613 static void
g_network_monitor_portal_iface_init(GNetworkMonitorInterface * monitor_iface)614 g_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
621 static void
g_network_monitor_portal_initable_iface_init(GInitableIface * iface)622 g_network_monitor_portal_initable_iface_init (GInitableIface *iface)
623 {
624 initable_parent_iface = g_type_interface_peek_parent (iface);
625
626 iface->init = g_network_monitor_portal_initable_init;
627 }
628