(root)/
glib-2.79.0/
gio/
gnetworkmonitorportal.c
       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  }