/*
* 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));
}