/* * 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 . */ #include #include #include "cc-wifi-connection-row.h" struct _CcWifiConnectionRow { AdwActionRow parent_instance; gboolean constructed; gboolean checkable; gboolean checked; NMDeviceWifi *device; GPtrArray *aps; NMConnection *connection; gboolean known_connection; GtkLabel *active_label; GtkCheckButton *checkbutton; GtkSpinner *connecting_spinner; GtkImage *encrypted_icon; GtkButton *options_button; GtkImage *strength_icon; }; enum { PROP_0, PROP_CHECKABLE, PROP_CHECKED, PROP_DEVICE, PROP_APS, PROP_CONNECTION, PROP_KNOWN_CONNECTION, PROP_LAST }; typedef enum { NM_AP_SEC_UNKNOWN, NM_AP_SEC_NONE, NM_AP_SEC_WEP, NM_AP_SEC_WPA, NM_AP_SEC_WPA2, NM_AP_SEC_SAE, NM_AP_SEC_OWE, NM_AP_SEC_OWE_TM } NMAccessPointSecurity; G_DEFINE_TYPE (CcWifiConnectionRow, cc_wifi_connection_row, ADW_TYPE_ACTION_ROW) static GParamSpec *props[PROP_LAST]; static void configure_clicked_cb (CcWifiConnectionRow *self); static NMAccessPointSecurity get_access_point_security (NMAccessPoint *ap) { NM80211ApFlags flags; NM80211ApSecurityFlags wpa_flags; NM80211ApSecurityFlags rsn_flags; NMAccessPointSecurity 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; } #if NM_CHECK_VERSION(1,20,6) else if (rsn_flags & NM_802_11_AP_SEC_KEY_MGMT_SAE) { type = NM_AP_SEC_SAE; } #endif #if NM_CHECK_VERSION(1,24,0) else if (rsn_flags & NM_802_11_AP_SEC_KEY_MGMT_OWE) { type = NM_AP_SEC_OWE; } #endif #if NM_CHECK_VERSION(1,26,0) else if (rsn_flags & NM_802_11_AP_SEC_KEY_MGMT_OWE_TM) { type = NM_AP_SEC_OWE_TM; } #endif else { type = NM_AP_SEC_WPA2; } return type; } static NMAccessPointSecurity get_connection_security (NMConnection *con) { NMSettingWirelessSecurity *sws; const gchar *key_mgmt; sws = nm_connection_get_setting_wireless_security (con); g_debug ("getting security from %p", sws); if (!sws) return NM_AP_SEC_NONE; key_mgmt = nm_setting_wireless_security_get_key_mgmt (sws); g_debug ("key management is %s", key_mgmt); if (!key_mgmt) return NM_AP_SEC_NONE; else if (g_str_equal (key_mgmt, "none")) return NM_AP_SEC_WEP; else if (g_str_equal (key_mgmt, "ieee8021x")) return NM_AP_SEC_WEP; else if (g_str_equal (key_mgmt, "wpa-eap")) return NM_AP_SEC_WPA2; else if (strncmp (key_mgmt, "wpa-", 4) == 0) return NM_AP_SEC_WPA; else if (g_str_equal (key_mgmt, "sae")) return NM_AP_SEC_SAE; else if (g_str_equal (key_mgmt, "owe")) return NM_AP_SEC_OWE; else return NM_AP_SEC_UNKNOWN; } static void update_ui (CcWifiConnectionRow *self) { GBytes *ssid; g_autofree gchar *title = NULL; NMActiveConnection *active_connection = NULL; gboolean active; gboolean connecting; NMAccessPointSecurity security = NM_AP_SEC_UNKNOWN; NMAccessPoint *best_ap; guint8 strength = 0; NMActiveConnectionState state; g_assert (self->device); g_assert (self->connection || self->aps->len > 0); best_ap = cc_wifi_connection_row_best_access_point (self); if (self->connection) { active_connection = nm_device_get_active_connection (NM_DEVICE (self->device)); if (active_connection && NM_CONNECTION (nm_active_connection_get_connection (active_connection)) != self->connection) active_connection = NULL; } if (self->connection) { NMSettingWireless *sw; const gchar *name = NULL; g_autofree gchar *ssid_str = NULL; gchar *ssid_pos; sw = nm_connection_get_setting_wireless (self->connection); ssid = nm_setting_wireless_get_ssid (sw); ssid_str = nm_utils_ssid_to_utf8 (g_bytes_get_data (ssid, NULL), g_bytes_get_size (ssid)); name = nm_connection_get_id (NM_CONNECTION (self->connection)); ssid_pos = strstr (name, ssid_str); if (ssid_pos == name && strlen (name) == strlen (ssid_str)) { title = g_markup_escape_text (name, -1); } else if (ssid_pos) { g_autofree gchar *before = g_strndup (name, ssid_pos - name); g_autofree gchar *after = g_strndup (ssid_pos + strlen (ssid_str), strlen(ssid_pos) - strlen(ssid_str)); title = g_markup_printf_escaped ("%s%s%s", before, ssid_str, after); } else { /* TRANSLATORS: This happens when the connection name does not contain the SSID. */ title = g_markup_printf_escaped (C_("Wi-Fi Connection", "%s (SSID: %s)"), name, ssid_str); } adw_preferences_row_set_title (ADW_PREFERENCES_ROW (self), title); } else { g_autofree char *title_escaped = NULL; ssid = nm_access_point_get_ssid (best_ap); title = nm_utils_ssid_to_utf8 (g_bytes_get_data (ssid, NULL), g_bytes_get_size (ssid)); title_escaped = g_markup_escape_text (title, -1); adw_preferences_row_set_title (ADW_PREFERENCES_ROW (self), title_escaped); } if (active_connection) { state = nm_active_connection_get_state (active_connection); active = state == NM_ACTIVE_CONNECTION_STATE_ACTIVATED; connecting = state == NM_ACTIVE_CONNECTION_STATE_ACTIVATING; } else { active = FALSE; connecting = FALSE; } if (self->connection) security = get_connection_security (self->connection); if (best_ap != NULL) { security = get_access_point_security (best_ap); strength = nm_access_point_get_strength (best_ap); } gtk_widget_set_visible (GTK_WIDGET (self->connecting_spinner), connecting); if (connecting) { gtk_spinner_start (self->connecting_spinner); } else { gtk_spinner_stop (self->connecting_spinner); } gtk_widget_set_visible (GTK_WIDGET (self->active_label), active); gtk_widget_set_visible (GTK_WIDGET (self->options_button), active || connecting || self->known_connection); if (security != NM_AP_SEC_UNKNOWN && security != NM_AP_SEC_NONE && security != NM_AP_SEC_OWE && security != NM_AP_SEC_OWE_TM) { const gchar *icon_name = "lock-small-symbolic"; gtk_widget_set_child_visible (GTK_WIDGET (self->encrypted_icon), TRUE); if (security == NM_AP_SEC_WEP) { icon_name = "warning-small-symbolic"; gtk_widget_set_tooltip_text (GTK_WIDGET (self->encrypted_icon), _("Insecure network (WEP)")); } else if (security == NM_AP_SEC_WPA) { gtk_widget_set_tooltip_text (GTK_WIDGET (self->encrypted_icon), _("Secure network (WPA)")); } else if (security == NM_AP_SEC_WPA2) { gtk_widget_set_tooltip_text (GTK_WIDGET (self->encrypted_icon), _("Secure network (WPA2)")); } else if (security == NM_AP_SEC_SAE) { gtk_widget_set_tooltip_text (GTK_WIDGET (self->encrypted_icon), _("Secure network (WPA3)")); } else { gtk_widget_set_tooltip_text (GTK_WIDGET (self->encrypted_icon), _("Secure network")); } gtk_image_set_from_icon_name (self->encrypted_icon, icon_name); } else { gtk_widget_set_child_visible (GTK_WIDGET (self->encrypted_icon), FALSE); } if (best_ap) { gchar *icon_name; 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"; g_object_set (self->strength_icon, "icon-name", icon_name, NULL); gtk_widget_set_child_visible (GTK_WIDGET (self->strength_icon), TRUE); } else { gtk_widget_set_child_visible (GTK_WIDGET (self->strength_icon), FALSE); } } static void cc_wifi_connection_row_constructed (GObject *object) { CcWifiConnectionRow *self = CC_WIFI_CONNECTION_ROW (object); G_OBJECT_CLASS (cc_wifi_connection_row_parent_class)->constructed (object); /* Reparent the label into the checkbox */ gtk_widget_set_visible (GTK_WIDGET (self->checkbutton), self->checkable); update_ui (CC_WIFI_CONNECTION_ROW (object)); } static void cc_wifi_connection_row_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { CcWifiConnectionRow *self = CC_WIFI_CONNECTION_ROW (object); GPtrArray *ptr_array; gint i; switch (prop_id) { case PROP_CHECKABLE: g_value_set_boolean (value, self->checkable); break; case PROP_CHECKED: g_value_set_boolean (value, self->checked); break; case PROP_DEVICE: g_value_set_object (value, self->device); break; case PROP_APS: ptr_array = g_ptr_array_new_full (self->aps->len, NULL); for (i = 0; i < self->aps->len; i++) g_ptr_array_add (ptr_array, g_ptr_array_index (self->aps, i)); g_value_take_boxed (value, ptr_array); break; case PROP_CONNECTION: g_value_set_object (value, self->connection); break; case PROP_KNOWN_CONNECTION: g_value_set_boolean (value, self->known_connection); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } } static void cc_wifi_connection_row_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { CcWifiConnectionRow *self = CC_WIFI_CONNECTION_ROW (object); GPtrArray *ptr_array; gint i; switch (prop_id) { case PROP_CHECKABLE: self->checkable = g_value_get_boolean (value); break; case PROP_CHECKED: self->checked = g_value_get_boolean (value); break; case PROP_DEVICE: self->device = g_value_dup_object (value); break; case PROP_APS: ptr_array = g_value_get_boxed (value); g_ptr_array_set_size (self->aps, 0); if (ptr_array) { for (i = 0; i < ptr_array->len; i++) g_ptr_array_add (self->aps, g_object_ref (g_ptr_array_index (ptr_array, i))); } if (self->constructed) update_ui (self); break; case PROP_CONNECTION: self->connection = g_value_dup_object (value); break; case PROP_KNOWN_CONNECTION: self->known_connection = g_value_get_boolean (value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } } static void cc_wifi_connection_row_finalize (GObject *object) { CcWifiConnectionRow *self = CC_WIFI_CONNECTION_ROW (object); g_clear_object (&self->device); g_clear_pointer (&self->aps, g_ptr_array_unref); g_clear_object (&self->connection); G_OBJECT_CLASS (cc_wifi_connection_row_parent_class)->finalize (object); } void cc_wifi_connection_row_class_init (CcWifiConnectionRowClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); object_class->constructed = cc_wifi_connection_row_constructed; object_class->get_property = cc_wifi_connection_row_get_property; object_class->set_property = cc_wifi_connection_row_set_property; object_class->finalize = cc_wifi_connection_row_finalize; gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/control-center/network/cc-wifi-connection-row.ui"); gtk_widget_class_bind_template_child (widget_class, CcWifiConnectionRow, active_label); gtk_widget_class_bind_template_child (widget_class, CcWifiConnectionRow, checkbutton); gtk_widget_class_bind_template_child (widget_class, CcWifiConnectionRow, connecting_spinner); gtk_widget_class_bind_template_child (widget_class, CcWifiConnectionRow, encrypted_icon); gtk_widget_class_bind_template_child (widget_class, CcWifiConnectionRow, options_button); gtk_widget_class_bind_template_child (widget_class, CcWifiConnectionRow, strength_icon); gtk_widget_class_bind_template_callback (widget_class, configure_clicked_cb); props[PROP_CHECKABLE] = g_param_spec_boolean ("checkable", "checkable", "Whether to show a checkbox to select the row", FALSE, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); props[PROP_CHECKED] = g_param_spec_boolean ("checked", "Checked", "Whether the row is selected by checking it", FALSE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); props[PROP_DEVICE] = g_param_spec_object ("device", "WiFi Device", "The WiFi Device for this connection/ap", NM_TYPE_DEVICE_WIFI, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); props[PROP_APS] = g_param_spec_boxed ("aps", "Access Points", "The access points for this connection (may be empty if a connection is given)", G_TYPE_PTR_ARRAY, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); props[PROP_CONNECTION] = g_param_spec_object ("connection", "Connection", "The NMConnection (may be NULL if there is an AP)", NM_TYPE_CONNECTION, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); props[PROP_KNOWN_CONNECTION] = g_param_spec_boolean ("known-connection", "Known Connection", "Whether this row is a known connection or not", FALSE, 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_ROW, G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 0); } static void configure_clicked_cb (CcWifiConnectionRow *self) { g_signal_emit_by_name (self, "configure"); } void cc_wifi_connection_row_init (CcWifiConnectionRow *self) { gtk_widget_init_template (GTK_WIDGET (self)); self->aps = g_ptr_array_new_with_free_func (g_object_unref); g_object_bind_property (self, "checked", self->checkbutton, "active", G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE); } CcWifiConnectionRow * cc_wifi_connection_row_new (NMDeviceWifi *device, NMConnection *connection, GPtrArray *aps, gboolean checkable, gboolean known_connection) { return g_object_new (CC_TYPE_WIFI_CONNECTION_ROW, "device", device, "connection", connection, "aps", aps, "checkable", checkable, "known-connection", known_connection, NULL); } gboolean cc_wifi_connection_row_get_checkable (CcWifiConnectionRow *self) { g_return_val_if_fail (CC_WIFI_CONNECTION_ROW (self), FALSE); return self->checkable; } gboolean cc_wifi_connection_row_get_checked (CcWifiConnectionRow *self) { g_return_val_if_fail (CC_WIFI_CONNECTION_ROW (self), FALSE); return self->checked; } NMDeviceWifi* cc_wifi_connection_row_get_device (CcWifiConnectionRow *self) { g_return_val_if_fail (CC_WIFI_CONNECTION_ROW (self), NULL); return self->device; } const GPtrArray* cc_wifi_connection_row_get_access_points (CcWifiConnectionRow *self) { g_return_val_if_fail (CC_WIFI_CONNECTION_ROW (self), NULL); return self->aps; } NMConnection* cc_wifi_connection_row_get_connection (CcWifiConnectionRow *self) { g_return_val_if_fail (CC_WIFI_CONNECTION_ROW (self), NULL); return self->connection; } void cc_wifi_connection_row_set_checked (CcWifiConnectionRow *self, gboolean value) { g_return_if_fail (CC_WIFI_CONNECTION_ROW (self)); self->checked = value; g_object_notify_by_pspec (G_OBJECT (self), props[PROP_CHECKED]); } NMAccessPoint* cc_wifi_connection_row_best_access_point (CcWifiConnectionRow *self) { NMAccessPoint *best_ap = NULL; NMAccessPoint *active_ap = NULL; guint8 strength = 0; gint i; g_return_val_if_fail (CC_WIFI_CONNECTION_ROW (self), NULL); if (self->aps->len == 0) return NULL; active_ap = nm_device_wifi_get_active_access_point (self->device); for (i = 0; i < self->aps->len; i++) { NMAccessPoint *cur; guint8 cur_strength; cur = g_ptr_array_index (self->aps, i); /* Prefer the active AP in all cases */ if (cur == active_ap) return cur; cur_strength = nm_access_point_get_strength (cur); /* Use if we don't have an AP, this is the current AP, or it is better */ if (!best_ap || cur_strength > strength) { best_ap = cur; strength = cur_strength; } } return best_ap; } void cc_wifi_connection_row_add_access_point (CcWifiConnectionRow *self, NMAccessPoint *ap) { g_return_if_fail (CC_WIFI_CONNECTION_ROW (self)); g_ptr_array_add (self->aps, g_object_ref (ap)); update_ui (self); } gboolean cc_wifi_connection_row_remove_access_point (CcWifiConnectionRow *self, NMAccessPoint *ap) { g_return_val_if_fail (CC_WIFI_CONNECTION_ROW (self), FALSE); if (!g_ptr_array_remove (self->aps, g_object_ref (ap))) return FALSE; /* Object might be invalid; this is alright if it is deleted right away */ if (self->aps->len > 0 || self->connection) { g_object_notify_by_pspec (G_OBJECT (self), props[PROP_APS]); update_ui (self); } return self->aps->len == 0; } gboolean cc_wifi_connection_row_has_access_point (CcWifiConnectionRow *self, NMAccessPoint *ap) { g_return_val_if_fail (CC_WIFI_CONNECTION_ROW (self), FALSE); return g_ptr_array_find (self->aps, ap, NULL); } void cc_wifi_connection_row_update (CcWifiConnectionRow *self) { update_ui (self); gtk_list_box_row_changed (GTK_LIST_BOX_ROW (self)); }