diff options
Diffstat (limited to 'panels/network/net-device-wifi.c')
-rw-r--r-- | panels/network/net-device-wifi.c | 1303 |
1 files changed, 1303 insertions, 0 deletions
diff --git a/panels/network/net-device-wifi.c b/panels/network/net-device-wifi.c new file mode 100644 index 0000000..28160b4 --- /dev/null +++ b/panels/network/net-device-wifi.c @@ -0,0 +1,1303 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2011-2012 Richard Hughes <richard@hughsie.com> + * + * Licensed under the GNU General Public License Version 2 + * + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" + +#include <glib-object.h> +#include <glib/gi18n.h> + +#include <netinet/ether.h> + +#include <NetworkManager.h> +#include <polkit/polkit.h> + +#include "cc-wifi-hotspot-dialog.h" +#include "hostname-helper.h" +#include "network-dialogs.h" +#include "panel-common.h" +#include "cc-list-row.h" + +#include "connection-editor/net-connection-editor.h" +#include "net-device-wifi.h" + +#include "cc-wifi-connection-list.h" +#include "cc-wifi-connection-row.h" + +#define PERIODIC_WIFI_SCAN_TIMEOUT 15 + +static void nm_device_wifi_refresh_ui (NetDeviceWifi *self); +static void show_wifi_list (NetDeviceWifi *self); +static void show_hotspot_ui (NetDeviceWifi *self); + + +struct _NetDeviceWifi +{ + AdwBin parent; + + GtkBox *center_box; + GtkListBoxRow *connect_hidden_row; + GtkSwitch *device_off_switch; + GtkBox *header_box; + GtkPopover *header_button_popover; + GtkBox *hotspot_box; + CcListRow *hotspot_name_row; + CcListRow *hotspot_security_row; + CcListRow *hotspot_password_row; + GtkBox *listbox_box; + GtkStack *stack; + GtkListBoxRow *start_hotspot_row; + GtkLabel *status_label; + GtkLabel *title_label; + + CcPanel *panel; + NMClient *client; + NMDevice *device; + gboolean updating_device; + gchar *selected_ssid_title; + gchar *selected_connection_id; + gchar *selected_ap_id; + CcWifiHotspotDialog *hotspot_dialog; + + gint64 last_scan; + gboolean scanning; + + guint monitor_scanning_id; + guint scan_id; + GCancellable *cancellable; +}; + +enum { + PROP_0, + PROP_SCANNING, + PROP_LAST, +}; + +G_DEFINE_TYPE (NetDeviceWifi, net_device_wifi, ADW_TYPE_BIN) + +static void +disable_scan_timeout (NetDeviceWifi *self) +{ + g_debug ("Disabling periodic Wi-Fi scan"); + if (self->monitor_scanning_id > 0) { + g_source_remove (self->monitor_scanning_id); + self->monitor_scanning_id = 0; + } + if (self->scan_id > 0) { + g_source_remove (self->scan_id); + self->scan_id = 0; + } +} + +static void +wireless_enabled_toggled (NetDeviceWifi *self) +{ + gboolean enabled; + + enabled = nm_client_wireless_get_enabled (self->client); + + self->updating_device = TRUE; + gtk_switch_set_active (self->device_off_switch, enabled); + if (!enabled) + disable_scan_timeout (self); + gtk_widget_set_sensitive (GTK_WIDGET (self->connect_hidden_row), enabled); + self->updating_device = FALSE; +} + +static NMConnection * +find_connection_for_device (NetDeviceWifi *self, + NMDevice *device) +{ + return net_device_get_find_connection (self->client, device); +} + +static gboolean +connection_is_shared (NMConnection *c) +{ + NMSettingIPConfig *s_ip4; + + s_ip4 = nm_connection_get_setting_ip4_config (c); + if (g_strcmp0 (nm_setting_ip_config_get_method (s_ip4), + NM_SETTING_IP4_CONFIG_METHOD_SHARED) != 0) { + return FALSE; + } + + return TRUE; +} + +static gboolean +device_is_hotspot (NetDeviceWifi *self) +{ + NMConnection *c; + + if (nm_device_get_active_connection (self->device) == NULL) + return FALSE; + + c = find_connection_for_device (self, self->device); + if (c == NULL) + return FALSE; + + return connection_is_shared (c); +} + +static GBytes * +device_get_hotspot_ssid (NetDeviceWifi *self, + NMDevice *device) +{ + NMConnection *c; + NMSettingWireless *sw; + + c = find_connection_for_device (self, device); + if (c == NULL) + return NULL; + + sw = nm_connection_get_setting_wireless (c); + return nm_setting_wireless_get_ssid (sw); +} + +static void +get_secrets_cb (GObject *source_object, + GAsyncResult *res, + gpointer data) +{ + NetDeviceWifi *self = data; + g_autoptr(GVariant) secrets = NULL; + g_autoptr(GError) error = NULL; + + secrets = nm_remote_connection_get_secrets_finish (NM_REMOTE_CONNECTION (source_object), res, &error); + if (!secrets) { + if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + g_warning ("Could not get secrets: %s", error->message); + return; + } + + nm_connection_update_secrets (NM_CONNECTION (source_object), + NM_SETTING_WIRELESS_SECURITY_SETTING_NAME, + secrets, NULL); + + nm_device_wifi_refresh_ui (self); +} + +static void +device_get_hotspot_security_details (NetDeviceWifi *self, + NMDevice *device, + gchar **secret, + gchar **security) +{ + NMConnection *c; + NMSettingWirelessSecurity *sws; + const gchar *key_mgmt; + const gchar *tmp_secret; + const gchar *tmp_security; + + c = find_connection_for_device (self, device); + if (c == NULL) + return; + + sws = nm_connection_get_setting_wireless_security (c); + if (sws == NULL) + return; + + tmp_secret = NULL; + tmp_security = C_("Wifi security", "None"); + + /* Key management values: + * "none" = WEP + * "wpa-none" = WPAv1 Ad-Hoc mode (not supported in NM >= 0.9.4) + * "wpa-psk" = WPAv2 Ad-Hoc mode (eg IBSS RSN) and AP-mode WPA v1 and v2 + */ + key_mgmt = nm_setting_wireless_security_get_key_mgmt (sws); + if (strcmp (key_mgmt, "none") == 0) { + tmp_secret = nm_setting_wireless_security_get_wep_key (sws, 0); + tmp_security = _("WEP"); + } + else if (strcmp (key_mgmt, "wpa-none") == 0 || + strcmp (key_mgmt, "wpa-psk") == 0) { + tmp_secret = nm_setting_wireless_security_get_psk (sws); + tmp_security = _("WPA"); + } else { + g_warning ("unhandled security key-mgmt: %s", key_mgmt); + } + + /* If we don't have secrets, request them from NM and bail. + * We'll refresh the UI when secrets arrive. + */ + if (tmp_secret == NULL) { + nm_remote_connection_get_secrets_async ((NMRemoteConnection*)c, + NM_SETTING_WIRELESS_SECURITY_SETTING_NAME, + self->cancellable, + get_secrets_cb, + self); + return; + } + + if (secret) + *secret = g_strdup (tmp_secret); + if (security) + *security = g_strdup (tmp_security); +} + +static void +nm_device_wifi_refresh_hotspot (NetDeviceWifi *self) +{ + GBytes *ssid; + g_autofree gchar *hotspot_secret = NULL; + g_autofree gchar *hotspot_security = NULL; + g_autofree gchar *hotspot_ssid = NULL; + + /* refresh hotspot ui */ + ssid = device_get_hotspot_ssid (self, self->device); + if (ssid) + hotspot_ssid = nm_utils_ssid_to_utf8 (g_bytes_get_data (ssid, NULL), g_bytes_get_size (ssid)); + device_get_hotspot_security_details (self, + self->device, + &hotspot_secret, + &hotspot_security); + + g_debug ("Refreshing hotspot labels to name: '%s', security key: '%s', security: '%s'", + hotspot_ssid, hotspot_secret, hotspot_security); + + cc_list_row_set_secondary_label (self->hotspot_name_row, hotspot_ssid); + gtk_widget_set_visible (GTK_WIDGET (self->hotspot_name_row), hotspot_ssid != NULL); + + cc_list_row_set_secondary_label (self->hotspot_password_row, hotspot_secret); + gtk_widget_set_visible (GTK_WIDGET (self->hotspot_password_row), hotspot_secret != NULL); + + cc_list_row_set_secondary_label (self->hotspot_security_row, hotspot_security); + gtk_widget_set_visible (GTK_WIDGET (self->hotspot_security_row), hotspot_security != NULL); +} + +static void +set_scanning (NetDeviceWifi *self, + gboolean scanning, + gint64 last_scan) +{ + gboolean scanning_changed = self->scanning != scanning; + + self->scanning = scanning; + self->last_scan = last_scan; + + if (scanning_changed) + g_object_notify (G_OBJECT (self), "scanning"); +} + +static gboolean +update_scanning (gpointer user_data) +{ + NetDeviceWifi *self = user_data; + gint64 last_scan; + + last_scan = nm_device_wifi_get_last_scan (NM_DEVICE_WIFI (self->device)); + + /* The last_scan property is updated after the device finished scanning, + * so notify about it and stop monitoring for changes. + */ + if (self->last_scan != last_scan) { + set_scanning (self, FALSE, last_scan); + self->monitor_scanning_id = 0; + return G_SOURCE_REMOVE; + } + + return G_SOURCE_CONTINUE; +} + +static gboolean +request_scan (gpointer user_data) +{ + NetDeviceWifi *self = user_data; + + g_debug ("Periodic Wi-Fi scan requested"); + + set_scanning (self, TRUE, + nm_device_wifi_get_last_scan (NM_DEVICE_WIFI (self->device))); + + if (self->monitor_scanning_id == 0) { + self->monitor_scanning_id = g_timeout_add (1500, update_scanning, + self); + } + + nm_device_wifi_request_scan_async (NM_DEVICE_WIFI (self->device), + self->cancellable, NULL, NULL); + + return G_SOURCE_CONTINUE; +} + +static void +nm_device_wifi_refresh_ui (NetDeviceWifi *self) +{ + g_autofree gchar *status = NULL; + + if (device_is_hotspot (self)) { + nm_device_wifi_refresh_hotspot (self); + show_hotspot_ui (self); + disable_scan_timeout (self); + return; + } + + if (self->scan_id == 0 && + nm_client_wireless_get_enabled (self->client)) { + self->scan_id = g_timeout_add_seconds (PERIODIC_WIFI_SCAN_TIMEOUT, + request_scan, self); + request_scan (self); + } + + /* keep this in sync with the signal handler setup in cc_network_panel_init */ + wireless_enabled_toggled (self); + + status = panel_device_status_to_localized_string (self->device, NULL); + gtk_label_set_label (self->status_label, status); + + /* update list of APs */ + show_wifi_list (self); +} + +static void +device_off_switch_changed_cb (NetDeviceWifi *self) +{ + gboolean active; + + if (self->updating_device) + return; + + active = gtk_switch_get_active (self->device_off_switch); + nm_client_dbus_set_property (self->client, + NM_DBUS_PATH, + NM_DBUS_INTERFACE, + "WirelessEnabled", + g_variant_new_boolean (active), + -1, + NULL, NULL, NULL); + if (!active) + disable_scan_timeout (self); + gtk_widget_set_sensitive (GTK_WIDGET (self->connect_hidden_row), active); +} + +static void +connect_hidden (NetDeviceWifi *self) +{ + GtkNative *native = gtk_widget_get_native (GTK_WIDGET (self)); + cc_network_panel_connect_to_hidden_network (GTK_WIDGET (native), self->client); + gtk_popover_popdown (self->header_button_popover); +} + +static void +connection_add_activate_cb (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + NMActiveConnection *conn; + g_autoptr(GError) error = NULL; + + conn = nm_client_add_and_activate_connection_finish (NM_CLIENT (source_object), res, &error); + if (!conn) { + if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) { + nm_device_wifi_refresh_ui (user_data); + /* failed to activate */ + g_warning ("Failed to add and activate connection '%d': %s", + error->code, + error->message); + } + return; + } +} + +static void +connection_activate_cb (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + g_autoptr(GError) error = NULL; + + if (!nm_client_activate_connection_finish (NM_CLIENT (source_object), res, &error)) { + if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) { + nm_device_wifi_refresh_ui (user_data); + /* failed to activate */ + g_debug ("Failed to add and activate connection '%d': %s", + error->code, + error->message); + } + return; + } +} + +static gboolean +is_8021x (NMDevice *device, + const char *ap_object_path) +{ + NM80211ApSecurityFlags wpa_flags, rsn_flags; + NMAccessPoint *ap; + + ap = nm_device_wifi_get_access_point_by_path (NM_DEVICE_WIFI (device), + ap_object_path); + if (!ap) + return FALSE; + + rsn_flags = nm_access_point_get_rsn_flags (ap); + if (rsn_flags & NM_802_11_AP_SEC_KEY_MGMT_802_1X) + return TRUE; + + wpa_flags = nm_access_point_get_wpa_flags (ap); + if (wpa_flags & NM_802_11_AP_SEC_KEY_MGMT_802_1X) + return TRUE; + return FALSE; +} + +static void +wireless_try_to_connect (NetDeviceWifi *self, + GBytes *ssid, + const gchar *ap_object_path) +{ + const gchar *ssid_target; + + if (self->updating_device) + return; + + if (ap_object_path == NULL || ap_object_path[0] == 0) + return; + + ssid_target = nm_utils_escape_ssid ((gpointer) g_bytes_get_data (ssid, NULL), g_bytes_get_size (ssid)); + g_debug ("try to connect to WIFI network %s [%s]", + ssid_target, ap_object_path); + + /* activate the connection */ + if (!is_8021x (self->device, ap_object_path)) { + g_autoptr(GPermission) permission = NULL; + gboolean allowed_to_share = FALSE; + g_autoptr(NMConnection) partial = NULL; + + permission = polkit_permission_new_sync ("org.freedesktop.NetworkManager.settings.modify.system", + NULL, NULL, NULL); + if (permission) + allowed_to_share = g_permission_get_allowed (permission); + + if (!allowed_to_share) { + NMSettingConnection *s_con; + + s_con = (NMSettingConnection *)nm_setting_connection_new (); + nm_setting_connection_add_permission (s_con, "user", g_get_user_name (), NULL); + partial = nm_simple_connection_new (); + nm_connection_add_setting (partial, NM_SETTING (s_con)); + } + + g_debug ("no existing connection found for %s, creating and activating one", ssid_target); + nm_client_add_and_activate_connection_async (self->client, + partial, + self->device, + ap_object_path, + self->cancellable, + connection_add_activate_cb, + self); + } else { + g_autoptr(GVariantBuilder) builder = NULL; + GVariant *parameters; + + g_debug ("no existing connection found for %s, creating", ssid_target); + builder = g_variant_builder_new (G_VARIANT_TYPE ("av")); + g_variant_builder_add (builder, "v", g_variant_new_string ("connect-8021x-wifi")); + g_variant_builder_add (builder, "v", g_variant_new_string (nm_object_get_path (NM_OBJECT (self->device)))); + g_variant_builder_add (builder, "v", g_variant_new_string (ap_object_path)); + parameters = g_variant_new ("av", builder); + + g_object_set (self->panel, "parameters", parameters, NULL); + } +} + +static gchar * +get_hostname (void) +{ + g_autoptr(GDBusConnection) bus = NULL; + g_autoptr(GVariant) res = NULL; + g_autoptr(GVariant) inner = NULL; + g_autoptr(GError) error = NULL; + + bus = g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, &error); + if (bus == NULL) { + g_warning ("Failed to get system bus connection: %s", error->message); + return NULL; + } + res = g_dbus_connection_call_sync (bus, + "org.freedesktop.hostname1", + "/org/freedesktop/hostname1", + "org.freedesktop.DBus.Properties", + "Get", + g_variant_new ("(ss)", + "org.freedesktop.hostname1", + "PrettyHostname"), + (GVariantType*)"(v)", + G_DBUS_CALL_FLAGS_NONE, + -1, + NULL, + &error); + + if (res == NULL) { + g_warning ("Getting pretty hostname failed: %s", error->message); + return NULL; + } + + g_variant_get (res, "(v)", &inner); + return g_variant_dup_string (inner, NULL); +} + +static gboolean +is_hotspot_connection (NMConnection *connection) +{ + NMSettingConnection *sc; + NMSettingWireless *sw; + NMSettingIPConfig *sip; + NMSetting *setting; + + sc = nm_connection_get_setting_connection (connection); + if (g_strcmp0 (nm_setting_connection_get_connection_type (sc), "802-11-wireless") != 0) { + return FALSE; + } + sw = nm_connection_get_setting_wireless (connection); + if (g_strcmp0 (nm_setting_wireless_get_mode (sw), "adhoc") != 0 && + g_strcmp0 (nm_setting_wireless_get_mode (sw), "ap") != 0) { + return FALSE; + } + setting = nm_connection_get_setting_by_name (connection, NM_SETTING_WIRELESS_SETTING_NAME); + if (!setting) + return FALSE; + + sip = nm_connection_get_setting_ip4_config (connection); + if (g_strcmp0 (nm_setting_ip_config_get_method (sip), "shared") != 0) { + return FALSE; + } + + return TRUE; +} + +static void +show_hotspot_ui (NetDeviceWifi *self) +{ + /* show hotspot tab */ + gtk_stack_set_visible_child (self->stack, GTK_WIDGET (self->hotspot_box)); +} + +static void +activate_cb (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + g_autoptr(GError) error = NULL; + + if (nm_client_activate_connection_finish (NM_CLIENT (source_object), res, &error) == NULL) { + g_warning ("Failed to add new connection: (%d) %s", + error->code, + error->message); + return; + } + + /* show hotspot tab */ + nm_device_wifi_refresh_ui (user_data); +} + +static void +activate_new_cb (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + NMActiveConnection *conn; + g_autoptr(GError) error = NULL; + + conn = nm_client_add_and_activate_connection_finish (NM_CLIENT (source_object), + res, &error); + if (!conn) { + g_warning ("Failed to add new connection: (%d) %s", + error->code, + error->message); + return; + } + + /* show hotspot tab */ + nm_device_wifi_refresh_ui (user_data); +} + +static NMConnection * +net_device_wifi_get_hotspot_connection (NetDeviceWifi *self) +{ + GSList *connections, *l; + NMConnection *c = NULL; + + connections = net_device_get_valid_connections (self->client, self->device); + for (l = connections; l; l = l->next) { + NMConnection *tmp = l->data; + if (is_hotspot_connection (tmp)) { + c = tmp; + break; + } + } + g_slist_free (connections); + + return c; +} + +static void +overwrite_ssid_cb (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + g_autoptr(GError) error = NULL; + NMRemoteConnection *connection; + NMConnection *c; + NetDeviceWifi *self; + + connection = NM_REMOTE_CONNECTION (source_object); + + if (!nm_remote_connection_commit_changes_finish (connection, res, &error)) { + if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + g_warning ("Failed to save hotspot's settings to disk: %s", + error->message); + return; + } + + self = user_data; + c = net_device_wifi_get_hotspot_connection (self); + + g_debug ("activate existing hotspot connection\n"); + nm_client_activate_connection_async (self->client, + c, + self->device, + NULL, + self->cancellable, + activate_cb, + self); +} + +static void +on_wifi_hotspot_dialog_respnse_cb (GtkDialog *dialog, + gint response, + NetDeviceWifi *self) +{ + if (response == GTK_RESPONSE_APPLY) { + NMConnection *connection; + + connection = cc_wifi_hotspot_dialog_get_connection (self->hotspot_dialog); + if (NM_IS_REMOTE_CONNECTION (connection)) + nm_remote_connection_commit_changes_async (NM_REMOTE_CONNECTION (connection), + TRUE, + self->cancellable, + overwrite_ssid_cb, + self); + else + nm_client_add_and_activate_connection_async (self->client, + connection, + self->device, + NULL, + self->cancellable, + activate_new_cb, + self); + } + + gtk_widget_hide (GTK_WIDGET (self->hotspot_dialog)); +} + +static void +start_hotspot (NetDeviceWifi *self) +{ + GtkNative *native; + NMConnection *c; + g_autofree gchar *hostname = NULL; + g_autofree gchar *ssid = NULL; + + native = gtk_widget_get_native (GTK_WIDGET (self)); + + if (!self->hotspot_dialog) + self->hotspot_dialog = cc_wifi_hotspot_dialog_new (GTK_WINDOW (native)); + cc_wifi_hotspot_dialog_set_device (self->hotspot_dialog, NM_DEVICE_WIFI (self->device)); + hostname = get_hostname (); + ssid = pretty_hostname_to_ssid (hostname); + cc_wifi_hotspot_dialog_set_hostname (self->hotspot_dialog, ssid); + c = net_device_wifi_get_hotspot_connection (self); + if (c) + cc_wifi_hotspot_dialog_set_connection (self->hotspot_dialog, c); + + g_signal_connect_after (self->hotspot_dialog, "response", G_CALLBACK (on_wifi_hotspot_dialog_respnse_cb), self); + gtk_window_present (GTK_WINDOW (self->hotspot_dialog)); + gtk_popover_popdown (self->header_button_popover); +} + +static void +stop_shared_connection (NetDeviceWifi *self) +{ + const GPtrArray *connections; + const GPtrArray *devices; + gint i; + NMActiveConnection *c; + gboolean found = FALSE; + + connections = nm_client_get_active_connections (self->client); + for (i = 0; connections && i < connections->len; i++) { + c = (NMActiveConnection *)connections->pdata[i]; + + devices = nm_active_connection_get_devices (c); + if (devices && devices->pdata[0] == self->device) { + nm_client_deactivate_connection_async (self->client, c, NULL, NULL, NULL); + found = TRUE; + break; + } + } + + if (!found) { + g_warning ("Could not stop hotspot connection as no connection attached to the device could be found."); + return; + } + + nm_device_wifi_refresh_ui (self); +} + +static void +show_wifi_list (NetDeviceWifi *self) +{ + gtk_stack_set_visible_child (self->stack, GTK_WIDGET (self->listbox_box)); +} + +static void +net_device_wifi_finalize (GObject *object) +{ + NetDeviceWifi *self = NET_DEVICE_WIFI (object); + + g_cancellable_cancel (self->cancellable); + g_clear_object (&self->cancellable); + disable_scan_timeout (self); + + g_clear_object (&self->client); + g_clear_object (&self->device); + g_clear_pointer (&self->selected_ssid_title, g_free); + g_clear_pointer (&self->selected_connection_id, g_free); + g_clear_pointer (&self->selected_ap_id, g_free); + + G_OBJECT_CLASS (net_device_wifi_parent_class)->finalize (object); +} + +static void +net_device_wifi_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + NetDeviceWifi *self = NET_DEVICE_WIFI (object); + + switch (prop_id) { + case PROP_SCANNING: + g_value_set_boolean (value, self->scanning); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +really_forgotten (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + g_autoptr(CcWifiConnectionList) list = user_data; + g_autoptr(GError) error = NULL; + + cc_wifi_connection_list_thaw (list); + + if (!nm_remote_connection_delete_finish (NM_REMOTE_CONNECTION (source_object), res, &error)) + g_warning ("failed to delete connection %s: %s", + nm_object_get_path (NM_OBJECT (source_object)), + error->message); +} + +static void +really_forget (GtkDialog *dialog, gint response, gpointer data) +{ + GtkWidget *forget = data; + CcWifiConnectionRow *row; + GList *rows; + GList *r; + NMRemoteConnection *connection; + NetDeviceWifi *self; + + gtk_window_destroy (GTK_WINDOW (dialog)); + + if (response != GTK_RESPONSE_OK) + return; + + self = NET_DEVICE_WIFI (g_object_get_data (G_OBJECT (forget), "net")); + rows = g_object_steal_data (G_OBJECT (forget), "rows"); + for (r = rows; r; r = r->next) { + row = r->data; + connection = NM_REMOTE_CONNECTION (cc_wifi_connection_row_get_connection (row)); + nm_remote_connection_delete_async (connection, self->cancellable, really_forgotten, g_object_ref (data)); + gtk_widget_hide (GTK_WIDGET (row)); + } + g_list_free (rows); +} + +static void +forget_selected (GtkButton *forget, CcWifiConnectionList *list) +{ + GtkNative *native; + GtkWidget *dialog; + + native = gtk_widget_get_native (GTK_WIDGET (forget)); + dialog = gtk_message_dialog_new (GTK_WINDOW (native), + GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_MESSAGE_OTHER, + GTK_BUTTONS_NONE, + NULL); + gtk_message_dialog_set_markup (GTK_MESSAGE_DIALOG (dialog), + _("Network details for the selected networks, including passwords and any custom configuration will be lost.")); + + gtk_dialog_add_buttons (GTK_DIALOG (dialog), + _("_Cancel"), GTK_RESPONSE_CANCEL, + _("_Forget"), GTK_RESPONSE_OK, + NULL); + g_signal_connect (dialog, "response", + G_CALLBACK (really_forget), list); + gtk_window_present (GTK_WINDOW (dialog)); +} + +static void +check_toggled (CcWifiConnectionRow *row, GParamSpec *pspec, CcWifiConnectionList *list) +{ + GtkWidget *forget; + gboolean active; + GList *rows; + + active = cc_wifi_connection_row_get_checked (row); + rows = g_object_steal_data (G_OBJECT (list), "rows"); + + if (active) { + cc_wifi_connection_list_freeze (list); + rows = g_list_prepend (rows, row); + } else { + cc_wifi_connection_list_thaw (list); + rows = g_list_remove (rows, row); + } + + g_object_set_data_full (G_OBJECT (list), "rows", rows, (GDestroyNotify)g_list_free); + forget = g_object_get_data (G_OBJECT (list), "forget"); + gtk_widget_set_sensitive (forget, rows != NULL); +} + +static gint +history_sort (gconstpointer a, gconstpointer b, gpointer data) +{ + guint64 ta, tb; + NMConnection *ca, *cb; + NMSettingConnection *sc; + + ca = cc_wifi_connection_row_get_connection (CC_WIFI_CONNECTION_ROW ((gpointer) a)); + cb = cc_wifi_connection_row_get_connection (CC_WIFI_CONNECTION_ROW ((gpointer) b)); + + if (ca) { + sc = nm_connection_get_setting_connection (ca); + ta = nm_setting_connection_get_timestamp (sc); + } else { + ta = 0; + } + + if (cb) { + sc = nm_connection_get_setting_connection (cb); + tb = nm_setting_connection_get_timestamp (sc); + } else { + tb = 0; + } + + if (ta > tb) return -1; + if (tb > ta) return 1; + + return 0; +} + +static gint +ap_sort (gconstpointer a, gconstpointer b, gpointer data) +{ + NetDeviceWifi *self = data; + CcWifiConnectionRow *a_row = CC_WIFI_CONNECTION_ROW ((gpointer) a); + CcWifiConnectionRow *b_row = CC_WIFI_CONNECTION_ROW ((gpointer) b); + NMActiveConnection *active_connection; + gboolean a_configured, b_configured; + NMAccessPoint *apa, *apb; + guint sa, sb; + + /* Show the connected AP first */ + active_connection = nm_device_get_active_connection (NM_DEVICE (self->device)); + if (active_connection != NULL) { + NMConnection *connection = NM_CONNECTION (nm_active_connection_get_connection (active_connection)); + if (connection == cc_wifi_connection_row_get_connection (a_row)) + return -1; + else if (connection == cc_wifi_connection_row_get_connection (b_row)) + return 1; + } + + /* Show configured networks before non-configured */ + a_configured = cc_wifi_connection_row_get_connection (a_row) != NULL; + b_configured = cc_wifi_connection_row_get_connection (b_row) != NULL; + if (a_configured != b_configured) { + if (a_configured) return -1; + if (b_configured) return 1; + } + + /* Show higher strength networks above lower strength ones */ + + apa = cc_wifi_connection_row_best_access_point (a_row); + apb = cc_wifi_connection_row_best_access_point (b_row); + + if (apa) + sa = nm_access_point_get_strength (apa); + else + sa = 0; + + if (apb) + sb = nm_access_point_get_strength (apb); + else + sb = 0; + + if (sa > sb) return -1; + if (sb > sa) return 1; + + return 0; +} + +static void +show_details_for_row (NetDeviceWifi *self, CcWifiConnectionRow *row, CcWifiConnectionList *list) +{ + NMConnection *connection; + NMAccessPoint *ap; + NetConnectionEditor *editor; + + connection = cc_wifi_connection_row_get_connection (row); + ap = cc_wifi_connection_row_best_access_point (row); + + editor = net_connection_editor_new (connection, self->device, ap, self->client); + gtk_window_set_transient_for (GTK_WINDOW (editor), GTK_WINDOW (gtk_widget_get_native (GTK_WIDGET (row)))); + gtk_window_present (GTK_WINDOW (editor)); +} + +static void +on_connection_list_row_added_cb (NetDeviceWifi *self, + CcWifiConnectionRow *row, + CcWifiConnectionList *list) +{ + g_signal_connect (row, "notify::checked", + G_CALLBACK (check_toggled), list); +} + +static void +on_connection_list_row_removed_cb (NetDeviceWifi *self, + CcWifiConnectionRow *row, + CcWifiConnectionList *list) +{ + GList *rows = g_object_steal_data (G_OBJECT (list), "rows"); + GList *item; + GtkWidget *forget; + + item = g_list_find (rows, row); + if (item) + { + rows = g_list_remove (rows, row); + cc_wifi_connection_list_thaw (list); + } + g_object_set_data_full (G_OBJECT (list), "rows", rows, (GDestroyNotify)g_list_free); + + forget = g_object_get_data (G_OBJECT (list), "forget"); + gtk_widget_set_sensitive (forget, rows != NULL); +} + +static void +on_connection_list_row_activated_cb (NetDeviceWifi *self, + CcWifiConnectionRow *row, + CcWifiConnectionList *list) +{ + cc_wifi_connection_row_set_checked (row, !cc_wifi_connection_row_get_checked (row)); +} + +static void +show_history (NetDeviceWifi *self) +{ + GtkNative *native; + GtkWidget *dialog; + GtkWidget *page; + GtkWidget *list_group; + GtkWidget *forget_group; + GtkListBox *listbox; + GtkWidget *list; + GtkWidget *forget; + GtkWidget *child; + + dialog = adw_preferences_window_new (); + adw_preferences_window_set_search_enabled (ADW_PREFERENCES_WINDOW (dialog), FALSE); + adw_preferences_window_set_can_navigate_back (ADW_PREFERENCES_WINDOW (dialog), FALSE); + native = gtk_widget_get_native (GTK_WIDGET (self)); + gtk_window_set_transient_for (GTK_WINDOW (dialog), GTK_WINDOW (native)); + gtk_window_set_title (GTK_WINDOW (dialog), _("Known Wi-Fi Networks")); + gtk_window_set_modal (GTK_WINDOW (dialog), TRUE); + gtk_window_set_default_size (GTK_WINDOW (dialog), 500, 400); + + page = adw_preferences_page_new (); + adw_preferences_window_add (ADW_PREFERENCES_WINDOW (dialog), ADW_PREFERENCES_PAGE (page)); + + list_group = adw_preferences_group_new (); + adw_preferences_page_add (ADW_PREFERENCES_PAGE (page), ADW_PREFERENCES_GROUP (list_group)); + + list = GTK_WIDGET (cc_wifi_connection_list_new (self->client, + NM_DEVICE_WIFI (self->device), + FALSE, FALSE, TRUE)); + listbox = cc_wifi_connection_list_get_list_box (CC_WIFI_CONNECTION_LIST (list)); + gtk_list_box_set_selection_mode (listbox, GTK_SELECTION_NONE); + gtk_list_box_set_sort_func (listbox, (GtkListBoxSortFunc)history_sort, NULL, NULL); + adw_preferences_group_add (ADW_PREFERENCES_GROUP (list_group), list); + + /* translators: This is the label for the "Forget wireless network" functionality */ + forget = gtk_button_new_with_mnemonic (C_("Wi-Fi Network", "_Forget")); + gtk_widget_set_sensitive (forget, FALSE); + gtk_style_context_add_class (gtk_widget_get_style_context (forget), "destructive-action"); + + g_signal_connect (forget, "clicked", + G_CALLBACK (forget_selected), list); + forget_group = adw_preferences_group_new (); + adw_preferences_group_add (ADW_PREFERENCES_GROUP (forget_group), forget); + adw_preferences_page_add (ADW_PREFERENCES_PAGE (page), ADW_PREFERENCES_GROUP (forget_group)); + + g_object_set_data (G_OBJECT (list), "forget", forget); + g_object_set_data (G_OBJECT (list), "net", self); + + g_signal_connect_object (list, "add-row", + G_CALLBACK (on_connection_list_row_added_cb), + self, + G_CONNECT_SWAPPED); + g_signal_connect_object (list, "remove-row", + G_CALLBACK (on_connection_list_row_removed_cb), + self, + G_CONNECT_SWAPPED); + g_signal_connect_object (listbox, "row-activated", + G_CALLBACK (on_connection_list_row_activated_cb), + self, + G_CONNECT_SWAPPED); + g_signal_connect_object (list, "configure", + G_CALLBACK (show_details_for_row), + self, + G_CONNECT_SWAPPED); + + for (child = gtk_widget_get_first_child (GTK_WIDGET (listbox)); + child; + child = gtk_widget_get_next_sibling (child)) + { + on_connection_list_row_added_cb (self, + CC_WIFI_CONNECTION_ROW (child), + CC_WIFI_CONNECTION_LIST (list)); + } + + gtk_window_present (GTK_WINDOW (dialog)); + gtk_popover_popdown (self->header_button_popover); +} + +static void +on_popover_row_activated_cb (GtkListBox *list, + GtkListBoxRow *row, + gpointer user_data) +{ + NetDeviceWifi *self = user_data; + + if (row == self->connect_hidden_row) + connect_hidden (self); + else if (row == self->start_hotspot_row) + start_hotspot (self); + else + show_history (self); +} + +static void +ap_activated (NetDeviceWifi *self, GtkListBoxRow *row) +{ + CcWifiConnectionRow *c_row; + NMConnection *connection; + NMAccessPoint *ap; + + /* The mockups want a row to connecto hidden networks; this could + * be handeled here. */ + if (!CC_IS_WIFI_CONNECTION_ROW (row)) + return; + + c_row = CC_WIFI_CONNECTION_ROW (row); + + connection = cc_wifi_connection_row_get_connection (c_row); + ap = cc_wifi_connection_row_best_access_point (c_row); + + if (ap != NULL) { + if (connection != NULL) { + nm_client_activate_connection_async (self->client, + connection, + self->device, NULL, self->cancellable, + connection_activate_cb, self); + } else { + GBytes *ssid; + const gchar *object_path; + + ssid = nm_access_point_get_ssid (ap); + object_path = nm_object_get_path (NM_OBJECT (ap)); + wireless_try_to_connect (self, ssid, object_path); + } + } +} + +static void +net_device_wifi_class_init (NetDeviceWifiClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + object_class->finalize = net_device_wifi_finalize; + object_class->get_property = net_device_wifi_get_property; + + g_object_class_install_property (object_class, + PROP_SCANNING, + g_param_spec_boolean ("scanning", + "Scanning", + "Whether the device is scanning for access points", + FALSE, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/control-center/network/network-wifi.ui"); + + gtk_widget_class_bind_template_child (widget_class, NetDeviceWifi, center_box); + gtk_widget_class_bind_template_child (widget_class, NetDeviceWifi, connect_hidden_row); + gtk_widget_class_bind_template_child (widget_class, NetDeviceWifi, device_off_switch); + gtk_widget_class_bind_template_child (widget_class, NetDeviceWifi, header_box); + gtk_widget_class_bind_template_child (widget_class, NetDeviceWifi, header_button_popover); + gtk_widget_class_bind_template_child (widget_class, NetDeviceWifi, hotspot_box); + gtk_widget_class_bind_template_child (widget_class, NetDeviceWifi, hotspot_name_row); + gtk_widget_class_bind_template_child (widget_class, NetDeviceWifi, hotspot_security_row); + gtk_widget_class_bind_template_child (widget_class, NetDeviceWifi, hotspot_password_row); + gtk_widget_class_bind_template_child (widget_class, NetDeviceWifi, listbox_box); + gtk_widget_class_bind_template_child (widget_class, NetDeviceWifi, stack); + gtk_widget_class_bind_template_child (widget_class, NetDeviceWifi, start_hotspot_row); + gtk_widget_class_bind_template_child (widget_class, NetDeviceWifi, status_label); + gtk_widget_class_bind_template_child (widget_class, NetDeviceWifi, title_label); + + gtk_widget_class_bind_template_callback (widget_class, device_off_switch_changed_cb); + gtk_widget_class_bind_template_callback (widget_class, on_popover_row_activated_cb); +} + +static void +net_device_wifi_init (NetDeviceWifi *self) +{ + gtk_widget_init_template (GTK_WIDGET (self)); + + self->cancellable = g_cancellable_new (); +} + + +static void +nm_client_on_permission_change (NetDeviceWifi *self) { + NMClientPermissionResult perm; + NMDeviceWifiCapabilities caps; + + if (nm_client_get_permissions_state (self->client) != NM_TERNARY_TRUE) { + /* permissions aren't ready yet */ + return; + } + + /* only enable the button if the user can create a hotspot */ + perm = nm_client_get_permission_result (self->client, NM_CLIENT_PERMISSION_WIFI_SHARE_OPEN); + caps = nm_device_wifi_get_capabilities (NM_DEVICE_WIFI (self->device)); + if (perm != NM_CLIENT_PERMISSION_RESULT_YES && + perm != NM_CLIENT_PERMISSION_RESULT_AUTH) { + gtk_widget_set_tooltip_text (GTK_WIDGET (self->start_hotspot_row), _("System policy prohibits use as a Hotspot")); + gtk_widget_set_sensitive (GTK_WIDGET (self->start_hotspot_row), FALSE); + } else if (!(caps & (NM_WIFI_DEVICE_CAP_AP | NM_WIFI_DEVICE_CAP_ADHOC))) { + gtk_widget_set_tooltip_text (GTK_WIDGET (self->start_hotspot_row), _("Wireless device does not support Hotspot mode")); + gtk_widget_set_sensitive (GTK_WIDGET (self->start_hotspot_row), FALSE); + } else + gtk_widget_set_sensitive (GTK_WIDGET (self->start_hotspot_row), TRUE); + +} + +NetDeviceWifi * +net_device_wifi_new (CcPanel *panel, NMClient *client, NMDevice *device) +{ + NetDeviceWifi *self; + GtkListBox *listbox; + GtkWidget *list; + + self = g_object_new (net_device_wifi_get_type (), NULL); + self->panel = panel; + self->client = g_object_ref (client); + self->device = g_object_ref (device); + + g_signal_connect_object (client, "notify::wireless-enabled", + G_CALLBACK (wireless_enabled_toggled), self, G_CONNECT_SWAPPED); + + g_signal_connect_object (device, "state-changed", G_CALLBACK (nm_device_wifi_refresh_ui), self, G_CONNECT_SWAPPED); + + list = GTK_WIDGET (cc_wifi_connection_list_new (client, NM_DEVICE_WIFI (device), TRUE, TRUE, FALSE)); + gtk_box_append (self->listbox_box, list); + + listbox = cc_wifi_connection_list_get_list_box (CC_WIFI_CONNECTION_LIST (list)); + gtk_list_box_set_sort_func (listbox, (GtkListBoxSortFunc)ap_sort, self, NULL); + + g_signal_connect_object (listbox, "row-activated", + G_CALLBACK (ap_activated), self, G_CONNECT_SWAPPED); + g_signal_connect_object (list, "configure", + G_CALLBACK (show_details_for_row), self, G_CONNECT_SWAPPED); + g_signal_connect_object (client, "notify", + G_CALLBACK(nm_client_on_permission_change), self, G_CONNECT_SWAPPED); + + nm_client_on_permission_change(self); + + nm_device_wifi_refresh_ui (self); + + return self; +} + +NMDevice * +net_device_wifi_get_device (NetDeviceWifi *self) +{ + g_return_val_if_fail (NET_IS_DEVICE_WIFI (self), NULL); + return self->device; +} + +void +net_device_wifi_set_title (NetDeviceWifi *self, const gchar *title) +{ + g_return_if_fail (NET_IS_DEVICE_WIFI (self)); + gtk_label_set_label (self->title_label, title); +} + +GtkWidget * +net_device_wifi_get_header_widget (NetDeviceWifi *self) +{ + g_return_val_if_fail (NET_IS_DEVICE_WIFI (self), NULL); + return GTK_WIDGET (self->header_box); +} + +GtkWidget * +net_device_wifi_get_title_widget (NetDeviceWifi *self) +{ + g_return_val_if_fail (NET_IS_DEVICE_WIFI (self), NULL); + return GTK_WIDGET (self->center_box); +} + +void +net_device_wifi_turn_off_hotspot (NetDeviceWifi *self) +{ + g_return_if_fail (NET_IS_DEVICE_WIFI (self)); + + stop_shared_connection (self); +} + |