/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ /* * Copyright (C) 2012 Red Hat * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . * * Written by: * Jasper St. Pierre */ /* Network page {{{1 */ #define PAGE_ID "network" #include "config.h" #include "network-resources.h" #include "gis-network-page.h" #include #include #include "network-dialogs.h" #include "gis-page-header.h" typedef enum { NM_AP_SEC_UNKNOWN, NM_AP_SEC_NONE, NM_AP_SEC_WEP, NM_AP_SEC_WPA, NM_AP_SEC_WPA2 } NMAccessPointSecurity; struct _GisNetworkPagePrivate { GtkWidget *network_list; GtkWidget *no_network_label; GtkWidget *no_network_spinner; GtkWidget *turn_on_label; GtkWidget *turn_on_switch; NMClient *nm_client; NMDevice *nm_device; gboolean refreshing; GtkSizeGroup *icons; guint refresh_timeout_id; /* TRUE if the page has ever been shown to the user. */ gboolean ever_shown; }; typedef struct _GisNetworkPagePrivate GisNetworkPagePrivate; G_DEFINE_TYPE_WITH_PRIVATE (GisNetworkPage, gis_network_page, GIS_TYPE_PAGE); static GPtrArray * get_strongest_unique_aps (const GPtrArray *aps) { GBytes *ssid; GBytes *ssid_tmp; GPtrArray *unique = NULL; gboolean add_ap; guint i; guint j; NMAccessPoint *ap; NMAccessPoint *ap_tmp; /* we will have multiple entries for typical hotspots, * just keep the one with the strongest signal */ unique = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref); if (aps == NULL) goto out; for (i = 0; i < aps->len; i++) { ap = NM_ACCESS_POINT (g_ptr_array_index (aps, i)); ssid = nm_access_point_get_ssid (ap); add_ap = TRUE; if (!ssid) continue; /* get already added list */ for (j = 0; j < unique->len; j++) { ap_tmp = NM_ACCESS_POINT (g_ptr_array_index (unique, j)); ssid_tmp = nm_access_point_get_ssid (ap_tmp); /* is this the same type and data? */ if (ssid_tmp && nm_utils_same_ssid (g_bytes_get_data (ssid, NULL), g_bytes_get_size (ssid), g_bytes_get_data (ssid_tmp, NULL), g_bytes_get_size (ssid_tmp), TRUE)) { /* the new access point is stronger */ if (nm_access_point_get_strength (ap) > nm_access_point_get_strength (ap_tmp)) { g_ptr_array_remove (unique, ap_tmp); add_ap = TRUE; } else { add_ap = FALSE; } break; } } if (add_ap) { g_ptr_array_add (unique, g_object_ref (ap)); } } out: return unique; } static guint get_access_point_security (NMAccessPoint *ap) { NM80211ApFlags flags; NM80211ApSecurityFlags wpa_flags; NM80211ApSecurityFlags rsn_flags; guint type; flags = nm_access_point_get_flags (ap); wpa_flags = nm_access_point_get_wpa_flags (ap); rsn_flags = nm_access_point_get_rsn_flags (ap); if (!(flags & NM_802_11_AP_FLAGS_PRIVACY) && wpa_flags == NM_802_11_AP_SEC_NONE && rsn_flags == NM_802_11_AP_SEC_NONE) type = NM_AP_SEC_NONE; else if ((flags & NM_802_11_AP_FLAGS_PRIVACY) && wpa_flags == NM_802_11_AP_SEC_NONE && rsn_flags == NM_802_11_AP_SEC_NONE) type = NM_AP_SEC_WEP; else if (!(flags & NM_802_11_AP_FLAGS_PRIVACY) && wpa_flags != NM_802_11_AP_SEC_NONE && rsn_flags != NM_802_11_AP_SEC_NONE) type = NM_AP_SEC_WPA; else type = NM_AP_SEC_WPA2; return type; } static gint ap_sort (GtkListBoxRow *a, GtkListBoxRow *b, gpointer data) { GtkWidget *wa, *wb; guint sa, sb; wa = gtk_list_box_row_get_child (a); wb = gtk_list_box_row_get_child (b); sa = GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (wa), "strength")); sb = GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (wb), "strength")); if (sa > sb) return -1; if (sb > sa) return 1; return 0; } static void add_access_point (GisNetworkPage *page, NMAccessPoint *ap, NMAccessPoint *active) { GisNetworkPagePrivate *priv = gis_network_page_get_instance_private (page); GBytes *ssid; GBytes *ssid_active = NULL; gchar *ssid_text; const gchar *object_path; gboolean activated, activating; guint security; guint strength; const gchar *icon_name; GtkWidget *row; GtkWidget *widget; GtkWidget *grid; GtkWidget *state_widget = NULL; ssid = nm_access_point_get_ssid (ap); object_path = nm_object_get_path (NM_OBJECT (ap)); if (ssid == NULL) return; ssid_text = nm_utils_ssid_to_utf8 (g_bytes_get_data (ssid, NULL), g_bytes_get_size (ssid)); if (active) ssid_active = nm_access_point_get_ssid (active); if (ssid_active && nm_utils_same_ssid (g_bytes_get_data (ssid, NULL), g_bytes_get_size (ssid), g_bytes_get_data (ssid_active, NULL), g_bytes_get_size (ssid_active), TRUE)) { switch (nm_device_get_state (priv->nm_device)) { case NM_DEVICE_STATE_PREPARE: case NM_DEVICE_STATE_CONFIG: case NM_DEVICE_STATE_NEED_AUTH: case NM_DEVICE_STATE_IP_CONFIG: case NM_DEVICE_STATE_SECONDARIES: activated = FALSE; activating = TRUE; break; case NM_DEVICE_STATE_ACTIVATED: activated = TRUE; activating = FALSE; break; default: activated = FALSE; activating = FALSE; break; } } else { activated = FALSE; activating = FALSE; } security = get_access_point_security (ap); strength = nm_access_point_get_strength (ap); row = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 12); gtk_widget_set_margin_start (row, 12); gtk_widget_set_margin_end (row, 12); widget = gtk_label_new (ssid_text); gtk_widget_set_margin_top (widget, 12); gtk_widget_set_margin_bottom (widget, 12); gtk_box_append (GTK_BOX (row), widget); if (activated) { state_widget = gtk_image_new_from_icon_name ("object-select-symbolic"); } else if (activating) { state_widget = gtk_spinner_new (); gtk_spinner_start (GTK_SPINNER (state_widget)); } if (state_widget) { gtk_widget_set_halign (state_widget, GTK_ALIGN_START); gtk_widget_set_valign (state_widget, GTK_ALIGN_CENTER); gtk_widget_set_hexpand (state_widget, TRUE); gtk_box_append (GTK_BOX (row), state_widget); } grid = gtk_grid_new (); gtk_grid_set_column_spacing (GTK_GRID (grid), 6); gtk_grid_set_column_homogeneous (GTK_GRID (grid), TRUE); gtk_widget_set_valign (grid, GTK_ALIGN_CENTER); gtk_size_group_add_widget (priv->icons, grid); gtk_box_append (GTK_BOX (row), grid); if (security != NM_AP_SEC_UNKNOWN && security != NM_AP_SEC_NONE) { widget = gtk_image_new_from_icon_name ("network-wireless-encrypted-symbolic"); gtk_grid_attach (GTK_GRID (grid), widget, 0, 0, 1, 1); } if (strength < 20) icon_name = "network-wireless-signal-none-symbolic"; else if (strength < 40) icon_name = "network-wireless-signal-weak-symbolic"; else if (strength < 50) icon_name = "network-wireless-signal-ok-symbolic"; else if (strength < 80) icon_name = "network-wireless-signal-good-symbolic"; else icon_name = "network-wireless-signal-excellent-symbolic"; widget = gtk_image_new_from_icon_name (icon_name); gtk_widget_set_halign (widget, GTK_ALIGN_END); gtk_grid_attach (GTK_GRID (grid), widget, 1, 0, 1, 1); /* if this connection is the active one or is being activated, then make sure * it's sorted before all others */ if (activating || activated) strength = G_MAXUINT; g_object_set_data (G_OBJECT (row), "object-path", (gpointer) object_path); g_object_set_data (G_OBJECT (row), "ssid", (gpointer) ssid); g_object_set_data (G_OBJECT (row), "strength", GUINT_TO_POINTER (strength)); gtk_list_box_append (GTK_LIST_BOX (priv->network_list), row); } static void add_access_point_other (GisNetworkPage *page) { GisNetworkPagePrivate *priv = gis_network_page_get_instance_private (page); GtkWidget *row; GtkWidget *widget; row = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 12); gtk_widget_set_margin_start (row, 12); gtk_widget_set_margin_end (row, 12); widget = gtk_label_new (C_("Wireless access point", "Other…")); gtk_widget_set_margin_top (widget, 12); gtk_widget_set_margin_bottom (widget, 12); gtk_box_append (GTK_BOX (row), widget); g_object_set_data (G_OBJECT (row), "object-path", "ap-other..."); g_object_set_data (G_OBJECT (row), "strength", GUINT_TO_POINTER (0)); gtk_list_box_append (GTK_LIST_BOX (priv->network_list), row); } static gboolean refresh_wireless_list (GisNetworkPage *page); static void cancel_periodic_refresh (GisNetworkPage *page) { GisNetworkPagePrivate *priv = gis_network_page_get_instance_private (page); if (priv->refresh_timeout_id == 0) return; g_debug ("Stopping periodic/scheduled Wi-Fi list refresh"); g_clear_handle_id (&priv->refresh_timeout_id, g_source_remove); } static gboolean refresh_again (gpointer user_data) { GisNetworkPage *page = GIS_NETWORK_PAGE (user_data); refresh_wireless_list (page); return G_SOURCE_REMOVE; } static void start_periodic_refresh (GisNetworkPage *page) { GisNetworkPagePrivate *priv = gis_network_page_get_instance_private (page); static const guint periodic_wifi_refresh_timeout_sec = 10; cancel_periodic_refresh (page); g_debug ("Starting periodic Wi-Fi list refresh (every %u secs)", periodic_wifi_refresh_timeout_sec); priv->refresh_timeout_id = g_timeout_add_seconds (periodic_wifi_refresh_timeout_sec, refresh_again, page); } static gboolean refresh_wireless_list (GisNetworkPage *page) { GisNetworkPagePrivate *priv = gis_network_page_get_instance_private (page); NMAccessPoint *active_ap = NULL; NMAccessPoint *ap; const GPtrArray *aps; GPtrArray *unique_aps; GtkWidget *child; guint i; gboolean enabled; g_debug ("Refreshing Wi-Fi networks list"); priv->refreshing = TRUE; g_assert (NM_IS_DEVICE_WIFI (priv->nm_device)); cancel_periodic_refresh (page); active_ap = nm_device_wifi_get_active_access_point (NM_DEVICE_WIFI (priv->nm_device)); while ((child = gtk_widget_get_first_child (priv->network_list)) != NULL) gtk_list_box_remove (GTK_LIST_BOX (priv->network_list), child); aps = nm_device_wifi_get_access_points (NM_DEVICE_WIFI (priv->nm_device)); enabled = nm_client_wireless_get_enabled (priv->nm_client); if (aps == NULL || aps->len == 0) { gboolean hw_enabled; hw_enabled = nm_client_wireless_hardware_get_enabled (priv->nm_client); if (!enabled || !hw_enabled) { gtk_label_set_text (GTK_LABEL (priv->no_network_label), _("Wireless networking is disabled")); gtk_widget_show (priv->no_network_label); gtk_widget_hide (priv->no_network_spinner); gtk_widget_set_visible (priv->turn_on_label, hw_enabled); gtk_widget_set_visible (priv->turn_on_switch, hw_enabled); } else { gtk_label_set_text (GTK_LABEL (priv->no_network_label), _("Checking for available wireless networks")); gtk_widget_show (priv->no_network_spinner); gtk_widget_show (priv->no_network_label); gtk_widget_hide (priv->turn_on_label); gtk_widget_hide (priv->turn_on_switch); } gtk_widget_hide (priv->network_list); goto out; } else { gtk_widget_hide (priv->no_network_spinner); gtk_widget_hide (priv->no_network_label); gtk_widget_hide (priv->turn_on_label); gtk_widget_hide (priv->turn_on_switch); gtk_widget_show (priv->network_list); } unique_aps = get_strongest_unique_aps (aps); for (i = 0; i < unique_aps->len; i++) { ap = NM_ACCESS_POINT (g_ptr_array_index (unique_aps, i)); add_access_point (page, ap, active_ap); } g_ptr_array_unref (unique_aps); add_access_point_other (page); out: if (enabled) start_periodic_refresh (page); priv->refreshing = FALSE; return G_SOURCE_REMOVE; } /* Avoid repeated calls to refreshing the wireless list by making it refresh at * most once per second */ static void schedule_refresh_wireless_list (GisNetworkPage *page) { static const guint refresh_wireless_list_timeout_sec = 1; GisNetworkPagePrivate *priv = gis_network_page_get_instance_private (page); cancel_periodic_refresh (page); g_debug ("Delaying Wi-Fi list refresh (for %u sec)", refresh_wireless_list_timeout_sec); priv->refresh_timeout_id = g_timeout_add_seconds (refresh_wireless_list_timeout_sec, (GSourceFunc) refresh_wireless_list, page); } static void connection_activate_cb (GObject *object, GAsyncResult *result, gpointer user_data) { NMClient *client = NM_CLIENT (object); NMActiveConnection *connection = NULL; g_autoptr(GError) error = NULL; connection = nm_client_activate_connection_finish (client, result, &error); if (connection != NULL) { g_clear_object (&connection); } else { /* failed to activate */ g_warning ("Failed to activate a connection: %s", error->message); } } static void connection_add_activate_cb (GObject *object, GAsyncResult *result, gpointer user_data) { NMClient *client = NM_CLIENT (object); NMActiveConnection *connection = NULL; g_autoptr(GError) error = NULL; connection = nm_client_add_and_activate_connection_finish (client, result, &error); if (connection != NULL) { g_clear_object (&connection); } else { /* failed to activate */ g_warning ("Failed to add and activate a connection: %s", error->message); } } static void connect_to_hidden_network (GisNetworkPage *page) { GisNetworkPagePrivate *priv = gis_network_page_get_instance_private (page); GtkRoot *root = gtk_widget_get_root (GTK_WIDGET (page)); cc_network_panel_connect_to_hidden_network (GTK_WIDGET (root), priv->nm_client); } static void row_activated (GtkListBox *box, GtkListBoxRow *row, GisNetworkPage *page) { GisNetworkPagePrivate *priv = gis_network_page_get_instance_private (page); gchar *object_path; const GPtrArray *list; GPtrArray *filtered; NMConnection *connection; NMConnection *connection_to_activate; NMSettingWireless *setting; GBytes *ssid; GBytes *ssid_target; GtkWidget *child; int i; if (priv->refreshing) return; child = gtk_list_box_row_get_child (row); object_path = g_object_get_data (G_OBJECT (child), "object-path"); ssid_target = g_object_get_data (G_OBJECT (child), "ssid"); if (g_strcmp0 (object_path, "ap-other...") == 0) { connect_to_hidden_network (page); goto out; } list = nm_client_get_connections (priv->nm_client); filtered = nm_device_filter_connections (priv->nm_device, list); connection_to_activate = NULL; for (i = 0; i < filtered->len; i++) { connection = NM_CONNECTION (filtered->pdata[i]); setting = nm_connection_get_setting_wireless (connection); if (!NM_IS_SETTING_WIRELESS (setting)) continue; ssid = nm_setting_wireless_get_ssid (setting); if (ssid == NULL) continue; if (nm_utils_same_ssid (g_bytes_get_data (ssid, NULL), g_bytes_get_size (ssid), g_bytes_get_data (ssid_target, NULL), g_bytes_get_size (ssid_target), TRUE)) { connection_to_activate = connection; break; } } g_ptr_array_unref (filtered); if (connection_to_activate != NULL) { nm_client_activate_connection_async (priv->nm_client, connection_to_activate, priv->nm_device, NULL, NULL, connection_activate_cb, page); return; } nm_client_add_and_activate_connection_async (priv->nm_client, NULL, priv->nm_device, object_path, NULL, connection_add_activate_cb, page); out: schedule_refresh_wireless_list (page); } static void connection_state_changed (NMActiveConnection *c, GParamSpec *pspec, GisNetworkPage *page) { schedule_refresh_wireless_list (page); } static void active_connections_changed (NMClient *client, GParamSpec *pspec, GisNetworkPage *page) { const GPtrArray *connections; guint i; connections = nm_client_get_active_connections (client); for (i = 0; connections && (i < connections->len); i++) { NMActiveConnection *connection; connection = g_ptr_array_index (connections, i); if (!g_object_get_data (G_OBJECT (connection), "has-state-changed-handler")) { g_signal_connect (connection, "notify::state", G_CALLBACK (connection_state_changed), page); g_object_set_data (G_OBJECT (connection), "has-state-changed-handler", GINT_TO_POINTER (1)); } } } static void sync_complete (GisNetworkPage *page) { GisNetworkPagePrivate *priv = gis_network_page_get_instance_private (page); gboolean has_device; gboolean activated; gboolean visible; has_device = priv->nm_device != NULL; activated = priv->nm_device != NULL && nm_device_get_state (priv->nm_device) == NM_DEVICE_STATE_ACTIVATED; if (priv->ever_shown) { visible = TRUE; } else if (!has_device) { g_debug ("No network device found, hiding network page"); visible = FALSE; } else if (activated) { g_debug ("Activated network device found, hiding network page"); visible = FALSE; } else { visible = TRUE; } gis_page_set_complete (GIS_PAGE (page), activated); gtk_widget_set_visible (GTK_WIDGET (page), visible); if (has_device) schedule_refresh_wireless_list (page); } static void device_state_changed (GObject *object, GParamSpec *param, GisNetworkPage *page) { sync_complete (page); } static void find_best_device (GisNetworkPage *page) { GisNetworkPagePrivate *priv = gis_network_page_get_instance_private (page); const GPtrArray *devices; guint i; /* FIXME: deal with multiple devices and devices being removed */ if (priv->nm_device != NULL) { g_debug ("Already showing network device %s", nm_device_get_description (priv->nm_device)); return; } devices = nm_client_get_devices (priv->nm_client); g_return_if_fail (devices != NULL); for (i = 0; i < devices->len; i++) { NMDevice *device = g_ptr_array_index (devices, i); if (!nm_device_get_managed (device)) continue; if (nm_device_get_device_type (device) == NM_DEVICE_TYPE_WIFI) { /* FIXME deal with multiple, dynamic devices */ priv->nm_device = g_object_ref (device); g_debug ("Showing network device %s", nm_device_get_description (priv->nm_device)); g_signal_connect (priv->nm_device, "notify::state", G_CALLBACK (device_state_changed), page); g_signal_connect (priv->nm_client, "notify::active-connections", G_CALLBACK (active_connections_changed), page); break; } } sync_complete (page); } static void device_notify_managed (NMDevice *device, GParamSpec *param, void *user_data) { GisNetworkPage *page = GIS_NETWORK_PAGE (user_data); find_best_device (page); } static void client_device_added (NMClient *client, NMDevice *device, void *user_data) { GisNetworkPage *page = GIS_NETWORK_PAGE (user_data); g_signal_connect_object (device, "notify::managed", G_CALLBACK (device_notify_managed), G_OBJECT (page), 0); find_best_device (page); } static void client_device_removed (NMClient *client, NMDevice *device, void *user_data) { GisNetworkPage *page = GIS_NETWORK_PAGE (user_data); /* TODO: reset page if priv->nm_device == device */ g_signal_handlers_disconnect_by_func (device, device_notify_managed, page); find_best_device (page); } static void monitor_network_devices (GisNetworkPage *page) { GisNetworkPagePrivate *priv = gis_network_page_get_instance_private (page); const GPtrArray *devices; guint i; g_assert (priv->nm_client != NULL); devices = nm_client_get_devices (priv->nm_client); g_return_if_fail (devices != NULL); for (i = 0; devices != NULL && i < devices->len; i++) { NMDevice *device = g_ptr_array_index (devices, i); g_signal_connect_object (device, "notify::managed", G_CALLBACK (device_notify_managed), G_OBJECT (page), 0); } g_signal_connect_object (priv->nm_client, "device-added", G_CALLBACK (client_device_added), G_OBJECT (page), 0); g_signal_connect_object (priv->nm_client, "device-removed", G_CALLBACK (client_device_removed), G_OBJECT (page), 0); find_best_device (page); } static void gis_network_page_constructed (GObject *object) { GisNetworkPage *page = GIS_NETWORK_PAGE (object); GisNetworkPagePrivate *priv = gis_network_page_get_instance_private (page); g_autoptr(GError) error = NULL; G_OBJECT_CLASS (gis_network_page_parent_class)->constructed (object); gis_page_set_skippable (GIS_PAGE (page), TRUE); priv->ever_shown = g_getenv ("GIS_ALWAYS_SHOW_NETWORK_PAGE") != NULL; priv->icons = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL); gtk_list_box_set_selection_mode (GTK_LIST_BOX (priv->network_list), GTK_SELECTION_NONE); gtk_list_box_set_sort_func (GTK_LIST_BOX (priv->network_list), ap_sort, NULL, NULL); g_signal_connect (priv->network_list, "row-activated", G_CALLBACK (row_activated), page); priv->nm_client = nm_client_new (NULL, &error); if (!priv->nm_client) { g_warning ("Can't create NetworkManager client, hiding network page: %s", error->message); sync_complete (page); return; } g_object_bind_property (priv->nm_client, "wireless-enabled", priv->turn_on_switch, "active", G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE); monitor_network_devices (page); } static void gis_network_page_dispose (GObject *object) { GisNetworkPage *page = GIS_NETWORK_PAGE (object); GisNetworkPagePrivate *priv = gis_network_page_get_instance_private (page); g_clear_object (&priv->nm_client); g_clear_object (&priv->nm_device); g_clear_object (&priv->icons); cancel_periodic_refresh (page); G_OBJECT_CLASS (gis_network_page_parent_class)->dispose (object); } static void gis_network_page_locale_changed (GisPage *page) { gis_page_set_title (GIS_PAGE (page), _("Network")); } static void gis_network_page_shown (GisPage *page) { GisNetworkPagePrivate *priv = gis_network_page_get_instance_private (GIS_NETWORK_PAGE (page)); priv->ever_shown = TRUE; } static void gis_network_page_class_init (GisNetworkPageClass *klass) { GisPageClass *page_class = GIS_PAGE_CLASS (klass); GObjectClass *object_class = G_OBJECT_CLASS (klass); gtk_widget_class_set_template_from_resource (GTK_WIDGET_CLASS (klass), "/org/gnome/initial-setup/gis-network-page.ui"); gtk_widget_class_bind_template_child_private (GTK_WIDGET_CLASS (klass), GisNetworkPage, network_list); gtk_widget_class_bind_template_child_private (GTK_WIDGET_CLASS (klass), GisNetworkPage, no_network_label); gtk_widget_class_bind_template_child_private (GTK_WIDGET_CLASS (klass), GisNetworkPage, no_network_spinner); gtk_widget_class_bind_template_child_private (GTK_WIDGET_CLASS (klass), GisNetworkPage, turn_on_label); gtk_widget_class_bind_template_child_private (GTK_WIDGET_CLASS (klass), GisNetworkPage, turn_on_switch); page_class->page_id = PAGE_ID; page_class->locale_changed = gis_network_page_locale_changed; page_class->shown = gis_network_page_shown; object_class->constructed = gis_network_page_constructed; object_class->dispose = gis_network_page_dispose; } static void gis_network_page_init (GisNetworkPage *page) { g_resources_register (network_get_resource ()); g_type_ensure (GIS_TYPE_PAGE_HEADER); gtk_widget_init_template (GTK_WIDGET (page)); } GisPage * gis_prepare_network_page (GisDriver *driver) { return g_object_new (GIS_TYPE_NETWORK_PAGE, "driver", driver, NULL); }