summaryrefslogtreecommitdiffstats
path: root/panels/network/cc-wifi-connection-list.c
diff options
context:
space:
mode:
Diffstat (limited to 'panels/network/cc-wifi-connection-list.c')
-rw-r--r--panels/network/cc-wifi-connection-list.c799
1 files changed, 799 insertions, 0 deletions
diff --git a/panels/network/cc-wifi-connection-list.c b/panels/network/cc-wifi-connection-list.c
new file mode 100644
index 0000000..6a11d20
--- /dev/null
+++ b/panels/network/cc-wifi-connection-list.c
@@ -0,0 +1,799 @@
+/*
+ * Copyright © 2018 Red Hat Inc.
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+#include "cc-wifi-connection-list.h"
+#include "cc-wifi-connection-row.h"
+
+struct _CcWifiConnectionList
+{
+ AdwBin parent_instance;
+
+ GtkListBox *listbox;
+
+ guint freeze_count;
+ gboolean updating;
+
+ gboolean checkable;
+ gboolean hide_unavailable;
+ gboolean show_aps;
+
+ NMClient *client;
+ NMDeviceWifi *device;
+
+ NMConnection *last_active;
+
+ GPtrArray *connections;
+ GPtrArray *connections_row;
+
+ /* AP SSID cache stores the APs SSID used for assigning it to a row.
+ * This is necessary to efficiently remove it when its SSID changes.
+ *
+ * Note that we only group APs that cannot be assigned to a connection
+ * by the SSID. In principle this is wrong, because other attributes may
+ * be different rendering them separate networks.
+ * In practice this will almost never happen, and if it does, we just
+ * show and select the strongest AP.
+ */
+ GHashTable *ap_ssid_cache;
+ GHashTable *ssid_to_row;
+};
+
+static void on_device_ap_added_cb (CcWifiConnectionList *self,
+ NMAccessPoint *ap,
+ NMDeviceWifi *device);
+static void on_device_ap_removed_cb (CcWifiConnectionList *self,
+ NMAccessPoint *ap,
+ NMDeviceWifi *device);
+static void on_row_configured_cb (CcWifiConnectionList *self,
+ CcWifiConnectionRow *row);
+
+G_DEFINE_TYPE (CcWifiConnectionList, cc_wifi_connection_list, ADW_TYPE_BIN)
+
+enum
+{
+ PROP_0,
+ PROP_CHECKABLE,
+ PROP_HIDE_UNAVAILABLE,
+ PROP_SHOW_APS,
+ PROP_CLIENT,
+ PROP_DEVICE,
+ PROP_LAST
+};
+
+static GParamSpec *props [PROP_LAST];
+
+static GBytes*
+new_hashable_ssid (GBytes *ssid)
+{
+ GBytes *res;
+ const guint8 *data;
+ gsize size;
+
+ /* This is what nm_utils_same_ssid does, but returning it so that we can
+ * use the result in other ways (i.e. hash table lookups). */
+ data = g_bytes_get_data ((GBytes*) ssid, &size);
+ if (data[size-1] == '\0')
+ size -= 1;
+ res = g_bytes_new (data, size);
+
+ return res;
+}
+
+static gboolean
+connection_ignored (NMConnection *connection)
+{
+ NMSettingWireless *sw;
+
+ /* Ignore AP and adhoc modes (i.e. accept infrastructure or empty) */
+ sw = nm_connection_get_setting_wireless (connection);
+ if (!sw)
+ return TRUE;
+ if (g_strcmp0 (nm_setting_wireless_get_mode (sw), "adhoc") == 0 ||
+ g_strcmp0 (nm_setting_wireless_get_mode (sw), "ap") == 0)
+ {
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static CcWifiConnectionRow*
+cc_wifi_connection_list_row_add (CcWifiConnectionList *self,
+ NMConnection *connection,
+ NMAccessPoint *ap,
+ gboolean known_connection)
+{
+ CcWifiConnectionRow *res;
+ g_autoptr(GPtrArray) aps = NULL;
+
+ if (ap)
+ {
+ aps = g_ptr_array_new ();
+ g_ptr_array_add (aps, ap);
+ }
+
+ res = cc_wifi_connection_row_new (self->device,
+ connection,
+ aps,
+ self->checkable,
+ known_connection);
+ gtk_list_box_append (self->listbox, GTK_WIDGET (res));
+
+ g_signal_connect_object (res, "configure", G_CALLBACK (on_row_configured_cb), self, G_CONNECT_SWAPPED);
+
+ g_signal_emit_by_name (self, "add-row", res);
+
+ return res;
+}
+
+static void
+clear_widget (CcWifiConnectionList *self)
+{
+ const GPtrArray *aps;
+ GHashTableIter iter;
+ CcWifiConnectionRow *row;
+ gint i;
+
+ /* Clear everything; disconnect all AP signals first */
+ aps = nm_device_wifi_get_access_points (self->device);
+ for (i = 0; i < aps->len; i++)
+ g_signal_handlers_disconnect_by_data (g_ptr_array_index (aps, i), self);
+
+ /* Remove all AP only rows */
+ g_hash_table_iter_init (&iter, self->ssid_to_row);
+ while (g_hash_table_iter_next (&iter, NULL, (gpointer*) &row))
+ {
+ g_hash_table_iter_remove (&iter);
+ g_signal_emit_by_name (self, "remove-row", row);
+ gtk_list_box_remove (self->listbox, GTK_WIDGET (row));
+ }
+
+ /* Remove all connection rows */
+ for (i = 0; i < self->connections_row->len; i++)
+ {
+ if (!g_ptr_array_index (self->connections_row, i))
+ continue;
+
+ row = g_ptr_array_index (self->connections_row, i);
+ g_ptr_array_index (self->connections_row, i) = NULL;
+ g_signal_emit_by_name (self, "remove-row", row);
+ gtk_list_box_remove (self->listbox, GTK_WIDGET (row));
+ }
+
+ /* Reset the internal state */
+ g_ptr_array_set_size (self->connections, 0);
+ g_ptr_array_set_size (self->connections_row, 0);
+ g_hash_table_remove_all (self->ssid_to_row);
+ g_hash_table_remove_all (self->ap_ssid_cache);
+}
+
+static void
+update_connections (CcWifiConnectionList *self)
+{
+ const GPtrArray *aps;
+ const GPtrArray *acs_client;
+ g_autoptr(GPtrArray) acs = NULL;
+ NMActiveConnection *ac;
+ NMConnection *ac_con = NULL;
+ gint i;
+
+ /* We don't want full UI rebuilds during some UI interactions, so allow freezing the list. */
+ if (self->freeze_count > 0)
+ return;
+
+ /* Prevent recursion (maybe move this into an idle handler instead?) */
+ if (self->updating)
+ return;
+ self->updating = TRUE;
+
+ clear_widget (self);
+
+ /* Copy the new connections; also create a row if we show unavailable
+ * connections */
+ acs_client = nm_client_get_connections (self->client);
+
+ acs = g_ptr_array_new_full (acs_client->len + 1, NULL);
+ for (i = 0; i < acs_client->len; i++)
+ g_ptr_array_add (acs, g_ptr_array_index (acs_client, i));
+
+ ac = nm_device_get_active_connection (NM_DEVICE (self->device));
+ if (ac)
+ ac_con = NM_CONNECTION (nm_active_connection_get_connection (ac));
+
+ if (ac_con && !g_ptr_array_find (acs, ac_con, NULL))
+ {
+ g_debug ("Adding remote connection for active connection");
+ g_ptr_array_add (acs, g_object_ref (ac_con));
+ }
+
+ for (i = 0; i < acs->len; i++)
+ {
+ NMConnection *con;
+
+ con = g_ptr_array_index (acs, i);
+ if (connection_ignored (con))
+ continue;
+
+ g_ptr_array_add (self->connections, g_object_ref (con));
+ if (self->hide_unavailable && con != ac_con)
+ g_ptr_array_add (self->connections_row, NULL);
+ else
+ g_ptr_array_add (self->connections_row,
+ cc_wifi_connection_list_row_add (self, con,
+ NULL, TRUE));
+ }
+
+ /* Coldplug all known APs again */
+ aps = nm_device_wifi_get_access_points (self->device);
+ for (i = 0; i < aps->len; i++)
+ on_device_ap_added_cb (self, g_ptr_array_index (aps, i), self->device);
+
+ self->updating = FALSE;
+}
+
+static void
+on_row_configured_cb (CcWifiConnectionList *self, CcWifiConnectionRow *row)
+{
+ g_signal_emit_by_name (self, "configure", row);
+}
+
+static void
+on_access_point_property_changed (CcWifiConnectionList *self,
+ GParamSpec *pspec,
+ NMAccessPoint *ap)
+{
+ CcWifiConnectionRow *row;
+ GBytes *ssid;
+ gboolean has_connection = FALSE;
+ gint i;
+
+ /* If the SSID changed then the AP needs to be added/removed from rows.
+ * Do this by simulating an AP addition/removal. */
+ if (g_str_equal (pspec->name, NM_ACCESS_POINT_SSID))
+ {
+ g_debug ("Simulating add/remove for SSID change");
+ on_device_ap_removed_cb (self, ap, self->device);
+ on_device_ap_added_cb (self, ap, self->device);
+ return;
+ }
+
+ /* Otherwise, find all rows that contain the AP and update it. Do this by
+ * first searching all rows with connections, and then looking it up in the
+ * SSID rows if not found. */
+ for (i = 0; i < self->connections_row->len; i++)
+ {
+ row = g_ptr_array_index (self->connections_row, i);
+ if (row && cc_wifi_connection_row_has_access_point (row, ap))
+ {
+ cc_wifi_connection_row_update (row);
+ has_connection = TRUE;
+ }
+ }
+
+ if (!self->show_aps || has_connection)
+ return;
+
+ ssid = g_hash_table_lookup (self->ap_ssid_cache, ap);
+ if (!ssid)
+ return;
+
+ row = g_hash_table_lookup (self->ssid_to_row, ssid);
+ if (!row)
+ g_assert_not_reached ();
+ else
+ cc_wifi_connection_row_update (row);
+}
+
+static void
+on_device_ap_added_cb (CcWifiConnectionList *self,
+ NMAccessPoint *ap,
+ NMDeviceWifi *device)
+{
+ g_autoptr(GPtrArray) connections = NULL;
+ NM80211ApSecurityFlags rsn_flags;
+ CcWifiConnectionRow *row;
+ GBytes *ap_ssid;
+ g_autoptr(GBytes) ssid = NULL;
+ guint i, j;
+
+ g_signal_connect_object (ap, "notify",
+ G_CALLBACK (on_access_point_property_changed),
+ self, G_CONNECT_SWAPPED);
+
+ connections = nm_access_point_filter_connections (ap, self->connections);
+
+ /* If this is the active AP, then add the active connection to the list. This
+ * is a workaround because nm_access_pointer_filter_connections() will not
+ * include it otherwise.
+ * So it seems like the dummy AP entry that NM creates internally is not actually
+ * compatible with the connection that is being activated.
+ */
+ if (ap == nm_device_wifi_get_active_access_point (device))
+ {
+ NMActiveConnection *ac;
+ NMConnection *ac_con;
+
+ ac = nm_device_get_active_connection (NM_DEVICE (self->device));
+
+ if (ac)
+ {
+ guint idx;
+
+ ac_con = NM_CONNECTION (nm_active_connection_get_connection (ac));
+
+ if (!g_ptr_array_find (connections, ac_con, NULL) &&
+ g_ptr_array_find (self->connections, ac_con, &idx))
+ {
+ g_debug ("Adding active connection to list of valid connections for AP");
+ g_ptr_array_add (connections, g_object_ref (ac_con));
+ }
+ }
+ }
+
+ /* Add the AP to all connection related rows, creating the row if neccessary. */
+ for (i = 0; i < connections->len; i++)
+ {
+ gboolean found = g_ptr_array_find (self->connections, g_ptr_array_index (connections, i), &j);
+
+ g_assert (found);
+
+ row = g_ptr_array_index (self->connections_row, j);
+ if (!row)
+ row = cc_wifi_connection_list_row_add (self, g_ptr_array_index (connections, i), NULL, TRUE);
+ cc_wifi_connection_row_add_access_point (row, ap);
+ g_ptr_array_index (self->connections_row, j) = row;
+ }
+
+ if (connections->len > 0)
+ return;
+
+ if (!self->show_aps)
+ return;
+
+ /* The AP is not compatible to any known connection, generate an entry for the
+ * SSID or add to existing one. However, not for hidden APs that don't have an SSID
+ * or a hidden OWE transition network.
+ */
+ ap_ssid = nm_access_point_get_ssid (ap);
+ if (ap_ssid == NULL)
+ return;
+
+ /* Skip OWE-TM network with OWE RSN */
+ rsn_flags = nm_access_point_get_rsn_flags (ap);
+ if (rsn_flags & NM_802_11_AP_SEC_KEY_MGMT_OWE && rsn_flags & NM_802_11_AP_SEC_KEY_MGMT_OWE_TM)
+ return;
+
+ ssid = new_hashable_ssid (ap_ssid);
+
+ g_hash_table_insert (self->ap_ssid_cache, ap, g_bytes_ref (ssid));
+
+ row = g_hash_table_lookup (self->ssid_to_row, ssid);
+ if (!row)
+ {
+ row = cc_wifi_connection_list_row_add (self, NULL, ap, FALSE);
+
+ g_hash_table_insert (self->ssid_to_row, g_bytes_ref (ssid), row);
+ }
+ else
+ {
+ cc_wifi_connection_row_add_access_point (row, ap);
+ }
+}
+
+static void
+on_device_ap_removed_cb (CcWifiConnectionList *self,
+ NMAccessPoint *ap,
+ NMDeviceWifi *device)
+{
+ CcWifiConnectionRow *row;
+ g_autoptr(GBytes) ssid = NULL;
+ gboolean found = FALSE;
+ gint i;
+
+ g_signal_handlers_disconnect_by_data (ap, self);
+
+ /* Find any connection related row with the AP and remove the AP from it. Remove the
+ * row if it was the last AP and we are hiding unavailable connections. */
+ for (i = 0; i < self->connections_row->len; i++)
+ {
+ row = g_ptr_array_index (self->connections_row, i);
+ if (row && cc_wifi_connection_row_remove_access_point (row, ap))
+ {
+ found = TRUE;
+
+ if (self->hide_unavailable)
+ {
+ g_ptr_array_index (self->connections_row, i) = NULL;
+ g_signal_emit_by_name (self, "remove-row", row);
+ gtk_list_box_remove (self->listbox, GTK_WIDGET (row));
+ }
+ }
+ }
+
+ if (found || !self->show_aps)
+ return;
+
+ /* If the AP was inserted into a row without a connection, then we will get an
+ * SSID for it here. */
+ g_hash_table_steal_extended (self->ap_ssid_cache, ap, NULL, (gpointer*) &ssid);
+ if (!ssid)
+ return;
+
+ /* And we can update the row (possibly removing it) */
+ row = g_hash_table_lookup (self->ssid_to_row, ssid);
+ g_assert (row != NULL);
+
+ if (cc_wifi_connection_row_remove_access_point (row, ap))
+ {
+ g_hash_table_remove (self->ssid_to_row, ssid);
+ g_signal_emit_by_name (self, "remove-row", row);
+ gtk_list_box_remove (self->listbox, GTK_WIDGET (row));
+ }
+}
+
+static void
+on_client_connection_added_cb (CcWifiConnectionList *self,
+ NMConnection *connection,
+ NMClient *client)
+{
+ if (!nm_device_connection_compatible (NM_DEVICE (self->device), connection, NULL))
+ return;
+
+ if (connection_ignored (connection))
+ return;
+
+ /* The approach we take to handle connection changes is to do a full rebuild.
+ * It happens seldom enough to make this feasible.
+ */
+ update_connections (self);
+}
+
+static void
+on_client_connection_removed_cb (CcWifiConnectionList *self,
+ NMConnection *connection,
+ NMClient *client)
+{
+ if (!g_ptr_array_find (self->connections, connection, NULL))
+ return;
+
+ /* The approach we take to handle connection changes is to do a full rebuild.
+ * It happens seldom enough to make this feasible.
+ */
+ update_connections (self);
+}
+
+static void
+on_device_state_changed_cb (CcWifiConnectionList *self,
+ GParamSpec *pspec,
+ NMDeviceWifi *device)
+{
+ NMActiveConnection *ac;
+ NMConnection *connection = NULL;
+ guint idx;
+
+ ac = nm_device_get_active_connection (NM_DEVICE (self->device));
+ if (ac)
+ connection = NM_CONNECTION (nm_active_connection_get_connection (ac));
+
+ /* Just update the corresponding row if the AC is still the same. */
+ if (self->last_active == connection &&
+ g_ptr_array_find (self->connections, connection, &idx) &&
+ g_ptr_array_index (self->connections_row, idx))
+ {
+ cc_wifi_connection_row_update (g_ptr_array_index (self->connections_row, idx));
+ return;
+ }
+
+ /* Give up and do a full update. */
+ update_connections (self);
+ self->last_active = connection;
+}
+
+static void
+on_device_active_ap_changed_cb (CcWifiConnectionList *self,
+ GParamSpec *pspec,
+ NMDeviceWifi *device)
+{
+ NMAccessPoint *ap;
+ /* We need to make sure the active AP is grouped with the active connection.
+ * Do so by simply removing and adding it.
+ *
+ * This is necessary because the AP is added before this property
+ * is updated. */
+ ap = nm_device_wifi_get_active_access_point (self->device);
+ if (ap)
+ {
+ g_debug ("Simulating add/remove for active AP change");
+ on_device_ap_removed_cb (self, ap, self->device);
+ on_device_ap_added_cb (self, ap, self->device);
+ }
+}
+
+static void
+cc_wifi_connection_list_dispose (GObject *object)
+{
+ CcWifiConnectionList *self = (CcWifiConnectionList *)object;
+
+ /* Prevent any further updates; clear_widget must not indirectly recurse
+ * through updates_connections */
+ self->updating = TRUE;
+
+ /* Drop all external references */
+ clear_widget (self);
+
+ G_OBJECT_CLASS (cc_wifi_connection_list_parent_class)->dispose (object);
+}
+
+static void
+cc_wifi_connection_list_finalize (GObject *object)
+{
+ CcWifiConnectionList *self = (CcWifiConnectionList *)object;
+
+ g_clear_object (&self->client);
+ g_clear_object (&self->device);
+
+ g_clear_pointer (&self->connections, g_ptr_array_unref);
+ g_clear_pointer (&self->connections_row, g_ptr_array_unref);
+ g_clear_pointer (&self->ssid_to_row, g_hash_table_unref);
+ g_clear_pointer (&self->ap_ssid_cache, g_hash_table_unref);
+
+ G_OBJECT_CLASS (cc_wifi_connection_list_parent_class)->finalize (object);
+}
+
+static void
+cc_wifi_connection_list_constructed (GObject *object)
+{
+ CcWifiConnectionList *self = (CcWifiConnectionList *)object;
+
+ G_OBJECT_CLASS (cc_wifi_connection_list_parent_class)->constructed (object);
+
+ g_assert (self->client);
+ g_assert (self->device);
+
+ g_signal_connect_object (self->client, "connection-added",
+ G_CALLBACK (on_client_connection_added_cb),
+ self, G_CONNECT_SWAPPED);
+ g_signal_connect_object (self->client, "connection-removed",
+ G_CALLBACK (on_client_connection_removed_cb),
+ self, G_CONNECT_SWAPPED);
+
+ g_signal_connect_object (self->device, "access-point-added",
+ G_CALLBACK (on_device_ap_added_cb),
+ self, G_CONNECT_SWAPPED);
+ g_signal_connect_object (self->device, "access-point-removed",
+ G_CALLBACK (on_device_ap_removed_cb),
+ self, G_CONNECT_SWAPPED);
+
+ g_signal_connect_object (self->device, "notify::state",
+ G_CALLBACK (on_device_state_changed_cb),
+ self, G_CONNECT_SWAPPED);
+ g_signal_connect_object (self->device, "notify::active-connection",
+ G_CALLBACK (on_device_state_changed_cb),
+ self, G_CONNECT_SWAPPED);
+ g_signal_connect_object (self->device, "notify::active-access-point",
+ G_CALLBACK (on_device_active_ap_changed_cb),
+ self, G_CONNECT_SWAPPED);
+ on_device_state_changed_cb (self, NULL, self->device);
+
+ /* Simulate a change notification on the available connections.
+ * This uses the implementation detail that the list is rebuild
+ * completely in this case. */
+ update_connections (self);
+}
+
+static void
+cc_wifi_connection_list_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ CcWifiConnectionList *self = CC_WIFI_CONNECTION_LIST (object);
+
+ switch (prop_id)
+ {
+ case PROP_CHECKABLE:
+ g_value_set_boolean (value, self->checkable);
+ break;
+
+ case PROP_HIDE_UNAVAILABLE:
+ g_value_set_boolean (value, self->hide_unavailable);
+ break;
+
+ case PROP_SHOW_APS:
+ g_value_set_boolean (value, self->show_aps);
+ break;
+
+ case PROP_CLIENT:
+ g_value_set_object (value, self->client);
+ break;
+
+ case PROP_DEVICE:
+ g_value_set_object (value, self->device);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+cc_wifi_connection_list_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ CcWifiConnectionList *self = CC_WIFI_CONNECTION_LIST (object);
+
+ switch (prop_id)
+ {
+ case PROP_CHECKABLE:
+ self->checkable = g_value_get_boolean (value);
+ break;
+
+ case PROP_HIDE_UNAVAILABLE:
+ self->hide_unavailable = g_value_get_boolean (value);
+ break;
+
+ case PROP_SHOW_APS:
+ self->show_aps = g_value_get_boolean (value);
+ break;
+
+ case PROP_CLIENT:
+ self->client = g_value_dup_object (value);
+ break;
+
+ case PROP_DEVICE:
+ self->device = g_value_dup_object (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+cc_wifi_connection_list_class_init (CcWifiConnectionListClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->constructed = cc_wifi_connection_list_constructed;
+ object_class->dispose = cc_wifi_connection_list_dispose;
+ object_class->finalize = cc_wifi_connection_list_finalize;
+ object_class->get_property = cc_wifi_connection_list_get_property;
+ object_class->set_property = cc_wifi_connection_list_set_property;
+
+ props[PROP_CHECKABLE] =
+ g_param_spec_boolean ("checkable", "checkable",
+ "Passed to the created rows to show/hide the checkbox for deletion",
+ FALSE,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
+
+ props[PROP_HIDE_UNAVAILABLE] =
+ g_param_spec_boolean ("hide-unavailable", "HideUnavailable",
+ "Whether to show or hide unavailable connections",
+ TRUE,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
+
+ props[PROP_SHOW_APS] =
+ g_param_spec_boolean ("show-aps", "ShowAPs",
+ "Whether to show available SSIDs/APs without a connection",
+ TRUE,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
+
+ props[PROP_CLIENT] =
+ g_param_spec_object ("client", "NMClient",
+ "The NM Client",
+ NM_TYPE_CLIENT,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
+
+ props[PROP_DEVICE] =
+ g_param_spec_object ("device", "WiFi Device",
+ "The WiFi Device for this connection list",
+ NM_TYPE_DEVICE_WIFI,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
+
+ g_object_class_install_properties (object_class,
+ PROP_LAST,
+ props);
+
+ g_signal_new ("configure",
+ CC_TYPE_WIFI_CONNECTION_LIST,
+ G_SIGNAL_RUN_LAST,
+ 0, NULL, NULL, NULL,
+ G_TYPE_NONE, 1, CC_TYPE_WIFI_CONNECTION_ROW);
+ g_signal_new ("add-row",
+ CC_TYPE_WIFI_CONNECTION_LIST,
+ G_SIGNAL_RUN_LAST,
+ 0, NULL, NULL, NULL,
+ G_TYPE_NONE, 1, CC_TYPE_WIFI_CONNECTION_ROW);
+ g_signal_new ("remove-row",
+ CC_TYPE_WIFI_CONNECTION_LIST,
+ G_SIGNAL_RUN_LAST,
+ 0, NULL, NULL, NULL,
+ G_TYPE_NONE, 1, CC_TYPE_WIFI_CONNECTION_ROW);
+}
+
+static void
+cc_wifi_connection_list_init (CcWifiConnectionList *self)
+{
+ self->listbox = GTK_LIST_BOX (gtk_list_box_new ());
+ gtk_list_box_set_selection_mode (GTK_LIST_BOX (self->listbox), GTK_SELECTION_NONE);
+ gtk_widget_set_valign (GTK_WIDGET (self->listbox), GTK_ALIGN_START);
+ gtk_widget_add_css_class (GTK_WIDGET (self->listbox), "boxed-list");
+ adw_bin_set_child (ADW_BIN (self), GTK_WIDGET (self->listbox));
+
+ self->hide_unavailable = TRUE;
+ self->show_aps = TRUE;
+
+ self->connections = g_ptr_array_new_with_free_func (g_object_unref);
+ self->connections_row = g_ptr_array_new ();
+ self->ssid_to_row = g_hash_table_new_full (g_bytes_hash, g_bytes_equal,
+ (GDestroyNotify) g_bytes_unref, NULL);
+ self->ap_ssid_cache = g_hash_table_new_full (g_direct_hash, g_direct_equal,
+ NULL, (GDestroyNotify) g_bytes_unref);
+}
+
+CcWifiConnectionList *
+cc_wifi_connection_list_new (NMClient *client,
+ NMDeviceWifi *device,
+ gboolean hide_unavailable,
+ gboolean show_aps,
+ gboolean checkable)
+{
+ return g_object_new (CC_TYPE_WIFI_CONNECTION_LIST,
+ "client", client,
+ "device", device,
+ "hide-unavailable", hide_unavailable,
+ "show-aps", show_aps,
+ "checkable", checkable,
+ NULL);
+}
+
+void
+cc_wifi_connection_list_freeze (CcWifiConnectionList *self)
+{
+ g_return_if_fail (CC_WIFI_CONNECTION_LIST (self));
+
+ if (self->freeze_count == 0)
+ g_debug ("wifi connection list has been frozen");
+
+ self->freeze_count += 1;
+}
+
+void
+cc_wifi_connection_list_thaw (CcWifiConnectionList *self)
+{
+ g_return_if_fail (CC_WIFI_CONNECTION_LIST (self));
+
+ g_return_if_fail (self->freeze_count > 0);
+
+ self->freeze_count -= 1;
+
+ if (self->freeze_count == 0)
+ {
+ g_debug ("wifi connection list has been thawed");
+ update_connections (self);
+ }
+}
+
+GtkListBox *
+cc_wifi_connection_list_get_list_box (CcWifiConnectionList *self)
+{
+ g_return_val_if_fail (CC_IS_WIFI_CONNECTION_LIST (self), NULL);
+
+ return self->listbox;
+}