diff options
Diffstat (limited to 'panels/network')
134 files changed, 27061 insertions, 0 deletions
diff --git a/panels/network/cc-network-panel.c b/panels/network/cc-network-panel.c new file mode 100644 index 0000000..aa271fd --- /dev/null +++ b/panels/network/cc-network-panel.c @@ -0,0 +1,806 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2010-2012 Richard Hughes <richard@hughsie.com> + * Copyright (C) 2012 Thomas Bechtold <thomasbechtold@jpberlin.de> + * Copyright (C) 2013 Aleksander Morgado <aleksander@gnu.org> + * + * 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 <config.h> +#include <glib/gi18n.h> +#include <stdlib.h> + +#include "shell/cc-object-storage.h" + +#include "cc-network-panel.h" +#include "cc-network-resources.h" + +#include <NetworkManager.h> + +#include "net-device-bluetooth.h" +#include "net-device-ethernet.h" +#include "net-device-mobile.h" +#include "net-device-wifi.h" +#include "net-proxy.h" +#include "net-vpn.h" + +#include "panel-common.h" + +#include "network-dialogs.h" +#include "connection-editor/net-connection-editor.h" + +#include <libmm-glib.h> + +typedef enum { + OPERATION_NULL, + OPERATION_SHOW_DEVICE, + OPERATION_CONNECT_MOBILE +} CmdlineOperation; + +struct _CcNetworkPanel +{ + CcPanel parent; + + GPtrArray *bluetooth_devices; + GPtrArray *ethernet_devices; + GPtrArray *mobile_devices; + GPtrArray *vpns; + GHashTable *nm_device_to_device; + + NMClient *client; + MMManager *modem_manager; + gboolean updating_device; + + /* widgets */ + GtkWidget *box_bluetooth; + GtkWidget *box_proxy; + GtkWidget *box_vpn; + GtkWidget *box_wired; + GtkWidget *container_bluetooth; + GtkWidget *empty_listbox; + GtkWidget *vpn_stack; + + /* wireless dialog stuff */ + CmdlineOperation arg_operation; + gchar *arg_device; + gchar *arg_access_point; + gboolean operation_done; +}; + +enum { + PROP_0, + PROP_PARAMETERS +}; + +static void handle_argv (CcNetworkPanel *self); + +CC_PANEL_REGISTER (CcNetworkPanel, cc_network_panel) + +static void +cc_network_panel_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + switch (property_id) { + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + } +} + +static CmdlineOperation +cmdline_operation_from_string (const gchar *string) +{ + if (g_strcmp0 (string, "connect-3g") == 0) + return OPERATION_CONNECT_MOBILE; + if (g_strcmp0 (string, "show-device") == 0) + return OPERATION_SHOW_DEVICE; + + g_warning ("Invalid additional argument %s", string); + return OPERATION_NULL; +} + +static void +reset_command_line_args (CcNetworkPanel *self) +{ + self->arg_operation = OPERATION_NULL; + g_clear_pointer (&self->arg_device, g_free); + g_clear_pointer (&self->arg_access_point, g_free); +} + +static gboolean +verify_argv (CcNetworkPanel *self, + const char **args) +{ + switch (self->arg_operation) { + case OPERATION_CONNECT_MOBILE: + case OPERATION_SHOW_DEVICE: + if (self->arg_device == NULL) { + g_warning ("Operation %s requires an object path", args[0]); + return FALSE; + } + default: + return TRUE; + } +} + +static GPtrArray * +variant_av_to_string_array (GVariant *array) +{ + GVariantIter iter; + GVariant *v; + GPtrArray *strv; + gsize count; + count = g_variant_iter_init (&iter, array); + strv = g_ptr_array_sized_new (count + 1); + while (g_variant_iter_next (&iter, "v", &v)) { + g_ptr_array_add (strv, (gpointer)g_variant_get_string (v, NULL)); + g_variant_unref (v); + } + g_ptr_array_add (strv, NULL); /* NULL-terminate the strv data array */ + return strv; +} + +static void +cc_network_panel_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + CcNetworkPanel *self = CC_NETWORK_PANEL (object); + + switch (property_id) { + case PROP_PARAMETERS: { + GVariant *parameters; + + reset_command_line_args (self); + + parameters = g_value_get_variant (value); + if (parameters) { + g_autoptr(GPtrArray) array = NULL; + const gchar **args; + array = variant_av_to_string_array (parameters); + args = (const gchar **) array->pdata; + + g_debug ("Invoked with operation %s", args[0]); + + if (args[0]) + self->arg_operation = cmdline_operation_from_string (args[0]); + if (args[0] && args[1]) + self->arg_device = g_strdup (args[1]); + if (args[0] && args[1] && args[2]) + self->arg_access_point = g_strdup (args[2]); + + if (verify_argv (self, (const char **) args) == FALSE) { + reset_command_line_args (self); + return; + } + g_debug ("Calling handle_argv() after setting property"); + handle_argv (self); + } + break; + } + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + } +} + +static void +cc_network_panel_dispose (GObject *object) +{ + CcNetworkPanel *self = CC_NETWORK_PANEL (object); + + g_clear_object (&self->client); + g_clear_object (&self->modem_manager); + + g_clear_pointer (&self->bluetooth_devices, g_ptr_array_unref); + g_clear_pointer (&self->ethernet_devices, g_ptr_array_unref); + g_clear_pointer (&self->mobile_devices, g_ptr_array_unref); + g_clear_pointer (&self->vpns, g_ptr_array_unref); + g_clear_pointer (&self->nm_device_to_device, g_hash_table_destroy); + + G_OBJECT_CLASS (cc_network_panel_parent_class)->dispose (object); +} + +static void +cc_network_panel_finalize (GObject *object) +{ + CcNetworkPanel *self = CC_NETWORK_PANEL (object); + + reset_command_line_args (self); + + G_OBJECT_CLASS (cc_network_panel_parent_class)->finalize (object); +} + +static const char * +cc_network_panel_get_help_uri (CcPanel *self) +{ + return "help:gnome-help/net"; +} + +static void +panel_refresh_device_titles (CcNetworkPanel *self) +{ + g_autoptr(GPtrArray) ndarray = NULL; + g_autoptr(GPtrArray) nmdarray = NULL; + GtkWidget **devices; + NMDevice **nm_devices; + g_auto(GStrv) titles = NULL; + guint i, num_devices; + + ndarray = g_ptr_array_new (); + nmdarray = g_ptr_array_new (); + for (i = 0; i < self->bluetooth_devices->len; i++) { + NetDeviceBluetooth *device = g_ptr_array_index (self->bluetooth_devices, i); + g_ptr_array_add (ndarray, device); + g_ptr_array_add (nmdarray, net_device_bluetooth_get_device (device)); + } + for (i = 0; i < self->ethernet_devices->len; i++) { + NetDeviceEthernet *device = g_ptr_array_index (self->ethernet_devices, i); + g_ptr_array_add (ndarray, device); + g_ptr_array_add (nmdarray, net_device_ethernet_get_device (device)); + } + for (i = 0; i < self->mobile_devices->len; i++) { + NetDeviceMobile *device = g_ptr_array_index (self->mobile_devices, i); + g_ptr_array_add (ndarray, device); + g_ptr_array_add (nmdarray, net_device_mobile_get_device (device)); + } + + if (ndarray->len == 0) + return; + + devices = (GtkWidget **)ndarray->pdata; + nm_devices = (NMDevice **)nmdarray->pdata; + num_devices = ndarray->len; + + titles = nm_device_disambiguate_names (nm_devices, num_devices); + for (i = 0; i < num_devices; i++) { + if (NM_IS_DEVICE_BT (nm_devices[i])) + adw_preferences_row_set_title (ADW_PREFERENCES_ROW (devices[i]), nm_device_bt_get_name (NM_DEVICE_BT (nm_devices[i]))); + else if (NET_IS_DEVICE_ETHERNET (devices[i])) + adw_preferences_group_set_title (ADW_PREFERENCES_GROUP (devices[i]), titles[i]); + else if (NET_IS_DEVICE_MOBILE (devices[i])) + net_device_mobile_set_title (NET_DEVICE_MOBILE (devices[i]), titles[i]); + } +} + +static gboolean +handle_argv_for_device (CcNetworkPanel *self, + NMDevice *device) +{ + GtkWidget *toplevel = cc_shell_get_toplevel (cc_panel_get_shell (CC_PANEL (self))); + + if (self->arg_operation == OPERATION_NULL) + return TRUE; + + if (g_strcmp0 (nm_object_get_path (NM_OBJECT (device)), self->arg_device) == 0) { + if (self->arg_operation == OPERATION_CONNECT_MOBILE) { + cc_network_panel_connect_to_3g_network (toplevel, self->client, device); + + reset_command_line_args (self); /* done */ + return TRUE; + } else if (self->arg_operation == OPERATION_SHOW_DEVICE) { + reset_command_line_args (self); /* done */ + return TRUE; + } + } + + return FALSE; +} + +static gboolean +handle_argv_for_connection (CcNetworkPanel *self, + NMConnection *connection) +{ + if (self->arg_operation == OPERATION_NULL) + return TRUE; + if (self->arg_operation != OPERATION_SHOW_DEVICE) + return FALSE; + + if (g_strcmp0 (nm_connection_get_path (connection), self->arg_device) == 0) { + reset_command_line_args (self); + return TRUE; + } + + return FALSE; +} + + +static void +handle_argv (CcNetworkPanel *self) +{ + gint i; + + if (self->arg_operation == OPERATION_NULL) + return; + + for (i = 0; i < self->bluetooth_devices->len; i++) { + NetDeviceBluetooth *device = g_ptr_array_index (self->bluetooth_devices, i); + if (handle_argv_for_device (self, net_device_bluetooth_get_device (device))) + return; + } + for (i = 0; i < self->ethernet_devices->len; i++) { + NetDeviceEthernet *device = g_ptr_array_index (self->ethernet_devices, i); + if (handle_argv_for_device (self, net_device_ethernet_get_device (device))) + return; + } + for (i = 0; i < self->mobile_devices->len; i++) { + NetDeviceMobile *device = g_ptr_array_index (self->mobile_devices, i); + if (handle_argv_for_device (self, net_device_mobile_get_device (device))) + return; + } + for (i = 0; i < self->vpns->len; i++) { + NetVpn *vpn = g_ptr_array_index (self->vpns, i); + if (handle_argv_for_connection (self, net_vpn_get_connection (vpn))) + return; + } + + g_debug ("Could not handle argv operation, no matching device yet?"); +} + +static void +update_vpn_section (CcNetworkPanel *self) +{ + gtk_stack_set_visible_child (GTK_STACK (self->vpn_stack), + self->vpns->len == 0 ? self->empty_listbox : self->box_vpn); +} + +static void +update_bluetooth_section (CcNetworkPanel *self) +{ + gtk_widget_set_visible (self->container_bluetooth, self->bluetooth_devices->len > 0); +} + +static gboolean +wwan_panel_supports_modem (GDBusObject *object) +{ + MMObject *mm_object; + MMModem *modem; + MMModemCapability capability, supported_capabilities; + + g_assert (G_IS_DBUS_OBJECT (object)); + + supported_capabilities = MM_MODEM_CAPABILITY_GSM_UMTS | MM_MODEM_CAPABILITY_LTE; +#if MM_CHECK_VERSION (1,14,0) + supported_capabilities |= MM_MODEM_CAPABILITY_5GNR; +#endif + + mm_object = MM_OBJECT (object); + modem = mm_object_get_modem (mm_object); + capability = mm_modem_get_current_capabilities (modem); + + return capability & supported_capabilities; +} + +static void +panel_add_device (CcNetworkPanel *self, NMDevice *device) +{ + NMDeviceType type; + NetDeviceEthernet *device_ethernet; + NetDeviceMobile *device_mobile; + NetDeviceBluetooth *device_bluetooth; + g_autoptr(GDBusObject) modem_object = NULL; + + /* does already exist */ + if (g_hash_table_lookup (self->nm_device_to_device, device) != NULL) + return; + + type = nm_device_get_device_type (device); + + g_debug ("device %s type %i path %s", + nm_device_get_udi (device), type, nm_object_get_path (NM_OBJECT (device))); + + /* map the NMDeviceType to the GType, or ignore */ + switch (type) { + case NM_DEVICE_TYPE_ETHERNET: + case NM_DEVICE_TYPE_INFINIBAND: + device_ethernet = net_device_ethernet_new (self->client, device); + gtk_box_append (GTK_BOX (self->box_wired), GTK_WIDGET (device_ethernet)); + g_ptr_array_add (self->ethernet_devices, device_ethernet); + g_hash_table_insert (self->nm_device_to_device, device, device_ethernet); + break; + case NM_DEVICE_TYPE_MODEM: + if (g_str_has_prefix (nm_device_get_udi (device), "/org/freedesktop/ModemManager1/Modem/")) { + if (self->modem_manager == NULL) { + g_warning ("Cannot grab information for modem at %s: No ModemManager support", + nm_device_get_udi (device)); + return; + } + + modem_object = g_dbus_object_manager_get_object (G_DBUS_OBJECT_MANAGER (self->modem_manager), + nm_device_get_udi (device)); + if (modem_object == NULL) { + g_warning ("Cannot grab information for modem at %s: Not found", + nm_device_get_udi (device)); + return; + } + + /* This will be handled by cellular panel */ + if (wwan_panel_supports_modem (modem_object)) + return; + } + + device_mobile = net_device_mobile_new (self->client, device, modem_object); + gtk_box_append (GTK_BOX (self->box_wired), GTK_WIDGET (device_mobile)); + g_ptr_array_add (self->mobile_devices, device_mobile); + g_hash_table_insert (self->nm_device_to_device, device, device_mobile); + break; + case NM_DEVICE_TYPE_BT: + device_bluetooth = net_device_bluetooth_new (self->client, device); + gtk_list_box_append (GTK_LIST_BOX (self->box_bluetooth), GTK_WIDGET (device_bluetooth)); + g_ptr_array_add (self->bluetooth_devices, device_bluetooth); + g_hash_table_insert (self->nm_device_to_device, device, device_bluetooth); + + /* Update the device_bluetooth section if we're adding a bluetooth + * device. This is a temporary solution though, for these will + * be handled by the future Mobile Broadband panel */ + update_bluetooth_section (self); + break; + + /* For Wi-Fi and VPN we handle connections separately; we correctly manage + * them, but not here. + */ + case NM_DEVICE_TYPE_WIFI: + case NM_DEVICE_TYPE_TUN: + /* And the rest we simply cannot deal with currently. */ + default: + return; + } +} + +static void +panel_remove_device (CcNetworkPanel *self, NMDevice *device) +{ + GtkWidget *net_device; + + net_device = g_hash_table_lookup (self->nm_device_to_device, device); + if (net_device == NULL) + return; + + g_ptr_array_remove (self->bluetooth_devices, net_device); + g_ptr_array_remove (self->ethernet_devices, net_device); + g_ptr_array_remove (self->mobile_devices, net_device); + g_hash_table_remove (self->nm_device_to_device, device); + + gtk_box_remove (GTK_BOX (gtk_widget_get_parent (net_device)), net_device); + + /* update vpn widgets */ + update_vpn_section (self); + + /* update device_bluetooth widgets */ + update_bluetooth_section (self); +} + +static void +connection_state_changed (CcNetworkPanel *self) +{ +} + +static void +active_connections_changed (CcNetworkPanel *self) +{ + const GPtrArray *connections; + int i, j; + + g_debug ("Active connections changed:"); + connections = nm_client_get_active_connections (self->client); + for (i = 0; connections && (i < connections->len); i++) { + NMActiveConnection *connection; + const GPtrArray *devices; + + connection = g_ptr_array_index (connections, i); + g_debug (" %s", nm_object_get_path (NM_OBJECT (connection))); + devices = nm_active_connection_get_devices (connection); + for (j = 0; devices && j < devices->len; j++) + g_debug (" %s", nm_device_get_udi (g_ptr_array_index (devices, j))); + if (NM_IS_VPN_CONNECTION (connection)) + g_debug (" VPN base connection: %s", nm_active_connection_get_specific_object_path (connection)); + + if (g_object_get_data (G_OBJECT (connection), "has-state-changed-handler") == NULL) { + g_signal_connect_object (connection, "notify::state", + G_CALLBACK (connection_state_changed), self, G_CONNECT_SWAPPED); + g_object_set_data (G_OBJECT (connection), "has-state-changed-handler", GINT_TO_POINTER (TRUE)); + } + } +} + +static void +device_managed_cb (CcNetworkPanel *self, GParamSpec *pspec, NMDevice *device) +{ + if (!nm_device_get_managed (device)) + return; + + panel_add_device (self, device); + panel_refresh_device_titles (self); +} + +static void +device_added_cb (CcNetworkPanel *self, NMDevice *device) +{ + g_debug ("New device added"); + + if (nm_device_get_managed (device)) + device_managed_cb (self, NULL, device); + else + g_signal_connect_object (device, "notify::managed", G_CALLBACK (device_managed_cb), self, G_CONNECT_SWAPPED); +} + +static void +device_removed_cb (CcNetworkPanel *self, NMDevice *device) +{ + g_debug ("Device removed"); + panel_remove_device (self, device); + panel_refresh_device_titles (self); + + g_signal_handlers_disconnect_by_func (device, + G_CALLBACK (device_managed_cb), + self); +} + +static void +manager_running (CcNetworkPanel *self) +{ + const GPtrArray *devices; + int i; + + /* clear all devices we added */ + if (!nm_client_get_nm_running (self->client)) { + g_debug ("NM disappeared"); + goto out; + } + + g_debug ("coldplugging devices"); + devices = nm_client_get_devices (self->client); + if (devices == NULL) { + g_debug ("No devices to add"); + return; + } + for (i = 0; i < devices->len; i++) { + NMDevice *device = g_ptr_array_index (devices, i); + device_added_cb (self, device); + } +out: + panel_refresh_device_titles (self); + + g_debug ("Calling handle_argv() after cold-plugging devices"); + handle_argv (self); +} + +static void +panel_add_vpn_device (CcNetworkPanel *self, NMConnection *connection) +{ + NetVpn *net_vpn; + guint i; + + /* does already exist */ + for (i = 0; i < self->vpns->len; i++) { + net_vpn = g_ptr_array_index (self->vpns, i); + if (net_vpn_get_connection (net_vpn) == connection) + return; + } + + net_vpn = net_vpn_new (self->client, connection); + gtk_list_box_append (GTK_LIST_BOX (self->box_vpn), GTK_WIDGET (net_vpn)); + + /* store in the devices array */ + g_ptr_array_add (self->vpns, net_vpn); + + /* update vpn widgets */ + update_vpn_section (self); +} + +static void +add_connection (CcNetworkPanel *self, NMConnection *connection) +{ + NMSettingConnection *s_con; + const gchar *type, *iface; + + s_con = NM_SETTING_CONNECTION (nm_connection_get_setting (connection, + NM_TYPE_SETTING_CONNECTION)); + type = nm_setting_connection_get_connection_type (s_con); + iface = nm_connection_get_interface_name (connection); + if (g_strcmp0 (type, "vpn") != 0 && iface == NULL) + return; + + /* Don't add the libvirtd bridge to the UI */ + if (g_strcmp0 (nm_setting_connection_get_interface_name (s_con), "virbr0") == 0) + return; + + g_debug ("add %s/%s remote connection: %s", + type, g_type_name_from_instance ((GTypeInstance*)connection), + nm_connection_get_path (connection)); + if (!iface) + panel_add_vpn_device (self, connection); +} + +static void +client_connection_removed_cb (CcNetworkPanel *self, NMConnection *connection) +{ + guint i; + + for (i = 0; i < self->vpns->len; i++) { + NetVpn *vpn = g_ptr_array_index (self->vpns, i); + if (net_vpn_get_connection (vpn) == connection) { + g_ptr_array_remove (self->vpns, vpn); + gtk_list_box_remove (GTK_LIST_BOX (self->box_vpn), GTK_WIDGET (vpn)); + update_vpn_section (self); + return; + } + } +} + +static void +panel_check_network_manager_version (CcNetworkPanel *self) +{ + const gchar *version; + + /* parse running version */ + version = nm_client_get_version (self->client); + if (version == NULL) { + GtkWidget *box; + GtkWidget *label; + g_autofree gchar *markup = NULL; + + box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 20); + gtk_box_set_homogeneous (GTK_BOX (box), TRUE); + gtk_widget_set_vexpand (box, TRUE); + adw_bin_set_child (ADW_BIN (self), box); + + label = gtk_label_new (_("Oops, something has gone wrong. Please contact your software vendor.")); + gtk_widget_set_vexpand (label, TRUE); + gtk_label_set_wrap (GTK_LABEL (label), TRUE); + gtk_widget_set_valign (label, GTK_ALIGN_END); + gtk_box_append (GTK_BOX (box), label); + + markup = g_strdup_printf ("<small><tt>%s</tt></small>", + _("NetworkManager needs to be running.")); + label = gtk_label_new (NULL); + gtk_widget_set_vexpand (label, TRUE); + gtk_label_set_markup (GTK_LABEL (label), markup); + gtk_label_set_wrap (GTK_LABEL (label), TRUE); + gtk_widget_set_valign (label, GTK_ALIGN_START); + gtk_box_append (GTK_BOX (box), label); + } else { + manager_running (self); + } +} + +static void +create_connection_cb (GtkWidget *button, + CcNetworkPanel *self) +{ + NetConnectionEditor *editor; + + editor = net_connection_editor_new (NULL, NULL, NULL, self->client); + gtk_window_set_transient_for (GTK_WINDOW (editor), + GTK_WINDOW (gtk_widget_get_native (GTK_WIDGET (self)))); + gtk_window_present (GTK_WINDOW (editor)); +} + +static void +cc_network_panel_map (GtkWidget *widget) +{ + GTK_WIDGET_CLASS (cc_network_panel_parent_class)->map (widget); + + /* is the user compiling against a new version, but not running + * the daemon? */ + panel_check_network_manager_version (CC_NETWORK_PANEL (widget)); +} + + +static void +cc_network_panel_class_init (CcNetworkPanelClass *klass) +{ + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + GObjectClass *object_class = G_OBJECT_CLASS (klass); + CcPanelClass *panel_class = CC_PANEL_CLASS (klass); + + panel_class->get_help_uri = cc_network_panel_get_help_uri; + + widget_class->map = cc_network_panel_map; + + object_class->get_property = cc_network_panel_get_property; + object_class->set_property = cc_network_panel_set_property; + object_class->dispose = cc_network_panel_dispose; + object_class->finalize = cc_network_panel_finalize; + + g_object_class_override_property (object_class, PROP_PARAMETERS, "parameters"); + + gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/control-center/network/cc-network-panel.ui"); + + gtk_widget_class_bind_template_child (widget_class, CcNetworkPanel, box_bluetooth); + gtk_widget_class_bind_template_child (widget_class, CcNetworkPanel, box_proxy); + gtk_widget_class_bind_template_child (widget_class, CcNetworkPanel, box_vpn); + gtk_widget_class_bind_template_child (widget_class, CcNetworkPanel, box_wired); + gtk_widget_class_bind_template_child (widget_class, CcNetworkPanel, container_bluetooth); + gtk_widget_class_bind_template_child (widget_class, CcNetworkPanel, empty_listbox); + gtk_widget_class_bind_template_child (widget_class, CcNetworkPanel, vpn_stack); + + gtk_widget_class_bind_template_callback (widget_class, create_connection_cb); +} + +static void +cc_network_panel_init (CcNetworkPanel *self) +{ + g_autoptr(GDBusConnection) system_bus = NULL; + g_autoptr(GError) error = NULL; + const GPtrArray *connections; + NetProxy *proxy; + guint i; + + g_resources_register (cc_network_get_resource ()); + + gtk_widget_init_template (GTK_WIDGET (self)); + + self->bluetooth_devices = g_ptr_array_new (); + self->ethernet_devices = g_ptr_array_new (); + self->mobile_devices = g_ptr_array_new (); + self->vpns = g_ptr_array_new (); + self->nm_device_to_device = g_hash_table_new (g_direct_hash, g_direct_equal); + + /* add the virtual proxy device */ + proxy = net_proxy_new (); + gtk_box_append (GTK_BOX (self->box_proxy), GTK_WIDGET (proxy)); + + /* Create and store a NMClient instance if it doesn't exist yet */ + if (!cc_object_storage_has_object (CC_OBJECT_NMCLIENT)) { + g_autoptr(NMClient) client = nm_client_new (NULL, NULL); + cc_object_storage_add_object (CC_OBJECT_NMCLIENT, client); + } + + /* use NetworkManager client */ + self->client = cc_object_storage_get_object (CC_OBJECT_NMCLIENT); + + g_signal_connect_object (self->client, "notify::nm-running" , + G_CALLBACK (manager_running), self, G_CONNECT_SWAPPED); + g_signal_connect_object (self->client, "notify::active-connections", + G_CALLBACK (active_connections_changed), self, G_CONNECT_SWAPPED); + g_signal_connect_object (self->client, "device-added", + G_CALLBACK (device_added_cb), self, G_CONNECT_SWAPPED); + g_signal_connect_object (self->client, "device-removed", + G_CALLBACK (device_removed_cb), self, G_CONNECT_SWAPPED); + + /* Setup ModemManager client */ + system_bus = g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, &error); + if (system_bus == NULL) { + g_warning ("Error connecting to system D-Bus: %s", + error->message); + } else { + self->modem_manager = mm_manager_new_sync (system_bus, + G_DBUS_OBJECT_MANAGER_CLIENT_FLAGS_NONE, + NULL, + &error); + if (self->modem_manager == NULL) + g_warning ("Error connecting to ModemManager: %s", + error->message); + } + + /* add remote settings such as VPN settings as virtual devices */ + g_signal_connect_object (self->client, NM_CLIENT_CONNECTION_ADDED, + G_CALLBACK (add_connection), self, G_CONNECT_SWAPPED); + g_signal_connect_object (self->client, NM_CLIENT_CONNECTION_REMOVED, + G_CALLBACK (client_connection_removed_cb), self, G_CONNECT_SWAPPED); + + /* Cold-plug existing connections */ + connections = nm_client_get_connections (self->client); + if (connections) { + for (i = 0; i < connections->len; i++) + add_connection (self, connections->pdata[i]); + } + + g_debug ("Calling handle_argv() after cold-plugging connections"); + handle_argv (self); +} diff --git a/panels/network/cc-network-panel.h b/panels/network/cc-network-panel.h new file mode 100644 index 0000000..9141ad0 --- /dev/null +++ b/panels/network/cc-network-panel.h @@ -0,0 +1,30 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- + * + * Copyright (C) 2010 Richard Hughes <richard@hughsie.com> + * + * 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/>. + * + */ + +#pragma once + +#include <shell/cc-panel.h> + +G_BEGIN_DECLS + +#define CC_TYPE_NETWORK_PANEL cc_network_panel_get_type() + +G_DECLARE_FINAL_TYPE (CcNetworkPanel, cc_network_panel, CC, NETWORK_PANEL, CcPanel) + +G_END_DECLS diff --git a/panels/network/cc-network-panel.ui b/panels/network/cc-network-panel.ui new file mode 100644 index 0000000..1ae1009 --- /dev/null +++ b/panels/network/cc-network-panel.ui @@ -0,0 +1,94 @@ +<?xml version="1.0" encoding="UTF-8"?> +<interface> + <template class="CcNetworkPanel" parent="CcPanel"> + + <child type="content"> + <object class="AdwPreferencesPage"> + <!-- Each group below will contain GtkStacks from the NetDevices --> + <child> + <object class="AdwPreferencesGroup"> + <child> + <object class="GtkBox" id="box_wired"> + <property name="orientation">vertical</property> + <property name="spacing">24</property> + </object> + </child> + </object> + </child> + <child> + <object class="AdwPreferencesGroup" id="container_bluetooth"> + <property name="visible">False</property> + <property name="title" translatable="yes">Other Devices</property> + <child> + <object class="GtkListBox" id="box_bluetooth"> + <property name="selection_mode">none</property> + <accessibility> + <property name="label" translatable="yes">Other Devices</property> + </accessibility> + <style> + <class name="boxed-list" /> + </style> + </object> + </child> + </object> + </child> + <child> + <object class="AdwPreferencesGRoup"> + <property name="title" translatable="yes">VPN</property> + <property name="header-suffix"> + <object class="GtkButton"> + <property name="icon_name">list-add-symbolic</property> + <accessibility> + <property name="label" translatable="yes">Add connection</property> + </accessibility> + <style> + <class name="flat" /> + </style> + <signal name="clicked" handler="create_connection_cb" object="CcNetworkPanel" swapped="no" /> + </object> + </property> + <child> + <object class="GtkStack" id="vpn_stack"> + <child> + <!-- "Not set up" row --> + <object class="GtkListBox" id="empty_listbox"> + <property name="selection_mode">none</property> + <style> + <class name="boxed-list" /> + </style> + <child> + <object class="AdwActionRow"> + <property name="activatable">False</property> + <property name="title" translatable="yes">Not set up</property> + </object> + </child> + </object> + </child> + <child> + <object class="GtkListBox" id="box_vpn"> + <property name="selection_mode">none</property> + <accessibility> + <property name="label" translatable="yes">VPN</property> + </accessibility> + <style> + <class name="boxed-list" /> + </style> + </object> + </child> + </object> + </child> + </object> + </child> + <child> + <object class="AdwPreferencesGroup"> + <child> + <object class="GtkBox" id="box_proxy"> + <property name="orientation">vertical</property> + </object> + </child> + </object> + </child> + </object> + </child> + </template> +</interface> diff --git a/panels/network/cc-qr-code.c b/panels/network/cc-qr-code.c new file mode 100644 index 0000000..8bb8a14 --- /dev/null +++ b/panels/network/cc-qr-code.c @@ -0,0 +1,186 @@ +/* -*- mode: c; c-basic-offset: 2; indent-tabs-mode: nil; -*- */ +/* cc-qr-code.c + * + * Copyright 2019 Purism SPC + * + * 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 3 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/>. + * + * Author(s): + * Mohammed Sadiq <sadiq@sadiqpk.org> + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "cc-qr-code" + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "cc-qr-code.h" +#include "qrcodegen.c" + +/** + * SECTION: cc-qr_code + * @title: CcQrCode + * @short_description: A Simple QR Code wrapper around libqrencode + * @include: "cc-qr-code.h" + * + * Generate a QR image from a given text. + */ + +#define BYTES_PER_R8G8B8 3 + +struct _CcQrCode +{ + GObject parent_instance; + + gchar *text; + GdkTexture *texture; + gint size; +}; + +G_DEFINE_TYPE (CcQrCode, cc_qr_code, G_TYPE_OBJECT) + + +static void +cc_qr_code_finalize (GObject *object) +{ + CcQrCode *self = (CcQrCode *)object; + + g_clear_object (&self->texture); + g_clear_pointer (&self->text, g_free); + + G_OBJECT_CLASS (cc_qr_code_parent_class)->finalize (object); +} + +static void +cc_qr_code_class_init (CcQrCodeClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = cc_qr_code_finalize; +} + +static void +cc_qr_code_init (CcQrCode *self) +{ +} + +CcQrCode * +cc_qr_code_new (void) +{ + return g_object_new (CC_TYPE_QR_CODE, NULL); +} + +gboolean +cc_qr_code_set_text (CcQrCode *self, + const gchar *text) +{ + g_return_val_if_fail (CC_IS_QR_CODE (self), FALSE); + g_return_val_if_fail (!text || *text, FALSE); + + if (g_strcmp0 (text, self->text) == 0) + return FALSE; + + g_clear_object (&self->texture); + g_free (self->text); + self->text = g_strdup (text); + + return TRUE; +} + +static void +cc_fill_pixel (GByteArray *array, + guint8 value, + int pixel_size) +{ + guint i; + + for (i = 0; i < pixel_size; i++) { + g_byte_array_append (array, &value, 1); /* R */ + g_byte_array_append (array, &value, 1); /* G */ + g_byte_array_append (array, &value, 1); /* B */ + } +} + +GdkPaintable * +cc_qr_code_get_paintable (CcQrCode *self, + gint size) +{ + uint8_t qr_code[qrcodegen_BUFFER_LEN_FOR_VERSION (qrcodegen_VERSION_MAX)]; + uint8_t temp_buf[qrcodegen_BUFFER_LEN_FOR_VERSION (qrcodegen_VERSION_MAX)]; + g_autoptr(GBytes) bytes = NULL; + GByteArray *qr_matrix; + gint pixel_size, qr_size, total_size; + gint column, row, i; + gboolean success = FALSE; + + g_return_val_if_fail (CC_IS_QR_CODE (self), NULL); + g_return_val_if_fail (size > 0, NULL); + + if (!self->text || !*self->text) + { + g_warn_if_reached (); + cc_qr_code_set_text (self, "invalid text"); + } + + if (self->texture && self->size == size) + return GDK_PAINTABLE (self->texture); + + self->size = size; + + success = qrcodegen_encodeText (self->text, + temp_buf, + qr_code, + qrcodegen_Ecc_LOW, + qrcodegen_VERSION_MIN, + qrcodegen_VERSION_MAX, + qrcodegen_Mask_AUTO, + FALSE); + + if (!success) + return NULL; + + qr_size = qrcodegen_getSize(qr_code); + pixel_size = MAX (1, size / (qr_size)); + total_size = qr_size * pixel_size; + qr_matrix = g_byte_array_sized_new (total_size * total_size * pixel_size * BYTES_PER_R8G8B8); + + for (column = 0; column < total_size; column++) + { + for (i = 0; i < pixel_size; i++) + { + for (row = 0; row < total_size / pixel_size; row++) + { + if (qrcodegen_getModule (qr_code, column, row)) + cc_fill_pixel (qr_matrix, 0x00, pixel_size); + else + cc_fill_pixel (qr_matrix, 0xff, pixel_size); + } + } + } + + bytes = g_byte_array_free_to_bytes (qr_matrix); + + g_clear_object (&self->texture); + self->texture = gdk_memory_texture_new (total_size, + total_size, + GDK_MEMORY_R8G8B8, + bytes, + total_size * BYTES_PER_R8G8B8); + + return GDK_PAINTABLE (self->texture); +} diff --git a/panels/network/cc-qr-code.h b/panels/network/cc-qr-code.h new file mode 100644 index 0000000..844dd2a --- /dev/null +++ b/panels/network/cc-qr-code.h @@ -0,0 +1,43 @@ +/* -*- mode: c; c-basic-offset: 2; indent-tabs-mode: nil; -*- */ +/* cc-qr-code.h + * + * Copyright 2019 Purism SPC + * + * 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 3 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/>. + * + * Author(s): + * Mohammed Sadiq <sadiq@sadiqpk.org> + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include <gtk/gtk.h> +#include <glib-object.h> +#include <cairo.h> + +G_BEGIN_DECLS + +#define CC_TYPE_QR_CODE (cc_qr_code_get_type ()) + +G_DECLARE_FINAL_TYPE (CcQrCode, cc_qr_code, CC, QR_CODE, GObject) + +CcQrCode *cc_qr_code_new (void); +gboolean cc_qr_code_set_text (CcQrCode *self, + const gchar *text); +GdkPaintable *cc_qr_code_get_paintable (CcQrCode *self, + gint size); + +G_END_DECLS 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; +} diff --git a/panels/network/cc-wifi-connection-list.h b/panels/network/cc-wifi-connection-list.h new file mode 100644 index 0000000..eeec65f --- /dev/null +++ b/panels/network/cc-wifi-connection-list.h @@ -0,0 +1,41 @@ +/* + * 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/>. + */ + +#pragma once + +#include <adwaita.h> +#include <NetworkManager.h> + +G_BEGIN_DECLS + +#define CC_TYPE_WIFI_CONNECTION_LIST (cc_wifi_connection_list_get_type()) + +G_DECLARE_FINAL_TYPE (CcWifiConnectionList, cc_wifi_connection_list, CC, WIFI_CONNECTION_LIST, AdwBin) + +CcWifiConnectionList *cc_wifi_connection_list_new (NMClient *client, + NMDeviceWifi *device, + gboolean hide_unavailable, + gboolean show_aps, + gboolean checkable); + + +void cc_wifi_connection_list_freeze (CcWifiConnectionList *list); +void cc_wifi_connection_list_thaw (CcWifiConnectionList *list); + +GtkListBox *cc_wifi_connection_list_get_list_box (CcWifiConnectionList *self); + +G_END_DECLS diff --git a/panels/network/cc-wifi-connection-row.c b/panels/network/cc-wifi-connection-row.c new file mode 100644 index 0000000..608dda6 --- /dev/null +++ b/panels/network/cc-wifi-connection-row.c @@ -0,0 +1,678 @@ +/* + * 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 <glib/gi18n.h> +#include <config.h> +#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 ("<i>%s</i>%s<i>%s</i>", + 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)); + +} + diff --git a/panels/network/cc-wifi-connection-row.h b/panels/network/cc-wifi-connection-row.h new file mode 100644 index 0000000..4d6f7ba --- /dev/null +++ b/panels/network/cc-wifi-connection-row.h @@ -0,0 +1,54 @@ +/* + * 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/>. + */ + +#pragma once + +#include <adwaita.h> +#include <NetworkManager.h> + +G_BEGIN_DECLS + +typedef struct _CcWifiConnectionRow CcWifiConnectionRow; + +#define CC_TYPE_WIFI_CONNECTION_ROW (cc_wifi_connection_row_get_type ()) +G_DECLARE_FINAL_TYPE (CcWifiConnectionRow, cc_wifi_connection_row, CC, WIFI_CONNECTION_ROW, AdwActionRow) + +CcWifiConnectionRow *cc_wifi_connection_row_new (NMDeviceWifi *device, + NMConnection *connection, + GPtrArray *aps, + gboolean checkable, + gboolean known_connection); + +gboolean cc_wifi_connection_row_get_checkable (CcWifiConnectionRow *row); +gboolean cc_wifi_connection_row_get_checked (CcWifiConnectionRow *row); +NMDeviceWifi *cc_wifi_connection_row_get_device (CcWifiConnectionRow *row); +const GPtrArray *cc_wifi_connection_row_get_access_points (CcWifiConnectionRow *row); +NMConnection *cc_wifi_connection_row_get_connection (CcWifiConnectionRow *row); + +void cc_wifi_connection_row_set_checked (CcWifiConnectionRow *row, + gboolean value); + +NMAccessPoint *cc_wifi_connection_row_best_access_point (CcWifiConnectionRow *row); +void cc_wifi_connection_row_add_access_point (CcWifiConnectionRow *row, + NMAccessPoint *ap); +gboolean cc_wifi_connection_row_remove_access_point (CcWifiConnectionRow *row, + NMAccessPoint *ap); +gboolean cc_wifi_connection_row_has_access_point (CcWifiConnectionRow *row, + NMAccessPoint *ap); + +void cc_wifi_connection_row_update (CcWifiConnectionRow *row); +G_END_DECLS diff --git a/panels/network/cc-wifi-connection-row.ui b/panels/network/cc-wifi-connection-row.ui new file mode 100644 index 0000000..d02dfe6 --- /dev/null +++ b/panels/network/cc-wifi-connection-row.ui @@ -0,0 +1,73 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- Generated with glade 3.20.0 --> +<interface> + <requires lib="gtk+" version="3.20"/> + <template class="CcWifiConnectionRow" parent="AdwActionRow"> + <property name="activatable">True</property> + <child type="prefix"> + <object class="GtkBox"> + <property name="valign">center</property> + <property name="margin_end">6</property> + <child> + <object class="GtkCheckButton" id="checkbutton"> + <property name="halign">start</property> + <property name="valign">center</property> + <property name="margin_end">6</property> + </object> + </child> + <child> + <object class="GtkImage" id="strength_icon"> + <property name="icon_name">network-wireless-signal-excellent-symbolic</property> + <property name="icon_size">1</property> + </object> + </child> + <child> + <object class="GtkImage" id="encrypted_icon"> + <property name="valign">end</property> + <property name="pixel-size">8</property> + </object> + </child> + </object> + </child> + + <child type="suffix"> + <object class="GtkBox"> + <property name="valign">center</property> + <property name="margin_start">6</property> + <property name="spacing">6</property> + <child> + <object class="GtkLabel" id="active_label"> + <property name="margin_end">6</property> + <property name="label" translatable="yes">Connected</property> + <style> + <class name="dim-label"/> + </style> + </object> + </child> + <child> + <object class="GtkSpinner" id="connecting_spinner"> + <property name="name">connecting_spinner</property> + <property name="halign">center</property> + <property name="valign">center</property> + </object> + </child> + <child> + <object class="GtkButton" id="options_button"> + <property name="name">options_button</property> + <property name="icon_name">emblem-system-symbolic</property> + <property name="halign">center</property> + <property name="valign">center</property> + <signal name="clicked" handler="configure_clicked_cb" object="CcWifiConnectionRow" swapped="yes"/> + <accessibility> + <property name="label" translatable="yes">Options…</property> + </accessibility> + <style> + <class name="flat"/> + </style> + </object> + </child> + </object> + </child> + + </template> +</interface> diff --git a/panels/network/cc-wifi-hotspot-dialog.c b/panels/network/cc-wifi-hotspot-dialog.c new file mode 100644 index 0000000..0610e24 --- /dev/null +++ b/panels/network/cc-wifi-hotspot-dialog.c @@ -0,0 +1,553 @@ +/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* cc-wifi-hotspot-dialog.c + * + * Copyright 2019 Purism SPC + * + * 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 3 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/>. + * + * Author(s): + * Mohammed Sadiq <sadiq@sadiqpk.org> + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "cc-wifi-hotspot-dialog" + +#include <config.h> +#include <glib/gi18n.h> +#include <libmm-glib.h> + +#include "cc-wifi-hotspot-dialog.h" +#include "cc-network-resources.h" +#include "ui-helpers.h" + +/** + * @short_description: WWAN network type selection dialog + */ + +struct _CcWifiHotspotDialog +{ + GtkDialog parent_instance; + + GtkLabel *connection_label; + GtkEntry *name_entry; + GtkEntry *password_entry; + GtkLabel *error_label; + GtkButton *ok_button; + + GCancellable *cancellable; + + NMDeviceWifi *device; + NMConnection *connection; + gchar *host_name; + gboolean wpa_supported; /* WPA/WPA2 supported */ +}; + +G_DEFINE_TYPE (CcWifiHotspotDialog, cc_wifi_hotspot_dialog, GTK_TYPE_DIALOG) + +static gchar * +get_random_wpa_key (void) +{ + gchar *key; + gint i; + + key = g_malloc (10 * sizeof (key)); + for (i = 0; i < 8; i++) + { + gint c = 0; + /* too many non alphanumeric characters are hard to remember for humans */ + while (!g_ascii_isalnum (c)) + c = g_random_int_range (33, 126); + + key[i] = (gchar) c; + } + key[i] = '\0'; + + return key; +} + +static gchar * +get_random_wep_key (void) +{ + const gchar *hexdigits = "0123456789abcdef"; + gchar *key; + gint i; + + key = g_malloc (12 * sizeof (key)); + + /* generate a 10-digit hex WEP key */ + for (i = 0; i < 10; i++) + { + gint digit; + digit = g_random_int_range (0, 16); + key[i] = hexdigits[digit]; + } + + key[i] = '\0'; + + return key; +} + +static void +wifi_hotspot_dialog_update_main_label (CcWifiHotspotDialog *self) +{ + NMAccessPoint *ap; + GBytes *ssid = NULL; + g_autofree gchar *active_ssid = NULL; + g_autofree gchar *escape = NULL; + g_autofree gchar *ssid_text = NULL; + g_autofree gchar *label = NULL; + + g_assert (CC_IS_WIFI_HOTSPOT_DIALOG (self)); + + gtk_label_set_markup (self->connection_label, ""); + + if (!self->device) + return; + + ap = nm_device_wifi_get_active_access_point (self->device); + + if (ap) + ssid = nm_access_point_get_ssid (ap); + if (ssid) + active_ssid = nm_utils_ssid_to_utf8 (g_bytes_get_data (ssid, NULL), g_bytes_get_size (ssid)); + + if (!active_ssid || !*active_ssid) + return; + + escape = g_markup_escape_text (active_ssid, -1); + ssid_text = g_strdup_printf ("<b>%s</b>", escape); + /* TRANSLATORS: ‘%s’ is a Wi-Fi Network(SSID) name */ + label = g_strdup_printf (_("Turning on the hotspot will disconnect from %s, " + "and it will not be possible to access the internet through Wi-Fi."), ssid_text); + gtk_label_set_markup (self->connection_label, label); +} + +static void +get_secrets_cb (GObject *source_object, + GAsyncResult *res, + gpointer data) +{ + CcWifiHotspotDialog *self; + g_autoptr(GVariant) secrets = NULL; + NMSettingWirelessSecurity *security_setting; + const gchar *key; + 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; + } + + self = CC_WIFI_HOTSPOT_DIALOG (data); + + nm_connection_update_secrets (self->connection, + NM_SETTING_WIRELESS_SECURITY_SETTING_NAME, + secrets, &error); + if (error) + { + g_warning ("Error updating secrets: %s", error->message); + return; + } + + security_setting = nm_connection_get_setting_wireless_security (self->connection); + if (self->wpa_supported) + key = nm_setting_wireless_security_get_psk (security_setting); + else + key = nm_setting_wireless_security_get_wep_key (security_setting, 0); + + if (key) + gtk_editable_set_text (GTK_EDITABLE (self->password_entry), key); + + nm_connection_clear_secrets (self->connection); +} + +static void +wifi_hotspot_dialog_update_entries (CcWifiHotspotDialog *self) +{ + NMSettingWireless *setting; + GBytes *ssid; + g_autofree gchar *ssid_text = NULL; + + g_assert (CC_IS_WIFI_HOTSPOT_DIALOG (self)); + + gtk_editable_set_text (GTK_EDITABLE (self->name_entry), ""); + gtk_editable_set_text (GTK_EDITABLE (self->password_entry), ""); + + if (!self->connection) + return; + + setting = nm_connection_get_setting_wireless (self->connection); + + ssid = nm_setting_wireless_get_ssid (setting); + ssid_text = nm_utils_ssid_to_utf8 (g_bytes_get_data (ssid, NULL), g_bytes_get_size (ssid)); + + if (!ssid_text && self->host_name) + ssid_text = g_strdup (self->host_name); + + if (ssid_text) + gtk_editable_set_text (GTK_EDITABLE (self->name_entry), ssid_text); + + if (!NM_IS_REMOTE_CONNECTION (self->connection)) + return; + + /* Secrets may not be already loaded, we have to manually load it. */ + nm_remote_connection_get_secrets_async (NM_REMOTE_CONNECTION (self->connection), + NM_SETTING_WIRELESS_SECURITY_SETTING_NAME, + self->cancellable, + get_secrets_cb, + self); +} + +static gboolean +hotspot_password_is_valid (CcWifiHotspotDialog *self, + const gchar *password) +{ + g_assert (CC_IS_WIFI_HOTSPOT_DIALOG (self)); + + if (!self->device) + return FALSE; + + if (!password || !*password) + return TRUE; + + if (self->wpa_supported) + return nm_utils_wpa_psk_valid (password); + else + return nm_utils_wep_key_valid (password, NM_WEP_KEY_TYPE_KEY); +} + +static void +hotspot_entry_changed_cb (CcWifiHotspotDialog *self) +{ + const gchar *ssid, *password, *error_label; + gboolean valid_ssid, valid_password; + + g_assert (CC_IS_WIFI_HOTSPOT_DIALOG (self)); + + valid_ssid = valid_password = FALSE; + ssid = gtk_editable_get_text (GTK_EDITABLE (self->name_entry)); + password = gtk_editable_get_text (GTK_EDITABLE (self->password_entry)); + + if (ssid && *ssid) + { + valid_ssid = TRUE; + widget_unset_error (GTK_WIDGET (self->name_entry)); + } + else + widget_set_error (GTK_WIDGET (self->name_entry)); + + valid_password = hotspot_password_is_valid (self, password); + + if (valid_password) + { + error_label = ""; + widget_unset_error (GTK_WIDGET (self->password_entry)); + } + else + { + if (strlen (password) < 8) + { + error_label = _("Must have a minimum of 8 characters"); + } + else + { + guint max_chars = self->wpa_supported ? 63 : 16; + error_label = g_strdup_printf (ngettext ("Must have a maximum of %d character", + "Must have a maximum of %d characters", max_chars), max_chars); + } + + widget_set_error (GTK_WIDGET(self->password_entry)); + } + + gtk_label_set_label (self->error_label, error_label); + gtk_widget_set_sensitive (GTK_WIDGET (self->ok_button), + valid_ssid && valid_password); +} + +static void +generate_password_clicked_cb (CcWifiHotspotDialog *self) +{ + g_autofree gchar *key = NULL; + + g_assert (CC_IS_WIFI_HOTSPOT_DIALOG (self)); + + if (self->wpa_supported) + key = get_random_wpa_key (); + else + key = get_random_wep_key (); + + gtk_editable_set_text (GTK_EDITABLE (self->password_entry), key); +} + +static void +hotspot_update_wireless_settings (CcWifiHotspotDialog *self) +{ + NMSettingWireless *setting; + g_autoptr(GBytes) ssid = NULL; + const gchar *ssid_text; + NMDeviceWifiCapabilities capabilities; + + g_assert (CC_IS_WIFI_HOTSPOT_DIALOG (self)); + + if (nm_connection_get_setting_wireless (self->connection) == NULL) + nm_connection_add_setting (self->connection, nm_setting_wireless_new ()); + + setting = nm_connection_get_setting_wireless (self->connection); + + capabilities = nm_device_wifi_get_capabilities (self->device); + if (capabilities & NM_WIFI_DEVICE_CAP_AP) + g_object_set (setting, "mode", "ap", NULL); + else + g_object_set (setting, "mode", "adhoc", NULL); + + ssid_text = gtk_editable_get_text (GTK_EDITABLE (self->name_entry)); + ssid = g_bytes_new (ssid_text, strlen (ssid_text)); + g_object_set (setting, "ssid", ssid, NULL); +} + +static void +hotspot_update_wireless_security_settings (CcWifiHotspotDialog *self) +{ + NMSettingWirelessSecurity *setting; + const gchar *value, *key_type; + + g_assert (CC_IS_WIFI_HOTSPOT_DIALOG (self)); + + if (nm_connection_get_setting_wireless_security (self->connection) == NULL) + nm_connection_add_setting (self->connection, nm_setting_wireless_security_new ()); + + setting = nm_connection_get_setting_wireless_security (self->connection); + nm_setting_wireless_security_clear_protos (setting); + nm_setting_wireless_security_clear_pairwise (setting); + nm_setting_wireless_security_clear_groups (setting); + value = gtk_editable_get_text (GTK_EDITABLE (self->password_entry)); + + if (self->wpa_supported) + key_type = "psk"; + else + key_type = "wep-key0"; + + if (self->wpa_supported) + g_object_set (setting, "key-mgmt", "wpa-psk", NULL); + else + g_object_set (setting, + "key-mgmt", "none", + "wep-key-type", NM_WEP_KEY_TYPE_KEY, + NULL); + + if (!value || !*value) + { + g_autofree gchar *key = NULL; + + if (self->wpa_supported) + key = get_random_wpa_key (); + else + key = get_random_wep_key (); + + g_object_set (setting, key_type, key, NULL); + } + else + g_object_set (setting, key_type, value, NULL); + + if (self->wpa_supported) + { + NMDeviceWifiCapabilities caps; + + caps = nm_device_wifi_get_capabilities (self->device); + + if (caps & NM_WIFI_DEVICE_CAP_RSN) + { + nm_setting_wireless_security_add_proto (setting, "rsn"); + nm_setting_wireless_security_add_pairwise (setting, "ccmp"); + nm_setting_wireless_security_add_group (setting, "ccmp"); + } + else if (caps & NM_WIFI_DEVICE_CAP_WPA) + { + nm_setting_wireless_security_add_proto (setting, "wpa"); + nm_setting_wireless_security_add_pairwise (setting, "tkip"); + nm_setting_wireless_security_add_group (setting, "tkip"); + } + } +} + +static void +cc_wifi_hotspot_dialog_finalize (GObject *object) +{ + CcWifiHotspotDialog *self = (CcWifiHotspotDialog *)object; + + g_cancellable_cancel(self->cancellable); + g_clear_object (&self->cancellable); + g_clear_pointer (&self->host_name, g_free); + g_clear_object (&self->device); + g_clear_object (&self->connection); + + G_OBJECT_CLASS (cc_wifi_hotspot_dialog_parent_class)->finalize (object); +} + +static void +cc_wifi_hotspot_dialog_show (GtkWidget *widget) +{ + CcWifiHotspotDialog *self = (CcWifiHotspotDialog *)widget; + g_warn_if_fail (self->device != NULL); + + gtk_widget_grab_focus (GTK_WIDGET (self->ok_button)); + wifi_hotspot_dialog_update_entries (self); + + if (!self->connection) + if (self->host_name) + gtk_editable_set_text (GTK_EDITABLE (self->name_entry), self->host_name); + + GTK_WIDGET_CLASS (cc_wifi_hotspot_dialog_parent_class)->show (widget); +} + +static void +cc_wifi_hotspot_dialog_response (GtkDialog *dialog, + gint response_id) +{ + CcWifiHotspotDialog *self = CC_WIFI_HOTSPOT_DIALOG (dialog); + NMSetting *setting; + + if (response_id != GTK_RESPONSE_APPLY) + return; + + if (!self->connection) + self->connection = NM_CONNECTION (nm_simple_connection_new ()); + + if (nm_connection_get_setting_connection (self->connection) == NULL) + { + setting = nm_setting_connection_new (); + g_object_set (setting, + "type", "802-11-wireless", + "id", "Hotspot", + "autoconnect", FALSE, + NULL); + nm_connection_add_setting (self->connection, setting); + } + + if (nm_connection_get_setting_ip4_config (self->connection) == NULL) + { + setting = nm_setting_ip4_config_new (); + g_object_set (setting, "method", "shared", NULL); + nm_connection_add_setting (self->connection, setting); + } + + hotspot_update_wireless_settings (self); + hotspot_update_wireless_security_settings (self); +} + +static void +cc_wifi_hotspot_dialog_class_init (CcWifiHotspotDialogClass *klass) +{ + GtkDialogClass *dialog_class = GTK_DIALOG_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = cc_wifi_hotspot_dialog_finalize; + + widget_class->show = cc_wifi_hotspot_dialog_show; + dialog_class->response = cc_wifi_hotspot_dialog_response; + + gtk_widget_class_set_template_from_resource (widget_class, + "/org/gnome/control-center/network/cc-wifi-hotspot-dialog.ui"); + + gtk_widget_class_bind_template_child (widget_class, CcWifiHotspotDialog, connection_label); + gtk_widget_class_bind_template_child (widget_class, CcWifiHotspotDialog, name_entry); + gtk_widget_class_bind_template_child (widget_class, CcWifiHotspotDialog, password_entry); + gtk_widget_class_bind_template_child (widget_class, CcWifiHotspotDialog, error_label); + gtk_widget_class_bind_template_child (widget_class, CcWifiHotspotDialog, ok_button); + + gtk_widget_class_bind_template_callback (widget_class, hotspot_entry_changed_cb); + gtk_widget_class_bind_template_callback (widget_class, generate_password_clicked_cb); +} + +static void +cc_wifi_hotspot_dialog_init (CcWifiHotspotDialog *self) +{ + self->cancellable = g_cancellable_new (); + + gtk_widget_init_template (GTK_WIDGET (self)); +} + +CcWifiHotspotDialog * +cc_wifi_hotspot_dialog_new (GtkWindow *parent_window) +{ + g_return_val_if_fail (GTK_IS_WINDOW (parent_window), NULL); + + return g_object_new (CC_TYPE_WIFI_HOTSPOT_DIALOG, + "use-header-bar", TRUE, + "transient-for", parent_window, + NULL); +} + +void +cc_wifi_hotspot_dialog_set_hostname (CcWifiHotspotDialog *self, + const gchar *host_name) +{ + g_return_if_fail (CC_IS_WIFI_HOTSPOT_DIALOG (self)); + + g_clear_pointer (&self->host_name, g_free); + self->host_name = g_strdup (host_name); +} + +void +cc_wifi_hotspot_dialog_set_device (CcWifiHotspotDialog *self, + NMDeviceWifi *device) +{ + g_return_if_fail (CC_IS_WIFI_HOTSPOT_DIALOG (self)); + g_return_if_fail (NM_IS_DEVICE_WIFI (device)); + + g_set_object (&self->device, device); + + if (device) + { + NMDeviceWifiCapabilities caps; + + caps = nm_device_wifi_get_capabilities (device); + self->wpa_supported = FALSE; + + if (caps & NM_WIFI_DEVICE_CAP_AP) + if (caps & (NM_WIFI_DEVICE_CAP_RSN | NM_WIFI_DEVICE_CAP_WPA)) + self->wpa_supported = TRUE; + } + + wifi_hotspot_dialog_update_main_label (self); +} + +NMConnection * +cc_wifi_hotspot_dialog_get_connection (CcWifiHotspotDialog *self) +{ + g_return_val_if_fail (CC_IS_WIFI_HOTSPOT_DIALOG (self), NULL); + + return self->connection; +} + +void +cc_wifi_hotspot_dialog_set_connection (CcWifiHotspotDialog *self, + NMConnection *connection) +{ + NMSettingWireless *setting; + + g_return_if_fail (CC_IS_WIFI_HOTSPOT_DIALOG (self)); + g_return_if_fail (NM_IS_CONNECTION (connection)); + + setting = nm_connection_get_setting_wireless (connection); + g_return_if_fail (setting); + + g_set_object (&self->connection, connection); +} diff --git a/panels/network/cc-wifi-hotspot-dialog.h b/panels/network/cc-wifi-hotspot-dialog.h new file mode 100644 index 0000000..29a326d --- /dev/null +++ b/panels/network/cc-wifi-hotspot-dialog.h @@ -0,0 +1,44 @@ +/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* wifi-hotspot-dialog.h + * + * Copyright 2019 Purism SPC + * + * 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 3 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/>. + * + * Author(s): + * Mohammed Sadiq <sadiq@sadiqpk.org> + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include <gtk/gtk.h> +#include <NetworkManager.h> + +G_BEGIN_DECLS + +#define CC_TYPE_WIFI_HOTSPOT_DIALOG (cc_wifi_hotspot_dialog_get_type()) +G_DECLARE_FINAL_TYPE (CcWifiHotspotDialog, cc_wifi_hotspot_dialog, CC, WIFI_HOTSPOT_DIALOG, GtkDialog) + +CcWifiHotspotDialog *cc_wifi_hotspot_dialog_new (GtkWindow *parent_window); +void cc_wifi_hotspot_dialog_set_hostname (CcWifiHotspotDialog *self, + const gchar *host_name); +void cc_wifi_hotspot_dialog_set_device (CcWifiHotspotDialog *self, + NMDeviceWifi *device); +NMConnection *cc_wifi_hotspot_dialog_get_connection (CcWifiHotspotDialog *self); +void cc_wifi_hotspot_dialog_set_connection (CcWifiHotspotDialog *self, + NMConnection *connection); + +G_END_DECLS diff --git a/panels/network/cc-wifi-hotspot-dialog.ui b/panels/network/cc-wifi-hotspot-dialog.ui new file mode 100644 index 0000000..1a69c46 --- /dev/null +++ b/panels/network/cc-wifi-hotspot-dialog.ui @@ -0,0 +1,140 @@ +<?xml version="1.0" encoding="UTF-8"?> +<interface> + <template class="CcWifiHotspotDialog" parent="GtkDialog"> + <property name="title" translatable="yes">Turn On Wi-Fi Hotspot?</property> + <property name="modal">1</property> + <property name="destroy-with-parent">1</property> + <property name="hide-on-close">True</property> + + <child internal-child="headerbar"> + <object class="GtkHeaderBar"> + <property name="show-title-buttons">0</property> + </object> + </child> + + <child internal-child="content_area"> + <object class="GtkBox"> + <property name="orientation">vertical</property> + <property name="margin-top">30</property> + <property name="margin-bottom">30</property> + <property name="margin-start">30</property> + <property name="margin-end">30</property> + <property name="spacing">20</property> + + <child> + <object class="GtkLabel" id="label"> + <property name="wrap">1</property> + <property name="max-width-chars">50</property> + <property name="label" translatable="yes">Wi-Fi hotspot allows others to share your internet connection, by creating a Wi-Fi network that they can connect to. To do this, you must have an internet connection through a source other than Wi-Fi.</property> + <property name="xalign">0.0</property> + </object> + </child> + + <child> + <object class="GtkLabel" id="connection_label"> + <property name="margin-bottom">18</property> + <property name="wrap">1</property> + <property name="max-width-chars">40</property> + <property name="use-markup">1</property> + <property name="xalign">0.0</property> + </object> + </child> + + <child> + <object class="GtkGrid"> + <property name="row-spacing">6</property> + <property name="column-spacing">12</property> + + <!-- Hotspot SSID Name --> + <child> + <object class="GtkLabel"> + <property name="label" translatable="yes">Network Name</property> + <property name="halign">end</property> + <property name="mnemonic_widget">name_entry</property> + <layout> + <property name="column">0</property> + <property name="row">0</property> + </layout> + </object> + </child> + <child> + <object class="GtkEntry" id="name_entry"> + <property name="hexpand">1</property> + <property name="max-length">64</property> + <signal name="changed" handler="hotspot_entry_changed_cb" swapped="yes" /> + <layout> + <property name="column">1</property> + <property name="row">0</property> + </layout> + </object> + </child> + + <!-- Hotspot Password --> + <child> + <object class="GtkLabel"> + <property name="label" translatable="yes">Password</property> + <property name="halign">end</property> + <property name="mnemonic_widget">password_entry</property> + <layout> + <property name="column">0</property> + <property name="row">1</property> + </layout> + </object> + </child> + <child> + <object class="GtkEntry" id="password_entry"> + <property name="max-length">64</property> + <property name="secondary-icon-name">view-refresh-symbolic</property> + <property name="secondary-icon-tooltip-text" translatable="yes">Generate Random Password</property> + <property name="placeholder-text" translatable="yes">Autogenerate Password</property> + <signal name="icon-press" handler="generate_password_clicked_cb" swapped="yes" /> + <signal name="changed" handler="hotspot_entry_changed_cb" swapped="yes" /> + <layout> + <property name="column">1</property> + <property name="row">1</property> + </layout> + </object> + </child> + + <!-- Error Label --> + <child> + <object class="GtkLabel" id="error_label"> + <property name="halign">start</property> + <layout> + <property name="column">1</property> + <property name="row">2</property> + </layout> + <style> + <class name="dim-label" /> + </style> + <attributes> + <attribute name="scale" value="0.83"/> + </attributes> + </object> + </child> + + </object> + </child> + + </object> + </child> + + <child type="action"> + <object class="GtkButton" id="cancel_button"> + <property name="use-underline">1</property> + <property name="label" translatable="yes">_Cancel</property> + </object> + </child> + <child type="action"> + <object class="GtkButton" id="ok_button"> + <property name="use-underline">1</property> + <property name="label" translatable="yes">_Turn On</property> + </object> + </child> + + <action-widgets> + <action-widget response="cancel">cancel_button</action-widget> + <action-widget response="apply" default="true">ok_button</action-widget> + </action-widgets> + </template> +</interface> diff --git a/panels/network/cc-wifi-panel.c b/panels/network/cc-wifi-panel.c new file mode 100644 index 0000000..e53bae7 --- /dev/null +++ b/panels/network/cc-wifi-panel.c @@ -0,0 +1,1079 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- + * + * Copyright (C) 2017 Georges Basile Stavracas Neto <georges.stavracas@gmail.com> + * + * 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-network-resources.h" +#include "cc-wifi-panel.h" +#include "cc-qr-code.h" +#include "net-device-wifi.h" +#include "network-dialogs.h" +#include "panel-common.h" +#include "cc-list-row.h" + +#include "shell/cc-application.h" +#include "shell/cc-debug.h" +#include "shell/cc-object-storage.h" + +#include <glib/gi18n.h> +#include <NetworkManager.h> + +#define QR_IMAGE_SIZE 180 + +typedef enum +{ + OPERATION_NULL, + OPERATION_SHOW_DEVICE, + OPERATION_CREATE_WIFI, + OPERATION_CONNECT_HIDDEN, + OPERATION_CONNECT_8021X +} CmdlineOperation; + +struct _CcWifiPanel +{ + CcPanel parent; + + /* RFKill (Airplane Mode) */ + GDBusProxy *rfkill_proxy; + CcListRow *rfkill_row; + GtkWidget *rfkill_widget; + + /* Main widgets */ + GtkStack *center_stack; + GtkStack *header_stack; + GtkBox *hotspot_box; + GtkLabel *list_label; + GtkStack *main_stack; + GtkWidget *spinner; + GtkStack *stack; + GtkPicture *wifi_qr_image; + CcQrCode *qr_code; + + NMClient *client; + + GPtrArray *devices; + + GBinding *spinner_binding; + + /* Command-line arguments */ + CmdlineOperation arg_operation; + gchar *arg_device; + gchar *arg_access_point; +}; + +static void rfkill_switch_notify_activate_cb (CcListRow *rfkill_row, + GParamSpec *pspec, + CcWifiPanel *self); + +static void update_devices_names (CcWifiPanel *self); + +G_DEFINE_TYPE (CcWifiPanel, cc_wifi_panel, CC_TYPE_PANEL) + +enum +{ + PROP_0, + PROP_PARAMETERS, + N_PROPS +}; + +/* Static init function */ + +static void +update_panel_visibility (NMClient *client) +{ + const GPtrArray *devices; + CcApplication *application; + gboolean visible; + guint i; + + CC_TRACE_MSG ("Updating Wi-Fi panel visibility"); + + devices = nm_client_get_devices (client); + visible = FALSE; + + for (i = 0; devices && i < devices->len; i++) + { + NMDevice *device = g_ptr_array_index (devices, i); + + visible |= NM_IS_DEVICE_WIFI (device); + + if (visible) + break; + } + + /* Set the new visibility */ + application = CC_APPLICATION (g_application_get_default ()); + cc_shell_model_set_panel_visibility (cc_application_get_model (application), + "wifi", + visible ? CC_PANEL_VISIBLE : CC_PANEL_VISIBLE_IN_SEARCH); + + g_debug ("Wi-Fi panel visible: %s", visible ? "yes" : "no"); +} + +void +cc_wifi_panel_static_init_func (void) +{ + g_autoptr(NMClient) client = NULL; + + g_debug ("Monitoring NetworkManager for Wi-Fi devices"); + + /* Create and store a NMClient instance if it doesn't exist yet */ + if (!cc_object_storage_has_object (CC_OBJECT_NMCLIENT)) + { + g_autoptr(NMClient) new_client = nm_client_new (NULL, NULL); + cc_object_storage_add_object (CC_OBJECT_NMCLIENT, new_client); + } + + client = cc_object_storage_get_object (CC_OBJECT_NMCLIENT); + + /* Update the panel visibility and monitor for changes */ + + g_signal_connect (client, "device-added", G_CALLBACK (update_panel_visibility), NULL); + g_signal_connect (client, "device-removed", G_CALLBACK (update_panel_visibility), NULL); + + update_panel_visibility (client); +} + +/* Auxiliary methods */ + +static gchar * +escape_string (const gchar *str, + gboolean quote) +{ + GString *string; + const char *next; + + if (!str) + return NULL; + + string = g_string_new (""); + if (quote) + g_string_append_c (string, '"'); + + while ((next = strpbrk (str, "\\;,:\""))) + { + g_string_append_len (string, str, next - str); + g_string_append_c (string, '\\'); + g_string_append_c (string, *next); + str = next + 1; + } + + g_string_append (string, str); + if (quote) + g_string_append_c (string, '"'); + + return g_string_free (string, FALSE); +} + +static const gchar * +get_connection_security_type (NMConnection *c) +{ + NMSettingWirelessSecurity *setting; + const char *key_mgmt; + + g_return_val_if_fail (c, "nopass"); + + setting = nm_connection_get_setting_wireless_security (c); + + if (!setting) + return "nopass"; + + key_mgmt = nm_setting_wireless_security_get_key_mgmt (setting); + + /* No IEEE 802.1x */ + if (g_strcmp0 (key_mgmt, "none") == 0) + return "WEP"; + + if (g_strcmp0 (key_mgmt, "wpa-none") == 0 || + g_strcmp0 (key_mgmt, "wpa-psk") == 0) + return "WPA"; + + return "nopass"; +} + +static gchar * +get_wifi_password (NMConnection *c) +{ + NMSettingWirelessSecurity *setting; + g_autoptr(GVariant) secrets = NULL; + g_autoptr(GError) error = NULL; + const gchar *sec_type, *password; + gint wep_index; + + g_assert (NM_IS_REMOTE_CONNECTION (c)); + + sec_type = get_connection_security_type (c); + setting = nm_connection_get_setting_wireless_security (c); + + if (g_str_equal (sec_type, "nopass")) + return NULL; + + secrets = nm_remote_connection_get_secrets (NM_REMOTE_CONNECTION (c), + NM_SETTING_WIRELESS_SECURITY_SETTING_NAME, + NULL, &error); + if (!error) + nm_connection_update_secrets (c, + NM_SETTING_WIRELESS_SECURITY_SETTING_NAME, + secrets, &error); + if (error) + { + g_warning ("Error: %s", error->message); + return NULL; + } + + if (g_str_equal (sec_type, "WEP")) + { + wep_index = nm_setting_wireless_security_get_wep_tx_keyidx (setting); + password = nm_setting_wireless_security_get_wep_key (setting, wep_index); + } + else + { + password = nm_setting_wireless_security_get_psk (setting); + } + + return escape_string (password, FALSE); +} + +/* Generate a string representing the hotspot @connection + * An example generated text: + * WIFI:S:hotspot;T:WPA;P:my-valid-pass;H:true; + * Where, + * S = ssid, T = security, P = password, H = hidden (Optional) + * + * See https://github.com/zxing/zxing/wiki/Barcode-Contents#wi-fi-network-config-android-ios-11 + */ +static gchar * +get_qr_string_for_hotspot (NMClient *nm_client, + NMConnection *c) +{ + NMSettingWireless *setting; + g_autofree char *ssid_text = NULL; + g_autofree char *escaped_ssid = NULL; + g_autofree char *password_str = NULL; + GString *string; + GBytes *ssid; + gboolean hidden; + + g_assert (NM_IS_CLIENT (nm_client)); + g_assert (NM_IS_REMOTE_CONNECTION (c)); + + setting = nm_connection_get_setting_wireless (c); + ssid = nm_setting_wireless_get_ssid (setting); + + if (!ssid) + return NULL; + + string = g_string_new ("WIFI:S:"); + + /* SSID */ + ssid_text = nm_utils_ssid_to_utf8 (g_bytes_get_data (ssid, NULL), + g_bytes_get_size (ssid)); + escaped_ssid = escape_string (ssid_text, FALSE); + g_string_append (string, escaped_ssid); + g_string_append_c (string, ';'); + + /* Security type */ + g_string_append (string, "T:"); + g_string_append (string, get_connection_security_type (c)); + g_string_append_c (string, ';'); + + /* Password */ + g_string_append (string, "P:"); + password_str = get_wifi_password (c); + if (password_str) + g_string_append (string, password_str); + g_string_append_c (string, ';'); + + /* WiFi Hidden */ + hidden = nm_setting_wireless_get_hidden (setting); + if (hidden) + g_string_append (string, "H:true"); + g_string_append_c (string, ';'); + + return g_string_free (string, FALSE); +} + +static NMConnection * +wifi_device_get_hotspot (CcWifiPanel *self, + NMDevice *device) +{ + NMSettingIPConfig *ip4_setting; + NMConnection *c; + + g_assert (CC_IS_WIFI_PANEL (self)); + g_assert (NM_IS_DEVICE (device)); + + if (nm_device_get_active_connection (device) == NULL) + return NULL; + + c = net_device_get_find_connection (self->client, device); + if (c == NULL) + return NULL; + + ip4_setting = nm_connection_get_setting_ip4_config (c); + if (g_strcmp0 (nm_setting_ip_config_get_method (ip4_setting), + NM_SETTING_IP4_CONFIG_METHOD_SHARED) != 0) + return NULL; + + return c; +} + +static void +wifi_panel_update_qr_image_cb (CcWifiPanel *self) +{ + NetDeviceWifi *child; + NMConnection *hotspot; + NMDevice *device; + + g_assert (CC_IS_WIFI_PANEL (self)); + + child = NET_DEVICE_WIFI (gtk_stack_get_visible_child (self->stack)); + device = net_device_wifi_get_device (child); + hotspot = wifi_device_get_hotspot (self, device); + + if (hotspot) + { + g_autofree gchar *str = NULL; + + if (!self->qr_code) + self->qr_code = cc_qr_code_new (); + + str = get_qr_string_for_hotspot (self->client, hotspot); + if (cc_qr_code_set_text (self->qr_code, str)) + { + GdkPaintable *paintable; + gint scale; + + scale = gtk_widget_get_scale_factor (GTK_WIDGET (self->wifi_qr_image)); + paintable = cc_qr_code_get_paintable (self->qr_code, QR_IMAGE_SIZE * scale); + gtk_picture_set_paintable (self->wifi_qr_image, paintable); + } + } + + gtk_widget_set_visible (GTK_WIDGET (self->hotspot_box), hotspot != NULL); + gtk_widget_set_opacity (GTK_WIDGET (self->list_label), hotspot == NULL); + gtk_widget_set_opacity (GTK_WIDGET (self->spinner), hotspot == NULL); +} + +static void +add_wifi_device (CcWifiPanel *self, + NMDevice *device) +{ + GtkWidget *header_widget; + NetDeviceWifi *net_device; + + /* Create the NetDevice */ + net_device = net_device_wifi_new (CC_PANEL (self), + self->client, + device); + gtk_widget_show (GTK_WIDGET (net_device)); + + /* And add to the header widgets */ + header_widget = net_device_wifi_get_header_widget (net_device); + + gtk_stack_add_named (self->header_stack, header_widget, nm_device_get_udi (device)); + + /* Setup custom title properties */ + g_ptr_array_add (self->devices, net_device); + + update_devices_names (self); + + /* Needs to be added after the device is added to the self->devices array */ + gtk_stack_add_titled (self->stack, GTK_WIDGET (net_device), + nm_device_get_udi (device), + nm_device_get_description (device)); + g_signal_connect_object (device, "state-changed", + G_CALLBACK (wifi_panel_update_qr_image_cb), + self, + G_CONNECT_SWAPPED); +} + +static void +remove_wifi_device (CcWifiPanel *self, + NMDevice *device) +{ + GtkWidget *child; + const gchar *id; + guint i; + + id = nm_device_get_udi (device); + + /* Remove from the devices list */ + for (i = 0; i < self->devices->len; i++) + { + NetDeviceWifi *net_device = g_ptr_array_index (self->devices, i); + + if (net_device_wifi_get_device (net_device) == device) + { + g_ptr_array_remove (self->devices, net_device); + break; + } + } + + /* Disconnect the signal to prevent assertion crash */ + g_signal_handlers_disconnect_by_func (device, + G_CALLBACK (wifi_panel_update_qr_image_cb), + self); + + /* Destroy all stack pages related to this device */ + child = gtk_stack_get_child_by_name (self->stack, id); + gtk_stack_remove (self->stack, child); + + child = gtk_stack_get_child_by_name (self->header_stack, id); + gtk_stack_remove (self->header_stack, child); + + /* Update the title widget */ + update_devices_names (self); +} + +static void +check_main_stack_page (CcWifiPanel *self) +{ + const gchar *nm_version; + gboolean airplane_mode_active; + gboolean wireless_enabled; + + nm_version = nm_client_get_version (self->client); + wireless_enabled = nm_client_wireless_get_enabled (self->client); + airplane_mode_active = cc_list_row_get_active (self->rfkill_row); + + if (!nm_version) + gtk_stack_set_visible_child_name (self->main_stack, "nm-not-running"); + else if (!wireless_enabled && airplane_mode_active) + gtk_stack_set_visible_child_name (self->main_stack, "airplane-mode"); + else if (!wireless_enabled || self->devices->len == 0) + gtk_stack_set_visible_child_name (self->main_stack, "no-wifi-devices"); + else + gtk_stack_set_visible_child_name (self->main_stack, "wifi-connections"); +} + +static void +load_wifi_devices (CcWifiPanel *self) +{ + const GPtrArray *devices; + guint i; + + devices = nm_client_get_devices (self->client); + + /* Cold-plug existing devices */ + if (devices) + { + for (i = 0; i < devices->len; i++) + { + NMDevice *device; + + device = g_ptr_array_index (devices, i); + if (!NM_IS_DEVICE_WIFI (device) || !nm_device_get_managed (device)) + continue; + add_wifi_device (self, device); + } + } + + check_main_stack_page (self); +} + +static inline gboolean +get_cached_rfkill_property (CcWifiPanel *self, + const gchar *property) +{ + g_autoptr(GVariant) result = NULL; + + result = g_dbus_proxy_get_cached_property (self->rfkill_proxy, property); + return result ? g_variant_get_boolean (result) : FALSE; +} + +static void +sync_airplane_mode_switch (CcWifiPanel *self) +{ + gboolean enabled, should_show, hw_enabled; + + enabled = get_cached_rfkill_property (self, "HasAirplaneMode"); + should_show = get_cached_rfkill_property (self, "ShouldShowAirplaneMode"); + + gtk_widget_set_visible (GTK_WIDGET (self->rfkill_widget), enabled && should_show); + if (!enabled || !should_show) + return; + + enabled = get_cached_rfkill_property (self, "AirplaneMode"); + hw_enabled = get_cached_rfkill_property (self, "HardwareAirplaneMode"); + + enabled |= hw_enabled; + + if (enabled != cc_list_row_get_active (self->rfkill_row)) + { + g_signal_handlers_block_by_func (self->rfkill_row, + rfkill_switch_notify_activate_cb, + self); + g_object_set (self->rfkill_row, "active", enabled, NULL); + check_main_stack_page (self); + g_signal_handlers_unblock_by_func (self->rfkill_row, + rfkill_switch_notify_activate_cb, + self); + } + + cc_list_row_set_switch_sensitive (self->rfkill_row, !hw_enabled); + + check_main_stack_page (self); +} + +static void +update_devices_names (CcWifiPanel *self) +{ + guint number_of_devices = self->devices->len; + + if (number_of_devices == 1) + { + GtkWidget *title_widget; + NetDeviceWifi *net_device; + + net_device = g_ptr_array_index (self->devices, 0); + title_widget = net_device_wifi_get_title_widget (net_device); + + gtk_stack_add_named (self->center_stack, title_widget, "single"); + gtk_stack_set_visible_child_name (self->center_stack, "single"); + + net_device_wifi_set_title (net_device, _("Wi-Fi")); + } + else + { + GtkWidget *single_page_widget; + guint i; + + for (i = 0; i < number_of_devices; i++) + { + NetDeviceWifi *net_device; + NMDevice *device; + + net_device = g_ptr_array_index (self->devices, i); + device = net_device_wifi_get_device (net_device); + + net_device_wifi_set_title (net_device, nm_device_get_description (device)); + } + + /* Remove the widget at the "single" page */ + single_page_widget = gtk_stack_get_child_by_name (self->center_stack, "single"); + + if (single_page_widget) + { + g_object_ref (single_page_widget); + gtk_stack_remove (self->center_stack, single_page_widget); + g_object_unref (single_page_widget); + } + + /* Show the stack-switcher page */ + gtk_stack_set_visible_child_name (self->center_stack, "many"); + } +} + +/* Command-line arguments */ + +static void +reset_command_line_args (CcWifiPanel *self) +{ + self->arg_operation = OPERATION_NULL; + g_clear_pointer (&self->arg_device, g_free); + g_clear_pointer (&self->arg_access_point, g_free); +} + +static gboolean +handle_argv_for_device (CcWifiPanel *self, NetDeviceWifi *net_device) +{ + GtkWidget *toplevel; + NMDevice *device; + gboolean ret; + + toplevel = cc_shell_get_toplevel (cc_panel_get_shell (CC_PANEL (self))); + device = net_device_wifi_get_device (net_device); + ret = FALSE; + + if (self->arg_operation == OPERATION_CREATE_WIFI) + { + cc_network_panel_create_wifi_network (toplevel, self->client); + ret = TRUE; + } + else if (self->arg_operation == OPERATION_CONNECT_HIDDEN) + { + cc_network_panel_connect_to_hidden_network (toplevel, self->client); + ret = TRUE; + } + else if (g_str_equal (nm_object_get_path (NM_OBJECT (device)), self->arg_device)) + { + if (self->arg_operation == OPERATION_CONNECT_8021X) + { + cc_network_panel_connect_to_8021x_network (toplevel, + self->client, + device, + self->arg_access_point); + ret = TRUE; + } + else if (self->arg_operation == OPERATION_SHOW_DEVICE) + { + gtk_stack_set_visible_child_name (self->stack, nm_device_get_udi (device)); + ret = TRUE; + } + } + + if (ret) + reset_command_line_args (self); + + return ret; +} + +static void +handle_argv (CcWifiPanel *self) +{ + guint i; + + if (self->arg_operation == OPERATION_NULL) + return; + + for (i = 0; i < self->devices->len; i++) + { + if (handle_argv_for_device (self, g_ptr_array_index (self->devices, i))) + break; + } +} + +static GPtrArray * +variant_av_to_string_array (GVariant *array) +{ + GVariantIter iter; + GVariant *v; + GPtrArray *strv; + gsize count; + + count = g_variant_iter_init (&iter, array); + strv = g_ptr_array_sized_new (count + 1); + + while (g_variant_iter_next (&iter, "v", &v)) + { + g_ptr_array_add (strv, (gpointer) g_variant_get_string (v, NULL)); + g_variant_unref (v); + } + + g_ptr_array_add (strv, NULL); /* NULL-terminate the strv data array */ + return strv; +} + +static gboolean +verify_argv (CcWifiPanel *self, + const char **args) +{ + switch (self->arg_operation) + { + case OPERATION_CONNECT_8021X: + case OPERATION_SHOW_DEVICE: + if (!self->arg_device) + { + g_warning ("Operation %s requires an object path", args[0]); + return FALSE; + } + default: + return TRUE; + } +} + +/* Callbacks */ + +static void +device_state_changed_cb (CcWifiPanel *self, GParamSpec *pspec, NMDevice *device) +{ + const gchar *id; + + id = nm_device_get_udi (device); + /* Don't add a device that has already been added */ + if (!NM_IS_DEVICE_WIFI (device) || !id) + return; + + if (nm_device_get_managed (device)) + { + if (gtk_stack_get_child_by_name (self->stack, id)) + return; + add_wifi_device (self, device); + check_main_stack_page (self); + } + else + { + if (!gtk_stack_get_child_by_name (self->stack, id)) + return; + remove_wifi_device (self, device); + check_main_stack_page (self); + } +} + +static void +device_added_cb (CcWifiPanel *self, NMDevice *device) +{ + if (!NM_IS_DEVICE_WIFI (device)) + return; + + if (nm_device_get_managed (device)) + { + add_wifi_device (self, device); + check_main_stack_page (self); + } + + g_signal_connect_object (device, + "notify::state", + G_CALLBACK (device_state_changed_cb), + self, + G_CONNECT_SWAPPED); +} + +static void +device_removed_cb (CcWifiPanel *self, NMDevice *device) +{ + const gchar *id; + + if (!NM_IS_DEVICE_WIFI (device)) + return; + + id = nm_device_get_udi (device); + /* Don't remove a device that has already been removed */ + if (!gtk_stack_get_child_by_name (self->stack, id)) + return; + + remove_wifi_device (self, device); + check_main_stack_page (self); + + g_signal_handlers_disconnect_by_func (device, + G_CALLBACK (device_state_changed_cb), + self); +} + +static void +wireless_enabled_cb (CcWifiPanel *self) +{ + check_main_stack_page (self); +} + +static void +on_rfkill_proxy_properties_changed_cb (CcWifiPanel *self) +{ + g_debug ("Rfkill properties changed"); + + sync_airplane_mode_switch (self); +} + +static void +rfkill_proxy_acquired_cb (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + CcWifiPanel *self; + GDBusProxy *proxy; + g_autoptr(GError) error = NULL; + + proxy = cc_object_storage_create_dbus_proxy_finish (res, &error); + + if (error) + { + if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + g_printerr ("Error creating rfkill proxy: %s\n", error->message); + + return; + } + + self = CC_WIFI_PANEL (user_data); + + self->rfkill_proxy = proxy; + + g_signal_connect_object (proxy, + "g-properties-changed", + G_CALLBACK (on_rfkill_proxy_properties_changed_cb), + self, + G_CONNECT_SWAPPED); + + sync_airplane_mode_switch (self); +} + +static void +rfkill_switch_notify_activate_cb (CcListRow *row, + GParamSpec *pspec, + CcWifiPanel *self) +{ + gboolean enable; + + enable = cc_list_row_get_active (row); + + g_dbus_proxy_call (self->rfkill_proxy, + "org.freedesktop.DBus.Properties.Set", + g_variant_new_parsed ("('org.gnome.SettingsDaemon.Rfkill'," + "'AirplaneMode', %v)", + g_variant_new_boolean (enable)), + G_DBUS_CALL_FLAGS_NONE, + -1, + cc_panel_get_cancellable (CC_PANEL (self)), + NULL, + NULL); +} + +static void +on_stack_visible_child_changed_cb (GtkStack *stack, + GParamSpec *pspec, + CcWifiPanel *self) +{ + const gchar *visible_device_id = NULL; + guint i; + + wifi_panel_update_qr_image_cb (self); + + /* Remove previous bindings */ + g_clear_pointer (&self->spinner_binding, g_binding_unbind); + + visible_device_id = gtk_stack_get_visible_child_name (stack); + for (i = 0; i < self->devices->len; i++) + { + NetDeviceWifi *net_device = g_ptr_array_index (self->devices, i); + + if (g_strcmp0 (nm_device_get_udi (net_device_wifi_get_device (net_device)), visible_device_id) == 0) + { + self->spinner_binding = g_object_bind_property (net_device, + "scanning", + self->spinner, + "spinning", + G_BINDING_DEFAULT | G_BINDING_SYNC_CREATE); + break; + } + } +} + +static void +on_stop_hotspot_dialog_response_cb (GtkDialog *dialog, + gint response, + CcWifiPanel *self) +{ + if (response == GTK_RESPONSE_OK) + { + NetDeviceWifi *child; + + child = NET_DEVICE_WIFI (gtk_stack_get_visible_child (self->stack)); + net_device_wifi_turn_off_hotspot (child); + } + + gtk_window_destroy (GTK_WINDOW (dialog)); +} + +static void +hotspot_stop_clicked_cb (CcWifiPanel *self) +{ + GtkWidget *dialog; + GtkNative *native; + + g_assert (CC_IS_WIFI_PANEL (self)); + + native = gtk_widget_get_native (GTK_WIDGET (self)); + dialog = gtk_message_dialog_new (GTK_WINDOW (native), + GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_MESSAGE_OTHER, + GTK_BUTTONS_NONE, + _("Stop hotspot and disconnect any users?")); + gtk_dialog_add_buttons (GTK_DIALOG (dialog), + _("_Cancel"), GTK_RESPONSE_CANCEL, + _("_Stop Hotspot"), GTK_RESPONSE_OK, + NULL); + + g_signal_connect (dialog, "response", G_CALLBACK (on_stop_hotspot_dialog_response_cb), self); + gtk_window_present (GTK_WINDOW (dialog)); + +} + +/* Overrides */ + +static const gchar * +cc_wifi_panel_get_help_uri (CcPanel *panel) +{ + return "help:gnome-help/net-wireless"; +} + +static void +cc_wifi_panel_finalize (GObject *object) +{ + CcWifiPanel *self = (CcWifiPanel *)object; + + g_clear_object (&self->client); + g_clear_object (&self->rfkill_proxy); + + g_clear_pointer (&self->devices, g_ptr_array_unref); + + reset_command_line_args (self); + + G_OBJECT_CLASS (cc_wifi_panel_parent_class)->finalize (object); +} + +static void +cc_wifi_panel_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); +} + +static void +cc_wifi_panel_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + CcWifiPanel *self = CC_WIFI_PANEL (object); + GVariant *parameters; + + switch (prop_id) + { + case PROP_PARAMETERS: + reset_command_line_args (self); + + parameters = g_value_get_variant (value); + + if (parameters) + { + g_autoptr(GPtrArray) array = NULL; + const gchar **args; + + array = variant_av_to_string_array (parameters); + args = (const gchar **) array->pdata; + + if (args[0]) + { + if (g_str_equal (args[0], "create-wifi")) + self->arg_operation = OPERATION_CREATE_WIFI; + else if (g_str_equal (args[0], "connect-hidden-wifi")) + self->arg_operation = OPERATION_CONNECT_HIDDEN; + else if (g_str_equal (args[0], "connect-8021x-wifi")) + self->arg_operation = OPERATION_CONNECT_8021X; + else if (g_str_equal (args[0], "show-device")) + self->arg_operation = OPERATION_SHOW_DEVICE; + else + self->arg_operation = OPERATION_NULL; + } + + if (args[0] && args[1]) + self->arg_device = g_strdup (args[1]); + if (args[0] && args[1] && args[2]) + self->arg_access_point = g_strdup (args[2]); + + if (!verify_argv (self, (const char **) args)) + { + reset_command_line_args (self); + return; + } + + handle_argv (self); + } + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +cc_wifi_panel_class_init (CcWifiPanelClass *klass) +{ + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + GObjectClass *object_class = G_OBJECT_CLASS (klass); + CcPanelClass *panel_class = CC_PANEL_CLASS (klass); + + panel_class->get_help_uri = cc_wifi_panel_get_help_uri; + + object_class->finalize = cc_wifi_panel_finalize; + object_class->get_property = cc_wifi_panel_get_property; + object_class->set_property = cc_wifi_panel_set_property; + + gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/control-center/network/cc-wifi-panel.ui"); + + gtk_widget_class_bind_template_child (widget_class, CcWifiPanel, center_stack); + gtk_widget_class_bind_template_child (widget_class, CcWifiPanel, header_stack); + gtk_widget_class_bind_template_child (widget_class, CcWifiPanel, hotspot_box); + gtk_widget_class_bind_template_child (widget_class, CcWifiPanel, list_label); + gtk_widget_class_bind_template_child (widget_class, CcWifiPanel, main_stack); + gtk_widget_class_bind_template_child (widget_class, CcWifiPanel, rfkill_row); + gtk_widget_class_bind_template_child (widget_class, CcWifiPanel, rfkill_widget); + gtk_widget_class_bind_template_child (widget_class, CcWifiPanel, spinner); + gtk_widget_class_bind_template_child (widget_class, CcWifiPanel, stack); + gtk_widget_class_bind_template_child (widget_class, CcWifiPanel, wifi_qr_image); + + gtk_widget_class_bind_template_callback (widget_class, rfkill_switch_notify_activate_cb); + gtk_widget_class_bind_template_callback (widget_class, on_stack_visible_child_changed_cb); + gtk_widget_class_bind_template_callback (widget_class, hotspot_stop_clicked_cb); + + g_object_class_override_property (object_class, PROP_PARAMETERS, "parameters"); +} + +static void +cc_wifi_panel_init (CcWifiPanel *self) +{ + g_autoptr(GtkCssProvider) provider = NULL; + + g_resources_register (cc_network_get_resource ()); + + gtk_widget_init_template (GTK_WIDGET (self)); + + self->devices = g_ptr_array_new (); + + /* Create and store a NMClient instance if it doesn't exist yet */ + if (!cc_object_storage_has_object (CC_OBJECT_NMCLIENT)) + { + g_autoptr(NMClient) client = nm_client_new (NULL, NULL); + cc_object_storage_add_object (CC_OBJECT_NMCLIENT, client); + } + + /* Load NetworkManager */ + self->client = cc_object_storage_get_object (CC_OBJECT_NMCLIENT); + + g_signal_connect_object (self->client, + "device-added", + G_CALLBACK (device_added_cb), + self, + G_CONNECT_SWAPPED); + + g_signal_connect_object (self->client, + "device-removed", + G_CALLBACK (device_removed_cb), + self, + G_CONNECT_SWAPPED); + + g_signal_connect_object (self->client, + "notify::wireless-enabled", + G_CALLBACK (wireless_enabled_cb), + self, + G_CONNECT_SWAPPED); + + /* Load Wi-Fi devices */ + load_wifi_devices (self); + + /* Acquire Airplane Mode proxy */ + cc_object_storage_create_dbus_proxy (G_BUS_TYPE_SESSION, + G_DBUS_PROXY_FLAGS_NONE, + "org.gnome.SettingsDaemon.Rfkill", + "/org/gnome/SettingsDaemon/Rfkill", + "org.gnome.SettingsDaemon.Rfkill", + cc_panel_get_cancellable (CC_PANEL (self)), + rfkill_proxy_acquired_cb, + self); + + /* Handle comment-line arguments after loading devices */ + handle_argv (self); + + /* use custom CSS */ + provider = gtk_css_provider_new (); + gtk_css_provider_load_from_resource (provider, "/org/gnome/control-center/network/wifi-panel.css"); + gtk_style_context_add_provider_for_display (gdk_display_get_default (), + GTK_STYLE_PROVIDER (provider), + GTK_STYLE_PROVIDER_PRIORITY_APPLICATION); +} diff --git a/panels/network/cc-wifi-panel.h b/panels/network/cc-wifi-panel.h new file mode 100644 index 0000000..20b6e34 --- /dev/null +++ b/panels/network/cc-wifi-panel.h @@ -0,0 +1,32 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- + * + * Copyright (C) 2017 Georges Basile Stavracas Neto <georges.stavracas@gmail.com> + * + * 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/>. + * + */ + +#pragma once + +#include <shell/cc-panel.h> + +G_BEGIN_DECLS + +#define CC_TYPE_WIFI_PANEL (cc_wifi_panel_get_type()) + +G_DECLARE_FINAL_TYPE (CcWifiPanel, cc_wifi_panel, CC, WIFI_PANEL, CcPanel) + +void cc_wifi_panel_static_init_func (void); + +G_END_DECLS diff --git a/panels/network/cc-wifi-panel.ui b/panels/network/cc-wifi-panel.ui new file mode 100644 index 0000000..572ef0e --- /dev/null +++ b/panels/network/cc-wifi-panel.ui @@ -0,0 +1,326 @@ +<?xml version="1.0" encoding="UTF-8"?> +<interface> + <template class="CcWifiPanel" parent="CcPanel"> + + <child type="titlebar"> + <object class="AdwHeaderBar" id="titlebar"> + <property name="show-end-title-buttons">True</property> + <property name="show-start-title-buttons" bind-source="CcWifiPanel" bind-property="folded" bind-flags="default|sync-create" /> + <child type="start"> + <object class="GtkButton"> + <property name="visible" bind-source="CcWifiPanel" bind-property="folded" bind-flags="default|sync-create" /> + <property name="icon-name">go-previous-symbolic</property> + <property name="action-name">window.navigate</property> + <property name="action-target">0</property> <!-- 0: ADW_NAVIGATION_DIRECTION_BACK --> + <accessibility> + <property name="label" translatable="yes">Back</property> + </accessibility> + </object> + </child> + + <!-- Center Widget --> + <property name="title-widget"> + <object class="GtkStack" id="center_stack"> + <property name="halign">center</property> + <property name="hhomogeneous">False</property> + <child> + <object class="GtkStackPage"> + <property name="name">many</property> + <property name="child"> + <object class="GtkStackSwitcher"> + <property name="stack">stack</property> + </object> + </property> + </object> + </child> + </object> + </property> + + <!-- End Stack --> + <child type="end"> + <object class="GtkStack" id="header_stack"> + <property name="halign">end</property> + </object> + </child> + + </object> + </child> + + <child type="content"> + <object class="GtkScrolledWindow"> + <property name="hscrollbar-policy">never</property> + <child> + <object class="AdwClamp"> + <property name="margin_top">32</property> + <property name="margin_bottom">32</property> + <property name="margin_start">12</property> + <property name="margin_end">12</property> + + <child> + <object class="GtkBox"> + <property name="hexpand">True</property> + <property name="orientation">vertical</property> + + <!-- Airplane Mode --> + <child> + <object class="GtkListBox" id="rfkill_widget"> + <property name="margin_bottom">32</property> + <property name="selection-mode">none</property> + <style> + <class name="boxed-list"/> + </style> + <child> + <object class="CcListRow" id="rfkill_row"> + <property name="show-switch">True</property> + <property name="title" translatable="yes">Airplane Mode</property> + <property name="subtitle" translatable="yes">Disables Wi-Fi, Bluetooth and mobile broadband</property> + <signal name="notify::active" handler="rfkill_switch_notify_activate_cb" object="CcWifiPanel" swapped="no" /> + </object> + </child> + </object> + </child> + + <child> + <object class="GtkStack" id="main_stack"> + <property name="hhomogeneous">False</property> + <property name="vhomogeneous">False</property> + <property name="transition_type">crossfade</property> + + <!-- "No Wi-Fi Adapter" page --> + <child> + <object class="GtkStackPage"> + <property name="name">no-wifi-devices</property> + <property name="child"> + <object class="GtkBox"> + <property name="hexpand">True</property> + <property name="vexpand">True</property> + <property name="halign">center</property> + <property name="valign">center</property> + <property name="orientation">vertical</property> + <child> + <object class="GtkImage"> + <property name="icon_name">network-wireless-no-route-symbolic</property> + <property name="pixel_size">256</property> + <property name="margin-bottom">18</property> + <style> + <class name="dim-label" /> + </style> + </object> + </child> + <child> + <object class="GtkLabel"> + <property name="wrap">True</property> + <property name="label" translatable="yes">No Wi-Fi Adapter Found</property> + <attributes> + <attribute name="weight" value="bold" /> + <attribute name="scale" value="1.2" /> + </attributes> + </object> + </child> + <child> + <object class="GtkLabel"> + <property name="wrap">True</property> + <property name="label" translatable="yes">Make sure you have a Wi-Fi adapter plugged and turned on</property> + </object> + </child> + </object> + </property> + </object> + </child> + + <!-- "Airplane Mode" page --> + <child> + <object class="GtkStackPage"> + <property name="name">airplane-mode</property> + <property name="child"> + <object class="GtkBox"> + <property name="hexpand">True</property> + <property name="vexpand">True</property> + <property name="halign">center</property> + <property name="valign">center</property> + <property name="orientation">vertical</property> + <child> + <object class="GtkImage"> + <property name="icon_name">airplane-mode-symbolic</property> + <property name="pixel_size">256</property> + <property name="margin-bottom">18</property> + <style> + <class name="dim-label" /> + </style> + </object> + </child> + <child> + <object class="GtkLabel"> + <property name="wrap">True</property> + <property name="label" translatable="yes">Airplane Mode On</property> + <attributes> + <attribute name="weight" value="bold" /> + <attribute name="scale" value="1.2" /> + </attributes> + </object> + </child> + <child> + <object class="GtkLabel"> + <property name="wrap">True</property> + <property name="label" translatable="yes">Turn off to use Wi-Fi</property> + </object> + </child> + </object> + </property> + </object> + </child> + + <!-- Wi-Fi connections and devices --> + <child> + <object class="GtkStackPage"> + <property name="name">wifi-connections</property> + <property name="child"> + <object class="GtkBox"> + <property name="orientation">vertical</property> + + <child> + <object class="GtkBox" id="hotspot_box"> + <property name="orientation">vertical</property> + + <!-- Hotspot QR code --> + <child> + <object class="GtkPicture" id="wifi_qr_image"> + <property name="halign">center</property> + <property name="valign">center</property> + <property name="width-request">180</property> + <property name="height-request">180</property> + <style> + <class name="frame"/> + <class name="qr-image"/> + </style> + </object> + </child> + + <child> + <object class="GtkLabel"> + <property name="margin-top">12</property> + <property name="label" translatable="yes">Wi-Fi Hotspot Active</property> + <attributes> + <attribute name="weight" value="bold" /> + <attribute name="scale" value="1.8" /> + </attributes> + </object> + </child> + + <child> + <object class="GtkLabel"> + <property name="label" translatable="yes">Mobile devices can scan the QR code to connect.</property> + </object> + </child> + + <child> + <object class="GtkButton"> + <property name="halign">center</property> + <property name="margin-top">12</property> + <property name="label" translatable="yes">Turn Off Hotspot…</property> + <signal name="clicked" handler="hotspot_stop_clicked_cb" swapped="yes"/> + <style> + <class name="destructive-action"/> + </style> + </object> + </child> + + </object> + </child> + + <!-- Visible Networks label & spinner --> + <child> + <object class="GtkBox"> + <property name="hexpand">True</property> + <property name="halign">start</property> + <property name="spacing">6</property> + <property name="margin_bottom">12</property> + <child> + <object class="GtkLabel" id="list_label"> + <property name="label" translatable="yes">Visible Networks</property> + <property name="xalign">0.0</property> + <attributes> + <attribute name="weight" value="bold"/> + </attributes> + </object> + </child> + <child> + <object class="GtkSpinner" id="spinner"> + <property name="hexpand">True</property> + </object> + </child> + </object> + </child> + + <!-- Stack with a listbox for each Wi-Fi device --> + <child> + <object class="GtkStack" id="stack"> + <property name="hexpand">True</property> + <property name="vexpand">True</property> + <property name="transition_type">crossfade</property> + <signal name="notify::visible-child-name" handler="on_stack_visible_child_changed_cb" object="CcWifiPanel" swapped="no" /> + </object> + </child> + + </object> + </property> + </object> + </child> + + <!-- "NetworkManager Not Running" page --> + <child> + <object class="GtkStackPage"> + <property name="name">nm-not-running</property> + <property name="child"> + <object class="GtkCenterBox"> + <property name="hexpand">True</property> + <property name="vexpand">True</property> + <property name="halign">center</property> + <property name="valign">center</property> + <property name="orientation">vertical</property> + <property name="margin-top">18</property> + <property name="margin-bottom">18</property> + <property name="margin-start">18</property> + <property name="margin-end">18</property> + <child type="center"> + <object class="GtkImage"> + <property name="icon_name">face-sad-symbolic</property> + <property name="pixel_size">128</property> + <style> + <class name="dim-label" /> + </style> + </object> + </child> + <child type="end"> + <object class="GtkLabel"> + <property name="wrap">True</property> + <property name="label" translatable="yes">NetworkManager needs to be running</property> + </object> + </child> + <child type="end"> + <object class="GtkLabel"> + <property name="wrap">True</property> + <property name="label" translatable="yes">Oops, something has gone wrong. Please contact your software vendor.</property> + <attributes> + <attribute name="scale" value="1.42" /> + </attributes> + </object> + </child> + </object> + </property> + </object> + </child> + + </object> + </child> + + + </object> + </child> + + </object> + </child> + </object> + </child> + </template> +</interface> diff --git a/panels/network/connection-editor/8021x-security-page.ui b/panels/network/connection-editor/8021x-security-page.ui new file mode 100644 index 0000000..5be96b3 --- /dev/null +++ b/panels/network/connection-editor/8021x-security-page.ui @@ -0,0 +1,45 @@ +<?xml version="1.0" encoding="UTF-8"?> +<interface> + <!-- interface-requires gtk+ 3.0 --> + <template class="CEPage8021xSecurity" parent="GtkGrid"> + <property name="margin_start">50</property> + <property name="margin_end">50</property> + <property name="margin_top">12</property> + <property name="margin_bottom">12</property> + <property name="hexpand">True</property> + <property name="vexpand">True</property> + <property name="row_spacing">10</property> + <property name="column_spacing">6</property> + <child> + <object class="GtkLabel" id="security_label"> + <property name="xalign">1</property> + <property name="label" translatable="yes">802.1x _Security</property> + <property name="use_underline">True</property> + <property name="mnemonic_widget">enable_8021x_switch</property> + <layout> + <property name="column">0</property> + <property name="row">0</property> + </layout> + </object> + </child> + <child> + <object class="GtkSwitch" id="enable_8021x_switch"> + <property name="halign">start</property> + <property name="hexpand">True</property> + <layout> + <property name="column">1</property> + <property name="row">0</property> + </layout> + </object> + </child> + <child> + <object class="GtkBox" id="box"> + <property name="orientation">vertical</property> + <layout> + <property name="column">0</property> + <property name="row">1</property> + </layout> + </object> + </child> + </template> +</interface> diff --git a/panels/network/connection-editor/ce-ip-address-entry.c b/panels/network/connection-editor/ce-ip-address-entry.c new file mode 100644 index 0000000..586ea5d --- /dev/null +++ b/panels/network/connection-editor/ce-ip-address-entry.c @@ -0,0 +1,99 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2020 Canonical Ltd. + * + * 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 <NetworkManager.h> + +#include "ce-ip-address-entry.h" + +struct _CEIPAddressEntry +{ + GtkEntry parent_instance; + + int family; +}; + +static void ce_ip_address_entry_editable_init (GtkEditableInterface *iface); + +G_DEFINE_TYPE_WITH_CODE (CEIPAddressEntry, ce_ip_address_entry, GTK_TYPE_ENTRY, + G_IMPLEMENT_INTERFACE (GTK_TYPE_EDITABLE, + ce_ip_address_entry_editable_init)) + +static void +ce_ip_address_entry_changed (GtkEditable *editable) +{ + CEIPAddressEntry *self = CE_IP_ADDRESS_ENTRY (editable); + GtkStyleContext *context; + + context = gtk_widget_get_style_context (GTK_WIDGET (self)); + if (ce_ip_address_entry_is_valid (self)) + gtk_style_context_remove_class (context, "error"); + else + gtk_style_context_add_class (context, "error"); +} + +static void +ce_ip_address_entry_init (CEIPAddressEntry *self) +{ +} + +static void +ce_ip_address_entry_editable_init (GtkEditableInterface *iface) +{ + iface->changed = ce_ip_address_entry_changed; +} + +static void +ce_ip_address_entry_class_init (CEIPAddressEntryClass *klass) +{ +} + +CEIPAddressEntry * +ce_ip_address_entry_new (int family) +{ + CEIPAddressEntry *self; + + self = CE_IP_ADDRESS_ENTRY (g_object_new (ce_ip_address_entry_get_type (), NULL)); + self->family = family; + + return self; +} + +gboolean +ce_ip_address_entry_is_empty (CEIPAddressEntry *self) +{ + const gchar *text; + + g_return_val_if_fail (CE_IS_IP_ADDRESS_ENTRY (self), FALSE); + + text = gtk_editable_get_text (GTK_EDITABLE (self)); + return text[0] == '\0'; +} + +gboolean +ce_ip_address_entry_is_valid (CEIPAddressEntry *self) +{ + const gchar *text; + + g_return_val_if_fail (CE_IS_IP_ADDRESS_ENTRY (self), FALSE); + + text = gtk_editable_get_text (GTK_EDITABLE (self)); + return text[0] == '\0' || nm_utils_ipaddr_valid (self->family, text); +} diff --git a/panels/network/connection-editor/ce-ip-address-entry.h b/panels/network/connection-editor/ce-ip-address-entry.h new file mode 100644 index 0000000..e89a7fb --- /dev/null +++ b/panels/network/connection-editor/ce-ip-address-entry.h @@ -0,0 +1,36 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2020 Canonical Ltd. + * + * 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. + */ + +#pragma once + +#include <gtk/gtk.h> + +G_BEGIN_DECLS + +G_DECLARE_FINAL_TYPE (CEIPAddressEntry, ce_ip_address_entry, CE, IP_ADDRESS_ENTRY, GtkEntry) + +CEIPAddressEntry *ce_ip_address_entry_new (int family); + +gboolean ce_ip_address_entry_is_empty (CEIPAddressEntry *entry); + +gboolean ce_ip_address_entry_is_valid (CEIPAddressEntry *entry); + +G_END_DECLS diff --git a/panels/network/connection-editor/ce-netmask-entry.c b/panels/network/connection-editor/ce-netmask-entry.c new file mode 100644 index 0000000..2fccf0f --- /dev/null +++ b/panels/network/connection-editor/ce-netmask-entry.c @@ -0,0 +1,137 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2020 Canonical Ltd. + * + * 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 <arpa/inet.h> +#include <NetworkManager.h> + +#include "ce-netmask-entry.h" + +struct _CENetmaskEntry +{ + GtkEntry parent_instance; +}; + +static void ce_netmask_entry_editable_init (GtkEditableInterface *iface); + +G_DEFINE_TYPE_WITH_CODE (CENetmaskEntry, ce_netmask_entry, GTK_TYPE_ENTRY, + G_IMPLEMENT_INTERFACE (GTK_TYPE_EDITABLE, + ce_netmask_entry_editable_init)) + +static gboolean +parse_netmask (const char *str, guint32 *prefix) +{ + struct in_addr tmp_addr; + glong tmp_prefix; + + /* Is it a prefix? */ + errno = 0; + if (!strchr (str, '.')) + { + tmp_prefix = strtol (str, NULL, 10); + if (!errno && tmp_prefix >= 0 && tmp_prefix <= 32) + { + if (prefix != NULL) + *prefix = tmp_prefix; + return TRUE; + } + } + + /* Is it a netmask? */ + if (inet_pton (AF_INET, str, &tmp_addr) > 0) + { + if (prefix != NULL) + *prefix = nm_utils_ip4_netmask_to_prefix (tmp_addr.s_addr); + return TRUE; + } + + return FALSE; +} + +static void +ce_netmask_entry_changed (GtkEditable *editable) +{ + CENetmaskEntry *self = CE_NETMASK_ENTRY (editable); + GtkStyleContext *context; + + context = gtk_widget_get_style_context (GTK_WIDGET (self)); + if (ce_netmask_entry_is_valid (self)) + gtk_style_context_remove_class (context, "error"); + else + gtk_style_context_add_class (context, "error"); +} + +static void +ce_netmask_entry_init (CENetmaskEntry *self) +{ +} + +static void +ce_netmask_entry_editable_init (GtkEditableInterface *iface) +{ + iface->changed = ce_netmask_entry_changed; +} + +static void +ce_netmask_entry_class_init (CENetmaskEntryClass *klass) +{ +} + +CENetmaskEntry * +ce_netmask_entry_new (void) +{ + return CE_NETMASK_ENTRY (g_object_new (ce_netmask_entry_get_type (), NULL)); +} + +gboolean +ce_netmask_entry_is_empty (CENetmaskEntry *self) +{ + const gchar *text; + + g_return_val_if_fail (CE_IS_NETMASK_ENTRY (self), FALSE); + + text = gtk_editable_get_text (GTK_EDITABLE (self)); + return text[0] == '\0'; +} + +gboolean +ce_netmask_entry_is_valid (CENetmaskEntry *self) +{ + const gchar *text; + + g_return_val_if_fail (CE_IS_NETMASK_ENTRY (self), FALSE); + + text = gtk_editable_get_text (GTK_EDITABLE (self)); + return text[0] == '\0' || parse_netmask (text, NULL); +} + +guint32 +ce_netmask_entry_get_prefix (CENetmaskEntry *self) +{ + const gchar *text; + guint32 prefix = 0; + + g_return_val_if_fail (CE_IS_NETMASK_ENTRY (self), 0); + + text = gtk_editable_get_text (GTK_EDITABLE (self)); + parse_netmask (text, &prefix); + + return prefix; +} diff --git a/panels/network/connection-editor/ce-netmask-entry.h b/panels/network/connection-editor/ce-netmask-entry.h new file mode 100644 index 0000000..ff6337e --- /dev/null +++ b/panels/network/connection-editor/ce-netmask-entry.h @@ -0,0 +1,38 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2020 Canonical Ltd. + * + * 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. + */ + +#pragma once + +#include <gtk/gtk.h> + +G_BEGIN_DECLS + +G_DECLARE_FINAL_TYPE (CENetmaskEntry, ce_netmask_entry, CE, NETMASK_ENTRY, GtkEntry) + +CENetmaskEntry *ce_netmask_entry_new (void); + +gboolean ce_netmask_entry_is_empty (CENetmaskEntry *entry); + +gboolean ce_netmask_entry_is_valid (CENetmaskEntry *entry); + +guint32 ce_netmask_entry_get_prefix (CENetmaskEntry *entry); + +G_END_DECLS diff --git a/panels/network/connection-editor/ce-page-8021x-security.c b/panels/network/connection-editor/ce-page-8021x-security.c new file mode 100644 index 0000000..90732fa --- /dev/null +++ b/panels/network/connection-editor/ce-page-8021x-security.c @@ -0,0 +1,202 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* NetworkManager Connection editor -- Connection editor for NetworkManager + * + * Dan Williams <dcbw@redhat.com> + * + * 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. + * + * (C) Copyright 2008 - 2012 Red Hat, Inc. + */ + +#include "config.h" + +#include <glib/gi18n.h> +#include <NetworkManager.h> +#include <string.h> + +#include "ws-wpa-eap.h" +#include "wireless-security.h" +#include "ce-page.h" +#include "ce-page-ethernet.h" +#include "ce-page-8021x-security.h" + +struct _CEPage8021xSecurity { + GtkGrid parent; + + GtkBox *box; + GtkSwitch *enable_8021x_switch; + GtkLabel *security_label; + + NMConnection *connection; + WirelessSecurityWPAEAP *security; + GtkSizeGroup *group; + gboolean initial_have_8021x; +}; + +static void ce_page_iface_init (CEPageInterface *); + +G_DEFINE_TYPE_WITH_CODE (CEPage8021xSecurity, ce_page_8021x_security, GTK_TYPE_GRID, + G_IMPLEMENT_INTERFACE (ce_page_get_type (), ce_page_iface_init)) + +static void +enable_toggled (CEPage8021xSecurity *self) +{ + gtk_widget_set_sensitive (GTK_WIDGET (self->security), gtk_switch_get_active (self->enable_8021x_switch)); + ce_page_changed (CE_PAGE (self)); +} + +static void +security_item_changed_cb (CEPage8021xSecurity *self) +{ + ce_page_changed (CE_PAGE (self)); +} + +static void +finish_setup (CEPage8021xSecurity *self, gpointer unused, GError *error, gpointer user_data) +{ + if (error) + return; + + self->group = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL); + + self->security = ws_wpa_eap_new (self->connection); + if (!self->security) { + g_warning ("Could not load 802.1x user interface."); + return; + } + + g_signal_connect_object (WIRELESS_SECURITY (self->security), "changed", G_CALLBACK (security_item_changed_cb), self, G_CONNECT_SWAPPED); + if (gtk_widget_get_parent (GTK_WIDGET (self->security))) + gtk_box_remove (self->box, GTK_WIDGET (self->security)); + + gtk_switch_set_active (self->enable_8021x_switch, self->initial_have_8021x); + g_signal_connect_object (self->enable_8021x_switch, "notify::active", G_CALLBACK (enable_toggled), self, G_CONNECT_SWAPPED); + gtk_widget_set_sensitive (GTK_WIDGET (self->security), self->initial_have_8021x); + + gtk_size_group_add_widget (self->group, GTK_WIDGET (self->security_label)); + wireless_security_add_to_size_group (WIRELESS_SECURITY (self->security), self->group); + + gtk_box_append (self->box, GTK_WIDGET (self->security)); + +} + +static const gchar * +ce_page_8021x_security_get_security_setting (CEPage *page) +{ + CEPage8021xSecurity *self = CE_PAGE_8021X_SECURITY (page); + + if (self->initial_have_8021x) + return NM_SETTING_802_1X_SETTING_NAME; + + return NULL; +} + +static const gchar * +ce_page_8021x_security_get_title (CEPage *page) +{ + return _("Security"); +} + +static gboolean +ce_page_8021x_security_validate (CEPage *cepage, NMConnection *connection, GError **error) +{ + CEPage8021xSecurity *self = CE_PAGE_8021X_SECURITY (cepage); + gboolean valid = TRUE; + + if (gtk_switch_get_active (self->enable_8021x_switch)) { + NMSetting *s_8021x; + + /* FIXME: get failed property and error out of wireless security objects */ + valid = wireless_security_validate (WIRELESS_SECURITY (self->security), error); + if (valid) { + g_autoptr(NMConnection) tmp_connection = NULL; + + /* Here's a nice hack to work around the fact that ws_802_1x_fill_connection needs wireless setting. */ + tmp_connection = nm_simple_connection_new_clone (connection); + nm_connection_add_setting (tmp_connection, nm_setting_wireless_new ()); + + ws_wpa_eap_fill_connection (self->security, tmp_connection); + + /* NOTE: It is important we create a copy of the settings, as the + * secrets might be cleared otherwise. + */ + s_8021x = nm_connection_get_setting (tmp_connection, NM_TYPE_SETTING_802_1X); + nm_connection_add_setting (connection, nm_setting_duplicate (NM_SETTING (s_8021x))); + } + } else { + nm_connection_remove_setting (connection, NM_TYPE_SETTING_802_1X); + valid = TRUE; + } + + return valid; +} + +static void +ce_page_8021x_security_init (CEPage8021xSecurity *self) +{ + gtk_widget_init_template (GTK_WIDGET (self)); +} + +static void +ce_page_8021x_security_dispose (GObject *object) +{ + CEPage8021xSecurity *self = CE_PAGE_8021X_SECURITY (object); + + g_clear_object (&self->connection); + g_clear_pointer ((GtkWidget **) &self->security, gtk_widget_unparent); + g_clear_object (&self->group); + + G_OBJECT_CLASS (ce_page_8021x_security_parent_class)->dispose (object); +} + +static void +ce_page_8021x_security_class_init (CEPage8021xSecurityClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + object_class->dispose = ce_page_8021x_security_dispose; + + gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/control-center/network/8021x-security-page.ui"); + + gtk_widget_class_bind_template_child (widget_class, CEPage8021xSecurity, box); + gtk_widget_class_bind_template_child (widget_class, CEPage8021xSecurity, enable_8021x_switch); + gtk_widget_class_bind_template_child (widget_class, CEPage8021xSecurity, security_label); +} + +static void +ce_page_iface_init (CEPageInterface *iface) +{ + iface->get_security_setting = ce_page_8021x_security_get_security_setting; + iface->get_title = ce_page_8021x_security_get_title; + iface->validate = ce_page_8021x_security_validate; +} + +CEPage8021xSecurity * +ce_page_8021x_security_new (NMConnection *connection) +{ + CEPage8021xSecurity *self; + + self = CE_PAGE_8021X_SECURITY (g_object_new (ce_page_8021x_security_get_type (), NULL)); + + self->connection = g_object_ref (connection); + + if (nm_connection_get_setting_802_1x (connection)) + self->initial_have_8021x = TRUE; + + g_signal_connect (self, "initialized", G_CALLBACK (finish_setup), NULL); + + return self; +} diff --git a/panels/network/connection-editor/ce-page-8021x-security.h b/panels/network/connection-editor/ce-page-8021x-security.h new file mode 100644 index 0000000..e783587 --- /dev/null +++ b/panels/network/connection-editor/ce-page-8021x-security.h @@ -0,0 +1,34 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* NetworkManager Connection editor -- Connection editor for NetworkManager + * + * Dan Williams <dcbw@redhat.com> + * + * 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. + * + * (C) Copyright 2008 - 2012 Red Hat, Inc. + */ + +#pragma once + +#include <gtk/gtk.h> +#include <NetworkManager.h> + +G_BEGIN_DECLS + +G_DECLARE_FINAL_TYPE (CEPage8021xSecurity, ce_page_8021x_security, CE, PAGE_8021X_SECURITY, GtkGrid) + +CEPage8021xSecurity *ce_page_8021x_security_new (NMConnection *connection); + +G_END_DECLS diff --git a/panels/network/connection-editor/ce-page-details.c b/panels/network/connection-editor/ce-page-details.c new file mode 100644 index 0000000..e0f43ed --- /dev/null +++ b/panels/network/connection-editor/ce-page-details.c @@ -0,0 +1,571 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2012 Red Hat, Inc + * + * 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/gi18n.h> + +#include <NetworkManager.h> + +#include "ce-page.h" +#include "ce-page-details.h" + +#include "../panel-common.h" + +struct _CEPageDetails +{ + GtkGrid parent; + + GtkCheckButton *all_user_check; + GtkCheckButton *auto_connect_check; + GtkLabel *dns4_heading_label; + GtkLabel *dns4_label; + GtkLabel *dns6_heading_label; + GtkLabel *dns6_label; + GtkButton *forget_button; + GtkLabel *freq_heading_label; + GtkLabel *freq_label; + GtkLabel *ipv4_heading_label; + GtkLabel *ipv4_label; + GtkLabel *ipv6_heading_label; + GtkLabel *ipv6_label; + GtkLabel *last_used_heading_label; + GtkLabel *last_used_label; + GtkLabel *mac_heading_label; + GtkLabel *mac_label; + GtkCheckButton *restrict_data_check; + GtkBox *restrict_data_check_container; + GtkLabel *route_heading_label; + GtkLabel *route_label; + GtkLabel *security_heading_label; + GtkLabel *security_label; + GtkLabel *speed_heading_label; + GtkLabel *speed_label; + GtkLabel *strength_heading_label; + GtkLabel *strength_label; + + NMConnection *connection; + NMDevice *device; + NMAccessPoint *ap; + NetConnectionEditor *editor; +}; + +static void ce_page_iface_init (CEPageInterface *); + +G_DEFINE_TYPE_WITH_CODE (CEPageDetails, ce_page_details, GTK_TYPE_GRID, + G_IMPLEMENT_INTERFACE (ce_page_get_type (), ce_page_iface_init)) + +static void +forget_cb (CEPageDetails *self) +{ + net_connection_editor_forget (self->editor); +} + +static gchar * +get_ap_security_string (NMAccessPoint *ap) +{ + NM80211ApSecurityFlags wpa_flags, rsn_flags; + NM80211ApFlags flags; + GString *str; + + 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); + + str = g_string_new (""); + if ((flags & NM_802_11_AP_FLAGS_PRIVACY) && + (wpa_flags == NM_802_11_AP_SEC_NONE) && + (rsn_flags == NM_802_11_AP_SEC_NONE)) { + /* TRANSLATORS: this WEP WiFi security */ + g_string_append_printf (str, "%s, ", _("WEP")); + } + if (wpa_flags != NM_802_11_AP_SEC_NONE) { + /* TRANSLATORS: this WPA WiFi security */ + g_string_append_printf (str, "%s, ", _("WPA")); + } + if (rsn_flags != NM_802_11_AP_SEC_NONE) { +#if NM_CHECK_VERSION(1,20,6) + if (rsn_flags & NM_802_11_AP_SEC_KEY_MGMT_SAE) { + /* TRANSLATORS: this WPA3 WiFi security */ + g_string_append_printf (str, "%s, ", _("WPA3")); + } +#if NM_CHECK_VERSION(1,24,0) + else if (rsn_flags & NM_802_11_AP_SEC_KEY_MGMT_OWE) { + /* TRANSLATORS: this Enhanced Open WiFi security */ + g_string_append_printf (str, "%s, ", _("Enhanced Open")); + } +#endif +#if NM_CHECK_VERSION(1,26,0) + else if (rsn_flags & NM_802_11_AP_SEC_KEY_MGMT_OWE_TM) { + /* Connected to open OWE-TM network. */ + } +#endif + else +#endif + { + /* TRANSLATORS: this WPA WiFi security */ + g_string_append_printf (str, "%s, ", _("WPA2")); + } + } + if ((wpa_flags & NM_802_11_AP_SEC_KEY_MGMT_802_1X) || + (rsn_flags & NM_802_11_AP_SEC_KEY_MGMT_802_1X)) { + /* TRANSLATORS: this Enterprise WiFi security */ + g_string_append_printf (str, "%s, ", _("Enterprise")); + } + if (str->len > 0) + g_string_set_size (str, str->len - 2); + else { + g_string_append (str, C_("Wifi security", "None")); + } + return g_string_free (str, FALSE); +} + +static void +update_last_used (CEPageDetails *self, NMConnection *connection) +{ + g_autofree gchar *last_used = NULL; + g_autoptr(GDateTime) now = NULL; + g_autoptr(GDateTime) then = NULL; + gint days; + GTimeSpan diff; + guint64 timestamp; + NMSettingConnection *s_con; + + s_con = nm_connection_get_setting_connection (connection); + if (s_con == NULL) + goto out; + timestamp = nm_setting_connection_get_timestamp (s_con); + if (timestamp == 0) { + last_used = g_strdup (_("Never")); + goto out; + } + + /* calculate the amount of time that has elapsed */ + now = g_date_time_new_now_utc (); + then = g_date_time_new_from_unix_utc (timestamp); + + diff = g_date_time_difference (now, then); + days = diff / G_TIME_SPAN_DAY; + if (days == 0) + last_used = g_strdup (_("Today")); + else if (days == 1) + last_used = g_strdup (_("Yesterday")); + else + last_used = g_strdup_printf (ngettext ("%i day ago", "%i days ago", days), days); +out: + gtk_label_set_label (self->last_used_label, last_used); + gtk_widget_set_visible (GTK_WIDGET (self->last_used_heading_label), last_used != NULL); + gtk_widget_set_visible (GTK_WIDGET (self->last_used_label), last_used != NULL); +} + +static void +all_user_changed (CEPageDetails *self) +{ + gboolean all_users; + NMSettingConnection *sc; + + sc = nm_connection_get_setting_connection (self->connection); + all_users = gtk_check_button_get_active (GTK_CHECK_BUTTON (self->all_user_check)); + + g_object_set (sc, "permissions", NULL, NULL); + if (!all_users) + nm_setting_connection_add_permission (sc, "user", g_get_user_name (), NULL); +} + +static void +restrict_data_changed (CEPageDetails *self) +{ + NMSettingConnection *s_con; + NMMetered metered; + + s_con = nm_connection_get_setting_connection (self->connection); + + if (gtk_check_button_get_active (GTK_CHECK_BUTTON (self->restrict_data_check))) + metered = NM_METERED_YES; + else + metered = NM_METERED_NO; + + g_object_set (s_con, "metered", metered, NULL); +} + +static void +update_restrict_data (CEPageDetails *self) +{ + NMSettingConnection *s_con; + NMMetered metered; + const gchar *type; + + s_con = nm_connection_get_setting_connection (self->connection); + + if (s_con == NULL) + return; + + /* Disable for VPN; NetworkManager does not implement that yet (see + * bug https://bugzilla.gnome.org/show_bug.cgi?id=792618) */ + type = nm_setting_connection_get_connection_type (s_con); + if (g_str_equal (type, NM_SETTING_VPN_SETTING_NAME)) + return; + + metered = nm_setting_connection_get_metered (s_con); + + gtk_check_button_set_active (GTK_CHECK_BUTTON (self->restrict_data_check), + metered == NM_METERED_YES || metered == NM_METERED_GUESS_YES); + gtk_widget_show (GTK_WIDGET (self->restrict_data_check_container)); + + g_signal_connect_object (self->restrict_data_check, "notify::active", G_CALLBACK (restrict_data_changed), self, G_CONNECT_SWAPPED); + g_signal_connect_object (self->restrict_data_check, "notify::active", G_CALLBACK (ce_page_changed), self, G_CONNECT_SWAPPED); +} + +static void +connect_details_page (CEPageDetails *self) +{ + NMSettingConnection *sc; + guint speed; + NMDeviceWifiCapabilities wifi_caps; + guint frequency; + guint strength; + NMDeviceState state; + NMAccessPoint *active_ap; + g_autofree gchar *speed_label = NULL; + const gchar *type; + const gchar *hw_address = NULL; + g_autofree gchar *security_string = NULL; + g_autofree gchar *freq_string = NULL; + const gchar *strength_label; + gboolean device_is_active; + NMIPConfig *ipv4_config = NULL, *ipv6_config = NULL; + gboolean have_ipv4_address = FALSE, have_ipv6_address = FALSE; + gboolean have_dns4 = FALSE, have_dns6 = FALSE; + const gchar *route4_text = NULL, *route6_text = NULL; + + sc = nm_connection_get_setting_connection (self->connection); + type = nm_setting_connection_get_connection_type (sc); + + if (NM_IS_DEVICE_WIFI (self->device)) + active_ap = nm_device_wifi_get_active_access_point (NM_DEVICE_WIFI (self->device)); + else + active_ap = NULL; + frequency = active_ap ? nm_access_point_get_frequency (active_ap) : 0; + + state = self->device ? nm_device_get_state (self->device) : NM_DEVICE_STATE_DISCONNECTED; + + device_is_active = FALSE; + speed = 0; + wifi_caps = 0; + if (active_ap && self->ap == active_ap && state != NM_DEVICE_STATE_UNAVAILABLE) { + device_is_active = TRUE; + if (NM_IS_DEVICE_WIFI (self->device)) { + speed = nm_device_wifi_get_bitrate (NM_DEVICE_WIFI (self->device)) / 1000; + wifi_caps = nm_device_wifi_get_capabilities (NM_DEVICE_WIFI (self->device)); + } + } else if (self->device) { + NMActiveConnection *ac; + const gchar *p1, *p2; + + ac = nm_device_get_active_connection (self->device); + p1 = ac ? nm_active_connection_get_uuid (ac) : NULL; + p2 = nm_connection_get_uuid (self->connection); + if (g_strcmp0 (p1, p2) == 0) { + device_is_active = TRUE; + if (NM_IS_DEVICE_WIFI (self->device)) { + speed = nm_device_wifi_get_bitrate (NM_DEVICE_WIFI (self->device)) / 1000; + wifi_caps = nm_device_wifi_get_capabilities (NM_DEVICE_WIFI (self->device)); + } else if (NM_IS_DEVICE_ETHERNET (self->device)) + speed = nm_device_ethernet_get_speed (NM_DEVICE_ETHERNET (self->device)); + } + } + + if (speed > 0 && frequency > 0) + speed_label = g_strdup_printf (_("%d Mb/s (%1.1f GHz)"), speed, (float) (frequency) / 1000.0); + else if (speed > 0) + speed_label = g_strdup_printf (_("%d Mb/s"), speed); + gtk_label_set_label (self->speed_label, speed_label); + gtk_widget_set_visible (GTK_WIDGET (self->speed_heading_label), speed_label != NULL); + gtk_widget_set_visible (GTK_WIDGET (self->speed_label), speed_label != NULL); + + if (self->device) + hw_address = nm_device_get_hw_address (self->device); + + gtk_label_set_label (self->mac_label, hw_address); + gtk_widget_set_visible (GTK_WIDGET (self->mac_heading_label), hw_address != NULL); + gtk_widget_set_visible (GTK_WIDGET (self->mac_label), hw_address != NULL); + + if (wifi_caps & NM_WIFI_DEVICE_CAP_FREQ_VALID) { + if (wifi_caps & NM_WIFI_DEVICE_CAP_FREQ_2GHZ && + wifi_caps & NM_WIFI_DEVICE_CAP_FREQ_5GHZ) + freq_string = g_strdup (_("2.4 GHz / 5 GHz")); + else if (wifi_caps & NM_WIFI_DEVICE_CAP_FREQ_2GHZ) + freq_string = g_strdup (_("2.4 GHz")); + else if (wifi_caps & NM_WIFI_DEVICE_CAP_FREQ_5GHZ) + freq_string = g_strdup (_("5 GHz")); + } + + gtk_label_set_label (self->freq_label, freq_string); + gtk_widget_set_visible (GTK_WIDGET (self->freq_heading_label), freq_string != NULL); + gtk_widget_set_visible (GTK_WIDGET (self->freq_label), freq_string != NULL); + + if (device_is_active && active_ap) + security_string = get_ap_security_string (active_ap); + gtk_label_set_label (self->security_label, security_string); + gtk_widget_set_visible (GTK_WIDGET (self->security_heading_label), security_string != NULL); + gtk_widget_set_visible (GTK_WIDGET (self->security_label), security_string != NULL); + + strength = 0; + if (self->ap != NULL) + strength = nm_access_point_get_strength (self->ap); + + if (strength <= 0) + strength_label = NULL; + else if (strength < 20) + strength_label = C_("Signal strength", "None"); + else if (strength < 40) + strength_label = C_("Signal strength", "Weak"); + else if (strength < 50) + strength_label = C_("Signal strength", "Ok"); + else if (strength < 80) + strength_label = C_("Signal strength", "Good"); + else + strength_label = C_("Signal strength", "Excellent"); + gtk_label_set_label (self->strength_label, strength_label); + gtk_widget_set_visible (GTK_WIDGET (self->strength_heading_label), strength_label != NULL); + gtk_widget_set_visible (GTK_WIDGET (self->strength_label), strength_label != NULL); + + if (device_is_active && self->device != NULL) { + ipv4_config = nm_device_get_ip4_config (self->device); + ipv6_config = nm_device_get_ip6_config (self->device); + } + + if (ipv4_config != NULL) { + GPtrArray *addresses; + const gchar *ipv4_text = NULL; + g_autofree gchar *ip4_dns = NULL; + + addresses = nm_ip_config_get_addresses (ipv4_config); + if (addresses->len > 0) + ipv4_text = nm_ip_address_get_address (g_ptr_array_index (addresses, 0)); + gtk_label_set_label (self->ipv4_label, ipv4_text); + gtk_widget_set_visible (GTK_WIDGET (self->ipv4_heading_label), ipv4_text != NULL); + gtk_widget_set_visible (GTK_WIDGET (self->ipv4_label), ipv4_text != NULL); + have_ipv4_address = ipv4_text != NULL; + + ip4_dns = g_strjoinv (" ", (char **) nm_ip_config_get_nameservers (ipv4_config)); + if (!*ip4_dns) + ip4_dns = NULL; + gtk_label_set_label (self->dns4_label, ip4_dns); + gtk_widget_set_visible (GTK_WIDGET (self->dns4_heading_label), ip4_dns != NULL); + gtk_widget_set_visible (GTK_WIDGET (self->dns4_label), ip4_dns != NULL); + have_dns4 = ip4_dns != NULL; + + route4_text = nm_ip_config_get_gateway (ipv4_config); + } else { + gtk_widget_hide (GTK_WIDGET (self->ipv4_heading_label)); + gtk_widget_hide (GTK_WIDGET (self->ipv4_label)); + gtk_widget_hide (GTK_WIDGET (self->dns4_heading_label)); + gtk_widget_hide (GTK_WIDGET (self->dns4_label)); + } + + if (ipv6_config != NULL) { + g_autofree gchar *ipv6_text = NULL; + g_autofree gchar *ip6_dns = NULL; + + ipv6_text = net_device_get_ip6_addresses (ipv6_config); + gtk_label_set_label (self->ipv6_label, ipv6_text); + gtk_widget_set_visible (GTK_WIDGET (self->ipv6_heading_label), ipv6_text != NULL); + gtk_widget_set_valign (GTK_WIDGET (self->ipv6_heading_label), GTK_ALIGN_START); + gtk_widget_set_visible (GTK_WIDGET (self->ipv6_label), ipv6_text != NULL); + have_ipv6_address = ipv6_text != NULL; + + ip6_dns = g_strjoinv (" ", (char **) nm_ip_config_get_nameservers (ipv6_config)); + if (!*ip6_dns) + ip6_dns = NULL; + gtk_label_set_label (self->dns6_label, ip6_dns); + gtk_widget_set_visible (GTK_WIDGET (self->dns6_heading_label), ip6_dns != NULL); + gtk_widget_set_visible (GTK_WIDGET (self->dns6_label), ip6_dns != NULL); + have_dns6 = ip6_dns != NULL; + + route6_text = nm_ip_config_get_gateway (ipv6_config); + } else { + gtk_widget_hide (GTK_WIDGET (self->ipv6_heading_label)); + gtk_widget_hide (GTK_WIDGET (self->ipv6_label)); + gtk_widget_hide (GTK_WIDGET (self->dns6_heading_label)); + gtk_widget_hide (GTK_WIDGET (self->dns6_label)); + } + + if (have_ipv4_address && have_ipv6_address) { + gtk_label_set_label (self->ipv4_heading_label, _("IPv4 Address")); + gtk_label_set_label (self->ipv6_heading_label, _("IPv6 Address")); + } else { + gtk_label_set_label (self->ipv4_heading_label, _("IP Address")); + gtk_label_set_label (self->ipv6_heading_label, _("IP Address")); + } + + if (have_dns4 && have_dns6) { + gtk_label_set_label (self->dns4_heading_label, _("DNS4")); + gtk_label_set_label (self->dns6_heading_label, _("DNS6")); + } else { + gtk_label_set_label (self->dns4_heading_label, _("DNS")); + gtk_label_set_label (self->dns6_heading_label, _("DNS")); + } + + if (route4_text != NULL || route6_text != NULL) { + g_autofree const gchar *routes_text = NULL; + + if (route4_text == NULL) { + routes_text = g_strdup (route6_text); + } else if (route6_text == NULL) { + routes_text = g_strdup (route4_text); + } else { + routes_text = g_strjoin ("\n", route4_text, route6_text, NULL); + } + gtk_label_set_label (self->route_label, routes_text); + gtk_widget_set_visible (GTK_WIDGET (self->route_heading_label), routes_text != NULL); + gtk_widget_set_valign (GTK_WIDGET (self->route_heading_label), GTK_ALIGN_START); + gtk_widget_set_visible (GTK_WIDGET (self->route_label), routes_text != NULL); + } else { + gtk_widget_hide (GTK_WIDGET (self->route_heading_label)); + gtk_widget_hide (GTK_WIDGET (self->route_label)); + } + + if (!device_is_active && self->connection) + update_last_used (self, self->connection); + else { + gtk_widget_set_visible (GTK_WIDGET (self->last_used_heading_label), FALSE); + gtk_widget_set_visible (GTK_WIDGET (self->last_used_label), FALSE); + } + + /* Auto connect check */ + if (g_str_equal (type, NM_SETTING_VPN_SETTING_NAME)) { + gtk_widget_hide (GTK_WIDGET (self->auto_connect_check)); + } else { + g_object_bind_property (sc, "autoconnect", + self->auto_connect_check, "active", + G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE); + g_signal_connect_object (self->auto_connect_check, "toggled", G_CALLBACK (ce_page_changed), self, G_CONNECT_SWAPPED); + } + + /* All users check */ + gtk_check_button_set_active (GTK_CHECK_BUTTON (self->all_user_check), + nm_setting_connection_get_num_permissions (sc) == 0); + g_signal_connect_object (self->all_user_check, "toggled", G_CALLBACK (all_user_changed), self, G_CONNECT_SWAPPED); + g_signal_connect_object (self->all_user_check, "toggled", G_CALLBACK (ce_page_changed), self, G_CONNECT_SWAPPED); + + /* Restrict Data check */ + update_restrict_data (self); + + /* Forget button */ + g_signal_connect_object (self->forget_button, "clicked", G_CALLBACK (forget_cb), self, G_CONNECT_SWAPPED); + + if (g_str_equal (type, NM_SETTING_WIRELESS_SETTING_NAME)) + gtk_button_set_label (self->forget_button, _("Forget Connection")); + else if (g_str_equal (type, NM_SETTING_WIRED_SETTING_NAME)) + gtk_button_set_label (self->forget_button, _("Remove Connection Profile")); + else if (g_str_equal (type, NM_SETTING_VPN_SETTING_NAME)) + gtk_button_set_label (self->forget_button, _("Remove VPN")); + else + gtk_widget_hide (GTK_WIDGET (self->forget_button)); +} + +static void +ce_page_details_dispose (GObject *object) +{ + CEPageDetails *self = CE_PAGE_DETAILS (object); + + g_clear_object (&self->connection); + + G_OBJECT_CLASS (ce_page_details_parent_class)->dispose (object); +} + +static const gchar * +ce_page_details_get_title (CEPage *page) +{ + return _("Details"); +} + +static void +ce_page_details_init (CEPageDetails *self) +{ + gtk_widget_init_template (GTK_WIDGET (self)); +} + +static void +ce_page_details_class_init (CEPageDetailsClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + object_class->dispose = ce_page_details_dispose; + + gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/control-center/network/details-page.ui"); + + gtk_widget_class_bind_template_child (widget_class, CEPageDetails, all_user_check); + gtk_widget_class_bind_template_child (widget_class, CEPageDetails, auto_connect_check); + gtk_widget_class_bind_template_child (widget_class, CEPageDetails, dns4_heading_label); + gtk_widget_class_bind_template_child (widget_class, CEPageDetails, dns4_label); + gtk_widget_class_bind_template_child (widget_class, CEPageDetails, dns6_heading_label); + gtk_widget_class_bind_template_child (widget_class, CEPageDetails, dns6_label); + gtk_widget_class_bind_template_child (widget_class, CEPageDetails, forget_button); + gtk_widget_class_bind_template_child (widget_class, CEPageDetails, freq_heading_label); + gtk_widget_class_bind_template_child (widget_class, CEPageDetails, freq_label); + gtk_widget_class_bind_template_child (widget_class, CEPageDetails, ipv4_heading_label); + gtk_widget_class_bind_template_child (widget_class, CEPageDetails, ipv4_label); + gtk_widget_class_bind_template_child (widget_class, CEPageDetails, ipv6_heading_label); + gtk_widget_class_bind_template_child (widget_class, CEPageDetails, ipv6_label); + gtk_widget_class_bind_template_child (widget_class, CEPageDetails, last_used_heading_label); + gtk_widget_class_bind_template_child (widget_class, CEPageDetails, last_used_label); + gtk_widget_class_bind_template_child (widget_class, CEPageDetails, mac_heading_label); + gtk_widget_class_bind_template_child (widget_class, CEPageDetails, mac_label); + gtk_widget_class_bind_template_child (widget_class, CEPageDetails, restrict_data_check); + gtk_widget_class_bind_template_child (widget_class, CEPageDetails, restrict_data_check_container); + gtk_widget_class_bind_template_child (widget_class, CEPageDetails, route_heading_label); + gtk_widget_class_bind_template_child (widget_class, CEPageDetails, route_label); + gtk_widget_class_bind_template_child (widget_class, CEPageDetails, security_heading_label); + gtk_widget_class_bind_template_child (widget_class, CEPageDetails, security_label); + gtk_widget_class_bind_template_child (widget_class, CEPageDetails, speed_heading_label); + gtk_widget_class_bind_template_child (widget_class, CEPageDetails, speed_label); + gtk_widget_class_bind_template_child (widget_class, CEPageDetails, strength_heading_label); + gtk_widget_class_bind_template_child (widget_class, CEPageDetails, strength_label); +} + +static void +ce_page_iface_init (CEPageInterface *iface) +{ + iface->get_title = ce_page_details_get_title; +} + +CEPageDetails * +ce_page_details_new (NMConnection *connection, + NMDevice *device, + NMAccessPoint *ap, + NetConnectionEditor *editor) +{ + CEPageDetails *self; + + self = CE_PAGE_DETAILS (g_object_new (ce_page_details_get_type (), NULL)); + + self->connection = g_object_ref (connection); + self->editor = editor; + self->device = device; + self->ap = ap; + + connect_details_page (self); + + return self; +} diff --git a/panels/network/connection-editor/ce-page-details.h b/panels/network/connection-editor/ce-page-details.h new file mode 100644 index 0000000..4fd660c --- /dev/null +++ b/panels/network/connection-editor/ce-page-details.h @@ -0,0 +1,38 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2012 Red Hat, Inc. + * + * 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. + */ + +#pragma once + +#include <gtk/gtk.h> +#include <NetworkManager.h> + +#include "net-connection-editor.h" + +G_BEGIN_DECLS + +G_DECLARE_FINAL_TYPE (CEPageDetails, ce_page_details, CE, PAGE_DETAILS, GtkGrid) + +CEPageDetails *ce_page_details_new (NMConnection *connection, + NMDevice *device, + NMAccessPoint *ap, + NetConnectionEditor *editor); + +G_END_DECLS diff --git a/panels/network/connection-editor/ce-page-ethernet.c b/panels/network/connection-editor/ce-page-ethernet.c new file mode 100644 index 0000000..79c1b7d --- /dev/null +++ b/panels/network/connection-editor/ce-page-ethernet.c @@ -0,0 +1,224 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2012 Red Hat, Inc + * + * 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/gi18n.h> +#include <net/if_arp.h> +#include <NetworkManager.h> + +#include "ce-page.h" +#include "ce-page-ethernet.h" +#include "ui-helpers.h" + +struct _CEPageEthernet +{ + GtkGrid parent; + + GtkComboBoxText *cloned_mac_combo; + GtkComboBoxText *mac_combo; + GtkSpinButton *mtu_spin; + GtkWidget *mtu_label; + GtkEntry *name_entry; + + NMClient *client; + NMSettingConnection *setting_connection; + NMSettingWired *setting_wired; +}; + +static void ce_page_iface_init (CEPageInterface *); + +G_DEFINE_TYPE_WITH_CODE (CEPageEthernet, ce_page_ethernet, GTK_TYPE_GRID, + G_IMPLEMENT_INTERFACE (ce_page_get_type (), ce_page_iface_init)) + +static void +mtu_changed (CEPageEthernet *self) +{ + if (gtk_spin_button_get_value_as_int (self->mtu_spin) == 0) + gtk_widget_hide (self->mtu_label); + else + gtk_widget_show (self->mtu_label); +} + +static void +mtu_output_cb (CEPageEthernet *self) +{ + gint defvalue; + gint val; + g_autofree gchar *buf = NULL; + + val = gtk_spin_button_get_value_as_int (self->mtu_spin); + defvalue = ce_get_property_default (NM_SETTING (self->setting_wired), NM_SETTING_WIRED_MTU); + if (val == defvalue) + buf = g_strdup (_("automatic")); + else + buf = g_strdup_printf ("%d", val); + + if (strcmp (buf, gtk_editable_get_text (GTK_EDITABLE (self->mtu_spin)))) + gtk_editable_set_text (GTK_EDITABLE (self->mtu_spin), buf); +} + +static void +connect_ethernet_page (CEPageEthernet *self) +{ + NMSettingWired *setting = self->setting_wired; + char **mac_list; + const char *s_mac_str; + const gchar *name; + const gchar *cloned_mac; + + name = nm_setting_connection_get_id (self->setting_connection); + gtk_editable_set_text (GTK_EDITABLE (self->name_entry), name); + + /* Device MAC address */ + mac_list = ce_page_get_mac_list (self->client, NM_TYPE_DEVICE_ETHERNET, + NM_DEVICE_ETHERNET_PERMANENT_HW_ADDRESS); + s_mac_str = nm_setting_wired_get_mac_address (setting); + ce_page_setup_mac_combo (self->mac_combo, s_mac_str, mac_list); + g_strfreev (mac_list); + g_signal_connect_object (self->mac_combo, "changed", G_CALLBACK (ce_page_changed), self, G_CONNECT_SWAPPED); + + /* Cloned MAC address */ + cloned_mac = nm_setting_wired_get_cloned_mac_address (setting); + ce_page_setup_cloned_mac_combo (self->cloned_mac_combo, cloned_mac); + g_signal_connect_object (self->cloned_mac_combo, "changed", G_CALLBACK (ce_page_changed), self, G_CONNECT_SWAPPED); + + /* MTU */ + g_signal_connect_object (self->mtu_spin, "output", G_CALLBACK (mtu_output_cb), self, G_CONNECT_SWAPPED); + gtk_spin_button_set_value (self->mtu_spin, (gdouble) nm_setting_wired_get_mtu (setting)); + g_signal_connect_object (self->mtu_spin, "value-changed", G_CALLBACK (mtu_changed), self, G_CONNECT_SWAPPED); + mtu_changed (self); + + g_signal_connect_object (self->name_entry, "changed", G_CALLBACK (ce_page_changed), self, G_CONNECT_SWAPPED); + g_signal_connect_object (self->mtu_spin, "value-changed", G_CALLBACK (ce_page_changed), self, G_CONNECT_SWAPPED); +} + +static void +ui_to_setting (CEPageEthernet *self) +{ + g_autofree gchar *device_mac = NULL; + g_autofree gchar *cloned_mac = NULL; + const gchar *text; + GtkWidget *entry; + + entry = gtk_combo_box_get_child (GTK_COMBO_BOX (self->mac_combo)); + if (entry) { + text = gtk_editable_get_text (GTK_EDITABLE (entry)); + device_mac = ce_page_trim_address (text); + } + + cloned_mac = ce_page_cloned_mac_get (self->cloned_mac_combo); + + g_object_set (self->setting_wired, + NM_SETTING_WIRED_MAC_ADDRESS, device_mac, + NM_SETTING_WIRED_CLONED_MAC_ADDRESS, cloned_mac, + NM_SETTING_WIRED_MTU, (guint32) gtk_spin_button_get_value_as_int (self->mtu_spin), + NULL); + + g_object_set (self->setting_connection, + NM_SETTING_CONNECTION_ID, gtk_editable_get_text (GTK_EDITABLE (self->name_entry)), + NULL); +} + +static const gchar * +ce_page_ethernet_get_title (CEPage *page) +{ + return _("Identity"); +} + +static gboolean +ce_page_ethernet_validate (CEPage *page, + NMConnection *connection, + GError **error) +{ + CEPageEthernet *self = CE_PAGE_ETHERNET (page); + GtkWidget *entry; + gboolean ret = TRUE; + + entry = gtk_combo_box_get_child (GTK_COMBO_BOX (self->mac_combo)); + if (entry) { + if (!ce_page_address_is_valid (gtk_editable_get_text (GTK_EDITABLE (entry)))) { + widget_set_error (entry); + ret = FALSE; + } else { + widget_unset_error (entry); + } + } + + if (!ce_page_cloned_mac_combo_valid (self->cloned_mac_combo)) { + widget_set_error (gtk_combo_box_get_child (GTK_COMBO_BOX (self->cloned_mac_combo))); + ret = FALSE; + } else { + widget_unset_error (gtk_combo_box_get_child (GTK_COMBO_BOX (self->cloned_mac_combo))); + } + + if (!ret) + return ret; + + ui_to_setting (self); + + return nm_setting_verify (NM_SETTING (self->setting_connection), NULL, error) && + nm_setting_verify (NM_SETTING (self->setting_wired), NULL, error); +} + +static void +ce_page_ethernet_init (CEPageEthernet *self) +{ + gtk_widget_init_template (GTK_WIDGET (self)); +} + +static void +ce_page_ethernet_class_init (CEPageEthernetClass *klass) +{ + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/control-center/network/ethernet-page.ui"); + + gtk_widget_class_bind_template_child (widget_class, CEPageEthernet, cloned_mac_combo); + gtk_widget_class_bind_template_child (widget_class, CEPageEthernet, mac_combo); + gtk_widget_class_bind_template_child (widget_class, CEPageEthernet, mtu_spin); + gtk_widget_class_bind_template_child (widget_class, CEPageEthernet, mtu_label); + gtk_widget_class_bind_template_child (widget_class, CEPageEthernet, name_entry); +} + +static void +ce_page_iface_init (CEPageInterface *iface) +{ + iface->get_title = ce_page_ethernet_get_title; + iface->validate = ce_page_ethernet_validate; +} + +CEPageEthernet * +ce_page_ethernet_new (NMConnection *connection, + NMClient *client) +{ + CEPageEthernet *self; + + self = CE_PAGE_ETHERNET (g_object_new (ce_page_ethernet_get_type (), NULL)); + + self->client = client; + self->setting_connection = nm_connection_get_setting_connection (connection); + self->setting_wired = nm_connection_get_setting_wired (connection); + + connect_ethernet_page (self); + + return self; +} diff --git a/panels/network/connection-editor/ce-page-ethernet.h b/panels/network/connection-editor/ce-page-ethernet.h new file mode 100644 index 0000000..5fce19f --- /dev/null +++ b/panels/network/connection-editor/ce-page-ethernet.h @@ -0,0 +1,34 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2012 Red Hat, Inc. + * + * 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. + */ + +#pragma once + +#include <gtk/gtk.h> +#include <NetworkManager.h> + +G_BEGIN_DECLS + +G_DECLARE_FINAL_TYPE (CEPageEthernet, ce_page_ethernet, CE, PAGE_ETHERNET, GtkGrid) + +CEPageEthernet *ce_page_ethernet_new (NMConnection *connection, + NMClient *client); + +G_END_DECLS diff --git a/panels/network/connection-editor/ce-page-ip4.c b/panels/network/connection-editor/ce-page-ip4.c new file mode 100644 index 0000000..c3f0864 --- /dev/null +++ b/panels/network/connection-editor/ce-page-ip4.c @@ -0,0 +1,846 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2012 Red Hat, Inc + * + * 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 <errno.h> +#include <stdlib.h> +#include <arpa/inet.h> +#include <glib/gi18n.h> +#include <NetworkManager.h> + +#include "ce-ip-address-entry.h" +#include "ce-netmask-entry.h" +#include "ce-page.h" +#include "ce-page-ip4.h" +#include "ui-helpers.h" + +static void ensure_empty_address_row (CEPageIP4 *self); +static void ensure_empty_routes_row (CEPageIP4 *self); + +struct _CEPageIP4 +{ + AdwBin parent; + + GtkLabel *address_address_label; + GtkBox *address_box; + GtkLabel *address_gateway_label; + GtkLabel *address_netmask_label; + GtkSizeGroup *address_sizegroup; + GtkSwitch *auto_dns_switch; + GtkSwitch *auto_routes_switch; + GtkCheckButton *automatic_radio; + GtkBox *content_box; + GtkCheckButton *disabled_radio; + GtkEntry *dns_entry; + GtkCheckButton *local_radio; + GtkGrid *main_box; + GtkCheckButton *manual_radio; + GtkCheckButton *never_default_check; + GtkLabel *routes_address_label; + GtkBox *routes_box; + GtkLabel *routes_gateway_label; + GtkLabel *routes_metric_label; + GtkSizeGroup *routes_metric_sizegroup; + GtkLabel *routes_netmask_label; + GtkSizeGroup *routes_sizegroup; + GtkCheckButton *shared_radio; + + NMSettingIPConfig *setting; + + GtkWidget *address_list; + GtkWidget *routes_list; +}; + +static void ce_page_iface_init (CEPageInterface *); + +G_DEFINE_TYPE_WITH_CODE (CEPageIP4, ce_page_ip4, ADW_TYPE_BIN, + G_IMPLEMENT_INTERFACE (ce_page_get_type (), ce_page_iface_init)) + +enum { + METHOD_COL_NAME, + METHOD_COL_METHOD +}; + +enum { + IP4_METHOD_AUTO, + IP4_METHOD_MANUAL, + IP4_METHOD_LINK_LOCAL, + IP4_METHOD_SHARED, + IP4_METHOD_DISABLED +}; + +static void +method_changed (CEPageIP4 *self) +{ + gboolean addr_enabled; + gboolean dns_enabled; + gboolean routes_enabled; + + if (gtk_check_button_get_active (GTK_CHECK_BUTTON (self->disabled_radio)) || + gtk_check_button_get_active (GTK_CHECK_BUTTON (self->shared_radio))) { + addr_enabled = FALSE; + dns_enabled = FALSE; + routes_enabled = FALSE; + } else { + addr_enabled = gtk_check_button_get_active (GTK_CHECK_BUTTON (self->manual_radio)); + dns_enabled = !gtk_check_button_get_active (GTK_CHECK_BUTTON (self->local_radio)); + routes_enabled = !gtk_check_button_get_active (GTK_CHECK_BUTTON (self->local_radio)); + } + + gtk_widget_set_visible (GTK_WIDGET (self->address_box), addr_enabled); + gtk_widget_set_sensitive (GTK_WIDGET (self->dns_entry), dns_enabled); + gtk_widget_set_sensitive (GTK_WIDGET (self->routes_list), routes_enabled); + gtk_widget_set_sensitive (GTK_WIDGET (self->never_default_check), routes_enabled); + + ce_page_changed (CE_PAGE (self)); +} + +static void +update_row_sensitivity (CEPageIP4 *self, GtkWidget *list) +{ + GtkWidget *child; + gint rows = 0, i = 0; + + for (child = gtk_widget_get_first_child (GTK_WIDGET (list)); + child; + child = gtk_widget_get_next_sibling (child)) { + GtkWidget *button; + + button = GTK_WIDGET (g_object_get_data (G_OBJECT (child), "delete-button")); + if (button != NULL) + rows++; + } + for (child = gtk_widget_get_first_child (GTK_WIDGET (list)); + child; + child = gtk_widget_get_next_sibling (child)) { + GtkWidget *button; + + button = GTK_WIDGET (g_object_get_data (G_OBJECT (child), "delete-button")); + if (button != NULL) + gtk_widget_set_sensitive (button, rows > 1 && ++i < rows); + } +} + +static void +update_row_gateway_sensitivity (CEPageIP4 *self) +{ + GtkWidget *child; + gint rows = 0; + + for (child = gtk_widget_get_first_child (self->address_list); + child; + child = gtk_widget_get_next_sibling (child)) { + GtkWidget *entry; + + entry = GTK_WIDGET (g_object_get_data (G_OBJECT (child), "gateway")); + + gtk_widget_set_sensitive (entry, (rows == 0)); + + rows++; + } +} + +static void +remove_row (CEPageIP4 *self, GtkButton *button) +{ + GtkWidget *list; + GtkWidget *row; + GtkWidget *row_box; + + row_box = gtk_widget_get_parent (GTK_WIDGET (button)); + row = gtk_widget_get_parent (row_box); + list = gtk_widget_get_parent (row); + + gtk_list_box_remove (GTK_LIST_BOX (list), row); + + ce_page_changed (CE_PAGE (self)); + + update_row_sensitivity (self, list); + if (list == self->address_list) + update_row_gateway_sensitivity (self); +} + +static gboolean +validate_row (GtkWidget *row) +{ + GtkWidget *child; + GtkWidget *box; + gboolean valid; + + valid = FALSE; + box = gtk_list_box_row_get_child (GTK_LIST_BOX_ROW (row)); + + for (child = gtk_widget_get_first_child (box); + child; + child = gtk_widget_get_next_sibling (child)) { + if (!GTK_IS_ENTRY (child)) + continue; + + valid = valid || gtk_entry_get_text_length (GTK_ENTRY (child)) > 0; + } + + return valid; +} + +static void +add_address_row (CEPageIP4 *self, + const gchar *address, + const gchar *network, + const gchar *gateway) +{ + GtkWidget *row; + GtkWidget *row_box; + GtkWidget *widget; + GtkWidget *delete_button; + + row = gtk_list_box_row_new (); + gtk_list_box_row_set_activatable (GTK_LIST_BOX_ROW (row), FALSE); + + row_box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0); + gtk_widget_add_css_class (row_box, "linked"); + + widget = GTK_WIDGET (ce_ip_address_entry_new (AF_INET)); + g_signal_connect_object (widget, "changed", G_CALLBACK (ce_page_changed), self, G_CONNECT_SWAPPED); + g_signal_connect_object (widget, "activate", G_CALLBACK (ensure_empty_address_row), self, G_CONNECT_SWAPPED); + g_object_set_data (G_OBJECT (row), "address", widget); + gtk_editable_set_text (GTK_EDITABLE (widget), address); + gtk_editable_set_width_chars (GTK_EDITABLE (widget), 16); + gtk_widget_set_hexpand (widget, TRUE); + gtk_accessible_update_relation (GTK_ACCESSIBLE (widget), + GTK_ACCESSIBLE_RELATION_LABELLED_BY, self->address_address_label, NULL, + -1); + gtk_box_append (GTK_BOX (row_box), widget); + + widget = GTK_WIDGET (ce_netmask_entry_new ()); + g_signal_connect_object (widget, "changed", G_CALLBACK (ce_page_changed), self, G_CONNECT_SWAPPED); + g_signal_connect_object (widget, "activate", G_CALLBACK (ensure_empty_address_row), self, G_CONNECT_SWAPPED); + g_object_set_data (G_OBJECT (row), "netmask", widget); + gtk_editable_set_text (GTK_EDITABLE (widget), network); + gtk_editable_set_width_chars (GTK_EDITABLE (widget), 16); + gtk_widget_set_hexpand (widget, TRUE); + gtk_accessible_update_relation (GTK_ACCESSIBLE (widget), + GTK_ACCESSIBLE_RELATION_LABELLED_BY, self->address_netmask_label, NULL, + -1); + gtk_box_append (GTK_BOX (row_box), widget); + + widget = GTK_WIDGET (ce_ip_address_entry_new (AF_INET)); + g_signal_connect_object (widget, "changed", G_CALLBACK (ce_page_changed), self, G_CONNECT_SWAPPED); + g_signal_connect_object (widget, "activate", G_CALLBACK (ensure_empty_address_row), self, G_CONNECT_SWAPPED); + g_object_set_data (G_OBJECT (row), "gateway", widget); + gtk_editable_set_text (GTK_EDITABLE (widget), gateway ? gateway : ""); + gtk_editable_set_width_chars (GTK_EDITABLE (widget), 16); + gtk_widget_set_hexpand (widget, TRUE); + gtk_accessible_update_relation (GTK_ACCESSIBLE (widget), + GTK_ACCESSIBLE_RELATION_LABELLED_BY, self->address_gateway_label, NULL, + -1); + gtk_box_append (GTK_BOX (row_box), widget); + + delete_button = gtk_button_new_from_icon_name ("edit-delete-symbolic"); + gtk_widget_set_sensitive (delete_button, FALSE); + g_signal_connect_object (delete_button, "clicked", G_CALLBACK (remove_row), self, G_CONNECT_SWAPPED); + gtk_accessible_update_property (GTK_ACCESSIBLE (delete_button), + GTK_ACCESSIBLE_PROPERTY_LABEL, _("Delete Address"), + -1); + gtk_box_append (GTK_BOX (row_box), delete_button); + g_object_set_data (G_OBJECT (row), "delete-button", delete_button); + + gtk_size_group_add_widget (self->address_sizegroup, delete_button); + + gtk_list_box_row_set_child (GTK_LIST_BOX_ROW (row), row_box); + gtk_list_box_append (GTK_LIST_BOX (self->address_list), row); + + update_row_gateway_sensitivity (self); + update_row_sensitivity (self, self->address_list); +} + +static void +ensure_empty_address_row (CEPageIP4 *self) +{ + GtkWidget *child = gtk_widget_get_last_child (self->address_list); + + /* Add the last, stub row if needed*/ + if (!child || validate_row (child)) + add_address_row (self, "", "", ""); +} + +static void +add_address_box (CEPageIP4 *self) +{ + GtkWidget *list; + gint i; + + self->address_list = list = gtk_list_box_new (); + gtk_list_box_set_selection_mode (GTK_LIST_BOX (list), GTK_SELECTION_NONE); + gtk_box_append (self->address_box, list); + + for (i = 0; i < nm_setting_ip_config_get_num_addresses (self->setting); i++) { + NMIPAddress *addr; + struct in_addr tmp_addr; + gchar network[INET_ADDRSTRLEN + 1]; + + addr = nm_setting_ip_config_get_address (self->setting, i); + if (!addr) + continue; + + tmp_addr.s_addr = nm_utils_ip4_prefix_to_netmask (nm_ip_address_get_prefix (addr)); + (void) inet_ntop (AF_INET, &tmp_addr, &network[0], sizeof (network)); + + add_address_row (self, + nm_ip_address_get_address (addr), + network, + i == 0 ? nm_setting_ip_config_get_gateway (self->setting) : ""); + } + if (nm_setting_ip_config_get_num_addresses (self->setting) == 0) + ensure_empty_address_row (self); +} + +static void +add_dns_section (CEPageIP4 *self) +{ + GString *string; + gint i; + + gtk_switch_set_active (self->auto_dns_switch, !nm_setting_ip_config_get_ignore_auto_dns (self->setting)); + g_signal_connect_object (self->auto_dns_switch, "notify::active", G_CALLBACK (ce_page_changed), self, G_CONNECT_SWAPPED); + + string = g_string_new (""); + + for (i = 0; i < nm_setting_ip_config_get_num_dns (self->setting); i++) { + const char *address; + + address = nm_setting_ip_config_get_dns (self->setting, i); + + if (i > 0) + g_string_append (string, ", "); + + g_string_append (string, address); + } + + gtk_editable_set_text (GTK_EDITABLE (self->dns_entry), string->str); + + g_signal_connect_object (self->dns_entry, "notify::text", G_CALLBACK (ce_page_changed), self, G_CONNECT_SWAPPED); + + g_string_free (string, TRUE); +} + +static void +add_route_row (CEPageIP4 *self, + const gchar *address, + const gchar *netmask, + const gchar *gateway, + gint metric) +{ + GtkWidget *row; + GtkWidget *row_box; + GtkWidget *widget; + GtkWidget *delete_button; + + row = gtk_list_box_row_new (); + gtk_list_box_row_set_activatable (GTK_LIST_BOX_ROW (row), FALSE); + + row_box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0); + gtk_widget_add_css_class (row_box, "linked"); + + widget = GTK_WIDGET (ce_ip_address_entry_new (AF_INET)); + g_signal_connect_object (widget, "changed", G_CALLBACK (ce_page_changed), self, G_CONNECT_SWAPPED); + g_signal_connect_object (widget, "activate", G_CALLBACK (ensure_empty_routes_row), self, G_CONNECT_SWAPPED); + g_object_set_data (G_OBJECT (row), "address", widget); + gtk_editable_set_text (GTK_EDITABLE (widget), address); + gtk_editable_set_width_chars (GTK_EDITABLE (widget), 16); + gtk_widget_set_hexpand (widget, TRUE); + gtk_accessible_update_relation (GTK_ACCESSIBLE (widget), + GTK_ACCESSIBLE_RELATION_LABELLED_BY, self->routes_address_label, NULL, + -1); + gtk_box_append (GTK_BOX (row_box), widget); + + widget = GTK_WIDGET (ce_netmask_entry_new ()); + g_signal_connect_object (widget, "changed", G_CALLBACK (ce_page_changed), self, G_CONNECT_SWAPPED); + g_signal_connect_object (widget, "activate", G_CALLBACK (ensure_empty_routes_row), self, G_CONNECT_SWAPPED); + g_object_set_data (G_OBJECT (row), "netmask", widget); + gtk_editable_set_text (GTK_EDITABLE (widget), netmask); + gtk_editable_set_width_chars (GTK_EDITABLE (widget), 16); + gtk_widget_set_hexpand (widget, TRUE); + gtk_accessible_update_relation (GTK_ACCESSIBLE (widget), + GTK_ACCESSIBLE_RELATION_LABELLED_BY, self->routes_netmask_label, NULL, + -1); + gtk_box_append (GTK_BOX (row_box), widget); + + widget = GTK_WIDGET (ce_ip_address_entry_new (AF_INET)); + g_signal_connect_object (widget, "changed", G_CALLBACK (ce_page_changed), self, G_CONNECT_SWAPPED); + g_signal_connect_object (widget, "activate", G_CALLBACK (ensure_empty_routes_row), self, G_CONNECT_SWAPPED); + g_object_set_data (G_OBJECT (row), "gateway", widget); + gtk_editable_set_text (GTK_EDITABLE (widget), gateway ? gateway : ""); + gtk_editable_set_width_chars (GTK_EDITABLE (widget), 16); + gtk_widget_set_hexpand (widget, TRUE); + gtk_accessible_update_relation (GTK_ACCESSIBLE (widget), + GTK_ACCESSIBLE_RELATION_LABELLED_BY, self->routes_gateway_label, NULL, + -1); + gtk_box_append (GTK_BOX (row_box), widget); + + widget = gtk_entry_new (); + g_signal_connect_object (widget, "changed", G_CALLBACK (ce_page_changed), self, G_CONNECT_SWAPPED); + g_signal_connect_object (widget, "activate", G_CALLBACK (ensure_empty_routes_row), self, G_CONNECT_SWAPPED); + g_object_set_data (G_OBJECT (row), "metric", widget); + if (metric >= 0) { + g_autofree gchar *s = g_strdup_printf ("%d", metric); + gtk_editable_set_text (GTK_EDITABLE (widget), s); + } + gtk_editable_set_width_chars (GTK_EDITABLE (widget), 5); + gtk_widget_set_hexpand (widget, TRUE); + gtk_accessible_update_relation (GTK_ACCESSIBLE (widget), + GTK_ACCESSIBLE_RELATION_LABELLED_BY, self->routes_metric_label, NULL, + -1); + gtk_box_append (GTK_BOX (row_box), widget); + + gtk_size_group_add_widget (self->routes_metric_sizegroup, widget); + + delete_button = gtk_button_new_from_icon_name ("edit-delete-symbolic"); + gtk_widget_set_sensitive (delete_button, FALSE); + g_signal_connect_object (delete_button, "clicked", G_CALLBACK (remove_row), self, G_CONNECT_SWAPPED); + gtk_accessible_update_property (GTK_ACCESSIBLE (delete_button), + GTK_ACCESSIBLE_PROPERTY_LABEL, _("Delete Route"), + -1); + gtk_widget_set_halign (delete_button, GTK_ALIGN_CENTER); + gtk_widget_set_valign (delete_button, GTK_ALIGN_CENTER); + gtk_box_append (GTK_BOX (row_box), delete_button); + g_object_set_data (G_OBJECT (row), "delete-button", delete_button); + + gtk_size_group_add_widget (self->routes_sizegroup, delete_button); + + gtk_list_box_row_set_child (GTK_LIST_BOX_ROW (row), row_box); + gtk_list_box_append (GTK_LIST_BOX (self->routes_list), row); + + update_row_sensitivity (self, self->routes_list); +} + +static void +ensure_empty_routes_row (CEPageIP4 *self) +{ + GtkWidget *child = gtk_widget_get_last_child (self->routes_list); + + /* Add the last, stub row if needed*/ + if (!child || validate_row (child)) + add_route_row (self, "", "", "", -1); +} + +static void +add_routes_box (CEPageIP4 *self) +{ + GtkWidget *list; + gint i; + + self->routes_list = list = gtk_list_box_new (); + gtk_list_box_set_selection_mode (GTK_LIST_BOX (list), GTK_SELECTION_NONE); + gtk_box_append (GTK_BOX (self->routes_box), list); + gtk_switch_set_active (self->auto_routes_switch, !nm_setting_ip_config_get_ignore_auto_routes (self->setting)); + g_signal_connect_object (self->auto_routes_switch, "notify::active", G_CALLBACK (ce_page_changed), self, G_CONNECT_SWAPPED); + + for (i = 0; i < nm_setting_ip_config_get_num_routes (self->setting); i++) { + NMIPRoute *route; + struct in_addr tmp_addr; + gchar netmask[INET_ADDRSTRLEN + 1]; + + route = nm_setting_ip_config_get_route (self->setting, i); + if (!route) + continue; + + tmp_addr.s_addr = nm_utils_ip4_prefix_to_netmask (nm_ip_route_get_prefix (route)); + (void) inet_ntop (AF_INET, &tmp_addr, &netmask[0], sizeof (netmask)); + + add_route_row (self, + nm_ip_route_get_dest (route), + netmask, + nm_ip_route_get_next_hop (route), + nm_ip_route_get_metric (route)); + } + if (nm_setting_ip_config_get_num_routes (self->setting) == 0) + ensure_empty_routes_row (self); +} + +static void +connect_ip4_page (CEPageIP4 *self) +{ + const gchar *str_method; + guint method; + + add_address_box (self); + add_dns_section (self); + add_routes_box (self); + + str_method = nm_setting_ip_config_get_method (self->setting); + g_signal_connect_object (self->disabled_radio, "notify::active", G_CALLBACK (ce_page_changed), self, G_CONNECT_SWAPPED); + g_object_bind_property (self->disabled_radio, "active", + self->content_box, "sensitive", + G_BINDING_SYNC_CREATE | G_BINDING_INVERT_BOOLEAN); + + g_signal_connect_object (self->shared_radio, "notify::active", G_CALLBACK (ce_page_changed), self, G_CONNECT_SWAPPED); + g_object_bind_property (self->shared_radio, "active", + self->content_box, "sensitive", + G_BINDING_SYNC_CREATE | G_BINDING_INVERT_BOOLEAN); + + method = IP4_METHOD_AUTO; + if (g_strcmp0 (str_method, NM_SETTING_IP4_CONFIG_METHOD_LINK_LOCAL) == 0) { + method = IP4_METHOD_LINK_LOCAL; + } else if (g_strcmp0 (str_method, NM_SETTING_IP4_CONFIG_METHOD_MANUAL) == 0) { + method = IP4_METHOD_MANUAL; + } else if (g_strcmp0 (str_method, NM_SETTING_IP4_CONFIG_METHOD_SHARED) == 0) { + method = IP4_METHOD_SHARED; + } else if (g_strcmp0 (str_method, NM_SETTING_IP4_CONFIG_METHOD_DISABLED) == 0) { + method = IP4_METHOD_DISABLED; + } + + gtk_check_button_set_active (GTK_CHECK_BUTTON (self->never_default_check), + nm_setting_ip_config_get_never_default (self->setting)); + g_signal_connect_object (self->never_default_check, "toggled", G_CALLBACK (ce_page_changed), self, G_CONNECT_SWAPPED); + + g_signal_connect_object (self->automatic_radio, "toggled", G_CALLBACK (method_changed), self, G_CONNECT_SWAPPED); + g_signal_connect_object (self->local_radio, "toggled", G_CALLBACK (method_changed), self, G_CONNECT_SWAPPED); + g_signal_connect_object (self->manual_radio, "toggled", G_CALLBACK (method_changed), self, G_CONNECT_SWAPPED); + g_signal_connect_object (self->disabled_radio, "toggled", G_CALLBACK (method_changed), self, G_CONNECT_SWAPPED); + + switch (method) { + case IP4_METHOD_AUTO: + gtk_check_button_set_active (GTK_CHECK_BUTTON (self->automatic_radio), TRUE); + break; + case IP4_METHOD_LINK_LOCAL: + gtk_check_button_set_active (GTK_CHECK_BUTTON (self->local_radio), TRUE); + break; + case IP4_METHOD_MANUAL: + gtk_check_button_set_active (GTK_CHECK_BUTTON (self->manual_radio), TRUE); + break; + case IP4_METHOD_SHARED: + gtk_check_button_set_active (GTK_CHECK_BUTTON (self->shared_radio), TRUE); + break; + case IP4_METHOD_DISABLED: + gtk_check_button_set_active (GTK_CHECK_BUTTON (self->disabled_radio), TRUE); + break; + default: + break; + } + + method_changed (self); +} + +static gboolean +ui_to_setting (CEPageIP4 *self) +{ + const gchar *method; + gboolean ignore_auto_dns; + gboolean ignore_auto_routes; + gboolean never_default; + GPtrArray *addresses = NULL; + GPtrArray *dns_servers = NULL; + GPtrArray *routes = NULL; + GtkWidget *child; + GStrv dns_addresses = NULL; + gboolean ret = TRUE; + const char *default_gateway = NULL; + gboolean add_addresses = FALSE; + gboolean add_routes = FALSE; + gchar *dns_text = NULL; + guint i; + + if (gtk_check_button_get_active (GTK_CHECK_BUTTON (self->disabled_radio))) + method = NM_SETTING_IP4_CONFIG_METHOD_DISABLED; + else if (gtk_check_button_get_active (GTK_CHECK_BUTTON (self->automatic_radio))) + method = NM_SETTING_IP4_CONFIG_METHOD_AUTO; + else if (gtk_check_button_get_active (GTK_CHECK_BUTTON (self->local_radio))) + method = NM_SETTING_IP4_CONFIG_METHOD_LINK_LOCAL; + else if (gtk_check_button_get_active (GTK_CHECK_BUTTON (self->manual_radio))) + method = NM_SETTING_IP4_CONFIG_METHOD_MANUAL; + else if (gtk_check_button_get_active (GTK_CHECK_BUTTON (self->shared_radio))) + method = NM_SETTING_IP4_CONFIG_METHOD_SHARED; + + addresses = g_ptr_array_new_with_free_func ((GDestroyNotify) nm_ip_address_unref); + add_addresses = g_str_equal (method, NM_SETTING_IP4_CONFIG_METHOD_MANUAL); + + for (child = gtk_widget_get_first_child (self->address_list); + add_addresses && child; + child = gtk_widget_get_next_sibling (child)) { + GtkWidget *row = child; + CEIPAddressEntry *address_entry; + CENetmaskEntry *netmask_entry; + CEIPAddressEntry *gateway_entry; + NMIPAddress *addr; + + address_entry = CE_IP_ADDRESS_ENTRY (g_object_get_data (G_OBJECT (row), "address")); + if (!address_entry) + continue; + + netmask_entry = CE_NETMASK_ENTRY (g_object_get_data (G_OBJECT (row), "netmask")); + gateway_entry = CE_IP_ADDRESS_ENTRY (g_object_get_data (G_OBJECT (row), "gateway")); + + if (ce_ip_address_entry_is_empty (address_entry) && ce_netmask_entry_is_empty (netmask_entry) && ce_ip_address_entry_is_empty (gateway_entry)) { + /* ignore empty rows */ + continue; + } + + if (!ce_ip_address_entry_is_valid (address_entry)) + ret = FALSE; + + if (!ce_netmask_entry_is_valid (netmask_entry)) + ret = FALSE; + + if (!ce_ip_address_entry_is_valid (gateway_entry)) { + ret = FALSE; + } else { + if (!ce_ip_address_entry_is_empty (gateway_entry)) { + g_assert (default_gateway == NULL); + default_gateway = gtk_editable_get_text (GTK_EDITABLE (gateway_entry)); + } + } + + if (!ret) + continue; + + addr = nm_ip_address_new (AF_INET, gtk_editable_get_text (GTK_EDITABLE (address_entry)), ce_netmask_entry_get_prefix (netmask_entry), NULL); + if (addr) + g_ptr_array_add (addresses, addr); + + if (!gtk_widget_get_next_sibling (row)) + ensure_empty_address_row (self); + } + + if (addresses->len == 0) { + g_ptr_array_free (addresses, TRUE); + addresses = NULL; + } + + dns_servers = g_ptr_array_new_with_free_func (g_free); + dns_text = g_strstrip (g_strdup (gtk_editable_get_text (GTK_EDITABLE (self->dns_entry)))); + if (g_str_equal (method, NM_SETTING_IP4_CONFIG_METHOD_AUTO) || + g_str_equal (method, NM_SETTING_IP4_CONFIG_METHOD_MANUAL)) + dns_addresses = g_strsplit_set (dns_text, ", ", -1); + else + dns_addresses = NULL; + + for (i = 0; dns_addresses && dns_addresses[i]; i++) { + const gchar *text; + + text = dns_addresses[i]; + + if (!text || !*text) + continue; + + if (!nm_utils_ipaddr_valid (AF_INET, text)) { + g_ptr_array_remove_range (dns_servers, 0, dns_servers->len); + widget_set_error (GTK_WIDGET (self->dns_entry)); + ret = FALSE; + break; + } else { + widget_unset_error (GTK_WIDGET (self->dns_entry)); + g_ptr_array_add (dns_servers, g_strdup (text)); + } + } + g_clear_pointer (&dns_addresses, g_strfreev); + + if (dns_servers->len == 0) { + g_ptr_array_free (dns_servers, TRUE); + dns_servers = NULL; + } else { + g_ptr_array_add (dns_servers, NULL); + } + + routes = g_ptr_array_new_with_free_func ((GDestroyNotify) nm_ip_route_unref); + add_routes = g_str_equal (method, NM_SETTING_IP4_CONFIG_METHOD_AUTO) || + g_str_equal (method, NM_SETTING_IP4_CONFIG_METHOD_MANUAL); + + for (child = gtk_widget_get_first_child (self->routes_list); + add_routes && child; + child = gtk_widget_get_next_sibling (child)) { + GtkWidget *row = child; + CEIPAddressEntry *address_entry; + CENetmaskEntry *netmask_entry; + CEIPAddressEntry *gateway_entry; + const gchar *text_metric; + gint64 metric; + NMIPRoute *route; + + address_entry = CE_IP_ADDRESS_ENTRY (g_object_get_data (G_OBJECT (row), "address")); + if (!address_entry) + continue; + + netmask_entry = CE_NETMASK_ENTRY (g_object_get_data (G_OBJECT (row), "netmask")); + gateway_entry = CE_IP_ADDRESS_ENTRY (g_object_get_data (G_OBJECT (row), "gateway")); + text_metric = gtk_editable_get_text (GTK_EDITABLE (g_object_get_data (G_OBJECT (row), "metric"))); + + if (ce_ip_address_entry_is_empty (address_entry) && ce_netmask_entry_is_empty (netmask_entry) && ce_ip_address_entry_is_empty (gateway_entry) && !*text_metric) { + /* ignore empty rows */ + continue; + } + + if (!ce_ip_address_entry_is_valid (address_entry)) + ret = FALSE; + + if (!ce_netmask_entry_is_valid (netmask_entry)) + ret = FALSE; + + if (!ce_ip_address_entry_is_valid (gateway_entry)) + ret = FALSE; + + metric = -1; + if (*text_metric) { + errno = 0; + metric = g_ascii_strtoull (text_metric, NULL, 10); + if (errno || metric < 0 || metric > G_MAXUINT32) { + widget_set_error (GTK_WIDGET (g_object_get_data (G_OBJECT (row), "metric"))); + ret = FALSE; + } else { + widget_unset_error (GTK_WIDGET (g_object_get_data (G_OBJECT (row), "metric"))); + } + } else { + widget_unset_error (GTK_WIDGET (g_object_get_data (G_OBJECT (row), "metric"))); + } + + if (!ret) + continue; + + route = nm_ip_route_new (AF_INET, + gtk_editable_get_text (GTK_EDITABLE (address_entry)), + ce_netmask_entry_get_prefix (netmask_entry), + gtk_editable_get_text (GTK_EDITABLE (gateway_entry)), + metric, NULL); + if (route) + g_ptr_array_add (routes, route); + + if (!gtk_widget_get_next_sibling (row)) + ensure_empty_routes_row (self); + } + + if (routes->len == 0) { + g_ptr_array_free (routes, TRUE); + routes = NULL; + } + + if (!ret) + goto out; + + ignore_auto_dns = !gtk_switch_get_active (self->auto_dns_switch); + ignore_auto_routes = !gtk_switch_get_active (self->auto_routes_switch); + never_default = gtk_check_button_get_active (GTK_CHECK_BUTTON (self->never_default_check)); + + g_object_set (self->setting, + NM_SETTING_IP_CONFIG_METHOD, method, + NM_SETTING_IP_CONFIG_ADDRESSES, addresses, + NM_SETTING_IP_CONFIG_GATEWAY, default_gateway, + NM_SETTING_IP_CONFIG_DNS, dns_servers ? dns_servers->pdata : NULL, + NM_SETTING_IP_CONFIG_ROUTES, routes, + NM_SETTING_IP_CONFIG_IGNORE_AUTO_DNS, ignore_auto_dns, + NM_SETTING_IP_CONFIG_IGNORE_AUTO_ROUTES, ignore_auto_routes, + NM_SETTING_IP_CONFIG_NEVER_DEFAULT, never_default, + NULL); + +out: + if (addresses) + g_ptr_array_free (addresses, TRUE); + + if (dns_servers) + g_ptr_array_free (dns_servers, TRUE); + + if (routes) + g_ptr_array_free (routes, TRUE); + + g_clear_pointer (&dns_text, g_free); + + return ret; +} + +static const gchar * +ce_page_ip4_get_title (CEPage *page) +{ + return _("IPv4"); +} + +static gboolean +ce_page_ip4_validate (CEPage *self, + NMConnection *connection, + GError **error) +{ + if (!ui_to_setting (CE_PAGE_IP4 (self))) + return FALSE; + + return nm_setting_verify (NM_SETTING (CE_PAGE_IP4 (self)->setting), NULL, error); +} + +static void +ce_page_ip4_init (CEPageIP4 *self) +{ + gtk_widget_init_template (GTK_WIDGET (self)); +} + +static void +ce_page_ip4_class_init (CEPageIP4Class *klass) +{ + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/control-center/network/ip4-page.ui"); + + gtk_widget_class_bind_template_child (widget_class, CEPageIP4, address_box); + gtk_widget_class_bind_template_child (widget_class, CEPageIP4, address_sizegroup); + gtk_widget_class_bind_template_child (widget_class, CEPageIP4, auto_dns_switch); + gtk_widget_class_bind_template_child (widget_class, CEPageIP4, auto_routes_switch); + gtk_widget_class_bind_template_child (widget_class, CEPageIP4, automatic_radio); + gtk_widget_class_bind_template_child (widget_class, CEPageIP4, content_box); + gtk_widget_class_bind_template_child (widget_class, CEPageIP4, disabled_radio); + gtk_widget_class_bind_template_child (widget_class, CEPageIP4, dns_entry); + gtk_widget_class_bind_template_child (widget_class, CEPageIP4, local_radio); + gtk_widget_class_bind_template_child (widget_class, CEPageIP4, main_box); + gtk_widget_class_bind_template_child (widget_class, CEPageIP4, manual_radio); + gtk_widget_class_bind_template_child (widget_class, CEPageIP4, never_default_check); + gtk_widget_class_bind_template_child (widget_class, CEPageIP4, address_address_label); + gtk_widget_class_bind_template_child (widget_class, CEPageIP4, address_netmask_label); + gtk_widget_class_bind_template_child (widget_class, CEPageIP4, address_gateway_label); + gtk_widget_class_bind_template_child (widget_class, CEPageIP4, routes_box); + gtk_widget_class_bind_template_child (widget_class, CEPageIP4, routes_address_label); + gtk_widget_class_bind_template_child (widget_class, CEPageIP4, routes_netmask_label); + gtk_widget_class_bind_template_child (widget_class, CEPageIP4, routes_gateway_label); + gtk_widget_class_bind_template_child (widget_class, CEPageIP4, routes_metric_label); + gtk_widget_class_bind_template_child (widget_class, CEPageIP4, routes_metric_sizegroup); + gtk_widget_class_bind_template_child (widget_class, CEPageIP4, routes_sizegroup); + gtk_widget_class_bind_template_child (widget_class, CEPageIP4, shared_radio); +} + +static void +ce_page_iface_init (CEPageInterface *iface) +{ + iface->get_title = ce_page_ip4_get_title; + iface->validate = ce_page_ip4_validate; +} + +CEPageIP4 * +ce_page_ip4_new (NMConnection *connection, + NMClient *client) +{ + CEPageIP4 *self; + + self = CE_PAGE_IP4 (g_object_new (ce_page_ip4_get_type (), NULL)); + + self->setting = nm_connection_get_setting_ip4_config (connection); + if (!self->setting) { + self->setting = NM_SETTING_IP_CONFIG (nm_setting_ip4_config_new ()); + nm_connection_add_setting (connection, NM_SETTING (self->setting)); + } + + connect_ip4_page (self); + + return self; +} diff --git a/panels/network/connection-editor/ce-page-ip4.h b/panels/network/connection-editor/ce-page-ip4.h new file mode 100644 index 0000000..3e6545f --- /dev/null +++ b/panels/network/connection-editor/ce-page-ip4.h @@ -0,0 +1,34 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2012 Red Hat, Inc. + * + * 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. + */ + +#pragma once + +#include <adwaita.h> +#include <NetworkManager.h> + +G_BEGIN_DECLS + +G_DECLARE_FINAL_TYPE (CEPageIP4, ce_page_ip4, CE, PAGE_IP4, AdwBin) + +CEPageIP4 *ce_page_ip4_new (NMConnection *connection, + NMClient *client); + +G_END_DECLS diff --git a/panels/network/connection-editor/ce-page-ip6.c b/panels/network/connection-editor/ce-page-ip6.c new file mode 100644 index 0000000..09b9eb6 --- /dev/null +++ b/panels/network/connection-editor/ce-page-ip6.c @@ -0,0 +1,817 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2012 Red Hat, Inc + * + * 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 <errno.h> +#include <stdlib.h> +#include <arpa/inet.h> +#include <glib/gi18n.h> +#include <NetworkManager.h> + +#include "ce-ip-address-entry.h" +#include "ce-page.h" +#include "ce-page-ip6.h" +#include "ui-helpers.h" + +static void ensure_empty_address_row (CEPageIP6 *self); +static void ensure_empty_routes_row (CEPageIP6 *self); + + +struct _CEPageIP6 +{ + AdwBin parent; + + GtkBox *address_box; + GtkLabel *address_address_label; + GtkLabel *address_prefix_label; + GtkLabel *address_gateway_label; + GtkSizeGroup *address_sizegroup; + GtkSwitch *auto_dns_switch; + GtkSwitch *auto_routes_switch; + GtkCheckButton *automatic_radio; + GtkBox *content_box; + GtkCheckButton *dhcp_radio; + GtkCheckButton *disabled_radio; + GtkEntry *dns_entry; + GtkCheckButton *local_radio; + GtkGrid *main_box; + GtkCheckButton *manual_radio; + GtkCheckButton *never_default_check; + GtkBox *routes_box; + GtkLabel *routes_address_label; + GtkLabel *routes_prefix_label; + GtkLabel *routes_gateway_label; + GtkLabel *routes_metric_label; + GtkSizeGroup *routes_metric_sizegroup; + GtkSizeGroup *routes_sizegroup; + GtkCheckButton *shared_radio; + + NMSettingIPConfig *setting; + + GtkWidget *address_list; + GtkWidget *routes_list; +}; + +static void ce_page_iface_init (CEPageInterface *); + +G_DEFINE_TYPE_WITH_CODE (CEPageIP6, ce_page_ip6, ADW_TYPE_BIN, + G_IMPLEMENT_INTERFACE (ce_page_get_type (), ce_page_iface_init)) + +enum { + METHOD_COL_NAME, + METHOD_COL_METHOD +}; + +enum { + IP6_METHOD_AUTO, + IP6_METHOD_DHCP, + IP6_METHOD_MANUAL, + IP6_METHOD_LINK_LOCAL, + IP6_METHOD_SHARED, + IP6_METHOD_DISABLED +}; + +static void +method_changed (CEPageIP6 *self) +{ + gboolean addr_enabled; + gboolean dns_enabled; + gboolean routes_enabled; + + if (gtk_check_button_get_active (self->disabled_radio) || + gtk_check_button_get_active (self->shared_radio)) { + addr_enabled = FALSE; + dns_enabled = FALSE; + routes_enabled = FALSE; + } else { + addr_enabled = gtk_check_button_get_active (self->manual_radio); + dns_enabled = !gtk_check_button_get_active (self->local_radio); + routes_enabled = !gtk_check_button_get_active (self->local_radio); + } + + gtk_widget_set_visible (GTK_WIDGET (self->address_box), addr_enabled); + gtk_widget_set_sensitive (GTK_WIDGET (self->dns_entry), dns_enabled); + gtk_widget_set_sensitive (GTK_WIDGET (self->routes_list), routes_enabled); + gtk_widget_set_sensitive (GTK_WIDGET (self->never_default_check), routes_enabled); + + ce_page_changed (CE_PAGE (self)); +} + +static void +update_row_sensitivity (CEPageIP6 *self, GtkWidget *list) +{ + GtkWidget *child; + gint rows = 0, i = 0; + + for (child = gtk_widget_get_first_child (GTK_WIDGET (list)); + child; + child = gtk_widget_get_next_sibling (child)) { + GtkWidget *button; + + button = GTK_WIDGET (g_object_get_data (G_OBJECT (child), "delete-button")); + if (button != NULL) + rows++; + } + for (child = gtk_widget_get_first_child (GTK_WIDGET (list)); + child; + child = gtk_widget_get_next_sibling (child)) { + GtkWidget *button; + + button = GTK_WIDGET (g_object_get_data (G_OBJECT (child), "delete-button")); + if (button != NULL) + gtk_widget_set_sensitive (button, rows > 1 && ++i < rows); + } +} + +static void +remove_row (CEPageIP6 *self, GtkButton *button) +{ + GtkWidget *row; + GtkWidget *row_box; + GtkWidget *list; + + row_box = gtk_widget_get_parent (GTK_WIDGET (button)); + row = gtk_widget_get_parent (row_box); + list = gtk_widget_get_parent (row); + + gtk_list_box_remove (GTK_LIST_BOX (list), row); + + ce_page_changed (CE_PAGE (self)); + + update_row_sensitivity (self, list); +} + +static gboolean +validate_row (GtkWidget *row) +{ + GtkWidget *child; + GtkWidget *box; + gboolean valid; + + valid = FALSE; + box = gtk_list_box_row_get_child (GTK_LIST_BOX_ROW (row)); + + for (child = gtk_widget_get_first_child (box); + child; + child = gtk_widget_get_next_sibling (child)) { + if (!GTK_IS_ENTRY (child)) + continue; + + valid = valid || gtk_entry_get_text_length (GTK_ENTRY (child)) > 0; + } + + return valid; +} + +static void +add_address_row (CEPageIP6 *self, + const gchar *address, + const gchar *network, + const gchar *gateway) +{ + GtkWidget *row; + GtkWidget *row_box; + GtkWidget *widget; + GtkWidget *delete_button; + + row = gtk_list_box_row_new (); + gtk_list_box_row_set_activatable (GTK_LIST_BOX_ROW (row), FALSE); + + row_box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0); + gtk_widget_add_css_class (row_box, "linked"); + + widget = GTK_WIDGET (ce_ip_address_entry_new (AF_INET6)); + g_signal_connect_object (widget, "changed", G_CALLBACK (ce_page_changed), self, G_CONNECT_SWAPPED); + g_signal_connect_object (widget, "activate", G_CALLBACK (ensure_empty_address_row), self, G_CONNECT_SWAPPED); + g_object_set_data (G_OBJECT (row), "address", widget); + gtk_editable_set_text (GTK_EDITABLE (widget), address); + gtk_editable_set_width_chars (GTK_EDITABLE (widget), 16); + gtk_widget_set_hexpand (widget, TRUE); + gtk_accessible_update_relation (GTK_ACCESSIBLE (widget), + GTK_ACCESSIBLE_RELATION_LABELLED_BY, self->address_address_label, NULL, + -1); + gtk_box_append (GTK_BOX (row_box), widget); + + widget = gtk_entry_new (); + g_signal_connect_object (widget, "changed", G_CALLBACK (ce_page_changed), self, G_CONNECT_SWAPPED); + g_signal_connect_object (widget, "activate", G_CALLBACK (ensure_empty_address_row), self, G_CONNECT_SWAPPED); + g_object_set_data (G_OBJECT (row), "prefix", widget); + gtk_editable_set_text (GTK_EDITABLE (widget), network); + gtk_editable_set_width_chars (GTK_EDITABLE (widget), 16); + gtk_widget_set_hexpand (widget, TRUE); + gtk_accessible_update_relation (GTK_ACCESSIBLE (widget), + GTK_ACCESSIBLE_RELATION_LABELLED_BY, self->address_prefix_label, NULL, + -1); + gtk_box_append (GTK_BOX (row_box), widget); + + widget = GTK_WIDGET (ce_ip_address_entry_new (AF_INET6)); + g_signal_connect_object (widget, "changed", G_CALLBACK (ce_page_changed), self, G_CONNECT_SWAPPED); + g_signal_connect_object (widget, "activate", G_CALLBACK (ensure_empty_address_row), self, G_CONNECT_SWAPPED); + g_object_set_data (G_OBJECT (row), "gateway", widget); + gtk_editable_set_text (GTK_EDITABLE (widget), gateway ? gateway : ""); + gtk_editable_set_width_chars (GTK_EDITABLE (widget), 16); + gtk_widget_set_hexpand (widget, TRUE); + gtk_accessible_update_relation (GTK_ACCESSIBLE (widget), + GTK_ACCESSIBLE_RELATION_LABELLED_BY, self->address_gateway_label, NULL, + -1); + gtk_box_append (GTK_BOX (row_box), widget); + + delete_button = gtk_button_new_from_icon_name ("edit-delete-symbolic"); + gtk_widget_set_sensitive (delete_button, FALSE); + g_signal_connect_object (delete_button, "clicked", G_CALLBACK (remove_row), self, G_CONNECT_SWAPPED); + gtk_accessible_update_property (GTK_ACCESSIBLE (delete_button), + GTK_ACCESSIBLE_PROPERTY_LABEL, _("Delete Address"), + -1); + gtk_box_append (GTK_BOX (row_box), delete_button); + g_object_set_data (G_OBJECT (row), "delete-button", delete_button); + + gtk_size_group_add_widget (self->address_sizegroup, delete_button); + + gtk_list_box_row_set_child (GTK_LIST_BOX_ROW (row), row_box); + gtk_list_box_append (GTK_LIST_BOX (self->address_list), row); + + update_row_sensitivity (self, self->address_list); +} + +static void +ensure_empty_address_row (CEPageIP6 *self) +{ + GtkWidget *child = gtk_widget_get_last_child (self->address_list); + + /* Add the last, stub row if needed*/ + if (!child || validate_row (child)) + add_address_row (self, "", "", ""); +} + +static void +add_address_box (CEPageIP6 *self) +{ + GtkWidget *list; + gint i; + + self->address_list = list = gtk_list_box_new (); + gtk_list_box_set_selection_mode (GTK_LIST_BOX (list), GTK_SELECTION_NONE); + gtk_box_append (self->address_box, list); + + for (i = 0; i < nm_setting_ip_config_get_num_addresses (self->setting); i++) { + NMIPAddress *addr; + g_autofree gchar *netmask = NULL; + + addr = nm_setting_ip_config_get_address (self->setting, i); + netmask = g_strdup_printf ("%u", nm_ip_address_get_prefix (addr)); + add_address_row (self, nm_ip_address_get_address (addr), netmask, + i == 0 ? nm_setting_ip_config_get_gateway (self->setting) : NULL); + } + if (nm_setting_ip_config_get_num_addresses (self->setting) == 0) + ensure_empty_address_row (self); +} + +static void +add_dns_section (CEPageIP6 *self) +{ + GString *string; + gint i; + + gtk_switch_set_active (self->auto_dns_switch, !nm_setting_ip_config_get_ignore_auto_dns (self->setting)); + g_signal_connect_object (self->auto_dns_switch, "notify::active", G_CALLBACK (ce_page_changed), self, G_CONNECT_SWAPPED); + + string = g_string_new (""); + + for (i = 0; i < nm_setting_ip_config_get_num_dns (self->setting); i++) { + const char *address; + + address = nm_setting_ip_config_get_dns (self->setting, i); + + if (i > 0) + g_string_append (string, ", "); + + g_string_append (string, address); + + } + + gtk_editable_set_text (GTK_EDITABLE (self->dns_entry), string->str); + + g_signal_connect_object (self->dns_entry, "notify::text", G_CALLBACK (ce_page_changed), self, G_CONNECT_SWAPPED); + + g_string_free (string, TRUE); +} + +static void +add_route_row (CEPageIP6 *self, + const gchar *address, + const gchar *prefix, + const gchar *gateway, + const gchar *metric) +{ + GtkWidget *row; + GtkWidget *row_box; + GtkWidget *widget; + GtkWidget *delete_button; + + row = gtk_list_box_row_new (); + gtk_list_box_row_set_activatable (GTK_LIST_BOX_ROW (row), FALSE); + + row_box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0); + gtk_widget_add_css_class (row_box, "linked"); + + widget = GTK_WIDGET (ce_ip_address_entry_new (AF_INET6)); + g_signal_connect_object (widget, "changed", G_CALLBACK (ce_page_changed), self, G_CONNECT_SWAPPED); + g_signal_connect_object (widget, "activate", G_CALLBACK (ensure_empty_routes_row), self, G_CONNECT_SWAPPED); + g_object_set_data (G_OBJECT (row), "address", widget); + gtk_editable_set_text (GTK_EDITABLE (widget), address); + gtk_editable_set_width_chars (GTK_EDITABLE (widget), 16); + gtk_widget_set_hexpand (widget, TRUE); + gtk_accessible_update_relation (GTK_ACCESSIBLE (widget), + GTK_ACCESSIBLE_RELATION_LABELLED_BY, self->routes_address_label, NULL, + -1); + gtk_box_append (GTK_BOX (row_box), widget); + + widget = gtk_entry_new (); + g_signal_connect_object (widget, "changed", G_CALLBACK (ce_page_changed), self, G_CONNECT_SWAPPED); + g_signal_connect_object (widget, "activate", G_CALLBACK (ensure_empty_routes_row), self, G_CONNECT_SWAPPED); + g_object_set_data (G_OBJECT (row), "prefix", widget); + gtk_editable_set_text (GTK_EDITABLE (widget), prefix ? prefix : ""); + gtk_editable_set_width_chars (GTK_EDITABLE (widget), 16); + gtk_widget_set_hexpand (widget, TRUE); + gtk_accessible_update_relation (GTK_ACCESSIBLE (widget), + GTK_ACCESSIBLE_RELATION_LABELLED_BY, self->routes_prefix_label, NULL, + -1); + gtk_box_append (GTK_BOX (row_box), widget); + + widget = GTK_WIDGET (ce_ip_address_entry_new (AF_INET6)); + g_signal_connect_object (widget, "changed", G_CALLBACK (ce_page_changed), self, G_CONNECT_SWAPPED); + g_signal_connect_object (widget, "activate", G_CALLBACK (ensure_empty_routes_row), self, G_CONNECT_SWAPPED); + g_object_set_data (G_OBJECT (row), "gateway", widget); + gtk_editable_set_text (GTK_EDITABLE (widget), gateway); + gtk_editable_set_width_chars (GTK_EDITABLE (widget), 16); + gtk_widget_set_hexpand (widget, TRUE); + gtk_accessible_update_relation (GTK_ACCESSIBLE (widget), + GTK_ACCESSIBLE_RELATION_LABELLED_BY, self->routes_gateway_label, NULL, + -1); + gtk_box_append (GTK_BOX (row_box), widget); + + widget = gtk_entry_new (); + g_signal_connect_object (widget, "changed", G_CALLBACK (ce_page_changed), self, G_CONNECT_SWAPPED); + g_signal_connect_object (widget, "activate", G_CALLBACK (ensure_empty_routes_row), self, G_CONNECT_SWAPPED); + g_object_set_data (G_OBJECT (row), "metric", widget); + gtk_editable_set_text (GTK_EDITABLE (widget), metric ? metric : ""); + gtk_editable_set_width_chars (GTK_EDITABLE (widget), 5); + gtk_widget_set_hexpand (widget, TRUE); + gtk_accessible_update_relation (GTK_ACCESSIBLE (widget), + GTK_ACCESSIBLE_RELATION_LABELLED_BY, self->routes_prefix_label, NULL, + -1); + gtk_box_append (GTK_BOX (row_box), widget); + + gtk_size_group_add_widget (self->routes_metric_sizegroup, widget); + + delete_button = gtk_button_new_from_icon_name ("edit-delete-symbolic"); + g_signal_connect_object (delete_button, "clicked", G_CALLBACK (remove_row), self, G_CONNECT_SWAPPED); + gtk_accessible_update_property (GTK_ACCESSIBLE (delete_button), + GTK_ACCESSIBLE_PROPERTY_LABEL, _("Delete Route"), + -1); + gtk_widget_set_halign (delete_button, GTK_ALIGN_CENTER); + gtk_widget_set_valign (delete_button, GTK_ALIGN_CENTER); + gtk_box_append (GTK_BOX (row_box), delete_button); + g_object_set_data (G_OBJECT (row), "delete-button", delete_button); + + gtk_size_group_add_widget (self->routes_sizegroup, delete_button); + + gtk_list_box_row_set_child (GTK_LIST_BOX_ROW (row), row_box); + gtk_list_box_append (GTK_LIST_BOX (self->routes_list), row); + + update_row_sensitivity (self, self->routes_list); +} + +static void +ensure_empty_routes_row (CEPageIP6 *self) +{ + GtkWidget *child = gtk_widget_get_last_child (self->routes_list); + + /* Add the last, stub row if needed*/ + if (!child || validate_row (child)) + add_route_row (self, "", NULL, "", NULL); +} + +static void +add_empty_route_row (CEPageIP6 *self) +{ + add_route_row (self, "", NULL, "", NULL); +} + +static void +add_routes_box (CEPageIP6 *self) +{ + GtkWidget *list; + gint i; + + self->routes_list = list = gtk_list_box_new (); + gtk_list_box_set_selection_mode (GTK_LIST_BOX (list), GTK_SELECTION_NONE); + gtk_box_append (self->routes_box, list); + gtk_switch_set_active (self->auto_routes_switch, !nm_setting_ip_config_get_ignore_auto_routes (self->setting)); + g_signal_connect_object (self->auto_routes_switch, "notify::active", G_CALLBACK (ce_page_changed), self, G_CONNECT_SWAPPED); + + for (i = 0; i < nm_setting_ip_config_get_num_routes (self->setting); i++) { + NMIPRoute *route; + g_autofree gchar *prefix = NULL; + g_autofree gchar *metric = NULL; + + route = nm_setting_ip_config_get_route (self->setting, i); + prefix = g_strdup_printf ("%u", nm_ip_route_get_prefix (route)); + metric = g_strdup_printf ("%" G_GINT64_FORMAT, nm_ip_route_get_metric (route)); + add_route_row (self, nm_ip_route_get_dest (route), + prefix, + nm_ip_route_get_next_hop (route), + metric); + } + if (nm_setting_ip_config_get_num_routes (self->setting) == 0) + add_empty_route_row (self); +} + +static void +connect_ip6_page (CEPageIP6 *self) +{ + const gchar *str_method; + guint method; + + add_address_box (self); + add_dns_section (self); + add_routes_box (self); + + str_method = nm_setting_ip_config_get_method (self->setting); + g_signal_connect_object (self->disabled_radio, "notify::active", G_CALLBACK (ce_page_changed), self, G_CONNECT_SWAPPED); + g_object_bind_property (self->disabled_radio, "active", + self->content_box, "sensitive", + G_BINDING_SYNC_CREATE | G_BINDING_INVERT_BOOLEAN); + + g_signal_connect_object (self->shared_radio, "notify::active", G_CALLBACK (ce_page_changed), self, G_CONNECT_SWAPPED); + g_object_bind_property (self->shared_radio, "active", + self->content_box, "sensitive", + G_BINDING_SYNC_CREATE | G_BINDING_INVERT_BOOLEAN); + + method = IP6_METHOD_AUTO; + if (g_strcmp0 (str_method, NM_SETTING_IP6_CONFIG_METHOD_DHCP) == 0) { + method = IP6_METHOD_DHCP; + } else if (g_strcmp0 (str_method, NM_SETTING_IP6_CONFIG_METHOD_LINK_LOCAL) == 0) { + method = IP6_METHOD_LINK_LOCAL; + } else if (g_strcmp0 (str_method, NM_SETTING_IP6_CONFIG_METHOD_MANUAL) == 0) { + method = IP6_METHOD_MANUAL; + } else if (g_strcmp0 (str_method, NM_SETTING_IP6_CONFIG_METHOD_SHARED) == 0) { + method = IP6_METHOD_SHARED; + } else if (g_strcmp0 (str_method, NM_SETTING_IP6_CONFIG_METHOD_DISABLED) == 0 || + g_strcmp0 (str_method, NM_SETTING_IP6_CONFIG_METHOD_IGNORE) == 0) { + method = IP6_METHOD_DISABLED; + } + + gtk_check_button_set_active (GTK_CHECK_BUTTON (self->never_default_check), + nm_setting_ip_config_get_never_default (self->setting)); + g_signal_connect_object (self->never_default_check, "toggled", G_CALLBACK (ce_page_changed), self, G_CONNECT_SWAPPED); + + g_signal_connect_object (self->automatic_radio, "toggled", G_CALLBACK (method_changed), self, G_CONNECT_SWAPPED); + g_signal_connect_object (self->dhcp_radio, "toggled", G_CALLBACK (method_changed), self, G_CONNECT_SWAPPED); + g_signal_connect_object (self->local_radio, "toggled", G_CALLBACK (method_changed), self, G_CONNECT_SWAPPED); + g_signal_connect_object (self->manual_radio, "toggled", G_CALLBACK (method_changed), self, G_CONNECT_SWAPPED); + g_signal_connect_object (self->disabled_radio, "toggled", G_CALLBACK (method_changed), self, G_CONNECT_SWAPPED); + + switch (method) { + case IP6_METHOD_AUTO: + gtk_check_button_set_active (GTK_CHECK_BUTTON (self->automatic_radio), TRUE); + break; + case IP6_METHOD_DHCP: + gtk_check_button_set_active (GTK_CHECK_BUTTON (self->dhcp_radio), TRUE); + break; + case IP6_METHOD_LINK_LOCAL: + gtk_check_button_set_active (GTK_CHECK_BUTTON (self->local_radio), TRUE); + break; + case IP6_METHOD_MANUAL: + gtk_check_button_set_active (GTK_CHECK_BUTTON (self->manual_radio), TRUE); + break; + case IP6_METHOD_SHARED: + gtk_check_button_set_active (GTK_CHECK_BUTTON (self->shared_radio), TRUE); + break; + case IP6_METHOD_DISABLED: + gtk_check_button_set_active (GTK_CHECK_BUTTON (self->disabled_radio), TRUE); + break; + default: + break; + } + + method_changed (self); +} + +static gboolean +ui_to_setting (CEPageIP6 *self) +{ + GtkWidget *child; + const gchar *method; + gboolean ignore_auto_dns; + gboolean ignore_auto_routes; + gboolean never_default; + gboolean add_addresses = FALSE; + gboolean add_routes = FALSE; + gboolean ret = TRUE; + GStrv dns_addresses = NULL; + gchar *dns_text = NULL; + guint i; + + if (gtk_check_button_get_active (self->disabled_radio)) + method = NM_SETTING_IP6_CONFIG_METHOD_DISABLED; + else if (gtk_check_button_get_active (self->manual_radio)) + method = NM_SETTING_IP6_CONFIG_METHOD_MANUAL; + else if (gtk_check_button_get_active (self->local_radio)) + method = NM_SETTING_IP6_CONFIG_METHOD_LINK_LOCAL; + else if (gtk_check_button_get_active (self->dhcp_radio)) + method = NM_SETTING_IP6_CONFIG_METHOD_DHCP; + else if (gtk_check_button_get_active (self->automatic_radio)) + method = NM_SETTING_IP6_CONFIG_METHOD_AUTO; + else if (gtk_check_button_get_active (self->shared_radio)) + method = NM_SETTING_IP6_CONFIG_METHOD_SHARED; + + nm_setting_ip_config_clear_addresses (self->setting); + if (g_str_equal (method, NM_SETTING_IP6_CONFIG_METHOD_MANUAL)) { + add_addresses = TRUE; + } else { + g_object_set (G_OBJECT (self->setting), + NM_SETTING_IP_CONFIG_GATEWAY, NULL, + NULL); + } + + for (child = gtk_widget_get_first_child (self->address_list); + add_addresses && child; + child = gtk_widget_get_next_sibling (child)) { + GtkWidget *row = child; + CEIPAddressEntry *address_entry; + CEIPAddressEntry *gateway_entry; + const gchar *text_prefix; + guint32 prefix; + gchar *end; + NMIPAddress *addr; + + address_entry = CE_IP_ADDRESS_ENTRY (g_object_get_data (G_OBJECT (row), "address")); + if (!address_entry) + continue; + + text_prefix = gtk_editable_get_text (GTK_EDITABLE (g_object_get_data (G_OBJECT (row), "prefix"))); + gateway_entry = CE_IP_ADDRESS_ENTRY (g_object_get_data (G_OBJECT (row), "gateway")); + + if (ce_ip_address_entry_is_empty (address_entry) && !*text_prefix && ce_ip_address_entry_is_empty (gateway_entry)) { + /* ignore empty rows */ + widget_unset_error (g_object_get_data (G_OBJECT (row), "prefix")); + continue; + } + + if (!ce_ip_address_entry_is_valid (address_entry)) + ret = FALSE; + + prefix = strtoul (text_prefix, &end, 10); + if (!end || *end || prefix == 0 || prefix > 128) { + widget_set_error (g_object_get_data (G_OBJECT (row), "prefix")); + ret = FALSE; + } else { + widget_unset_error (g_object_get_data (G_OBJECT (row), "prefix")); + } + + if (!ce_ip_address_entry_is_valid (gateway_entry)) + ret = FALSE; + + if (!ret) + continue; + + addr = nm_ip_address_new (AF_INET6, gtk_editable_get_text (GTK_EDITABLE (address_entry)), prefix, NULL); + if (!ce_ip_address_entry_is_empty (gateway_entry)) + g_object_set (G_OBJECT (self->setting), + NM_SETTING_IP_CONFIG_GATEWAY, gtk_editable_get_text (GTK_EDITABLE (gateway_entry)), + NULL); + nm_setting_ip_config_add_address (self->setting, addr); + + if (!gtk_widget_get_next_sibling (row)) + ensure_empty_address_row (self); + } + + nm_setting_ip_config_clear_dns (self->setting); + dns_text = g_strstrip (g_strdup (gtk_editable_get_text (GTK_EDITABLE (self->dns_entry)))); + + if (g_str_equal (method, NM_SETTING_IP6_CONFIG_METHOD_AUTO) || + g_str_equal (method, NM_SETTING_IP6_CONFIG_METHOD_DHCP) || + g_str_equal (method, NM_SETTING_IP6_CONFIG_METHOD_MANUAL)) + dns_addresses = g_strsplit_set (dns_text, ", ", -1); + else + dns_addresses = NULL; + + for (i = 0; dns_addresses && dns_addresses[i]; i++) { + const gchar *text; + struct in6_addr tmp_addr; + + text = dns_addresses[i]; + + if (!text || !*text) + continue; + + if (inet_pton (AF_INET6, text, &tmp_addr) <= 0) { + g_clear_pointer (&dns_addresses, g_strfreev); + widget_set_error (GTK_WIDGET (self->dns_entry)); + ret = FALSE; + break; + } else { + widget_unset_error (GTK_WIDGET (self->dns_entry)); + nm_setting_ip_config_add_dns (self->setting, text); + } + } + + nm_setting_ip_config_clear_routes (self->setting); + add_routes = g_str_equal (method, NM_SETTING_IP6_CONFIG_METHOD_AUTO) || + g_str_equal (method, NM_SETTING_IP6_CONFIG_METHOD_DHCP) || + g_str_equal (method, NM_SETTING_IP6_CONFIG_METHOD_MANUAL); + + for (child = gtk_widget_get_first_child (self->routes_list); + add_routes && child; + child = gtk_widget_get_next_sibling (child)) { + GtkWidget *row = child; + CEIPAddressEntry *address_entry; + CEIPAddressEntry *gateway_entry; + const gchar *text_prefix; + const gchar *text_metric; + guint32 prefix; + gint64 metric; + gchar *end; + NMIPRoute *route; + + address_entry = CE_IP_ADDRESS_ENTRY (g_object_get_data (G_OBJECT (row), "address")); + if (!address_entry) + continue; + + text_prefix = gtk_editable_get_text (GTK_EDITABLE (g_object_get_data (G_OBJECT (row), "prefix"))); + gateway_entry = CE_IP_ADDRESS_ENTRY (g_object_get_data (G_OBJECT (row), "gateway")); + text_metric = gtk_editable_get_text (GTK_EDITABLE (g_object_get_data (G_OBJECT (row), "metric"))); + + if (ce_ip_address_entry_is_empty (address_entry) && !*text_prefix && ce_ip_address_entry_is_empty (gateway_entry) && !*text_metric) { + /* ignore empty rows */ + widget_unset_error (g_object_get_data (G_OBJECT (row), "prefix")); + widget_unset_error (g_object_get_data (G_OBJECT (row), "metric")); + continue; + } + + if (!ce_ip_address_entry_is_valid (address_entry)) + ret = FALSE; + + prefix = strtoul (text_prefix, &end, 10); + if (!end || *end || prefix == 0 || prefix > 128) { + widget_set_error (g_object_get_data (G_OBJECT (row), "prefix")); + ret = FALSE; + } else { + widget_unset_error (g_object_get_data (G_OBJECT (row), "prefix")); + } + + if (!ce_ip_address_entry_is_valid (gateway_entry)) + ret = FALSE; + + metric = -1; + if (*text_metric) { + errno = 0; + metric = g_ascii_strtoull (text_metric, NULL, 10); + if (errno) { + widget_set_error (g_object_get_data (G_OBJECT (row), "metric")); + ret = FALSE; + } else { + widget_unset_error (g_object_get_data (G_OBJECT (row), "metric")); + } + } else { + widget_unset_error (g_object_get_data (G_OBJECT (row), "metric")); + } + + if (!ret) + continue; + + route = nm_ip_route_new (AF_INET6, + gtk_editable_get_text (GTK_EDITABLE (address_entry)), + prefix, + gtk_editable_get_text (GTK_EDITABLE (gateway_entry)), + metric, + NULL); + nm_setting_ip_config_add_route (self->setting, route); + nm_ip_route_unref (route); + + if (!gtk_widget_get_next_sibling (row)) + ensure_empty_routes_row (self); + } + + if (!ret) + goto out; + + ignore_auto_dns = !gtk_switch_get_active (self->auto_dns_switch); + ignore_auto_routes = !gtk_switch_get_active (self->auto_routes_switch); + never_default = gtk_check_button_get_active (self->never_default_check); + + g_object_set (self->setting, + NM_SETTING_IP_CONFIG_METHOD, method, + NM_SETTING_IP_CONFIG_IGNORE_AUTO_DNS, ignore_auto_dns, + NM_SETTING_IP_CONFIG_IGNORE_AUTO_ROUTES, ignore_auto_routes, + NM_SETTING_IP_CONFIG_NEVER_DEFAULT, never_default, + NULL); + +out: + g_clear_pointer (&dns_addresses, g_strfreev); + g_clear_pointer (&dns_text, g_free); + + return ret; +} + +static const gchar * +ce_page_ip6_get_title (CEPage *page) +{ + return _("IPv6"); +} + +static gboolean +ce_page_ip6_validate (CEPage *self, + NMConnection *connection, + GError **error) +{ + if (!ui_to_setting (CE_PAGE_IP6 (self))) + return FALSE; + + return nm_setting_verify (NM_SETTING (CE_PAGE_IP6 (self)->setting), NULL, error); +} + +static void +ce_page_ip6_init (CEPageIP6 *self) +{ + gtk_widget_init_template (GTK_WIDGET (self)); +} + +static void +ce_page_ip6_class_init (CEPageIP6Class *klass) +{ + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/control-center/network/ip6-page.ui"); + + gtk_widget_class_bind_template_child (widget_class, CEPageIP6, address_box); + gtk_widget_class_bind_template_child (widget_class, CEPageIP6, address_address_label); + gtk_widget_class_bind_template_child (widget_class, CEPageIP6, address_prefix_label); + gtk_widget_class_bind_template_child (widget_class, CEPageIP6, address_gateway_label); + gtk_widget_class_bind_template_child (widget_class, CEPageIP6, address_sizegroup); + gtk_widget_class_bind_template_child (widget_class, CEPageIP6, auto_dns_switch); + gtk_widget_class_bind_template_child (widget_class, CEPageIP6, auto_routes_switch); + gtk_widget_class_bind_template_child (widget_class, CEPageIP6, automatic_radio); + gtk_widget_class_bind_template_child (widget_class, CEPageIP6, content_box); + gtk_widget_class_bind_template_child (widget_class, CEPageIP6, dhcp_radio); + gtk_widget_class_bind_template_child (widget_class, CEPageIP6, disabled_radio); + gtk_widget_class_bind_template_child (widget_class, CEPageIP6, dns_entry); + gtk_widget_class_bind_template_child (widget_class, CEPageIP6, local_radio); + gtk_widget_class_bind_template_child (widget_class, CEPageIP6, main_box); + gtk_widget_class_bind_template_child (widget_class, CEPageIP6, manual_radio); + gtk_widget_class_bind_template_child (widget_class, CEPageIP6, never_default_check); + gtk_widget_class_bind_template_child (widget_class, CEPageIP6, routes_box); + gtk_widget_class_bind_template_child (widget_class, CEPageIP6, routes_address_label); + gtk_widget_class_bind_template_child (widget_class, CEPageIP6, routes_prefix_label); + gtk_widget_class_bind_template_child (widget_class, CEPageIP6, routes_gateway_label); + gtk_widget_class_bind_template_child (widget_class, CEPageIP6, routes_metric_label); + gtk_widget_class_bind_template_child (widget_class, CEPageIP6, routes_metric_sizegroup); + gtk_widget_class_bind_template_child (widget_class, CEPageIP6, routes_sizegroup); + gtk_widget_class_bind_template_child (widget_class, CEPageIP6, shared_radio); +} + +static void +ce_page_iface_init (CEPageInterface *iface) +{ + iface->get_title = ce_page_ip6_get_title; + iface->validate = ce_page_ip6_validate; +} + +CEPageIP6 * +ce_page_ip6_new (NMConnection *connection, + NMClient *client) +{ + CEPageIP6 *self; + + self = CE_PAGE_IP6 (g_object_new (ce_page_ip6_get_type (), NULL)); + + self->setting = nm_connection_get_setting_ip6_config (connection); + if (!self->setting) { + self->setting = NM_SETTING_IP_CONFIG (nm_setting_ip6_config_new ()); + nm_connection_add_setting (connection, NM_SETTING (self->setting)); + } + + connect_ip6_page (self); + + return self; +} diff --git a/panels/network/connection-editor/ce-page-ip6.h b/panels/network/connection-editor/ce-page-ip6.h new file mode 100644 index 0000000..c2c7d1b --- /dev/null +++ b/panels/network/connection-editor/ce-page-ip6.h @@ -0,0 +1,34 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2012 Red Hat, Inc. + * + * 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. + */ + +#pragma once + +#include <adwaita.h> +#include <NetworkManager.h> + +G_BEGIN_DECLS + +G_DECLARE_FINAL_TYPE (CEPageIP6, ce_page_ip6, CE, PAGE_IP6, AdwBin) + +CEPageIP6 *ce_page_ip6_new (NMConnection *connection, + NMClient *client); + +G_END_DECLS diff --git a/panels/network/connection-editor/ce-page-security.c b/panels/network/connection-editor/ce-page-security.c new file mode 100644 index 0000000..c7cd7d9 --- /dev/null +++ b/panels/network/connection-editor/ce-page-security.c @@ -0,0 +1,553 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2012 Red Hat, Inc + * + * 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/gi18n.h> + +#include <NetworkManager.h> + +#include "ce-page.h" +#include "ce-page-security.h" +#include "wireless-security.h" +#include "ws-dynamic-wep.h" +#include "ws-leap.h" +#include "ws-sae.h" +#include "ws-wep-key.h" +#include "ws-wpa-eap.h" +#include "ws-wpa-psk.h" + +struct _CEPageSecurity +{ + GtkGrid parent; + + GtkBox *box; + GtkComboBox *security_combo; + GtkLabel *security_label; + + NMConnection *connection; + const gchar *security_setting; + GtkSizeGroup *group; + gboolean adhoc; +}; + +static void ce_page_iface_init (CEPageInterface *); + +G_DEFINE_TYPE_WITH_CODE (CEPageSecurity, ce_page_security, GTK_TYPE_GRID, + G_IMPLEMENT_INTERFACE (ce_page_get_type (), ce_page_iface_init)) + +enum { + S_NAME_COLUMN, + S_SEC_COLUMN, + S_ADHOC_VALID_COLUMN +}; + +static gboolean +find_proto (NMSettingWirelessSecurity *sec, const char *item) +{ + guint32 i; + + for (i = 0; i < nm_setting_wireless_security_get_num_protos (sec); i++) { + if (!strcmp (item, nm_setting_wireless_security_get_proto (sec, i))) + return TRUE; + } + return FALSE; +} + +static NMUtilsSecurityType +get_default_type_for_security (NMSettingWirelessSecurity *sec) +{ + const char *key_mgmt, *auth_alg; + + g_return_val_if_fail (sec != NULL, NMU_SEC_NONE); + + key_mgmt = nm_setting_wireless_security_get_key_mgmt (sec); + auth_alg = nm_setting_wireless_security_get_auth_alg (sec); + + /* No IEEE 802.1x */ + if (!strcmp (key_mgmt, "none")) + return NMU_SEC_STATIC_WEP; + + if (!strcmp (key_mgmt, "ieee8021x")) { + if (auth_alg && !strcmp (auth_alg, "leap")) + return NMU_SEC_LEAP; + return NMU_SEC_DYNAMIC_WEP; + } + +#if NM_CHECK_VERSION(1,24,0) + if (!strcmp (key_mgmt, "owe")) { + return NMU_SEC_OWE; + } +#endif + +#if NM_CHECK_VERSION(1,20,6) + if (!strcmp (key_mgmt, "sae")) { + return NMU_SEC_SAE; + } +#endif + + if ( !strcmp (key_mgmt, "wpa-none") + || !strcmp (key_mgmt, "wpa-psk")) { + if (find_proto (sec, "rsn")) + return NMU_SEC_WPA2_PSK; + else if (find_proto (sec, "wpa")) + return NMU_SEC_WPA_PSK; + else + return NMU_SEC_WPA_PSK; + } + + if (!strcmp (key_mgmt, "wpa-eap")) { + if (find_proto (sec, "rsn")) + return NMU_SEC_WPA2_ENTERPRISE; + else if (find_proto (sec, "wpa")) + return NMU_SEC_WPA_ENTERPRISE; + else + return NMU_SEC_WPA_ENTERPRISE; + } + + return NMU_SEC_INVALID; +} + +static WirelessSecurity * +security_combo_get_active (CEPageSecurity *self) +{ + GtkTreeIter iter; + GtkTreeModel *model; + WirelessSecurity *sec = NULL; + + model = gtk_combo_box_get_model (self->security_combo); + if (!gtk_combo_box_get_active_iter (self->security_combo, &iter)) + return NULL; + gtk_tree_model_get (model, &iter, S_SEC_COLUMN, &sec, -1); + + return sec; +} + +static void +wsec_size_group_clear (GtkSizeGroup *group) +{ + GSList *children; + GSList *iter; + + g_return_if_fail (group != NULL); + + children = gtk_size_group_get_widgets (group); + for (iter = children; iter; iter = g_slist_next (iter)) + gtk_size_group_remove_widget (group, GTK_WIDGET (iter->data)); +} + +static void +security_combo_changed (CEPageSecurity *self) +{ + g_autoptr(WirelessSecurity) sec = NULL; + GtkWidget *child; + + wsec_size_group_clear (self->group); + + while ((child = gtk_widget_get_first_child (GTK_WIDGET (self->box))) != NULL) + gtk_box_remove (self->box, child); + + sec = security_combo_get_active (self); + if (sec) { + if (gtk_widget_get_parent (GTK_WIDGET (sec))) + gtk_box_remove (self->box, GTK_WIDGET (sec)); + + gtk_size_group_add_widget (self->group, GTK_WIDGET (self->security_label)); + wireless_security_add_to_size_group (sec, self->group); + + gtk_box_append (self->box, g_object_ref (GTK_WIDGET (sec))); + } + + ce_page_changed (CE_PAGE (self)); +} + +static void +security_item_changed_cb (CEPageSecurity *self) +{ + ce_page_changed (CE_PAGE (self)); +} + +static void +add_security_item (CEPageSecurity *self, + WirelessSecurity *sec, + GtkListStore *model, + GtkTreeIter *iter, + const char *text, + gboolean adhoc_valid) +{ + g_signal_connect_object (sec, "changed", G_CALLBACK (security_item_changed_cb), self, G_CONNECT_SWAPPED); + gtk_list_store_append (model, iter); + gtk_list_store_set (model, iter, + S_NAME_COLUMN, text, + S_SEC_COLUMN, sec, + S_ADHOC_VALID_COLUMN, adhoc_valid, + -1); + g_object_unref (sec); +} + +static void +set_sensitive (GtkCellLayout *cell_layout, + GtkCellRenderer *cell, + GtkTreeModel *tree_model, + GtkTreeIter *iter, + gpointer data) +{ + gboolean *adhoc = data; + gboolean sensitive = TRUE, adhoc_valid = TRUE; + + gtk_tree_model_get (tree_model, iter, S_ADHOC_VALID_COLUMN, &adhoc_valid, -1); + if (*adhoc && !adhoc_valid) + sensitive = FALSE; + + g_object_set (cell, "sensitive", sensitive, NULL); +} + +static void +finish_setup (CEPageSecurity *self) +{ + NMSettingWireless *sw; + NMSettingWirelessSecurity *sws; + gboolean is_adhoc = FALSE; + g_autoptr(GtkListStore) sec_model = NULL; + GtkTreeIter iter; + const gchar *mode; + guint32 dev_caps = 0; + NMUtilsSecurityType default_type = NMU_SEC_NONE; + int active = -1; + int item = 0; + GtkCellRenderer *renderer; + + sw = nm_connection_get_setting_wireless (self->connection); + g_assert (sw); + + self->group = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL); + + dev_caps = NM_WIFI_DEVICE_CAP_CIPHER_WEP40 + | NM_WIFI_DEVICE_CAP_CIPHER_WEP104 + | NM_WIFI_DEVICE_CAP_CIPHER_TKIP + | NM_WIFI_DEVICE_CAP_CIPHER_CCMP + | NM_WIFI_DEVICE_CAP_WPA + | NM_WIFI_DEVICE_CAP_RSN; + + mode = nm_setting_wireless_get_mode (sw); + if (mode && !strcmp (mode, "adhoc")) + is_adhoc = TRUE; + self->adhoc = is_adhoc; + + sws = nm_connection_get_setting_wireless_security (self->connection); + if (sws) + default_type = get_default_type_for_security (sws); + + sec_model = gtk_list_store_new (3, G_TYPE_STRING, wireless_security_get_type (), G_TYPE_BOOLEAN); + + if (nm_utils_security_valid (NMU_SEC_NONE, dev_caps, FALSE, is_adhoc, 0, 0, 0)) { + gtk_list_store_insert_with_values (sec_model, &iter, -1, + S_NAME_COLUMN, C_("Wi-Fi/Ethernet security", "None"), + S_ADHOC_VALID_COLUMN, TRUE, + -1); + if (default_type == NMU_SEC_NONE) + active = item; + item++; + } + +#if NM_CHECK_VERSION(1,24,0) + if (nm_utils_security_valid (NMU_SEC_OWE, dev_caps, FALSE, is_adhoc, 0, 0, 0)) { + gtk_list_store_insert_with_values (sec_model, &iter, -1, + S_NAME_COLUMN, _("Enhanced Open"), + S_ADHOC_VALID_COLUMN, FALSE, + -1); + if (active < 0 && default_type == NMU_SEC_OWE) + active = item; + item++; + } +#endif + + if (nm_utils_security_valid (NMU_SEC_STATIC_WEP, dev_caps, FALSE, is_adhoc, 0, 0, 0)) { + WirelessSecurityWEPKey *ws_wep; + NMWepKeyType wep_type = NM_WEP_KEY_TYPE_KEY; + + if (default_type == NMU_SEC_STATIC_WEP) { + sws = nm_connection_get_setting_wireless_security (self->connection); + if (sws) + wep_type = nm_setting_wireless_security_get_wep_key_type (sws); + if (wep_type == NM_WEP_KEY_TYPE_UNKNOWN) + wep_type = NM_WEP_KEY_TYPE_KEY; + } + + ws_wep = ws_wep_key_new (self->connection, NM_WEP_KEY_TYPE_KEY); + if (ws_wep) { + add_security_item (self, WIRELESS_SECURITY (ws_wep), sec_model, + &iter, _("WEP 40/128-bit Key (Hex or ASCII)"), + TRUE); + if ((active < 0) && (default_type == NMU_SEC_STATIC_WEP) && (wep_type == NM_WEP_KEY_TYPE_KEY)) + active = item; + item++; + } + + ws_wep = ws_wep_key_new (self->connection, NM_WEP_KEY_TYPE_PASSPHRASE); + if (ws_wep) { + add_security_item (self, WIRELESS_SECURITY (ws_wep), sec_model, + &iter, _("WEP 128-bit Passphrase"), TRUE); + if ((active < 0) && (default_type == NMU_SEC_STATIC_WEP) && (wep_type == NM_WEP_KEY_TYPE_PASSPHRASE)) + active = item; + item++; + } + } + + if (nm_utils_security_valid (NMU_SEC_LEAP, dev_caps, FALSE, is_adhoc, 0, 0, 0)) { + WirelessSecurityLEAP *ws_leap; + + ws_leap = ws_leap_new (self->connection); + if (ws_leap) { + add_security_item (self, WIRELESS_SECURITY (ws_leap), sec_model, + &iter, _("LEAP"), FALSE); + if ((active < 0) && (default_type == NMU_SEC_LEAP)) + active = item; + item++; + } + } + + if (nm_utils_security_valid (NMU_SEC_DYNAMIC_WEP, dev_caps, FALSE, is_adhoc, 0, 0, 0)) { + WirelessSecurityDynamicWEP *ws_dynamic_wep; + + ws_dynamic_wep = ws_dynamic_wep_new (self->connection); + if (ws_dynamic_wep) { + add_security_item (self, WIRELESS_SECURITY (ws_dynamic_wep), sec_model, + &iter, _("Dynamic WEP (802.1x)"), FALSE); + if ((active < 0) && (default_type == NMU_SEC_DYNAMIC_WEP)) + active = item; + item++; + } + } + + if (nm_utils_security_valid (NMU_SEC_WPA_PSK, dev_caps, FALSE, is_adhoc, 0, 0, 0) || + nm_utils_security_valid (NMU_SEC_WPA2_PSK, dev_caps, FALSE, is_adhoc, 0, 0, 0)) { + WirelessSecurityWPAPSK *ws_wpa_psk; + + ws_wpa_psk = ws_wpa_psk_new (self->connection); + if (ws_wpa_psk) { + add_security_item (self, WIRELESS_SECURITY (ws_wpa_psk), sec_model, + &iter, _("WPA & WPA2 Personal"), FALSE); + if ((active < 0) && ((default_type == NMU_SEC_WPA_PSK) || (default_type == NMU_SEC_WPA2_PSK))) + active = item; + item++; + } + } + + if (nm_utils_security_valid (NMU_SEC_WPA_ENTERPRISE, dev_caps, FALSE, is_adhoc, 0, 0, 0) || + nm_utils_security_valid (NMU_SEC_WPA2_ENTERPRISE, dev_caps, FALSE, is_adhoc, 0, 0, 0)) { + WirelessSecurityWPAEAP *ws_wpa_eap; + + ws_wpa_eap = ws_wpa_eap_new (self->connection); + if (ws_wpa_eap) { + add_security_item (self, WIRELESS_SECURITY (ws_wpa_eap), sec_model, + &iter, _("WPA & WPA2 Enterprise"), FALSE); + if ((active < 0) && ((default_type == NMU_SEC_WPA_ENTERPRISE) || (default_type == NMU_SEC_WPA2_ENTERPRISE))) + active = item; + item++; + } + } + +#if NM_CHECK_VERSION(1,20,6) + if (nm_utils_security_valid (NMU_SEC_SAE, dev_caps, FALSE, is_adhoc, 0, 0, 0)) { + WirelessSecuritySAE *ws_sae; + + ws_sae = ws_sae_new (self->connection); + if (ws_sae) { + add_security_item (self, WIRELESS_SECURITY (ws_sae), sec_model, + &iter, _("WPA3 Personal"), FALSE); + if ((active < 0) && ((default_type == NMU_SEC_SAE))) + active = item; + item++; + } + } +#endif + + gtk_combo_box_set_model (self->security_combo, GTK_TREE_MODEL (sec_model)); + gtk_cell_layout_clear (GTK_CELL_LAYOUT (self->security_combo)); + + renderer = gtk_cell_renderer_text_new (); + gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (self->security_combo), renderer, TRUE); + gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (self->security_combo), renderer, "text", S_NAME_COLUMN, NULL); + gtk_cell_layout_set_cell_data_func (GTK_CELL_LAYOUT (self->security_combo), renderer, set_sensitive, &self->adhoc, NULL); + + gtk_combo_box_set_active (self->security_combo, active < 0 ? 0 : (guint32) active); + + security_combo_changed (self); + g_signal_connect_object (self->security_combo, "changed", + G_CALLBACK (security_combo_changed), self, G_CONNECT_SWAPPED); +} + +static void +ce_page_security_dispose (GObject *object) +{ + CEPageSecurity *self = CE_PAGE_SECURITY (object); + + g_clear_object (&self->connection); + g_clear_object (&self->group); + + G_OBJECT_CLASS (ce_page_security_parent_class)->dispose (object); +} + +static const gchar * +ce_page_security_get_security_setting (CEPage *page) +{ + return CE_PAGE_SECURITY (page)->security_setting; +} + +static const gchar * +ce_page_security_get_title (CEPage *page) +{ + return _("Security"); +} + +static gboolean +ce_page_security_validate (CEPage *page, + NMConnection *connection, + GError **error) +{ + CEPageSecurity *self = CE_PAGE_SECURITY (page); + NMSettingWireless *sw; + g_autoptr(WirelessSecurity) sec = NULL; + gboolean valid = FALSE; + const char *mode; + + sw = nm_connection_get_setting_wireless (connection); + + mode = nm_setting_wireless_get_mode (sw); + if (g_strcmp0 (mode, NM_SETTING_WIRELESS_MODE_ADHOC) == 0) + CE_PAGE_SECURITY (self)->adhoc = TRUE; + else + CE_PAGE_SECURITY (self)->adhoc = FALSE; + + sec = security_combo_get_active (CE_PAGE_SECURITY (self)); + if (sec) { + GBytes *ssid = nm_setting_wireless_get_ssid (sw); + + if (ssid) { + /* FIXME: get failed property and error out of wifi security objects */ + valid = wireless_security_validate (sec, error); + if (valid) + wireless_security_fill_connection (sec, connection); + } else { + g_set_error (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_MISSING_SETTING, "Missing SSID"); + valid = FALSE; + } + + if (self->adhoc) { + if (!wireless_security_adhoc_compatible (sec)) { + if (valid) + g_set_error (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_SETTING, "Security not compatible with Ad-Hoc mode"); + valid = FALSE; + } + } + } else { + + if (gtk_combo_box_get_active ((CE_PAGE_SECURITY (self))->security_combo) == 0) { + /* No security, unencrypted */ + nm_connection_remove_setting (connection, NM_TYPE_SETTING_WIRELESS_SECURITY); + nm_connection_remove_setting (connection, NM_TYPE_SETTING_802_1X); + valid = TRUE; + } else { + /* owe case: + * fill the connection manually until libnma implements OWE wireless security + */ + NMSetting *sws; + + sws = nm_setting_wireless_security_new (); + g_object_set (sws, NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, "owe", NULL); + nm_connection_add_setting (connection, sws); + nm_connection_remove_setting (connection, NM_TYPE_SETTING_802_1X); + valid = TRUE; + } + + } + + return valid; +} + +static void +ce_page_security_init (CEPageSecurity *self) +{ + gtk_widget_init_template (GTK_WIDGET (self)); +} + +static void +ce_page_security_class_init (CEPageSecurityClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + object_class->dispose = ce_page_security_dispose; + + gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/control-center/network/security-page.ui"); + + gtk_widget_class_bind_template_child (widget_class, CEPageSecurity, box); + gtk_widget_class_bind_template_child (widget_class, CEPageSecurity, security_label); + gtk_widget_class_bind_template_child (widget_class, CEPageSecurity, security_combo); +} + +static void +ce_page_iface_init (CEPageInterface *iface) +{ + iface->get_security_setting = ce_page_security_get_security_setting; + iface->get_title = ce_page_security_get_title; + iface->validate = ce_page_security_validate; +} + +CEPageSecurity * +ce_page_security_new (NMConnection *connection) +{ + CEPageSecurity *self; + NMUtilsSecurityType default_type = NMU_SEC_NONE; + NMSettingWirelessSecurity *sws; + + self = CE_PAGE_SECURITY (g_object_new (ce_page_security_get_type (), NULL)); + + self->connection = g_object_ref (connection); + + sws = nm_connection_get_setting_wireless_security (connection); + if (sws) + default_type = get_default_type_for_security (sws); + + if (default_type == NMU_SEC_STATIC_WEP || + default_type == NMU_SEC_LEAP || + default_type == NMU_SEC_WPA_PSK || +#if NM_CHECK_VERSION(1,20,6) + default_type == NMU_SEC_SAE || +#endif +#if NM_CHECK_VERSION(1,24,0) + default_type == NMU_SEC_OWE || +#endif + default_type == NMU_SEC_WPA2_PSK) { + self->security_setting = NM_SETTING_WIRELESS_SECURITY_SETTING_NAME; + } + + if (default_type == NMU_SEC_DYNAMIC_WEP || + default_type == NMU_SEC_WPA_ENTERPRISE || + default_type == NMU_SEC_WPA2_ENTERPRISE) { + self->security_setting = NM_SETTING_802_1X_SETTING_NAME; + } + + g_signal_connect (self, "initialized", G_CALLBACK (finish_setup), NULL); + + return self; +} diff --git a/panels/network/connection-editor/ce-page-security.h b/panels/network/connection-editor/ce-page-security.h new file mode 100644 index 0000000..e7c84e5 --- /dev/null +++ b/panels/network/connection-editor/ce-page-security.h @@ -0,0 +1,33 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2012 Red Hat, Inc. + * + * 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. + */ + +#pragma once + +#include <gtk/gtk.h> +#include <NetworkManager.h> + +G_BEGIN_DECLS + +G_DECLARE_FINAL_TYPE (CEPageSecurity, ce_page_security, CE, PAGE_SECURITY, GtkGrid) + +CEPageSecurity *ce_page_security_new (NMConnection *connection); + +G_END_DECLS diff --git a/panels/network/connection-editor/ce-page-vpn.c b/panels/network/connection-editor/ce-page-vpn.c new file mode 100644 index 0000000..36afde5 --- /dev/null +++ b/panels/network/connection-editor/ce-page-vpn.c @@ -0,0 +1,227 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2013 Red Hat, Inc + * + * 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/gi18n.h> + +#include <NetworkManager.h> + +#include "ce-page.h" +#include "ce-page-vpn.h" +#include "vpn-helpers.h" + +struct _CEPageVpn +{ + GtkBox parent; + + GtkLabel *failure_label; + GtkEntry *name_entry; + + NMConnection *connection; + NMSettingConnection *setting_connection; + NMSettingVpn *setting_vpn; + + NMVpnEditorPlugin *plugin; + NMVpnEditor *editor; +}; + +static void ce_page_iface_init (CEPageInterface *); + +G_DEFINE_TYPE_WITH_CODE (CEPageVpn, ce_page_vpn, GTK_TYPE_BOX, + G_IMPLEMENT_INTERFACE (ce_page_get_type (), ce_page_iface_init)) + +/* Hack to make the plugin-provided editor widget fit in better with + * the control center by changing + * + * Foo: [__________] + * Bar baz: [__________] + * + * to + * + * Foo [__________] + * Bar baz [__________] + */ +static void +vpn_gnome3ify_editor (GtkWidget *widget) +{ + if (GTK_IS_LABEL (widget)) { + const char *text; + gfloat xalign; + g_autofree gchar *newtext = NULL; + int len; + + xalign = gtk_label_get_xalign (GTK_LABEL (widget)); + if (xalign != 0.0) + return; + text = gtk_label_get_text (GTK_LABEL (widget)); + len = strlen (text); + if (len < 2 || text[len - 1] != ':') + return; + + newtext = g_strndup (text, len - 1); + gtk_label_set_text (GTK_LABEL (widget), newtext); + gtk_label_set_xalign (GTK_LABEL (widget), 1.0); + } else if (GTK_IS_WIDGET (widget)) { + GtkWidget *child; + + for (child = gtk_widget_get_first_child (widget); + child; + child = gtk_widget_get_next_sibling (child)) + vpn_gnome3ify_editor (child); + } +} + +static void +load_vpn_plugin (CEPageVpn *self) +{ + GtkWidget *ui_widget; + + self->editor = nm_vpn_editor_plugin_get_editor (self->plugin, + self->connection, + NULL); + ui_widget = NULL; + if (self->editor) + ui_widget = GTK_WIDGET (nm_vpn_editor_get_widget (self->editor)); + + if (!ui_widget) { + g_clear_object (&self->editor); + self->plugin = NULL; + return; + } + vpn_gnome3ify_editor (ui_widget); + + gtk_box_remove (GTK_BOX (self), GTK_WIDGET (self->failure_label)); + gtk_box_append (GTK_BOX (self), ui_widget); + + g_signal_connect_object (self->editor, "changed", G_CALLBACK (ce_page_changed), self, G_CONNECT_SWAPPED); +} + +static void +connect_vpn_page (CEPageVpn *self) +{ + const gchar *name; + + name = nm_setting_connection_get_id (self->setting_connection); + gtk_editable_set_text (GTK_EDITABLE (self->name_entry), name); + g_signal_connect_object (self->name_entry, "changed", G_CALLBACK (ce_page_changed), self, G_CONNECT_SWAPPED); +} + +static void +ce_page_vpn_dispose (GObject *object) +{ + CEPageVpn *self = CE_PAGE_VPN (object); + + g_clear_object (&self->connection); + g_clear_object (&self->editor); + + G_OBJECT_CLASS (ce_page_vpn_parent_class)->dispose (object); +} + +static const gchar * +ce_page_vpn_get_security_setting (CEPage *page) +{ + return NM_SETTING_VPN_SETTING_NAME; +} + +static const gchar * +ce_page_vpn_get_title (CEPage *page) +{ + return _("Identity"); +} + +static gboolean +ce_page_vpn_validate (CEPage *page, + NMConnection *connection, + GError **error) +{ + CEPageVpn *self = CE_PAGE_VPN (page); + + g_object_set (self->setting_connection, + NM_SETTING_CONNECTION_ID, gtk_editable_get_text (GTK_EDITABLE (self->name_entry)), + NULL); + + if (!nm_setting_verify (NM_SETTING (self->setting_connection), NULL, error)) + return FALSE; + + if (!self->editor) + return TRUE; + + return nm_vpn_editor_update_connection (self->editor, connection, error); +} + +static void +ce_page_vpn_init (CEPageVpn *self) +{ + gtk_widget_init_template (GTK_WIDGET (self)); +} + +static void +ce_page_vpn_class_init (CEPageVpnClass *class) +{ + GObjectClass *object_class = G_OBJECT_CLASS (class); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class); + + object_class->dispose = ce_page_vpn_dispose; + + gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/control-center/network/vpn-page.ui"); + + gtk_widget_class_bind_template_child (widget_class, CEPageVpn, failure_label); + gtk_widget_class_bind_template_child (widget_class, CEPageVpn, name_entry); +} + +static void +ce_page_iface_init (CEPageInterface *iface) +{ + iface->get_security_setting = ce_page_vpn_get_security_setting; + iface->get_title = ce_page_vpn_get_title; + iface->validate = ce_page_vpn_validate; +} + +static void +finish_setup (CEPageVpn *self, gpointer unused, GError *error, gpointer user_data) +{ + const char *vpn_type; + + self->setting_connection = nm_connection_get_setting_connection (self->connection); + self->setting_vpn = nm_connection_get_setting_vpn (self->connection); + vpn_type = nm_setting_vpn_get_service_type (self->setting_vpn); + + self->plugin = vpn_get_plugin_by_service (vpn_type); + if (self->plugin) + load_vpn_plugin (self); + + connect_vpn_page (self); +} + +CEPageVpn * +ce_page_vpn_new (NMConnection *connection) +{ + CEPageVpn *self; + + self = CE_PAGE_VPN (g_object_new (ce_page_vpn_get_type (), NULL)); + + self->connection = g_object_ref (connection); + + g_signal_connect (self, "initialized", G_CALLBACK (finish_setup), NULL); + + return self; +} diff --git a/panels/network/connection-editor/ce-page-vpn.h b/panels/network/connection-editor/ce-page-vpn.h new file mode 100644 index 0000000..f194080 --- /dev/null +++ b/panels/network/connection-editor/ce-page-vpn.h @@ -0,0 +1,33 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2013 Red Hat, Inc. + * + * 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. + */ + +#pragma once + +#include <gtk/gtk.h> +#include <NetworkManager.h> + +G_BEGIN_DECLS + +G_DECLARE_FINAL_TYPE (CEPageVpn, ce_page_vpn, CE, PAGE_VPN, GtkBox) + +CEPageVpn *ce_page_vpn_new (NMConnection *connection); + +G_END_DECLS diff --git a/panels/network/connection-editor/ce-page-wifi.c b/panels/network/connection-editor/ce-page-wifi.c new file mode 100644 index 0000000..5a66292 --- /dev/null +++ b/panels/network/connection-editor/ce-page-wifi.c @@ -0,0 +1,212 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2012 Red Hat, Inc + * + * 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/gi18n.h> +#include <NetworkManager.h> +#include <net/if_arp.h> + +#include "ce-page.h" +#include "ce-page-wifi.h" +#include "ui-helpers.h" + +struct _CEPageWifi +{ + GtkGrid parent; + + GtkComboBoxText *bssid_combo; + GtkComboBoxText *cloned_mac_combo; + GtkComboBoxText *mac_combo; + GtkEntry *ssid_entry; + + NMClient *client; + NMSettingWireless *setting; +}; + +static void ce_page_iface_init (CEPageInterface *); + +G_DEFINE_TYPE_WITH_CODE (CEPageWifi, ce_page_wifi, GTK_TYPE_GRID, + G_IMPLEMENT_INTERFACE (ce_page_get_type (), ce_page_iface_init)) + +static void +connect_wifi_page (CEPageWifi *self) +{ + GBytes *ssid; + g_autofree gchar *utf8_ssid = NULL; + GPtrArray *bssid_array; + gchar **bssid_list; + const char *s_bssid_str; + gchar **mac_list; + const gchar *s_mac_str; + const gchar *cloned_mac; + gint i; + + ssid = nm_setting_wireless_get_ssid (self->setting); + if (ssid) + utf8_ssid = nm_utils_ssid_to_utf8 (g_bytes_get_data (ssid, NULL), g_bytes_get_size (ssid)); + else + utf8_ssid = g_strdup (""); + gtk_editable_set_text (GTK_EDITABLE (self->ssid_entry), utf8_ssid); + + g_signal_connect_object (self->ssid_entry, "changed", G_CALLBACK (ce_page_changed), self, G_CONNECT_SWAPPED); + + bssid_array = g_ptr_array_new (); + for (i = 0; i < nm_setting_wireless_get_num_seen_bssids (self->setting); i++) { + g_ptr_array_add (bssid_array, g_strdup (nm_setting_wireless_get_seen_bssid (self->setting, i))); + } + g_ptr_array_add (bssid_array, NULL); + bssid_list = (gchar **) g_ptr_array_free (bssid_array, FALSE); + s_bssid_str = nm_setting_wireless_get_bssid (self->setting); + ce_page_setup_mac_combo (self->bssid_combo, s_bssid_str, bssid_list); + g_strfreev (bssid_list); + g_signal_connect_object (self->bssid_combo, "changed", G_CALLBACK (ce_page_changed), self, G_CONNECT_SWAPPED); + + mac_list = ce_page_get_mac_list (self->client, NM_TYPE_DEVICE_WIFI, + NM_DEVICE_WIFI_PERMANENT_HW_ADDRESS); + s_mac_str = nm_setting_wireless_get_mac_address (self->setting); + ce_page_setup_mac_combo (self->mac_combo, s_mac_str, mac_list); + g_strfreev (mac_list); + g_signal_connect_object (self->mac_combo, "changed", G_CALLBACK (ce_page_changed), self, G_CONNECT_SWAPPED); + + cloned_mac = nm_setting_wireless_get_cloned_mac_address (self->setting); + ce_page_setup_cloned_mac_combo (self->cloned_mac_combo, cloned_mac); + g_signal_connect_object (self->cloned_mac_combo, "changed", G_CALLBACK (ce_page_changed), self, G_CONNECT_SWAPPED); +} + +static void +ui_to_setting (CEPageWifi *self) +{ + g_autoptr(GBytes) ssid = NULL; + const gchar *utf8_ssid, *bssid; + GtkWidget *entry; + g_autofree gchar *device_mac = NULL; + g_autofree gchar *cloned_mac = NULL; + + utf8_ssid = gtk_editable_get_text (GTK_EDITABLE (self->ssid_entry)); + if (!utf8_ssid || !*utf8_ssid) + ssid = NULL; + else { + ssid = g_bytes_new_static (utf8_ssid, strlen (utf8_ssid)); + } + entry = gtk_combo_box_get_child (GTK_COMBO_BOX (self->bssid_combo)); + bssid = gtk_editable_get_text (GTK_EDITABLE (entry)); + if (*bssid == '\0') + bssid = NULL; + entry = gtk_combo_box_get_child (GTK_COMBO_BOX (self->mac_combo)); + device_mac = ce_page_trim_address (gtk_editable_get_text (GTK_EDITABLE (entry))); + cloned_mac = ce_page_cloned_mac_get (self->cloned_mac_combo); + + g_object_set (self->setting, + NM_SETTING_WIRELESS_SSID, ssid, + NM_SETTING_WIRELESS_BSSID, bssid, + NM_SETTING_WIRELESS_MAC_ADDRESS, device_mac, + NM_SETTING_WIRELESS_CLONED_MAC_ADDRESS, cloned_mac, + NULL); +} + +static const gchar * +ce_page_wifi_get_title (CEPage *page) +{ + return _("Identity"); +} + +static gboolean +ce_page_wifi_class_validate (CEPage *parent, + NMConnection *connection, + GError **error) +{ + CEPageWifi *self = (CEPageWifi *) parent; + GtkWidget *entry; + gboolean ret = TRUE; + + entry = gtk_combo_box_get_child (GTK_COMBO_BOX (self->bssid_combo)); + if (!ce_page_address_is_valid (gtk_editable_get_text (GTK_EDITABLE (entry)))) { + widget_set_error (entry); + ret = FALSE; + } else { + widget_unset_error (entry); + } + + entry = gtk_combo_box_get_child (GTK_COMBO_BOX (self->mac_combo)); + if (!ce_page_address_is_valid (gtk_editable_get_text (GTK_EDITABLE (entry)))) { + widget_set_error (entry); + ret = FALSE; + } else { + widget_unset_error (entry); + } + + if (!ce_page_cloned_mac_combo_valid (self->cloned_mac_combo)) { + widget_set_error (gtk_combo_box_get_child (GTK_COMBO_BOX (self->cloned_mac_combo))); + ret = FALSE; + } else { + widget_unset_error (gtk_combo_box_get_child (GTK_COMBO_BOX (self->cloned_mac_combo))); + } + + if (!ret) + return ret; + + ui_to_setting (CE_PAGE_WIFI (self)); + + return ret; +} + +static void +ce_page_wifi_init (CEPageWifi *self) +{ + gtk_widget_init_template (GTK_WIDGET (self)); +} + +static void +ce_page_wifi_class_init (CEPageWifiClass *klass) +{ + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/control-center/network/wifi-page.ui"); + + gtk_widget_class_bind_template_child (widget_class, CEPageWifi, bssid_combo); + gtk_widget_class_bind_template_child (widget_class, CEPageWifi, cloned_mac_combo); + gtk_widget_class_bind_template_child (widget_class, CEPageWifi, mac_combo); + gtk_widget_class_bind_template_child (widget_class, CEPageWifi, ssid_entry); +} + +static void +ce_page_iface_init (CEPageInterface *iface) +{ + iface->get_title = ce_page_wifi_get_title; + iface->validate = ce_page_wifi_class_validate; +} + +CEPageWifi * +ce_page_wifi_new (NMConnection *connection, + NMClient *client) +{ + CEPageWifi *self; + + self = CE_PAGE_WIFI (g_object_new (ce_page_wifi_get_type (), NULL)); + + self->client = client; + self->setting = nm_connection_get_setting_wireless (connection); + + connect_wifi_page (self); + + return self; +} diff --git a/panels/network/connection-editor/ce-page-wifi.h b/panels/network/connection-editor/ce-page-wifi.h new file mode 100644 index 0000000..8beabad --- /dev/null +++ b/panels/network/connection-editor/ce-page-wifi.h @@ -0,0 +1,32 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2012 Red Hat, Inc. + * + * 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. + */ + +#pragma once + +#include <gtk/gtk.h> +#include <NetworkManager.h> + +G_DECLARE_FINAL_TYPE (CEPageWifi, ce_page_wifi, CE, PAGE_WIFI, GtkGrid) + +CEPageWifi *ce_page_wifi_new (NMConnection *connection, + NMClient *client); + +G_END_DECLS diff --git a/panels/network/connection-editor/ce-page.c b/panels/network/connection-editor/ce-page.c new file mode 100644 index 0000000..b6f0779 --- /dev/null +++ b/panels/network/connection-editor/ce-page.c @@ -0,0 +1,417 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2012 Red Hat, Inc + * + * 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 <string.h> + +#include <net/if_arp.h> +#include <netinet/ether.h> + +#include <NetworkManager.h> + +#include <glib/gi18n.h> + +#include "ce-page.h" + + +G_DEFINE_INTERFACE (CEPage, ce_page, G_TYPE_OBJECT) + +enum { + CHANGED, + INITIALIZED, + LAST_SIGNAL +}; + +static guint signals[LAST_SIGNAL] = { 0 }; + +gboolean +ce_page_validate (CEPage *self, NMConnection *connection, GError **error) +{ + g_return_val_if_fail (CE_IS_PAGE (self), FALSE); + g_return_val_if_fail (NM_IS_CONNECTION (connection), FALSE); + + if (CE_PAGE_GET_IFACE (self)->validate) + return CE_PAGE_GET_IFACE (self)->validate (self, connection, error); + + return TRUE; +} + +const char * +ce_page_get_title (CEPage *self) +{ + g_return_val_if_fail (CE_IS_PAGE (self), NULL); + + return CE_PAGE_GET_IFACE (self)->get_title (self); +} + +void +ce_page_changed (CEPage *self) +{ + g_return_if_fail (CE_IS_PAGE (self)); + + g_signal_emit (self, signals[CHANGED], 0); +} + +static void +ce_page_default_init (CEPageInterface *iface) +{ + signals[CHANGED] = + g_signal_new ("changed", + G_TYPE_FROM_INTERFACE (iface), + G_SIGNAL_RUN_FIRST, + 0, + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + signals[INITIALIZED] = + g_signal_new ("initialized", + G_TYPE_FROM_INTERFACE (iface), + G_SIGNAL_RUN_FIRST, + 0, + NULL, NULL, + g_cclosure_marshal_VOID__POINTER, + G_TYPE_NONE, 1, G_TYPE_POINTER); +} + +static void +emit_initialized (CEPage *self, + GError *error) +{ + g_signal_emit (self, signals[INITIALIZED], 0, error); + g_clear_error (&error); +} + +void +ce_page_complete_init (CEPage *self, + NMConnection *connection, + const gchar *setting_name, + GVariant *secrets, + GError *error) +{ + g_autoptr(GError) update_error = NULL; + g_autoptr(GVariant) setting_dict = NULL; + gboolean ignore_error = FALSE; + + g_return_if_fail (self != NULL); + g_return_if_fail (CE_IS_PAGE (self)); + + if (error) { + ignore_error = g_error_matches (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_SETTING_NOT_FOUND) || + g_error_matches (error, NM_SECRET_AGENT_ERROR, NM_SECRET_AGENT_ERROR_NO_SECRETS); + } + + /* Ignore missing settings errors */ + if (error && !ignore_error) { + emit_initialized (self, error); + return; + } else if (!setting_name || !secrets || g_variant_n_children (secrets) == 0) { + /* Success, no secrets */ + emit_initialized (self, NULL); + return; + } + + g_assert (setting_name); + g_assert (secrets); + + setting_dict = g_variant_lookup_value (secrets, setting_name, NM_VARIANT_TYPE_SETTING); + if (!setting_dict) { + /* Success, no secrets */ + emit_initialized (self, NULL); + return; + } + + /* Update the connection with the new secrets */ + if (!nm_connection_update_secrets (connection, + setting_name, + secrets, + &update_error)) + g_warning ("Couldn't update secrets: %s", update_error->message); + + emit_initialized (self, NULL); +} + +gchar ** +ce_page_get_mac_list (NMClient *client, + GType device_type, + const gchar *mac_property) +{ + const GPtrArray *devices; + GPtrArray *macs; + int i; + + macs = g_ptr_array_new (); + devices = nm_client_get_devices (client); + for (i = 0; devices && (i < devices->len); i++) { + NMDevice *dev = g_ptr_array_index (devices, i); + const char *iface; + g_autofree gchar *mac = NULL; + g_autofree gchar *item = NULL; + + if (!G_TYPE_CHECK_INSTANCE_TYPE (dev, device_type)) + continue; + + g_object_get (G_OBJECT (dev), mac_property, &mac, NULL); + iface = nm_device_get_iface (NM_DEVICE (dev)); + item = g_strdup_printf ("%s (%s)", mac, iface); + g_ptr_array_add (macs, g_steal_pointer (&item)); + } + + g_ptr_array_add (macs, NULL); + return (char **)g_ptr_array_free (macs, FALSE); +} + +void +ce_page_setup_mac_combo (GtkComboBoxText *combo, + const gchar *current_mac, + gchar **mac_list) +{ + gchar **m, *active_mac = NULL; + gint current_mac_len; + GtkWidget *entry; + + if (current_mac) + current_mac_len = strlen (current_mac); + else + current_mac_len = -1; + + for (m= mac_list; m && *m; m++) { + gtk_combo_box_text_append_text (combo, *m); + if (current_mac && + g_ascii_strncasecmp (*m, current_mac, current_mac_len) == 0 + && ((*m)[current_mac_len] == '\0' || (*m)[current_mac_len] == ' ')) + active_mac = *m; + } + + if (current_mac) { + if (!active_mac) { + gtk_combo_box_text_prepend_text (combo, current_mac); + } + + entry = gtk_combo_box_get_child (GTK_COMBO_BOX (combo)); + if (entry) + gtk_editable_set_text (GTK_EDITABLE (entry), active_mac ? active_mac : current_mac); + } +} + +gchar * +ce_page_trim_address (const gchar *addr) +{ + char *space; + + if (!addr || *addr == '\0') + return NULL; + + space = strchr (addr, ' '); + if (space != NULL) + return g_strndup (addr, space - addr); + return g_strdup (addr); +} + +void +ce_page_setup_cloned_mac_combo (GtkComboBoxText *combo, const char *current) +{ + GtkWidget *entry; + static const char *entries[][2] = { { "preserve", N_("Preserve") }, + { "permanent", N_("Permanent") }, + { "random", N_("Random") }, + { "stable", N_("Stable") } }; + int i, active = -1; + + gtk_widget_set_tooltip_text (GTK_WIDGET (combo), + _("The MAC address entered here will be used as hardware address for " + "the network device this connection is activated on. This feature is " + "known as MAC cloning or spoofing. Example: 00:11:22:33:44:55")); + + gtk_combo_box_text_remove_all (combo); + + for (i = 0; i < G_N_ELEMENTS (entries); i++) { + gtk_combo_box_text_append (combo, entries[i][0], _(entries[i][1])); + if (g_strcmp0 (current, entries[i][0]) == 0) + active = i; + } + + if (active != -1) { + gtk_combo_box_set_active (GTK_COMBO_BOX (combo), active); + } else if (current && current[0]) { + entry = gtk_combo_box_get_child (GTK_COMBO_BOX (combo)); + g_assert (entry); + gtk_editable_set_text (GTK_EDITABLE (entry), current); + } +} + +char * +ce_page_cloned_mac_get (GtkComboBoxText *combo) +{ + g_autofree gchar *active_text = NULL; + const char *id; + + id = gtk_combo_box_get_active_id (GTK_COMBO_BOX (combo)); + if (id) + return g_strdup (id); + + active_text = gtk_combo_box_text_get_active_text (combo); + + if (active_text[0] == '\0') + return NULL; + + return g_steal_pointer (&active_text); +} + +gboolean +ce_page_address_is_valid (const gchar *addr) +{ + guint8 invalid_addr[4][ETH_ALEN] = { + {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x44, 0x44, 0x44, 0x44, 0x44, 0x44}, + {0x00, 0x30, 0xb4, 0x00, 0x00, 0x00}, /* prism54 dummy MAC */ + }; + guint8 addr_bin[ETH_ALEN]; + g_autofree gchar *trimmed_addr = NULL; + guint i; + + if (!addr || *addr == '\0') + return TRUE; + + trimmed_addr = ce_page_trim_address (addr); + + if (!nm_utils_hwaddr_valid (trimmed_addr, -1)) + return FALSE; + + if (!nm_utils_hwaddr_aton (trimmed_addr, addr_bin, ETH_ALEN)) + return FALSE; + + /* Check for multicast address */ + if ((((guint8 *) addr_bin)[0]) & 0x01) + return FALSE; + + for (i = 0; i < G_N_ELEMENTS (invalid_addr); i++) { + if (nm_utils_hwaddr_matches (addr_bin, ETH_ALEN, invalid_addr[i], ETH_ALEN)) + return FALSE; + } + + return TRUE; +} + +gboolean +ce_page_cloned_mac_combo_valid (GtkComboBoxText *combo) +{ + g_autofree gchar *active_text = NULL; + + if (gtk_combo_box_get_active (GTK_COMBO_BOX (combo)) != -1) + return TRUE; + + active_text = gtk_combo_box_text_get_active_text (combo); + + return active_text[0] == '\0' || ce_page_address_is_valid (active_text); +} + +const gchar * +ce_page_get_security_setting (CEPage *self) +{ + if (CE_PAGE_GET_IFACE (self)->get_security_setting) + return CE_PAGE_GET_IFACE (self)->get_security_setting (self); + + return NULL; +} + +gint +ce_get_property_default (NMSetting *setting, const gchar *property_name) +{ + GParamSpec *spec; + GValue value = { 0, }; + + spec = g_object_class_find_property (G_OBJECT_GET_CLASS (setting), property_name); + g_return_val_if_fail (spec != NULL, -1); + + g_value_init (&value, spec->value_type); + g_param_value_set_default (spec, &value); + + if (G_VALUE_HOLDS_CHAR (&value)) + return (int) g_value_get_schar (&value); + else if (G_VALUE_HOLDS_INT (&value)) + return g_value_get_int (&value); + else if (G_VALUE_HOLDS_INT64 (&value)) + return (int) g_value_get_int64 (&value); + else if (G_VALUE_HOLDS_LONG (&value)) + return (int) g_value_get_long (&value); + else if (G_VALUE_HOLDS_UINT (&value)) + return (int) g_value_get_uint (&value); + else if (G_VALUE_HOLDS_UINT64 (&value)) + return (int) g_value_get_uint64 (&value); + else if (G_VALUE_HOLDS_ULONG (&value)) + return (int) g_value_get_ulong (&value); + else if (G_VALUE_HOLDS_UCHAR (&value)) + return (int) g_value_get_uchar (&value); + g_return_val_if_fail (FALSE, 0); + return 0; +} + +gchar * +ce_page_get_next_available_name (const GPtrArray *connections, + NameFormat format, + const gchar *type_name) +{ + GSList *names = NULL, *l; + gchar *cname = NULL; + gint i = 0; + guint con_idx; + + for (con_idx = 0; con_idx < connections->len; con_idx++) { + NMConnection *connection = g_ptr_array_index (connections, con_idx); + const gchar *id; + + id = nm_connection_get_id (connection); + g_assert (id); + names = g_slist_append (names, (gpointer) id); + } + + /* Find the next available unique connection name */ + while (!cname && (i++ < 10000)) { + g_autofree gchar *temp = NULL; + gboolean found = FALSE; + + switch (format) { + case NAME_FORMAT_TYPE: + temp = g_strdup_printf ("%s %d", type_name, i); + break; + case NAME_FORMAT_PROFILE: + temp = g_strdup_printf (_("Profile %d"), i); + break; + default: + g_assert_not_reached (); + } + + for (l = names; l; l = l->next) { + if (!strcmp (l->data, temp)) { + found = TRUE; + break; + } + } + if (!found) + cname = g_steal_pointer (&temp); + } + g_slist_free (names); + + return cname; +} diff --git a/panels/network/connection-editor/ce-page.h b/panels/network/connection-editor/ce-page.h new file mode 100644 index 0000000..a180afb --- /dev/null +++ b/panels/network/connection-editor/ce-page.h @@ -0,0 +1,79 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2012 Red Hat, Inc. + * + * 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. + */ + +#pragma once + +#include <glib-object.h> + +#include <NetworkManager.h> + +#include <gtk/gtk.h> + +G_BEGIN_DECLS + +G_DECLARE_INTERFACE (CEPage, ce_page, CE, PAGE, GObject) + +struct _CEPageInterface +{ + GTypeInterface g_iface; + + gboolean (*validate) (CEPage *page, NMConnection *connection, GError **error); + const gchar *(*get_title) (CEPage *page); + const gchar *(*get_security_setting) (CEPage *page); +}; + +const gchar *ce_page_get_title (CEPage *page); +const gchar *ce_page_get_security_setting (CEPage *page); +gboolean ce_page_validate (CEPage *page, + NMConnection *connection, + GError **error); +void ce_page_changed (CEPage *page); +void ce_page_complete_init (CEPage *page, + NMConnection *connection, + const gchar *setting_name, + GVariant *variant, + GError *error); + +gchar **ce_page_get_mac_list (NMClient *client, + GType device_type, + const gchar *mac_property); +void ce_page_setup_mac_combo (GtkComboBoxText *combo, + const gchar *current_mac, + gchar **mac_list); +void ce_page_setup_cloned_mac_combo (GtkComboBoxText *combo, + const char *current); +gint ce_get_property_default (NMSetting *setting, + const gchar *property_name); +gboolean ce_page_address_is_valid (const gchar *addr); +gchar *ce_page_trim_address (const gchar *addr); +char *ce_page_cloned_mac_get (GtkComboBoxText *combo); +gboolean ce_page_cloned_mac_combo_valid (GtkComboBoxText *combo); + +typedef enum { + NAME_FORMAT_TYPE, + NAME_FORMAT_PROFILE +} NameFormat; + +gchar * ce_page_get_next_available_name (const GPtrArray *connections, + NameFormat format, + const gchar *type_name); + +G_END_DECLS diff --git a/panels/network/connection-editor/connection-editor.gresource.xml b/panels/network/connection-editor/connection-editor.gresource.xml new file mode 100644 index 0000000..3d06f5a --- /dev/null +++ b/panels/network/connection-editor/connection-editor.gresource.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="UTF-8"?> +<gresources> + <gresource prefix="/org/gnome/control-center/network"> + <file preprocess="xml-stripblanks">8021x-security-page.ui</file> + <file preprocess="xml-stripblanks">connection-editor.ui</file> + <file preprocess="xml-stripblanks">details-page.ui</file> + <file preprocess="xml-stripblanks">ethernet-page.ui</file> + <file preprocess="xml-stripblanks">ip4-page.ui</file> + <file preprocess="xml-stripblanks">ip6-page.ui</file> + <file preprocess="xml-stripblanks">security-page.ui</file> + <file preprocess="xml-stripblanks">vpn-page.ui</file> + <file preprocess="xml-stripblanks">wifi-page.ui</file> + </gresource> +</gresources> diff --git a/panels/network/connection-editor/connection-editor.ui b/panels/network/connection-editor/connection-editor.ui new file mode 100644 index 0000000..18031e8 --- /dev/null +++ b/panels/network/connection-editor/connection-editor.ui @@ -0,0 +1,75 @@ +<?xml version="1.0" encoding="UTF-8"?> +<interface> + <!-- interface-requires gtk+ 3.0 --> + <template class="NetConnectionEditor" parent="GtkDialog"> + <property name="resizable">False</property> + <property name="modal">True</property> + <property name="default_width">500</property> + <property name="default_height">600</property> + <!-- This doesn't seem to work for a template, so it is also hardcoded. --> + <property name="use_header_bar">1</property> + <signal name="close-request" handler="close_request_cb" swapped="yes"/> + <child type="action"> + <object class="GtkButton" id="cancel_button"> + <property name="label" translatable="yes">_Cancel</property> + <property name="use_underline">True</property> + <signal name="clicked" handler="cancel_clicked_cb" object="NetConnectionEditor" swapped="yes"/> + </object> + </child> + <child type="action"> + <object class="GtkButton" id="apply_button"> + <property name="label" translatable="yes">_Apply</property> + <property name="use_underline">True</property> + <signal name="clicked" handler="apply_clicked_cb" object="NetConnectionEditor" swapped="yes"/> + </object> + </child> + <child> + <object class="GtkBox"> + <property name="orientation">vertical</property> + <property name="spacing">0</property> + <child> + <object class="GtkStack" id="toplevel_stack"> + <property name="hexpand">True</property> + <property name="vexpand">True</property> + <child> + <object class="GtkSpinner" id="spinner"> + <property name="halign">center</property> + <property name="valign">center</property> + <property name="spinning">True</property> + </object> + </child> + <child> + <object class="GtkNotebook" id="notebook"> + <property name="show_border">False</property> + </object> + </child> + <child> + <object class="GtkBox" id="add_connection_box"> + <property name="vexpand">True</property> + <child> + <object class="GtkBox"> + <property name="hexpand">True</property> + <property name="vexpand">True</property> + <property name="orientation">vertical</property> + <child> + <object class="AdwBin" id="add_connection_frame"> + <property name="hexpand">True</property> + <property name="vexpand">True</property> + <property name="width_request">300</property> + <property name="valign">start</property> + </object> + </child> + </object> + </child> + </object> + </child> + </object> + </child> + </object> + </child> + <action-widgets> + <action-widget response="cancel">cancel_button</action-widget> + <action-widget response="apply" default="true">apply_button</action-widget> + </action-widgets> + </template> +</interface> diff --git a/panels/network/connection-editor/details-page.ui b/panels/network/connection-editor/details-page.ui new file mode 100644 index 0000000..c8e4595 --- /dev/null +++ b/panels/network/connection-editor/details-page.ui @@ -0,0 +1,458 @@ +<?xml version="1.0" encoding="UTF-8"?> +<interface> + <!-- interface-requires gtk+ 3.0 --> + <template class="CEPageDetails" parent="GtkGrid"> + <property name="margin_start">24</property> + <property name="margin_end">24</property> + <property name="margin_top">24</property> + <property name="margin_bottom">24</property> + <property name="row_spacing">12</property> + <property name="column_spacing">12</property> + <child> + <object class="GtkLabel" id="strength_heading_label"> + <property name="xalign">1</property> + <property name="label" translatable="yes">Signal Strength</property> + <property name="mnemonic_widget">strength_label</property> + <layout> + <property name="column">0</property> + <property name="row">0</property> + <property name="column-span">1</property> + <property name="row-span">1</property> + </layout> + <style> + <class name="dim-label"/> + </style> + </object> + </child> + <child> + <object class="GtkLabel" id="strength_label"> + <property name="hexpand">True</property> + <property name="xalign">0</property> + <property name="label">Weak</property> + <property name="selectable">True</property> + <property name="max-width-chars">50</property> + <property name="ellipsize">end</property> + <layout> + <property name="column">1</property> + <property name="row">0</property> + <property name="column-span">1</property> + <property name="row-span">1</property> + </layout> + </object> + </child> + <child> + <object class="GtkLabel" id="speed_heading_label"> + <property name="xalign">1</property> + <property name="label" translatable="yes">Link speed</property> + <property name="mnemonic_widget">speed_label</property> + <layout> + <property name="column">0</property> + <property name="row">1</property> + <property name="column-span">1</property> + <property name="row-span">1</property> + </layout> + <style> + <class name="dim-label"/> + </style> + </object> + </child> + <child> + <object class="GtkLabel" id="speed_label"> + <property name="xalign">0</property> + <property name="label">1Mb/sec</property> + <property name="selectable">True</property> + <property name="hexpand">True</property> + <property name="max-width-chars">50</property> + <property name="ellipsize">end</property> + <layout> + <property name="column">1</property> + <property name="row">1</property> + <property name="column-span">1</property> + <property name="row-span">1</property> + </layout> + </object> + </child> + <child> + <object class="GtkLabel" id="security_heading_label"> + <property name="xalign">1</property> + <property name="label" translatable="yes">Security</property> + <property name="mnemonic_widget">security_label</property> + <layout> + <property name="column">0</property> + <property name="row">2</property> + <property name="column-span">1</property> + <property name="row-span">1</property> + </layout> + <style> + <class name="dim-label"/> + </style> + </object> + </child> + <child> + <object class="GtkLabel" id="ipv4_heading_label"> + <property name="xalign">1</property> + <property name="label" translatable="yes">IPv4 Address</property> + <property name="mnemonic_widget">ipv4_label</property> + <layout> + <property name="column">0</property> + <property name="row">3</property> + <property name="column-span">1</property> + <property name="row-span">1</property> + </layout> + <style> + <class name="dim-label"/> + </style> + </object> + </child> + <child> + <object class="GtkLabel" id="ipv6_heading_label"> + <property name="xalign">1</property> + <property name="label" translatable="yes">IPv6 Address</property> + <property name="mnemonic_widget">ipv6_label</property> + <layout> + <property name="column">0</property> + <property name="row">4</property> + <property name="column-span">1</property> + <property name="row-span">1</property> + </layout> + <style> + <class name="dim-label"/> + </style> + </object> + </child> + <child> + <object class="GtkLabel" id="mac_heading_label"> + <property name="xalign">1</property> + <property name="label" translatable="yes">Hardware Address</property> + <property name="mnemonic_widget">mac_label</property> + <layout> + <property name="column">0</property> + <property name="row">5</property> + <property name="column-span">1</property> + <property name="row-span">1</property> + </layout> + <style> + <class name="dim-label"/> + </style> + </object> + </child> + <child> + <object class="GtkLabel" id="freq_heading_label"> + <property name="xalign">1</property> + <property name="label" translatable="yes">Supported Frequencies</property> + <property name="mnemonic_widget">freq_label</property> + <layout> + <property name="column">0</property> + <property name="row">6</property> + <property name="column-span">1</property> + <property name="row-span">1</property> + </layout> + <style> + <class name="dim-label"/> + </style> + </object> + </child> + <child> + <object class="GtkLabel" id="route_heading_label"> + <property name="xalign">1</property> + <property name="label" translatable="yes">Default Route</property> + <property name="mnemonic_widget">route_label</property> + <layout> + <property name="column">0</property> + <property name="row">7</property> + <property name="column-span">1</property> + <property name="row-span">1</property> + </layout> + <style> + <class name="dim-label"/> + </style> + </object> + </child> + <child> + <object class="GtkLabel" id="dns4_heading_label"> + <property name="xalign">1</property> + <property name="yalign">0</property> + <property name="label" translatable="yes">DNS</property> + <property name="mnemonic_widget">dns4_label</property> + <layout> + <property name="column">0</property> + <property name="row">8</property> + <property name="column-span">1</property> + <property name="row-span">1</property> + </layout> + <style> + <class name="dim-label"/> + </style> + </object> + </child> + <child> + <object class="GtkLabel" id="dns6_heading_label"> + <property name="xalign">1</property> + <property name="yalign">0</property> + <property name="label" translatable="yes">DNS</property> + <property name="mnemonic_widget">dns6_label</property> + <layout> + <property name="column">0</property> + <property name="row">9</property> + <property name="column-span">1</property> + <property name="row-span">1</property> + </layout> + <style> + <class name="dim-label"/> + </style> + </object> + </child> + <child> + <object class="GtkLabel" id="last_used_heading_label"> + <property name="xalign">1</property> + <property name="label" translatable="yes">Last Used</property> + <property name="mnemonic_widget">last_used_label</property> + <layout> + <property name="column">0</property> + <property name="row">10</property> + <property name="column-span">1</property> + <property name="row-span">1</property> + </layout> + <style> + <class name="dim-label"/> + </style> + </object> + </child> + <child> + <object class="GtkLabel" id="security_label"> + <property name="xalign">0</property> + <property name="label">WPA</property> + <property name="selectable">True</property> + <property name="hexpand">True</property> + <property name="max-width-chars">50</property> + <property name="ellipsize">end</property> + <layout> + <property name="column">1</property> + <property name="row">2</property> + <property name="column-span">1</property> + <property name="row-span">1</property> + </layout> + </object> + </child> + <child> + <object class="GtkLabel" id="ipv4_label"> + <property name="xalign">0</property> + <property name="label">127.0.0.1</property> + <property name="selectable">True</property> + <property name="hexpand">True</property> + <property name="max-width-chars">50</property> + <property name="ellipsize">end</property> + <layout> + <property name="column">1</property> + <property name="row">3</property> + <property name="column-span">1</property> + <property name="row-span">1</property> + </layout> + </object> + </child> + <child> + <object class="GtkLabel" id="ipv6_label"> + <property name="xalign">0</property> + <property name="label">::1</property> + <property name="selectable">True</property> + <property name="hexpand">True</property> + <property name="max-width-chars">50</property> + <property name="ellipsize">end</property> + <layout> + <property name="column">1</property> + <property name="row">4</property> + <property name="column-span">1</property> + <property name="row-span">1</property> + </layout> + </object> + </child> + <child> + <object class="GtkLabel" id="mac_label"> + <property name="xalign">0</property> + <property name="label">AA:BB:CC:DD:55:66:77:88</property> + <property name="selectable">True</property> + <property name="hexpand">True</property> + <property name="max-width-chars">50</property> + <property name="ellipsize">end</property> + <layout> + <property name="column">1</property> + <property name="row">5</property> + <property name="column-span">1</property> + <property name="row-span">1</property> + </layout> + </object> + </child> + <child> + <object class="GtkLabel" id="freq_label"> + <property name="xalign">0</property> + <property name="label">2.4 GHz / 5 GHz</property> + <property name="selectable">True</property> + <property name="hexpand">True</property> + <layout> + <property name="column">1</property> + <property name="row">6</property> + <property name="column-span">1</property> + <property name="row-span">1</property> + </layout> + </object> + </child> + <child> + <object class="GtkLabel" id="route_label"> + <property name="xalign">0</property> + <property name="label">127.0.0.1</property> + <property name="selectable">True</property> + <property name="hexpand">True</property> + <property name="max-width-chars">50</property> + <property name="ellipsize">end</property> + <layout> + <property name="column">1</property> + <property name="row">7</property> + <property name="column-span">1</property> + <property name="row-span">1</property> + </layout> + </object> + </child> + <child> + <object class="GtkLabel" id="dns4_label"> + <property name="xalign">0</property> + <property name="yalign">0</property> + <property name="label">127.0.0.1</property> + <property name="wrap">True</property> + <property name="selectable">True</property> + <property name="hexpand">True</property> + <property name="max-width-chars">50</property> + <property name="ellipsize">end</property> + <layout> + <property name="column">1</property> + <property name="row">8</property> + <property name="column-span">1</property> + <property name="row-span">1</property> + </layout> + </object> + </child> + <child> + <object class="GtkLabel" id="dns6_label"> + <property name="xalign">0</property> + <property name="yalign">0</property> + <property name="label">::1</property> + <property name="wrap">True</property> + <property name="selectable">True</property> + <property name="hexpand">True</property> + <property name="max-width-chars">50</property> + <property name="ellipsize">end</property> + <layout> + <property name="column">1</property> + <property name="row">9</property> + <property name="column-span">1</property> + <property name="row-span">1</property> + </layout> + </object> + </child> + <child> + <object class="GtkLabel" id="last_used_label"> + <property name="xalign">0</property> + <property name="label">today</property> + <property name="selectable">True</property> + <property name="hexpand">True</property> + <property name="max-width-chars">50</property> + <property name="ellipsize">end</property> + <layout> + <property name="column">1</property> + <property name="row">10</property> + <property name="column-span">1</property> + <property name="row-span">1</property> + </layout> + </object> + </child> + <child> + <object class="GtkCheckButton" id="auto_connect_check"> + <property name="label" translatable="yes">Connect _automatically</property> + <property name="valign">end</property> + <property name="use_underline">True</property> + <property name="margin_top">12</property> + <layout> + <property name="column">0</property> + <property name="row">11</property> + <property name="column-span">2</property> + <property name="row-span">1</property> + </layout> + </object> + </child> + <child> + <object class="GtkCheckButton" id="all_user_check"> + <property name="label" translatable="yes">Make available to _other users</property> + <property name="use_underline">True</property> + <layout> + <property name="column">0</property> + <property name="row">12</property> + <property name="column-span">2</property> + <property name="row-span">1</property> + </layout> + </object> + </child> + + <!-- "Restrict Data Usage" section --> + <child> + <object class="GtkBox" id="restrict_data_check_container"> + <layout> + <property name="column">0</property> + <property name="row">13</property> + <property name="column-span">2</property> + <property name="row-span">1</property> + </layout> + <child> + <object class="GtkCheckButton" id="restrict_data_check"> + <property name="margin_bottom">12</property> + </object> + </child> + <child> + <object class="GtkBox"> + <property name="orientation">vertical</property> + <child> + <object class="GtkLabel"> + <property name="xalign">0</property> + <property name="label" translatable="yes">_Metered connection: has data limits or can incur charges</property> + <property name="hexpand">True</property> + <property name="use_underline">True</property> + <property name="mnemonic_widget">restrict_data_check</property> + </object> + </child> + <child> + <object class="GtkLabel"> + <property name="xalign">0</property> + <property name="label" translatable="yes">Software updates and other large downloads will not be started automatically.</property> + <property name="wrap">True</property> + <property name="max_width_chars">60</property> + <style> + <class name="dim-label" /> + </style> + <attributes> + <attribute name="scale" value="0.8" /> + </attributes> + </object> + </child> + </object> + </child> + </object> + </child> + + <child> + <object class="GtkButton" id="forget_button"> + <property name="use_underline">True</property> + <property name="hexpand">True</property> + <property name="vexpand">True</property> + <property name="halign">end</property> + <property name="valign">end</property> + <layout> + <property name="column">0</property> + <property name="row">14</property> + <property name="column-span">2</property> + <property name="row-span">1</property> + </layout> + <style> + <class name="destructive-action" /> + </style> + </object> + </child> + </template> +</interface> diff --git a/panels/network/connection-editor/ethernet-page.ui b/panels/network/connection-editor/ethernet-page.ui new file mode 100644 index 0000000..ad4f2cb --- /dev/null +++ b/panels/network/connection-editor/ethernet-page.ui @@ -0,0 +1,121 @@ +<?xml version="1.0" encoding="UTF-8"?> +<interface> + <!-- interface-requires gtk+ 3.0 --> + <object class="GtkAdjustment" id="mtu_adjustment"> + <property name="upper">10000</property> + <property name="step_increment">1</property> + <property name="page_increment">10</property> + </object> + <template class="CEPageEthernet" parent="GtkGrid"> + <property name="margin_start">50</property> + <property name="margin_end">50</property> + <property name="margin_top">12</property> + <property name="margin_bottom">12</property> + <property name="hexpand">True</property> + <property name="vexpand">True</property> + <property name="row_spacing">10</property> + <property name="column_spacing">6</property> + <child> + <object class="GtkLabel"> + <property name="xalign">1</property> + <property name="label" translatable="yes">_Name</property> + <property name="use_underline">True</property> + <property name="mnemonic_widget">name_entry</property> + <layout> + <property name="column">0</property> + <property name="row">0</property> + </layout> + </object> + </child> + <child> + <object class="GtkEntry" id="name_entry"> + <property name="invisible_char">●</property> + <layout> + <property name="column">1</property> + <property name="row">0</property> + </layout> + </object> + </child> + <child> + <object class="GtkLabel"> + <property name="xalign">1</property> + <property name="label" translatable="yes">_MAC Address</property> + <property name="use_underline">True</property> + <property name="mnemonic_widget">mac_combo</property> + <layout> + <property name="column">0</property> + <property name="row">1</property> + </layout> + </object> + </child> + <child> + <object class="GtkComboBoxText" id="mac_combo"> + <property name="has_entry">True</property> + <property name="entry_text_column">0</property> + <property name="id_column">1</property> + <layout> + <property name="column">1</property> + <property name="row">1</property> + </layout> + </object> + </child> + <child> + <object class="GtkComboBoxText" id="cloned_mac_combo"> + <property name="has_entry">True</property> + <property name="hexpand">True</property> + <property name="active_id">0</property> + <layout> + <property name="column">1</property> + <property name="row">2</property> + </layout> + <child internal-child="entry"> + <object class="GtkEntry"> + </object> + </child> + </object> + </child> + <child> + <object class="GtkLabel"> + <property name="xalign">1</property> + <property name="label" translatable="yes">M_TU</property> + <property name="use_underline">True</property> + <property name="mnemonic_widget">mtu_spin</property> + <layout> + <property name="column">0</property> + <property name="row">3</property> + </layout> + </object> + </child> + <child> + <object class="GtkLabel"> + <property name="valign">center</property> + <property name="xalign">1</property> + <property name="label" translatable="yes">_Cloned Address</property> + <property name="use_underline">True</property> + <property name="mnemonic_widget">cloned_mac_combo</property> + <layout> + <property name="column">0</property> + <property name="row">2</property> + </layout> + </object> + </child> + <child> + <object class="GtkLabel" id="mtu_label"> + <property name="label" translatable="yes">bytes</property> + <layout> + <property name="column">2</property> + <property name="row">3</property> + </layout> + </object> + </child> + <child> + <object class="GtkSpinButton" id="mtu_spin"> + <property name="adjustment">mtu_adjustment</property> + <layout> + <property name="column">1</property> + <property name="row">3</property> + </layout> + </object> + </child> + </template> +</interface> diff --git a/panels/network/connection-editor/ip4-page.ui b/panels/network/connection-editor/ip4-page.ui new file mode 100644 index 0000000..a2210ea --- /dev/null +++ b/panels/network/connection-editor/ip4-page.ui @@ -0,0 +1,339 @@ +<?xml version="1.0" encoding="UTF-8"?> +<interface> + <!-- interface-requires gtk+ 3.0 --> + <template class="CEPageIP4" parent="AdwBin"> + <child> + <object class="GtkScrolledWindow"> + <property name="hscrollbar_policy">never</property> + <child> + <object class="GtkViewport"> + <child> + <object class="GtkGrid" id="main_box"> + <property name="margin_start">24</property> + <property name="margin_end">24</property> + <property name="margin_top">24</property> + <property name="margin_bottom">24</property> + <property name="orientation">vertical</property> + <property name="row-spacing">6</property> + <property name="column-spacing">6</property> + <child> + <object class="GtkLabel"> + <property name="label" translatable="yes">IPv_4 Method</property> + <property name="use_underline">True</property> + <property name="mnemonic_widget">disabled_radio</property> + <property name="xalign">0.0</property> + <layout> + <property name="row">0</property> + <property name="column">0</property> + </layout> + <attributes> + <attribute name="weight" value="bold"/> + </attributes> + </object> + </child> + <child> + <object class="GtkCheckButton" id="automatic_radio"> + <property name="label" translatable="yes">Automatic (DHCP)</property> + <layout> + <property name="row">0</property> + <property name="column">1</property> + </layout> + </object> + </child> + <child> + <object class="GtkCheckButton" id="local_radio"> + <property name="label" translatable="yes">Link-Local Only</property> + <property name="group">automatic_radio</property> + <layout> + <property name="row">0</property> + <property name="column">2</property> + </layout> + </object> + </child> + <child> + <object class="GtkCheckButton" id="manual_radio"> + <property name="label" translatable="yes">Manual</property> + <property name="group">automatic_radio</property> + <layout> + <property name="row">1</property> + <property name="column">1</property> + </layout> + </object> + </child> + <child> + <object class="GtkCheckButton" id="disabled_radio"> + <property name="label" translatable="yes">Disable</property> + <property name="group">automatic_radio</property> + <layout> + <property name="row">1</property> + <property name="column">2</property> + </layout> + </object> + </child> + <child> + <object class="GtkCheckButton" id="shared_radio"> + <property name="label" translatable="yes">Shared to other computers</property> + <property name="group">automatic_radio</property> + <layout> + <property name="row">2</property> + <property name="column">1</property> + </layout> + </object> + </child> + <child> + <object class="GtkBox" id="content_box"> + <property name="orientation">vertical</property> + <property name="spacing">6</property> + <layout> + <property name="row">3</property> + <property name="column">0</property> + <property name="column-span">3</property> + </layout> + <child> + <object class="GtkBox" id="address_box"> + <property name="orientation">vertical</property> + <child> + <object class="GtkLabel"> + <property name="xalign">0</property> + <property name="label" translatable="yes">Addresses</property> + <property name="margin_top">24</property> + <property name="margin_bottom">8</property> + <attributes> + <attribute name="weight" value="bold"/> + </attributes> + </object> + </child> + <child> + <object class="GtkBox"> + <property name="orientation">horizontal</property> + <child> + <object class="GtkLabel" id="address_address_label"> + <property name="hexpand">True</property> + <property name="label" translatable="yes">Address</property> + <style> + <class name="dim-label" /> + </style> + <attributes> + <attribute name="scale" value="0.8"/> + </attributes> + </object> + </child> + <child> + <object class="GtkLabel" id="address_netmask_label"> + <property name="hexpand">True</property> + <property name="label" translatable="yes">Netmask</property> + <style> + <class name="dim-label" /> + </style> + <attributes> + <attribute name="scale" value="0.8"/> + </attributes> + </object> + </child> + <child> + <object class="GtkLabel" id="address_gateway_label"> + <property name="hexpand">True</property> + <property name="label" translatable="yes">Gateway</property> + <style> + <class name="dim-label" /> + </style> + <attributes> + <attribute name="scale" value="0.8"/> + </attributes> + </object> + </child> + + <!-- This invisible box is used to add some width in the + end of the header row, assuming the space used by the + delete button in the rows --> + <child> + <object class="GtkBox" id="address_stub_box"> + </object> + </child> + </object> + </child> + </object> + </child> + <child> + <object class="GtkBox"> + <property name="margin_top">24</property> + <property name="spacing">6</property> + <child> + <object class="GtkLabel" id="dns4_label"> + <property name="hexpand">True</property> + <property name="xalign">0</property> + <property name="label" translatable="yes">DNS</property> + <attributes> + <attribute name="weight" value="bold"/> + </attributes> + </object> + </child> + <child> + <object class="GtkLabel"> + <property name="hexpand">True</property> + <property name="xalign">1</property> + <property name="label" translatable="yes">Automatic</property> + </object> + </child> + <child> + <object class="GtkSwitch" id="auto_dns_switch"> + <property name="halign">end</property> + <property name="valign">center</property> + <accessibility> + <property name="label" translatable="yes">Automatic DNS</property> + </accessibility> + </object> + </child> + </object> + </child> + <child> + <object class="GtkEntry" id="dns_entry"> + <accessibility> + <property name="label" translatable="yes">DNS server address(es)</property> + <relation name="described-by">dns_multiple_help</relation> + </accessibility> + </object> + </child> + <child> + <object class="GtkLabel" id="dns_multiple_help"> + <property name="xalign">0</property> + <property name="label" translatable="yes">Separate IP addresses with commas</property> + <style> + <class name="dim-label" /> + </style> + <attributes> + <attribute name="scale" value="0.8"/> + </attributes> + </object> + </child> + <child> + <object class="GtkBox"> + <property name="margin_top">24</property> + <property name="spacing">6</property> + <child> + <object class="GtkLabel"> + <property name="xalign">0</property> + <property name="hexpand">True</property> + <property name="label" translatable="yes">Routes</property> + <attributes> + <attribute name="weight" value="bold"/> + </attributes> + </object> + </child> + <child> + <object class="GtkLabel"> + <property name="hexpand">True</property> + <property name="xalign">1</property> + <property name="label" translatable="yes">Automatic</property> + </object> + </child> + <child> + <object class="GtkSwitch" id="auto_routes_switch"> + <property name="halign">end</property> + <property name="valign">center</property> + <accessibility> + <property name="label" translatable="yes">Automatic Routes</property> + </accessibility> + </object> + </child> + </object> + </child> + <child> + <object class="GtkBox" id="routes_box"> + <property name="orientation">vertical</property> + <child> + <object class="GtkBox"> + <property name="orientation">horizontal</property> + <child> + <object class="GtkLabel" id="routes_address_label"> + <property name="hexpand">True</property> + <property name="label" translatable="yes">Address</property> + <style> + <class name="dim-label" /> + </style> + <attributes> + <attribute name="scale" value="0.8"/> + </attributes> + </object> + </child> + <child> + <object class="GtkLabel" id="routes_netmask_label"> + <property name="hexpand">True</property> + <property name="label" translatable="yes">Netmask</property> + <style> + <class name="dim-label" /> + </style> + <attributes> + <attribute name="scale" value="0.8"/> + </attributes> + </object> + </child> + <child> + <object class="GtkLabel" id="routes_gateway_label"> + <property name="hexpand">True</property> + <property name="label" translatable="yes">Gateway</property> + <style> + <class name="dim-label" /> + </style> + <attributes> + <attribute name="scale" value="0.8"/> + </attributes> + </object> + </child> + <child> + <object class="GtkLabel" id="routes_metric_label"> + <property name="label" translatable="yes" comments="Translators: Please see https://en.wikipedia.org/wiki/Metrics_(networking)">Metric</property> + <style> + <class name="dim-label" /> + </style> + <attributes> + <attribute name="scale" value="0.8"/> + </attributes> + </object> + </child> + + <!-- This invisible box is used to add some width in the + end of the header row, assuming the space used by the + delete button in the rows --> + <child> + <object class="GtkBox" id="routes_stub_box"> + </object> + </child> + </object> + </child> + </object> + </child> + <child> + <object class="GtkCheckButton" id="never_default_check"> + <property name="label" translatable="yes">Use this connection _only for resources on its network</property> + <property name="use_underline">True</property> + </object> + </child> + </object> + </child> + </object> + </child> + </object> + </child> + </object> + </child> + </template> + <object class="GtkSizeGroup" id="routes_metric_sizegroup"> + <property name="mode">horizontal</property> + <widgets> + <widget name="routes_metric_label" /> + </widgets> + </object> + <object class="GtkSizeGroup" id="routes_sizegroup"> + <property name="mode">horizontal</property> + <widgets> + <widget name="routes_stub_box" /> + </widgets> + </object> + <object class="GtkSizeGroup" id="address_sizegroup"> + <property name="mode">horizontal</property> + <widgets> + <widget name="address_stub_box" /> + </widgets> + </object> +</interface> diff --git a/panels/network/connection-editor/ip6-page.ui b/panels/network/connection-editor/ip6-page.ui new file mode 100644 index 0000000..c22fb0f --- /dev/null +++ b/panels/network/connection-editor/ip6-page.ui @@ -0,0 +1,349 @@ +<?xml version="1.0" encoding="UTF-8"?> +<interface> + <!-- interface-requires gtk+ 3.0 --> + <template class="CEPageIP6" parent="AdwBin">column + <child> + <object class="GtkScrolledWindow"> + <property name="hscrollbar_policy">never</property> + <child> + <object class="GtkViewport"> + <child> + <object class="GtkGrid" id="main_box"> + <property name="margin_start">24</property> + <property name="margin_end">24</property> + <property name="margin_top">24</property> + <property name="margin_bottom">24</property> + <property name="orientation">vertical</property> + <property name="row-spacing">6</property> + <property name="column-spacing">6</property> + <child> + <object class="GtkLabel"> + <property name="label" translatable="yes">IPv_6 Method</property> + <property name="use_underline">True</property> + <property name="mnemonic_widget">disabled_radio</property> + <property name="xalign">0.0</property> + <layout> + <property name="row">0</property> + <property name="column">0</property> + </layout> + <attributes> + <attribute name="weight" value="bold"/> + </attributes> + </object> + </child> + <child> + <object class="GtkCheckButton" id="automatic_radio"> + <property name="label" translatable="yes">Automatic</property> + <layout> + <property name="row">0</property> + <property name="column">1</property> + </layout> + </object> + </child> + <child> + <object class="GtkCheckButton" id="dhcp_radio"> + <property name="label" translatable="yes">Automatic, DHCP only</property> + <property name="group">automatic_radio</property> + <layout> + <property name="row">0</property> + <property name="column">2</property> + </layout> + </object> + </child> + <child> + <object class="GtkCheckButton" id="local_radio"> + <property name="label" translatable="yes">Link-Local Only</property> + <property name="group">automatic_radio</property> + <layout> + <property name="row">1</property> + <property name="column">1</property> + </layout> + </object> + </child> + <child> + <object class="GtkCheckButton" id="manual_radio"> + <property name="label" translatable="yes">Manual</property> + <property name="group">automatic_radio</property> + <layout> + <property name="row">1</property> + <property name="column">2</property> + </layout> + </object> + </child> + <child> + <object class="GtkCheckButton" id="disabled_radio"> + <property name="label" translatable="yes">Disable</property> + <property name="group">automatic_radio</property> + <layout> + <property name="row">2</property> + <property name="column">1</property> + </layout> + </object> + </child> + <child> + <object class="GtkCheckButton" id="shared_radio"> + <property name="label" translatable="yes">Shared to other computers</property> + <property name="group">automatic_radio</property> + <layout> + <property name="row">2</property> + <property name="column">2</property> + </layout> + </object> + </child> + <child> + <object class="GtkBox" id="content_box"> + <property name="orientation">vertical</property> + <property name="spacing">6</property> + <layout> + <property name="row">3</property> + <property name="column">0</property> + <property name="column-span">3</property> + </layout> + <child> + <object class="GtkBox" id="address_box"> + <property name="orientation">vertical</property> + <child> + <object class="GtkLabel"> + <property name="xalign">0</property> + <property name="label" translatable="yes">Addresses</property> + <property name="margin_top">24</property> + <property name="margin_bottom">8</property> + <attributes> + <attribute name="weight" value="bold"/> + </attributes> + </object> + </child> + <child> + <object class="GtkBox"> + <property name="orientation">horizontal</property> + <child> + <object class="GtkLabel" id="address_address_label"> + <property name="hexpand">True</property> + <property name="label" translatable="yes">Address</property> + <style> + <class name="dim-label" /> + </style> + <attributes> + <attribute name="scale" value="0.8"/> + </attributes> + </object> + </child> + <child> + <object class="GtkLabel" id="address_prefix_label"> + <property name="hexpand">True</property> + <property name="label" translatable="yes">Prefix</property> + <style> + <class name="dim-label" /> + </style> + <attributes> + <attribute name="scale" value="0.8"/> + </attributes> + </object> + </child> + <child> + <object class="GtkLabel" id="address_gateway_label"> + <property name="hexpand">True</property> + <property name="label" translatable="yes">Gateway</property> + <style> + <class name="dim-label" /> + </style> + <attributes> + <attribute name="scale" value="0.8"/> + </attributes> + </object> + </child> + + <!-- This invisible box is used to add some width in the + end of the header row, assuming the space used by the + delete button in the rows --> + <child> + <object class="GtkBox" id="address_stub_box"> + </object> + </child> + </object> + </child> + </object> + </child> + <child> + <object class="GtkBox"> + <property name="margin_top">24</property> + <property name="spacing">6</property> + <child> + <object class="GtkLabel" id="dns6_label"> + <property name="hexpand">True</property> + <property name="xalign">0</property> + <property name="label" translatable="yes">DNS</property> + <attributes> + <attribute name="weight" value="bold"/> + </attributes> + </object> + </child> + <child> + <object class="GtkLabel"> + <property name="hexpand">True</property> + <property name="xalign">1</property> + <property name="label" translatable="yes">Automatic</property> + </object> + </child> + <child> + <object class="GtkSwitch" id="auto_dns_switch"> + <property name="halign">end</property> + <property name="valign">center</property> + <accessibility> + <property name="label" translatable="yes">Automatic DNS</property> + </accessibility> + </object> + </child> + </object> + </child> + <child> + <object class="GtkEntry" id="dns_entry"> + <accessibility> + <property name="label" translatable="yes">DNS server address(es)</property> + <relation name="described-by">dns_multiple_help</relation> + </accessibility> + </object> + </child> + <child> + <object class="GtkLabel" id="dns_multiple_help"> + <property name="xalign">0</property> + <property name="label" translatable="yes">Separate IP addresses with commas</property> + <style> + <class name="dim-label" /> + </style> + <attributes> + <attribute name="scale" value="0.8"/> + </attributes> + </object> + </child> + <child> + <object class="GtkBox"> + <property name="margin_top">24</property> + <property name="spacing">6</property> + <child> + <object class="GtkLabel"> + <property name="xalign">0</property> + <property name="hexpand">True</property> + <property name="label" translatable="yes">Routes</property> + <attributes> + <attribute name="weight" value="bold"/> + </attributes> + </object> + </child> + <child> + <object class="GtkLabel"> + <property name="hexpand">True</property> + <property name="xalign">1</property> + <property name="label" translatable="yes">Automatic</property> + </object> + </child> + <child> + <object class="GtkSwitch" id="auto_routes_switch"> + <property name="halign">end</property> + <property name="valign">center</property> + <accessibility> + <property name="label" translatable="yes">Automatic Routes</property> + </accessibility> + </object> + </child> + </object> + </child> + <child> + <object class="GtkBox" id="routes_box"> + <property name="orientation">vertical</property> + <child> + <object class="GtkBox"> + <property name="orientation">horizontal</property> + <child> + <object class="GtkLabel" id="routes_address_label"> + <property name="hexpand">True</property> + <property name="label" translatable="yes">Address</property> + <style> + <class name="dim-label" /> + </style> + <attributes> + <attribute name="scale" value="0.8"/> + </attributes> + </object> + </child> + <child> + <object class="GtkLabel" id="routes_prefix_label"> + <property name="hexpand">True</property> + <property name="label" translatable="yes">Prefix</property> + <style> + <class name="dim-label" /> + </style> + <attributes> + <attribute name="scale" value="0.8"/> + </attributes> + </object> + </child> + <child> + <object class="GtkLabel" id="routes_gateway_label"> + <property name="hexpand">True</property> + <property name="label" translatable="yes">Gateway</property> + <style> + <class name="dim-label" /> + </style> + <attributes> + <attribute name="scale" value="0.8"/> + </attributes> + </object> + </child> + <child> + <object class="GtkLabel" id="routes_metric_label"> + <property name="label" translatable="yes" comments="Translators: Please see https://en.wikipedia.org/wiki/Metrics_(networking)">Metric</property> + <style> + <class name="dim-label" /> + </style> + <attributes> + <attribute name="scale" value="0.8"/> + </attributes> + </object> + </child> + + <!-- This invisible box is used to add some width in the + end of the header row, assuming the space used by the + delete button in the rows --> + <child> + <object class="GtkBox" id="routes_stub_box"> + </object> + </child> + </object> + </child> + </object> + </child> + <child> + <object class="GtkCheckButton" id="never_default_check"> + <property name="label" translatable="yes">Use this connection _only for resources on its network</property> + <property name="use_underline">True</property> + </object> + </child> + </object> + </child> + </object> + </child> + </object> + </child> + </object> + </child> + </template> + <object class="GtkSizeGroup" id="routes_metric_sizegroup"> + <property name="mode">horizontal</property> + <widgets> + <widget name="routes_metric_label" /> + </widgets> + </object> + <object class="GtkSizeGroup" id="routes_sizegroup"> + <property name="mode">horizontal</property> + <widgets> + <widget name="routes_stub_box" /> + </widgets> + </object> + <object class="GtkSizeGroup" id="address_sizegroup"> + <property name="mode">horizontal</property> + <widgets> + <widget name="address_stub_box" /> + </widgets> + </object> +</interface> diff --git a/panels/network/connection-editor/meson.build b/panels/network/connection-editor/meson.build new file mode 100644 index 0000000..fd4ddf9 --- /dev/null +++ b/panels/network/connection-editor/meson.build @@ -0,0 +1,48 @@ +name = 'connection-editor' + +sources = files( + 'ce-ip-address-entry.c', + 'ce-netmask-entry.c', + 'ce-page-8021x-security.c', + 'ce-page-details.c', + 'ce-page-ethernet.c', + 'ce-page-ip4.c', + 'ce-page-ip6.c', + 'ce-page-security.c', + 'ce-page-vpn.c', + 'ce-page-wifi.c', + 'ce-page.c', + 'net-connection-editor.c', + 'vpn-helpers.c' +) + +resource_data = files( + '8021x-security-page.ui', + 'connection-editor.ui', + 'details-page.ui', + 'ethernet-page.ui', + 'ip4-page.ui', + 'ip6-page.ui', + 'security-page.ui', + 'vpn-page.ui', + 'wifi-page.ui' +) + +c_name = 'net-' + name + +sources += gnome.compile_resources( + c_name + '-resources', + name + '.gresource.xml', + c_name: c_name.underscorify(), + dependencies: resource_data, + export: true +) + +libconnection_editor = static_library( + name, + sources: sources, + include_directories: [top_inc, common_inc, network_inc, wireless_security_inc], + dependencies: deps, + c_args: cflags, + link_with: libwireless_security +) diff --git a/panels/network/connection-editor/net-connection-editor.c b/panels/network/connection-editor/net-connection-editor.c new file mode 100644 index 0000000..9de938c --- /dev/null +++ b/panels/network/connection-editor/net-connection-editor.c @@ -0,0 +1,830 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2012 Red Hat, Inc + * + * 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 <NetworkManager.h> + +#include "net-connection-editor.h" +#include "net-connection-editor-resources.h" +#include "ce-page.h" +#include "ce-page-details.h" +#include "ce-page-wifi.h" +#include "ce-page-ip4.h" +#include "ce-page-ip6.h" +#include "ce-page-security.h" +#include "ce-page-ethernet.h" +#include "ce-page-8021x-security.h" +#include "ce-page-vpn.h" +#include "vpn-helpers.h" +#include "eap-method.h" + +enum { + DONE, + LAST_SIGNAL +}; + +static guint signals[LAST_SIGNAL] = { 0 }; + +struct _NetConnectionEditor +{ + GtkDialog parent; + + GtkBox *add_connection_box; + AdwBin *add_connection_frame; + GtkButton *apply_button; + GtkButton *cancel_button; + GtkNotebook *notebook; + GtkStack *toplevel_stack; + + NMClient *client; + NMDevice *device; + + NMConnection *connection; + NMConnection *orig_connection; + gboolean is_new_connection; + gboolean is_changed; + NMAccessPoint *ap; + + GSList *initializing_pages; + + NMClientPermissionResult can_modify; + + gboolean title_set; +}; + +G_DEFINE_TYPE (NetConnectionEditor, net_connection_editor, GTK_TYPE_DIALOG) + +static void page_changed (NetConnectionEditor *self); + +static void +cancel_editing (NetConnectionEditor *self) +{ + g_signal_emit (self, signals[DONE], 0, FALSE); + gtk_window_destroy (GTK_WINDOW (self)); +} + +static void +close_request_cb (NetConnectionEditor *self) +{ + cancel_editing (self); +} + +static void +cancel_clicked_cb (NetConnectionEditor *self) +{ + cancel_editing (self); +} + +static void +update_connection (NetConnectionEditor *self) +{ + g_autoptr(GVariant) settings = NULL; + + settings = nm_connection_to_dbus (self->connection, NM_CONNECTION_SERIALIZE_ALL); + nm_connection_replace_settings (self->orig_connection, settings, NULL); +} + +static void +update_complete (NetConnectionEditor *self, + gboolean success) +{ + gtk_widget_hide (GTK_WIDGET (self)); + g_signal_emit (self, signals[DONE], 0, success); +} + +static void +updated_connection_cb (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + NetConnectionEditor *self; + g_autoptr(GError) error = NULL; + gboolean success = TRUE; + + if (!nm_remote_connection_commit_changes_finish (NM_REMOTE_CONNECTION (source_object), + res, &error)) { + g_warning ("Failed to commit changes: %s", error->message); + success = FALSE; + //return; FIXME return if cancelled + } + + nm_connection_clear_secrets (NM_CONNECTION (source_object)); + + self = user_data; + update_complete (self, success); +} + +static void +added_connection_cb (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + NetConnectionEditor *self; + g_autoptr(GError) error = NULL; + gboolean success = TRUE; + + if (!nm_client_add_connection_finish (NM_CLIENT (source_object), res, &error)) { + g_warning ("Failed to add connection: %s", error->message); + success = FALSE; + /* Leave the editor open */ + // return; FIXME return if cancelled + } + + self = user_data; + update_complete (self, success); +} + +static void +apply_clicked_cb (NetConnectionEditor *self) +{ + update_connection (self); + + eap_method_ca_cert_ignore_save (self->connection); + + if (self->is_new_connection) { + nm_client_add_connection_async (self->client, + self->orig_connection, + TRUE, + NULL, + added_connection_cb, + self); + } else { + nm_remote_connection_commit_changes_async (NM_REMOTE_CONNECTION (self->orig_connection), + TRUE, + NULL, + updated_connection_cb, self); + } +} + +static void +net_connection_editor_init (NetConnectionEditor *self) +{ + gtk_widget_init_template (GTK_WIDGET (self)); +} + +static void +net_connection_editor_finalize (GObject *object) +{ + NetConnectionEditor *self = NET_CONNECTION_EDITOR (object); + + g_clear_object (&self->connection); + g_clear_object (&self->orig_connection); + g_clear_object (&self->device); + g_clear_object (&self->client); + g_clear_object (&self->ap); + + G_OBJECT_CLASS (net_connection_editor_parent_class)->finalize (object); +} + +static void +net_connection_editor_class_init (NetConnectionEditorClass *class) +{ + GObjectClass *object_class = G_OBJECT_CLASS (class); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class); + + g_resources_register (net_connection_editor_get_resource ()); + + object_class->finalize = net_connection_editor_finalize; + + signals[DONE] = g_signal_new ("done", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_FIRST, + 0, + NULL, NULL, + NULL, + G_TYPE_NONE, 1, G_TYPE_BOOLEAN); + + gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/control-center/network/connection-editor.ui"); + + gtk_widget_class_bind_template_child (widget_class, NetConnectionEditor, add_connection_box); + gtk_widget_class_bind_template_child (widget_class, NetConnectionEditor, add_connection_frame); + gtk_widget_class_bind_template_child (widget_class, NetConnectionEditor, apply_button); + gtk_widget_class_bind_template_child (widget_class, NetConnectionEditor, cancel_button); + gtk_widget_class_bind_template_child (widget_class, NetConnectionEditor, notebook); + gtk_widget_class_bind_template_child (widget_class, NetConnectionEditor, toplevel_stack); + + gtk_widget_class_bind_template_callback (widget_class, cancel_clicked_cb); + gtk_widget_class_bind_template_callback (widget_class, close_request_cb); + gtk_widget_class_bind_template_callback (widget_class, apply_clicked_cb); +} + +static void +net_connection_editor_error_dialog (NetConnectionEditor *self, + const char *primary_text, + const char *secondary_text) +{ + GtkWidget *dialog; + GtkWindow *parent; + + if (gtk_widget_is_visible (GTK_WIDGET (self))) + parent = GTK_WINDOW (self); + else + parent = gtk_window_get_transient_for (GTK_WINDOW (self)); + + dialog = gtk_message_dialog_new (parent, + GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_MESSAGE_ERROR, + GTK_BUTTONS_OK, + "%s", primary_text); + + if (secondary_text) { + gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog), + "%s", secondary_text); + } + + gtk_window_present (GTK_WINDOW (dialog)); +} + +static void +net_connection_editor_do_fallback (NetConnectionEditor *self, const gchar *type) +{ + g_autofree gchar *cmdline = NULL; + g_autoptr(GError) error = NULL; + + if (self->is_new_connection) { + cmdline = g_strdup_printf ("nm-connection-editor --type='%s' --create", type); + } else { + cmdline = g_strdup_printf ("nm-connection-editor --edit='%s'", + nm_connection_get_uuid (self->connection)); + } + + g_spawn_command_line_async (cmdline, &error); + + if (error) + net_connection_editor_error_dialog (self, + _("Unable to open connection editor"), + error->message); + + g_signal_emit (self, signals[DONE], 0, FALSE); +} + +static void +net_connection_editor_update_title (NetConnectionEditor *self) +{ + g_autofree gchar *id = NULL; + + if (self->title_set) + return; + + if (self->is_new_connection) { + if (self->device) { + id = g_strdup (_("New Profile")); + } else { + /* Leave it set to "Add New Connection" */ + return; + } + } else { + NMSettingWireless *sw; + sw = nm_connection_get_setting_wireless (self->connection); + if (sw) { + GBytes *ssid; + ssid = nm_setting_wireless_get_ssid (sw); + id = nm_utils_ssid_to_utf8 (g_bytes_get_data (ssid, NULL), g_bytes_get_size (ssid)); + } else { + id = g_strdup (nm_connection_get_id (self->connection)); + } + } + gtk_window_set_title (GTK_WINDOW (self), id); +} + +static gboolean +editor_is_initialized (NetConnectionEditor *self) +{ + return self->initializing_pages == NULL; +} + +static void +update_sensitivity (NetConnectionEditor *self) +{ + NMSettingConnection *sc; + gboolean sensitive; + gint i; + + if (!editor_is_initialized (self)) + return; + + sc = nm_connection_get_setting_connection (self->connection); + + if (nm_setting_connection_get_read_only (sc)) { + sensitive = FALSE; + } else { + sensitive = self->can_modify; + } + + for (i = 0; i < gtk_notebook_get_n_pages (self->notebook); i++) { + GtkWidget *page = gtk_notebook_get_nth_page (self->notebook, i); + gtk_widget_set_sensitive (page, sensitive); + } +} + +static void +validate (NetConnectionEditor *self) +{ + gboolean valid = FALSE; + g_autofree gchar *apply_tooltip = NULL; + gint i; + + if (!editor_is_initialized (self)) + goto done; + + valid = TRUE; + for (i = 0; i < gtk_notebook_get_n_pages (self->notebook); i++) { + CEPage *page = CE_PAGE (gtk_notebook_get_nth_page (self->notebook, i)); + g_autoptr(GError) error = NULL; + + if (!ce_page_validate (page, self->connection, &error)) { + valid = FALSE; + if (error) { + apply_tooltip = g_strdup_printf (_("Invalid setting %s: %s"), ce_page_get_title (page), error->message); + g_debug ("%s", apply_tooltip); + } else { + apply_tooltip = g_strdup_printf (_("Invalid setting %s"), ce_page_get_title (page)); + g_debug ("%s", apply_tooltip); + } + } + } + + update_sensitivity (self); +done: + if (apply_tooltip != NULL) + gtk_widget_set_tooltip_text(GTK_WIDGET (self->apply_button), apply_tooltip); + + gtk_widget_set_sensitive (GTK_WIDGET (self->apply_button), valid && self->is_changed); +} + +static void +page_changed (NetConnectionEditor *self) +{ + if (editor_is_initialized (self)) + self->is_changed = TRUE; + validate (self); +} + +static gboolean +idle_validate (gpointer user_data) +{ + validate (NET_CONNECTION_EDITOR (user_data)); + + return G_SOURCE_REMOVE; +} + +static void +recheck_initialization (NetConnectionEditor *self) +{ + if (!editor_is_initialized (self)) + return; + + gtk_stack_set_visible_child (self->toplevel_stack, GTK_WIDGET (self->notebook)); + gtk_notebook_set_current_page (self->notebook, 0); + + g_idle_add (idle_validate, self); +} + +static void +page_initialized (NetConnectionEditor *self, GError *error, CEPage *page) +{ + GtkWidget *label; + gint position; + gint i; + + position = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (page), "position")); + g_object_set_data (G_OBJECT (page), "position", GINT_TO_POINTER (position)); + for (i = 0; i < gtk_notebook_get_n_pages (self->notebook); i++) { + GtkWidget *page = gtk_notebook_get_nth_page (self->notebook, i); + gint pos = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (page), "position")); + if (pos > position) + break; + } + + label = gtk_label_new (ce_page_get_title (page)); + + gtk_notebook_insert_page (self->notebook, GTK_WIDGET (page), label, i); + + self->initializing_pages = g_slist_remove (self->initializing_pages, page); + + recheck_initialization (self); +} + +typedef struct { + NetConnectionEditor *editor; + CEPage *page; + const gchar *setting_name; +} GetSecretsInfo; + +static void +get_secrets_cb (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + NMRemoteConnection *connection; + g_autofree GetSecretsInfo *info = user_data; + g_autoptr(GError) error = NULL; + g_autoptr(GVariant) variant = NULL; + + connection = NM_REMOTE_CONNECTION (source_object); + variant = nm_remote_connection_get_secrets_finish (connection, res, &error); + + if (!variant && g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + return; + + ce_page_complete_init (info->page, info->editor->connection, info->setting_name, variant, g_steal_pointer (&error)); +} + +static void +get_secrets_for_page (NetConnectionEditor *self, + CEPage *page, + const gchar *setting_name) +{ + GetSecretsInfo *info; + + info = g_new0 (GetSecretsInfo, 1); + info->editor = self; + info->page = page; + info->setting_name = setting_name; + + nm_remote_connection_get_secrets_async (NM_REMOTE_CONNECTION (self->orig_connection), + setting_name, + NULL, //FIXME + get_secrets_cb, + info); +} + +static void +add_page (NetConnectionEditor *self, CEPage *page) +{ + gint position; + + position = g_slist_length (self->initializing_pages); + g_object_set_data (G_OBJECT (page), "position", GINT_TO_POINTER (position)); + + self->initializing_pages = g_slist_append (self->initializing_pages, page); + + g_signal_connect_object (page, "changed", G_CALLBACK (page_changed), self, G_CONNECT_SWAPPED); + g_signal_connect_object (page, "initialized", G_CALLBACK (page_initialized), self, G_CONNECT_SWAPPED); +} + +static void +net_connection_editor_set_connection (NetConnectionEditor *self, + NMConnection *connection) +{ + GSList *pages, *l; + NMSettingConnection *sc; + const gchar *type; + gboolean is_wired; + gboolean is_wifi; + gboolean is_vpn; + + self->is_new_connection = !nm_client_get_connection_by_uuid (self->client, + nm_connection_get_uuid (connection)); + + if (self->is_new_connection) { + gtk_button_set_label (self->apply_button, _("_Add")); + self->is_changed = TRUE; + } + + self->connection = nm_simple_connection_new_clone (connection); + self->orig_connection = g_object_ref (connection); + + net_connection_editor_update_title (self); + + eap_method_ca_cert_ignore_load (self->connection); + + sc = nm_connection_get_setting_connection (connection); + type = nm_setting_connection_get_connection_type (sc); + + is_wired = g_str_equal (type, NM_SETTING_WIRED_SETTING_NAME); + is_wifi = g_str_equal (type, NM_SETTING_WIRELESS_SETTING_NAME); + is_vpn = g_str_equal (type, NM_SETTING_VPN_SETTING_NAME); + + if (!self->is_new_connection) + add_page (self, CE_PAGE (ce_page_details_new (self->connection, self->device, self->ap, self))); + + if (is_wifi) + add_page (self, CE_PAGE (ce_page_wifi_new (self->connection, self->client))); + else if (is_wired) + add_page (self, CE_PAGE (ce_page_ethernet_new (self->connection, self->client))); + else if (is_vpn) + add_page (self, CE_PAGE (ce_page_vpn_new (self->connection))); + else { + /* Unsupported type */ + net_connection_editor_do_fallback (self, type); + return; + } + + add_page (self, CE_PAGE (ce_page_ip4_new (self->connection, self->client))); + add_page (self, CE_PAGE (ce_page_ip6_new (self->connection, self->client))); + + if (is_wifi) + add_page (self, CE_PAGE (ce_page_security_new (self->connection))); + else if (is_wired) + add_page (self, CE_PAGE (ce_page_8021x_security_new (self->connection))); + + pages = g_slist_copy (self->initializing_pages); + for (l = pages; l; l = l->next) { + CEPage *page = l->data; + const gchar *security_setting; + + security_setting = ce_page_get_security_setting (page); + if (!security_setting || self->is_new_connection) { + ce_page_complete_init (page, NULL, NULL, NULL, NULL); + } else { + get_secrets_for_page (self, page, security_setting); + } + } + g_slist_free (pages); +} + +static NMConnection * +complete_vpn_connection (NetConnectionEditor *self, NMConnection *connection) +{ + NMSettingConnection *s_con; + NMSetting *s_type; + + if (!connection) + connection = nm_simple_connection_new (); + + s_con = nm_connection_get_setting_connection (connection); + if (!s_con) { + s_con = NM_SETTING_CONNECTION (nm_setting_connection_new ()); + nm_connection_add_setting (connection, NM_SETTING (s_con)); + } + + if (!nm_setting_connection_get_uuid (s_con)) { + g_autofree gchar *uuid = nm_utils_uuid_generate (); + g_object_set (s_con, + NM_SETTING_CONNECTION_UUID, uuid, + NULL); + } + + if (!nm_setting_connection_get_id (s_con)) { + const GPtrArray *connections; + g_autofree gchar *id = NULL; + + connections = nm_client_get_connections (self->client); + id = ce_page_get_next_available_name (connections, NAME_FORMAT_TYPE, _("VPN")); + g_object_set (s_con, + NM_SETTING_CONNECTION_ID, id, + NULL); + } + + s_type = nm_connection_get_setting (connection, NM_TYPE_SETTING_VPN); + if (!s_type) { + s_type = g_object_new (NM_TYPE_SETTING_VPN, NULL); + nm_connection_add_setting (connection, s_type); + } + + if (!nm_setting_connection_get_connection_type (s_con)) { + g_object_set (s_con, + NM_SETTING_CONNECTION_TYPE, nm_setting_get_name (s_type), + NULL); + } + + return connection; +} + +static void +finish_add_connection (NetConnectionEditor *self, NMConnection *connection) +{ + adw_bin_set_child (self->add_connection_frame, NULL); + gtk_stack_set_visible_child (self->toplevel_stack, GTK_WIDGET (self->notebook)); + gtk_widget_show (GTK_WIDGET (self->apply_button)); + + if (connection) + net_connection_editor_set_connection (self, connection); +} + +static void +vpn_import_complete (NMConnection *connection, gpointer user_data) +{ + NetConnectionEditor *self = user_data; + + if (!connection) { + /* The import code shows its own error dialogs. */ + g_signal_emit (self, signals[DONE], 0, FALSE); + return; + } + + complete_vpn_connection (self, connection); + finish_add_connection (self, connection); +} + +static void +vpn_type_activated (NetConnectionEditor *self, GtkWidget *row) +{ + const char *service_name = g_object_get_data (G_OBJECT (row), "service_name"); + NMConnection *connection; + NMSettingVpn *s_vpn; + NMSettingConnection *s_con; + + if (!strcmp (service_name, "import")) { + vpn_import (GTK_WINDOW (self), vpn_import_complete, self); + return; + } + + connection = complete_vpn_connection (self, NULL); + s_vpn = nm_connection_get_setting_vpn (connection); + g_object_set (s_vpn, NM_SETTING_VPN_SERVICE_TYPE, service_name, NULL); + + /* Mark the connection as private to this user, and non-autoconnect */ + s_con = nm_connection_get_setting_connection (connection); + g_object_set (s_con, NM_SETTING_CONNECTION_AUTOCONNECT, FALSE, NULL); + nm_setting_connection_add_permission (s_con, "user", g_get_user_name (), NULL); + + finish_add_connection (self, connection); +} + +static void +select_vpn_type (NetConnectionEditor *self, GtkListBox *list) +{ + GSList *vpn_plugins, *iter; + GtkWidget *row, *row_box; + GtkWidget *name_label, *desc_label; + GtkWidget *child; + + /* Get the available VPN types */ + vpn_plugins = vpn_get_plugins (); + + /* Remove the previous menu contents */ + while ((child = gtk_widget_get_first_child (GTK_WIDGET (list))) != NULL) + gtk_list_box_remove (list, child); + + /* Add the VPN types */ + for (iter = vpn_plugins; iter; iter = iter->next) { + NMVpnEditorPlugin *plugin = nm_vpn_plugin_info_get_editor_plugin (iter->data); + g_autofree gchar *name = NULL; + g_autofree gchar *desc = NULL; + g_autofree gchar *desc_markup = NULL; + g_autofree gchar *service_name = NULL; + + g_object_get (plugin, + NM_VPN_EDITOR_PLUGIN_NAME, &name, + NM_VPN_EDITOR_PLUGIN_DESCRIPTION, &desc, + NM_VPN_EDITOR_PLUGIN_SERVICE, &service_name, + NULL); + desc_markup = g_markup_printf_escaped ("<span size='smaller'>%s</span>", desc); + + row = gtk_list_box_row_new (); + + row_box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6); + gtk_widget_set_margin_start (row_box, 12); + gtk_widget_set_margin_end (row_box, 12); + gtk_widget_set_margin_top (row_box, 12); + gtk_widget_set_margin_bottom (row_box, 12); + + name_label = gtk_label_new (name); + gtk_widget_set_halign (name_label, GTK_ALIGN_START); + gtk_box_append (GTK_BOX (row_box), name_label); + + desc_label = gtk_label_new (NULL); + gtk_label_set_markup (GTK_LABEL (desc_label), desc_markup); + gtk_label_set_wrap (GTK_LABEL (desc_label), TRUE); + gtk_widget_set_halign (desc_label, GTK_ALIGN_START); + gtk_widget_add_css_class (desc_label, "dim-label"); + gtk_box_append (GTK_BOX (row_box), desc_label); + + gtk_list_box_row_set_child (GTK_LIST_BOX_ROW (row), row_box); + g_object_set_data_full (G_OBJECT (row), "service_name", g_steal_pointer (&service_name), g_free); + gtk_list_box_append (list, row); + } + + /* Import */ + row = gtk_list_box_row_new (); + + row_box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6); + gtk_widget_set_margin_start (row_box, 12); + gtk_widget_set_margin_end (row_box, 12); + gtk_widget_set_margin_top (row_box, 12); + gtk_widget_set_margin_bottom (row_box, 12); + + name_label = gtk_label_new (_("Import from file…")); + gtk_widget_set_halign (name_label, GTK_ALIGN_START); + gtk_box_append (GTK_BOX (row_box), name_label); + + gtk_list_box_row_set_child (GTK_LIST_BOX_ROW (row), row_box); + g_object_set_data (G_OBJECT (row), "service_name", "import"); + gtk_list_box_append (list, row); + + g_signal_connect_object (list, "row-activated", + G_CALLBACK (vpn_type_activated), self, G_CONNECT_SWAPPED); +} + +static void +net_connection_editor_add_connection (NetConnectionEditor *self) +{ + GtkListBox *list; + + list = GTK_LIST_BOX (gtk_list_box_new ()); + gtk_list_box_set_selection_mode (list, GTK_SELECTION_NONE); + + select_vpn_type (self, list); + + adw_bin_set_child (self->add_connection_frame, GTK_WIDGET (list)); + + gtk_stack_set_visible_child (self->toplevel_stack, GTK_WIDGET (self->add_connection_box)); + gtk_widget_hide (GTK_WIDGET (self->apply_button)); + gtk_window_set_title (GTK_WINDOW (self), _("Add VPN")); +} + +static void +permission_changed (NetConnectionEditor *self, + NMClientPermission permission, + NMClientPermissionResult result) +{ + if (permission != NM_CLIENT_PERMISSION_SETTINGS_MODIFY_SYSTEM) + return; + + if (result == NM_CLIENT_PERMISSION_RESULT_YES || result == NM_CLIENT_PERMISSION_RESULT_AUTH) + self->can_modify = TRUE; + else + self->can_modify = FALSE; + + validate (self); +} + +NetConnectionEditor * +net_connection_editor_new (NMConnection *connection, + NMDevice *device, + NMAccessPoint *ap, + NMClient *client) +{ + NetConnectionEditor *self; + + self = g_object_new (net_connection_editor_get_type (), + /* This doesn't seem to work for a template, so it is also hardcoded. */ + "use-header-bar", 1, + NULL); + + if (ap) + self->ap = g_object_ref (ap); + if (device) + self->device = g_object_ref (device); + self->client = g_object_ref (client); + + self->can_modify = nm_client_get_permission_result (client, NM_CLIENT_PERMISSION_SETTINGS_MODIFY_SYSTEM); + g_signal_connect_object (self->client, "permission-changed", + G_CALLBACK (permission_changed), self, G_CONNECT_SWAPPED); + + if (connection) + net_connection_editor_set_connection (self, connection); + else + net_connection_editor_add_connection (self); + + return self; +} + +static void +forgotten_cb (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + NMRemoteConnection *connection = NM_REMOTE_CONNECTION (source_object); + NetConnectionEditor *self = user_data; + g_autoptr(GError) error = NULL; + + if (!nm_remote_connection_delete_finish (connection, res, &error)) { + if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + g_warning ("Failed to delete connection %s: %s", + nm_connection_get_id (NM_CONNECTION (connection)), + error->message); + return; + } + + cancel_editing (self); +} + +void +net_connection_editor_forget (NetConnectionEditor *self) +{ + nm_remote_connection_delete_async (NM_REMOTE_CONNECTION (self->orig_connection), + NULL, forgotten_cb, self); +} + +void +net_connection_editor_set_title (NetConnectionEditor *self, + const gchar *title) +{ + gtk_window_set_title (GTK_WINDOW (self), title); + self->title_set = TRUE; +} diff --git a/panels/network/connection-editor/net-connection-editor.h b/panels/network/connection-editor/net-connection-editor.h new file mode 100644 index 0000000..cbd197f --- /dev/null +++ b/panels/network/connection-editor/net-connection-editor.h @@ -0,0 +1,40 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2012 Red Hat, Inc. + * + * 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. + */ + +#pragma once + +#include <gtk/gtk.h> +#include <NetworkManager.h> + +G_BEGIN_DECLS + +G_DECLARE_FINAL_TYPE (NetConnectionEditor, net_connection_editor, NET, CONNECTION_EDITOR, GtkDialog) + +NetConnectionEditor *net_connection_editor_new (NMConnection *connection, + NMDevice *device, + NMAccessPoint *ap, + NMClient *client); +void net_connection_editor_set_title (NetConnectionEditor *editor, + const gchar *title); +void net_connection_editor_forget (NetConnectionEditor *editor); + +G_END_DECLS + diff --git a/panels/network/connection-editor/security-page.ui b/panels/network/connection-editor/security-page.ui new file mode 100644 index 0000000..9a1c0de --- /dev/null +++ b/panels/network/connection-editor/security-page.ui @@ -0,0 +1,45 @@ +<?xml version="1.0" encoding="UTF-8"?> +<interface> + <!-- interface-requires gtk+ 3.0 --> + <template class="CEPageSecurity" parent="GtkGrid"> + <property name="margin_start">50</property> + <property name="margin_end">50</property> + <property name="margin_top">12</property> + <property name="margin_bottom">12</property> + <property name="hexpand">True</property> + <property name="vexpand">True</property> + <property name="row_spacing">10</property> + <property name="column_spacing">6</property> + <child> + <object class="GtkLabel" id="security_label"> + <property name="xalign">1</property> + <property name="label" translatable="yes">S_ecurity</property> + <property name="use_underline">True</property> + <property name="mnemonic_widget">security_combo</property> + <layout> + <property name="column">0</property> + <property name="row">0</property> + </layout> + </object> + </child> + <child> + <object class="GtkComboBox" id="security_combo"> + <property name="hexpand">True</property> + <layout> + <property name="column">1</property> + <property name="row">0</property> + </layout> + </object> + </child> + <child> + <object class="GtkBox" id="box"> + <property name="orientation">vertical</property> + <layout> + <property name="column">0</property> + <property name="row">1</property> + <property name="column-span">2</property> + </layout> + </object> + </child> + </template> +</interface> diff --git a/panels/network/connection-editor/vpn-helpers.c b/panels/network/connection-editor/vpn-helpers.c new file mode 100644 index 0000000..e0358a9 --- /dev/null +++ b/panels/network/connection-editor/vpn-helpers.c @@ -0,0 +1,358 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* NetworkManager Connection editor -- Connection editor for NetworkManager + * + * Dan Williams <dcbw@redhat.com> + * + * 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. + * + * (C) Copyright 2008 Red Hat, Inc. + */ + +#include "config.h" + +#include <string.h> +#include <glib.h> +#include <gmodule.h> +#include <gtk/gtk.h> +#include <glib/gi18n.h> +#include <NetworkManager.h> + +#include "vpn-helpers.h" + +NMVpnEditorPlugin * +vpn_get_plugin_by_service (const char *service) +{ + NMVpnPluginInfo *plugin_info; + + g_return_val_if_fail (service != NULL, NULL); + + plugin_info = nm_vpn_plugin_info_list_find_by_service (vpn_get_plugins (), service); + if (plugin_info) + return nm_vpn_plugin_info_get_editor_plugin (plugin_info); + return NULL; +} + +static gint +_sort_vpn_plugins (NMVpnPluginInfo *aa, NMVpnPluginInfo *bb) +{ + return strcmp (nm_vpn_plugin_info_get_name (aa), nm_vpn_plugin_info_get_name (bb)); +} + +GSList * +vpn_get_plugins (void) +{ + static GSList *plugins = NULL; + GSList *p; + + p = nm_vpn_plugin_info_list_load (); + plugins = NULL; + while (p) { + g_autoptr(NMVpnPluginInfo) plugin_info = NM_VPN_PLUGIN_INFO (p->data); + g_autoptr(GError) error = NULL; + + /* load the editor plugin, and preserve only those NMVpnPluginInfo that can + * successfully load the plugin. */ + if (nm_vpn_plugin_info_load_editor_plugin (plugin_info, &error)) + plugins = g_slist_prepend (plugins, g_steal_pointer (&plugin_info)); + else { + if ( !nm_vpn_plugin_info_get_plugin (plugin_info) + && nm_vpn_plugin_info_lookup_property (plugin_info, NM_VPN_PLUGIN_INFO_KF_GROUP_GNOME, "properties")) { + g_message ("vpn: (%s,%s) cannot load legacy-only plugin", + nm_vpn_plugin_info_get_name (plugin_info), + nm_vpn_plugin_info_get_filename (plugin_info)); + } else if (g_error_matches (error, G_FILE_ERROR, G_FILE_ERROR_NOENT)) { + g_message ("vpn: (%s,%s) file \"%s\" not found. Did you install the client package?", + nm_vpn_plugin_info_get_name (plugin_info), + nm_vpn_plugin_info_get_filename (plugin_info), + nm_vpn_plugin_info_get_plugin (plugin_info)); + } else { + g_warning ("vpn: (%s,%s) could not load plugin: %s", + nm_vpn_plugin_info_get_name (plugin_info), + nm_vpn_plugin_info_get_filename (plugin_info), + error->message); + } + } + p = g_slist_delete_link (p, p); + } + + /* sort the list of plugins alphabetically. */ + plugins = g_slist_sort (plugins, (GCompareFunc) _sort_vpn_plugins); + return plugins; +} + + +typedef struct { + GMainLoop *mainloop; + gint response; +} RunData; + +static void +on_dialog_close_request_cb (GtkDialog *dialog, + gint response, + RunData *data) +{ + data->response = GTK_RESPONSE_CLOSE; + g_main_loop_quit (data->mainloop); +} + +static void +on_dialog_response_cb (GtkDialog *dialog, + gint response, + RunData *data) +{ + data->response = response; + g_main_loop_quit (data->mainloop); +} + +static int +run_dialog (GtkDialog *dialog) +{ + g_autoptr(GMainLoop) mainloop = NULL; + RunData run_data; + gulong response_id; + gulong close_id; + + mainloop = g_main_loop_new (NULL, FALSE); + run_data = (RunData) { + .response = GTK_RESPONSE_CLOSE, + .mainloop = mainloop, + }; + + close_id = g_signal_connect (dialog, "close-request", G_CALLBACK (on_dialog_close_request_cb), &run_data); + response_id = g_signal_connect_swapped (dialog, "response", G_CALLBACK (on_dialog_response_cb), &run_data); + + gtk_window_present (GTK_WINDOW (dialog)); + g_main_loop_run (mainloop); + + g_clear_signal_handler (&close_id, dialog); + g_clear_signal_handler (&response_id, dialog); + + return run_data.response; +} + +typedef struct { + VpnImportCallback callback; + gpointer user_data; +} ActionInfo; + +static void +import_vpn_from_file_cb (GtkWidget *dialog, gint response, gpointer user_data) +{ + g_autofree gchar *filename = NULL; + g_autoptr(GFile) file = NULL; + ActionInfo *info = (ActionInfo *) user_data; + NMConnection *connection = NULL; + g_autoptr(GError) error = NULL; + GSList *iter; + + if (response != GTK_RESPONSE_ACCEPT) + goto out; + + file = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (dialog)); + if (!file) { + g_warning ("%s: didn't get a filename back from the chooser!", __func__); + goto out; + } + + filename = g_file_get_path (file); + for (iter = vpn_get_plugins (); !connection && iter; iter = iter->next) { + NMVpnEditorPlugin *plugin; + + plugin = nm_vpn_plugin_info_get_editor_plugin (iter->data); + g_clear_error (&error); + connection = nm_vpn_editor_plugin_import (plugin, filename, &error); + } + + if (!connection) { + GtkWidget *err_dialog; + g_autofree gchar *bname = g_path_get_basename (filename); + + err_dialog = gtk_message_dialog_new (GTK_WINDOW (dialog), + GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_MESSAGE_ERROR, + GTK_BUTTONS_OK, + _("Cannot import VPN connection")); + gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (err_dialog), + _("The file “%s” could not be read or does not contain recognized VPN connection information\n\nError: %s."), + bname, error ? error->message : "unknown error"); + run_dialog (GTK_DIALOG (err_dialog)); + } + +out: + gtk_window_destroy (GTK_WINDOW (dialog)); + + info->callback (connection, info->user_data); + g_free (info); +} + +static gboolean +destroy_import_chooser (GtkWidget *dialog, gpointer user_data) +{ + ActionInfo *info = (ActionInfo *) user_data; + + info->callback (NULL, info->user_data); + g_free (info); + + return FALSE; +} + +void +vpn_import (GtkWindow *parent, VpnImportCallback callback, gpointer user_data) +{ + g_autoptr(GFile) home_folder = NULL; + GtkWidget *dialog; + ActionInfo *info; + + dialog = gtk_file_chooser_dialog_new (_("Select file to import"), + parent, + GTK_FILE_CHOOSER_ACTION_OPEN, + _("_Cancel"), GTK_RESPONSE_CANCEL, + _("_Open"), GTK_RESPONSE_ACCEPT, + NULL); + gtk_window_set_modal (GTK_WINDOW (dialog), TRUE); + home_folder = g_file_new_for_path (g_get_home_dir ()); + gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (dialog), home_folder, NULL); + + info = g_malloc0 (sizeof (ActionInfo)); + info->callback = callback; + info->user_data = user_data; + + g_signal_connect (G_OBJECT (dialog), "close-request", G_CALLBACK (destroy_import_chooser), info); + g_signal_connect (G_OBJECT (dialog), "response", G_CALLBACK (import_vpn_from_file_cb), info); + gtk_window_present (GTK_WINDOW (dialog)); +} + +static void +export_vpn_to_file_cb (GtkWidget *dialog, gint response, gpointer user_data) +{ + g_autoptr(NMConnection) connection = NM_CONNECTION (user_data); + g_autoptr(GFile) file = NULL; + char *filename = NULL; + g_autoptr(GError) error = NULL; + NMVpnEditorPlugin *plugin; + NMSettingConnection *s_con = NULL; + NMSettingVpn *s_vpn = NULL; + const char *service_type; + const char *id = NULL; + gboolean success = FALSE; + + if (response != GTK_RESPONSE_ACCEPT) + goto out; + + file = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (dialog)); + if (!file) { + g_set_error (&error, G_IO_ERROR, G_IO_ERROR_FAILED, "no filename"); + goto done; + } + + filename = g_file_get_path (file); + if (g_file_test (filename, G_FILE_TEST_EXISTS)) { + int replace_response; + GtkWidget *replace_dialog; + g_autofree gchar *bname = NULL; + + bname = g_path_get_basename (filename); + replace_dialog = gtk_message_dialog_new (NULL, + GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_MESSAGE_QUESTION, + GTK_BUTTONS_CANCEL, + _("A file named “%s” already exists."), + bname); + gtk_dialog_add_buttons (GTK_DIALOG (replace_dialog), _("_Replace"), GTK_RESPONSE_OK, NULL); + gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (replace_dialog), + _("Do you want to replace %s with the VPN connection you are saving?"), bname); + replace_response = run_dialog (GTK_DIALOG (replace_dialog)); + gtk_window_destroy (GTK_WINDOW (replace_dialog)); + if (replace_response != GTK_RESPONSE_OK) + goto out; + } + + s_con = nm_connection_get_setting_connection (connection); + id = s_con ? nm_setting_connection_get_id (s_con) : NULL; + if (!id) { + g_set_error (&error, G_IO_ERROR, G_IO_ERROR_FAILED, "connection setting invalid"); + goto done; + } + + s_vpn = nm_connection_get_setting_vpn (connection); + service_type = s_vpn ? nm_setting_vpn_get_service_type (s_vpn) : NULL; + + if (!service_type) { + g_set_error (&error, G_IO_ERROR, G_IO_ERROR_FAILED, "VPN setting invalid"); + goto done; + } + + plugin = vpn_get_plugin_by_service (service_type); + if (plugin) + success = nm_vpn_editor_plugin_export (plugin, filename, connection, &error); + +done: + if (!success) { + GtkWidget *err_dialog; + g_autofree gchar *bname = filename ? g_path_get_basename (filename) : g_strdup ("(none)"); + + err_dialog = gtk_message_dialog_new (NULL, + GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_MESSAGE_ERROR, + GTK_BUTTONS_OK, + _("Cannot export VPN connection")); + gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (err_dialog), + _("The VPN connection “%s” could not be exported to %s.\n\nError: %s."), + id ? id : "(unknown)", bname, error ? error->message : "unknown error"); + run_dialog (GTK_DIALOG (err_dialog)); + } + +out: + gtk_window_destroy (GTK_WINDOW (dialog)); +} + +void +vpn_export (NMConnection *connection) +{ + g_autoptr(GFile) home_folder = NULL; + GtkWidget *dialog; + NMVpnEditorPlugin *plugin; + NMSettingVpn *s_vpn = NULL; + const char *service_type; + + s_vpn = nm_connection_get_setting_vpn (connection); + service_type = s_vpn ? nm_setting_vpn_get_service_type (s_vpn) : NULL; + + if (!service_type) { + g_warning ("%s: invalid VPN connection!", __func__); + return; + } + + dialog = gtk_file_chooser_dialog_new (_("Export VPN connection"), + NULL, + GTK_FILE_CHOOSER_ACTION_SAVE, + _("_Cancel"), GTK_RESPONSE_CANCEL, + _("_Save"), GTK_RESPONSE_ACCEPT, + NULL); + home_folder = g_file_new_for_path (g_get_home_dir ()); + gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (dialog), home_folder, NULL); + + plugin = vpn_get_plugin_by_service (service_type); + if (plugin) { + g_autofree gchar *suggested = NULL; + + suggested = nm_vpn_editor_plugin_get_suggested_filename (plugin, connection); + if (suggested) + gtk_file_chooser_set_current_name (GTK_FILE_CHOOSER (dialog), suggested); + } + + g_signal_connect (G_OBJECT (dialog), "response", G_CALLBACK (export_vpn_to_file_cb), g_object_ref (connection)); + gtk_window_present (GTK_WINDOW (dialog)); +} diff --git a/panels/network/connection-editor/vpn-helpers.h b/panels/network/connection-editor/vpn-helpers.h new file mode 100644 index 0000000..578f68c --- /dev/null +++ b/panels/network/connection-editor/vpn-helpers.h @@ -0,0 +1,39 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* NetworkManager Connection editor -- Connection editor for NetworkManager + * + * Dan Williams <dcbw@redhat.com> + * + * 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. + * + * (C) Copyright 2008 Red Hat, Inc. + */ + +#ifndef _VPN_HELPERS_H_ +#define _VPN_HELPERS_H_ + +#include <glib.h> +#include <gtk/gtk.h> +#include <NetworkManager.h> + +GSList *vpn_get_plugins (void); + +NMVpnEditorPlugin *vpn_get_plugin_by_service (const char *service); + +typedef void (*VpnImportCallback) (NMConnection *connection, gpointer user_data); +void vpn_import (GtkWindow *parent, VpnImportCallback callback, gpointer user_data); + +void vpn_export (NMConnection *connection); + +#endif /* _VPN_HELPERS_H_ */ diff --git a/panels/network/connection-editor/vpn-page.ui b/panels/network/connection-editor/vpn-page.ui new file mode 100644 index 0000000..98f801b --- /dev/null +++ b/panels/network/connection-editor/vpn-page.ui @@ -0,0 +1,41 @@ +<?xml version="1.0" encoding="UTF-8"?> +<interface> + <!-- interface-requires gtk+ 3.0 --> + <template class="CEPageVpn" parent="GtkBox"> + <property name="margin_start">50</property> + <property name="margin_end">50</property> + <property name="margin_top">12</property> + <property name="margin_bottom">12</property> + <property name="orientation">vertical</property> + <property name="spacing">10</property> + <child> + <object class="GtkBox"> + <property name="spacing">6</property> + <child> + <object class="GtkLabel"> + <property name="xalign">1</property> + <property name="label" translatable="yes">_Name</property> + <property name="use_underline">True</property> + <property name="mnemonic_widget">name_entry</property> + </object> + </child> + <child> + <object class="GtkEntry" id="name_entry"> + <property name="hexpand">True</property> + <property name="invisible_char">●</property> + <property name="invisible_char_set">True</property> + </object> + </child> + </object> + </child> + <child> + <object class="GtkLabel" id="failure_label"> + <property name="xalign">0</property> + <property name="label" translatable="yes">(Error: unable to load VPN connection editor)</property> + <attributes> + <attribute name="style" value="italic"/> + </attributes> + </object> + </child> + </template> +</interface> diff --git a/panels/network/connection-editor/wifi-page.ui b/panels/network/connection-editor/wifi-page.ui new file mode 100644 index 0000000..6662b8f --- /dev/null +++ b/panels/network/connection-editor/wifi-page.ui @@ -0,0 +1,109 @@ +<?xml version="1.0" encoding="UTF-8"?> +<interface> + <!-- interface-requires gtk+ 3.0 --> + <template class="CEPageWifi" parent="GtkGrid"> + <property name="margin_start">50</property> + <property name="margin_end">50</property> + <property name="margin_top">12</property> + <property name="margin_bottom">12</property> + <property name="hexpand">True</property> + <property name="vexpand">True</property> + <property name="row_spacing">10</property> + <property name="column_spacing">6</property> + <child> + <object class="GtkLabel"> + <property name="xalign">1</property> + <property name="label" translatable="yes">_SSID</property> + <property name="use_underline">True</property> + <property name="mnemonic_widget">ssid_entry</property> + <layout> + <property name="column">0</property> + <property name="row">0</property> + </layout> + </object> + </child> + <child> + <object class="GtkLabel"> + <property name="xalign">1</property> + <property name="label" translatable="yes">_BSSID</property> + <property name="use_underline">True</property> + <property name="mnemonic_widget">bssid_combo</property> + <layout> + <property name="column">0</property> + <property name="row">1</property> + </layout> + </object> + </child> + <child> + <object class="GtkEntry" id="ssid_entry"> + <property name="hexpand">True</property> + <property name="invisible_char">●</property> + <property name="invisible_char_set">True</property> + <layout> + <property name="column">1</property> + <property name="row">0</property> + </layout> + </object> + </child> + <child> + <object class="GtkLabel"> + <property name="xalign">1</property> + <property name="label" translatable="yes">_MAC Address</property> + <property name="use_underline">True</property> + <property name="mnemonic_widget">mac_combo</property> + <layout> + <property name="column">0</property> + <property name="row">2</property> + </layout> + </object> + </child> + <child> + <object class="GtkComboBoxText" id="cloned_mac_combo"> + <property name="has_entry">True</property> + <property name="hexpand">True</property> + <property name="active_id">0</property> + <layout> + <property name="column">1</property> + <property name="row">3</property> + </layout> + <child internal-child="entry"> + <object class="GtkEntry"> + </object> + </child> + </object> + </child> + <child> + <object class="GtkLabel"> + <property name="label" translatable="yes">_Cloned Address</property> + <property name="use_underline">True</property> + <property name="mnemonic_widget">cloned_mac_combo</property> + <layout> + <property name="column">0</property> + <property name="row">3</property> + </layout> + </object> + </child> + <child> + <object class="GtkComboBoxText" id="bssid_combo"> + <property name="has_entry">True</property> + <property name="entry_text_column">0</property> + <property name="id_column">1</property> + <layout> + <property name="column">1</property> + <property name="row">1</property> + </layout> + </object> + </child> + <child> + <object class="GtkComboBoxText" id="mac_combo"> + <property name="has_entry">True</property> + <property name="entry_text_column">0</property> + <property name="id_column">1</property> + <layout> + <property name="column">1</property> + <property name="row">2</property> + </layout> + </object> + </child> + </template> +</interface> diff --git a/panels/network/gnome-network-panel.desktop.in.in b/panels/network/gnome-network-panel.desktop.in.in new file mode 100644 index 0000000..d9ab05c --- /dev/null +++ b/panels/network/gnome-network-panel.desktop.in.in @@ -0,0 +1,18 @@ +[Desktop Entry] +Name=Network +Comment=Control how you connect to the Internet +Exec=gnome-control-center network +# Translators: Do NOT translate or transliterate this text (this is an icon file name)! +Icon=org.gnome.Settings-network-symbolic +Terminal=false +Type=Application +NoDisplay=true +StartupNotify=true +Categories=GNOME;GTK;Settings;HardwareSettings;X-GNOME-Settings-Panel;X-GNOME-ConnectivitySettings; +OnlyShowIn=GNOME;Unity; +X-GNOME-Bugzilla-Bugzilla=GNOME +X-GNOME-Bugzilla-Product=gnome-control-center +X-GNOME-Bugzilla-Component=network +X-GNOME-Bugzilla-Version=@VERSION@ +# Translators: Search terms to find the Network panel. Do NOT translate or localize the semicolons! The list MUST also end with a semicolon! +Keywords=Network;IP;LAN;Proxy;WAN;Broadband;Modem;Bluetooth;vpn;DNS; diff --git a/panels/network/gnome-wifi-panel.desktop.in.in b/panels/network/gnome-wifi-panel.desktop.in.in new file mode 100644 index 0000000..8104ede --- /dev/null +++ b/panels/network/gnome-wifi-panel.desktop.in.in @@ -0,0 +1,18 @@ +[Desktop Entry] +Name=Wi-Fi +Comment=Control how you connect to Wi-Fi networks +Exec=gnome-control-center wifi +# Translators: Do NOT translate or transliterate this text (this is an icon file name)! +Icon=network-wireless +Terminal=false +Type=Application +NoDisplay=true +StartupNotify=true +Categories=GNOME;GTK;Settings;HardwareSettings;X-GNOME-Settings-Panel;X-GNOME-ConnectivitySettings; +OnlyShowIn=GNOME;Unity; +X-GNOME-Bugzilla-Bugzilla=GNOME +X-GNOME-Bugzilla-Product=gnome-control-center +X-GNOME-Bugzilla-Component=network +X-GNOME-Bugzilla-Version=@VERSION@ +# Translators: Search terms to find the Wi-Fi panel. Do NOT translate or localize the semicolons! The list MUST also end with a semicolon! +Keywords=Network;Wireless;Wi-Fi;Wifi;IP;LAN;Broadband;DNS;Hotspot; diff --git a/panels/network/icons/meson.build b/panels/network/icons/meson.build new file mode 100644 index 0000000..0501dfe --- /dev/null +++ b/panels/network/icons/meson.build @@ -0,0 +1,4 @@ +install_data( + 'scalable/org.gnome.Settings-network-symbolic.svg', + install_dir: join_paths(control_center_icondir, 'hicolor', 'scalable', 'apps') +) diff --git a/panels/network/icons/scalable/org.gnome.Settings-network-symbolic.svg b/panels/network/icons/scalable/org.gnome.Settings-network-symbolic.svg new file mode 100644 index 0000000..a6ef9ce --- /dev/null +++ b/panels/network/icons/scalable/org.gnome.Settings-network-symbolic.svg @@ -0,0 +1,7 @@ +<?xml version="1.0" encoding="UTF-8"?> +<svg height="16px" viewBox="0 0 16 16" width="16px" xmlns="http://www.w3.org/2000/svg"> + <g fill="#2e3436"> + <path d="m 3.75 0.00390625 c -0.953125 0.00390625 -1.75 0.80078175 -1.75 1.74999975 v 5.496094 c 0 0.953125 0.796875 1.75 1.75 1.75 h 0.75 s 0.5 0.007812 0.5 0.507812 c 0 0.492188 -0.5 0.492188 -0.5 0.492188 h -0.5 v 1.992188 h 8 v -2 h -0.5 s -0.5 0.007812 -0.5 -0.492188 s 0.5 -0.507812 0.5 -0.507812 h 0.75 c 0.953125 0 1.75 -0.796876 1.75 -1.75 v -5.492188 c 0 -0.953125 -0.796875 -1.75 -1.75 -1.75 z m 0.25 1.99999975 l 8 -0.003906 v 4.992188 l -8 0.007812 z m 0 0"/> + <path d="m 6.96875 10.003906 l 0.03125 3.996094 h -5.5 c -0.277344 0 -0.5 0.222656 -0.5 0.5 v 1 c 0 0.277344 0.222656 0.5 0.5 0.5 h 13 c 0.277344 0 0.5 -0.222656 0.5 -0.5 v -1 c 0 -0.277344 -0.222656 -0.5 -0.5 -0.5 h -5.5 v -4 z m 0 0" fill-rule="evenodd"/> + </g> +</svg> diff --git a/panels/network/lock-small-symbolic.svg b/panels/network/lock-small-symbolic.svg new file mode 100644 index 0000000..5d2e2a2 --- /dev/null +++ b/panels/network/lock-small-symbolic.svg @@ -0,0 +1 @@ +<svg width="10" height="10" xmlns="http://www.w3.org/2000/svg"><path style="fill:#36343e;" d="M5 0C3.355 0 2 1.355 2 3v2H1v5h8V5H8V3c0-1.645-1.355-3-3-3zm0 2c.571 0 1 .429 1 1v2H4V3c0-.571.429-1 1-1z"/></svg> diff --git a/panels/network/meson.build b/panels/network/meson.build new file mode 100644 index 0000000..e6340ce --- /dev/null +++ b/panels/network/meson.build @@ -0,0 +1,85 @@ +deps = common_deps + network_manager_deps + [ + polkit_gobject_dep, + dependency('gmodule-2.0') +] + +network_inc = include_directories('.') + +subdir('wireless-security') +subdir('connection-editor') +subdir('icons') + +panel_names = [ + cappletname, + 'wifi' +] + +panels_list += panel_names + +foreach name: panel_names + desktop = 'gnome-' + name + '-panel.desktop' + + desktop_in = configure_file( + input: desktop + '.in.in', + output: desktop + '.in', + configuration: desktop_conf + ) + + i18n.merge_file( + type: 'desktop', + input: desktop_in, + output: desktop, + po_dir: po_dir, + install: true, + install_dir: control_center_desktopdir + ) +endforeach + +sources = files( + 'cc-qr-code.c', + 'cc-network-panel.c', + 'cc-wifi-connection-row.c', + 'cc-wifi-connection-list.c', + 'cc-wifi-panel.c', + 'cc-wifi-hotspot-dialog.c', + 'net-device-bluetooth.c', + 'net-device-ethernet.c', + 'net-device-mobile.c', + 'net-device-wifi.c', + 'net-proxy.c', + 'net-vpn.c', + 'network-dialogs.c', + 'panel-common.c', + 'ui-helpers.c' +) + +resource_data = files( + 'cc-network-panel.ui', + 'cc-wifi-connection-row.ui', + 'cc-wifi-panel.ui', + 'cc-wifi-hotspot-dialog.ui', + 'network-bluetooth.ui', + 'network-ethernet.ui', + 'network-mobile.ui', + 'network-proxy.ui', + 'network-vpn.ui', + 'network-wifi.ui', +) + +sources += gnome.compile_resources( + 'cc-' + cappletname + '-resources', + cappletname + '.gresource.xml', + c_name: 'cc_' + cappletname, + dependencies: resource_data, + export: true +) + +network_panel_lib = static_library( + cappletname, + sources: sources, + include_directories: [top_inc, common_inc], + dependencies: deps, + c_args: cflags, + link_with: libconnection_editor +) +panels_libs += network_panel_lib diff --git a/panels/network/net-device-bluetooth.c b/panels/network/net-device-bluetooth.c new file mode 100644 index 0000000..303f4a8 --- /dev/null +++ b/panels/network/net-device-bluetooth.c @@ -0,0 +1,201 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2011-2012 Richard Hughes <richard@hughsie.com> + * Copyright (C) 2012 Red Hat, Inc. + * + * 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 <NetworkManager.h> + +#include "panel-common.h" + +#include "net-device-bluetooth.h" + +struct _NetDeviceBluetooth +{ + AdwActionRow parent; + + GtkSwitch *device_off_switch; + GtkButton *options_button; + + NMClient *client; + NMDevice *device; + gboolean updating_device; +}; + +G_DEFINE_TYPE (NetDeviceBluetooth, net_device_bluetooth, ADW_TYPE_ACTION_ROW) + +static void +update_off_switch_from_device_state (GtkSwitch *sw, + NMDeviceState state, + NetDeviceBluetooth *self) +{ + self->updating_device = TRUE; + switch (state) { + case NM_DEVICE_STATE_UNMANAGED: + case NM_DEVICE_STATE_UNAVAILABLE: + case NM_DEVICE_STATE_DISCONNECTED: + case NM_DEVICE_STATE_DEACTIVATING: + case NM_DEVICE_STATE_FAILED: + gtk_switch_set_active (sw, FALSE); + break; + default: + gtk_switch_set_active (sw, TRUE); + break; + } + self->updating_device = FALSE; +} + +static void +nm_device_bluetooth_refresh_ui (NetDeviceBluetooth *self) +{ + NMDeviceState state; + g_autofree gchar *path = NULL; + + /* set up the device on/off switch */ + state = nm_device_get_state (self->device); + gtk_widget_set_visible (GTK_WIDGET (self->device_off_switch), + state != NM_DEVICE_STATE_UNAVAILABLE + && state != NM_DEVICE_STATE_UNMANAGED); + update_off_switch_from_device_state (self->device_off_switch, state, self); + + /* set up the Options button */ + path = g_find_program_in_path ("nm-connection-editor"); + gtk_widget_set_visible (GTK_WIDGET (self->options_button), state != NM_DEVICE_STATE_UNMANAGED && path != NULL); +} + +static void +device_off_switch_changed_cb (NetDeviceBluetooth *self) +{ + const GPtrArray *acs; + gboolean active; + gint i; + NMActiveConnection *a; + NMConnection *connection; + + if (self->updating_device) + return; + + connection = net_device_get_find_connection (self->client, self->device); + if (connection == NULL) + return; + + active = gtk_switch_get_active (self->device_off_switch); + if (active) { + nm_client_activate_connection_async (self->client, + connection, + self->device, + NULL, NULL, NULL, NULL); + } else { + const gchar *uuid; + + uuid = nm_connection_get_uuid (connection); + acs = nm_client_get_active_connections (self->client); + for (i = 0; acs && i < acs->len; i++) { + a = (NMActiveConnection*)acs->pdata[i]; + if (strcmp (nm_active_connection_get_uuid (a), uuid) == 0) { + nm_client_deactivate_connection_async (self->client, a, NULL, NULL, NULL); + break; + } + } + } +} + +static void +options_button_clicked_cb (NetDeviceBluetooth *self) +{ + const gchar *uuid; + g_autofree gchar *cmdline = NULL; + g_autoptr(GError) error = NULL; + NMConnection *connection; + + connection = net_device_get_find_connection (self->client, self->device); + uuid = nm_connection_get_uuid (connection); + cmdline = g_strdup_printf ("nm-connection-editor --edit %s", uuid); + g_debug ("Launching '%s'\n", cmdline); + if (!g_spawn_command_line_async (cmdline, &error)) + g_warning ("Failed to launch nm-connection-editor: %s", error->message); +} + +static void +net_device_bluetooth_finalize (GObject *object) +{ + NetDeviceBluetooth *self = NET_DEVICE_BLUETOOTH (object); + + g_clear_object (&self->client); + g_clear_object (&self->device); + + G_OBJECT_CLASS (net_device_bluetooth_parent_class)->finalize (object); +} + +static void +net_device_bluetooth_class_init (NetDeviceBluetoothClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + object_class->finalize = net_device_bluetooth_finalize; + + gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/control-center/network/network-bluetooth.ui"); + + gtk_widget_class_bind_template_child (widget_class, NetDeviceBluetooth, device_off_switch); + gtk_widget_class_bind_template_child (widget_class, NetDeviceBluetooth, options_button); + + gtk_widget_class_bind_template_callback (widget_class, device_off_switch_changed_cb); + gtk_widget_class_bind_template_callback (widget_class, options_button_clicked_cb); + +} + +static void +net_device_bluetooth_init (NetDeviceBluetooth *self) +{ + g_autofree gchar *path = NULL; + + gtk_widget_init_template (GTK_WIDGET (self)); + + path = g_find_program_in_path ("nm-connection-editor"); + gtk_widget_set_visible (GTK_WIDGET (self->options_button), path != NULL); +} + +NetDeviceBluetooth * +net_device_bluetooth_new (NMClient *client, NMDevice *device) +{ + NetDeviceBluetooth *self; + + self = g_object_new (net_device_bluetooth_get_type (), NULL); + self->client = g_object_ref (client); + self->device = g_object_ref (device); + + g_signal_connect_object (device, "state-changed", G_CALLBACK (nm_device_bluetooth_refresh_ui), self, G_CONNECT_SWAPPED); + + nm_device_bluetooth_refresh_ui (self); + + return self; +} + +NMDevice * +net_device_bluetooth_get_device (NetDeviceBluetooth *self) +{ + g_return_val_if_fail (NET_IS_DEVICE_BLUETOOTH (self), NULL); + return self->device; +} diff --git a/panels/network/net-device-bluetooth.h b/panels/network/net-device-bluetooth.h new file mode 100644 index 0000000..b0ce5ae --- /dev/null +++ b/panels/network/net-device-bluetooth.h @@ -0,0 +1,38 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2011-2012 Richard Hughes <richard@hughsie.com> + * Copyright (C) 2012 Red Hat, Inc. + * + * 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. + */ + +#pragma once + +#include <adwaita.h> +#include <gtk/gtk.h> +#include <NetworkManager.h> + +G_BEGIN_DECLS + +G_DECLARE_FINAL_TYPE (NetDeviceBluetooth, net_device_bluetooth, NET, DEVICE_BLUETOOTH, AdwActionRow) + +NetDeviceBluetooth *net_device_bluetooth_new (NMClient *client, + NMDevice *device); + +NMDevice *net_device_bluetooth_get_device (NetDeviceBluetooth *device); + +G_END_DECLS diff --git a/panels/network/net-device-ethernet.c b/panels/network/net-device-ethernet.c new file mode 100644 index 0000000..a230498 --- /dev/null +++ b/panels/network/net-device-ethernet.c @@ -0,0 +1,540 @@ +/* -*- 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 <NetworkManager.h> + +#include "panel-common.h" + +#include "connection-editor/net-connection-editor.h" +#include "connection-editor/ce-page.h" + +#include "net-device-ethernet.h" + +struct _NetDeviceEthernet +{ + AdwPreferencesGroup parent; + + GtkListBox *connection_list; + GtkStack *connection_stack; + GtkButton *details_button; + GtkListBox *details_listbox; + AdwActionRow *details_row; + GtkSwitch *device_off_switch; + + NMClient *client; + NMDevice *device; + gboolean updating_device; + GHashTable *connections; +}; + +G_DEFINE_TYPE (NetDeviceEthernet, net_device_ethernet, ADW_TYPE_PREFERENCES_GROUP) + +static void +add_details_row (GtkWidget *details, gint top, const gchar *heading, const gchar *value) +{ + GtkWidget *heading_label; + GtkWidget *value_label; + + heading_label = gtk_label_new (heading); + gtk_style_context_add_class (gtk_widget_get_style_context (heading_label), "dim-label"); + gtk_widget_set_halign (heading_label, GTK_ALIGN_END); + gtk_widget_set_valign (heading_label, GTK_ALIGN_START); + gtk_widget_set_hexpand (heading_label, TRUE); + + gtk_grid_attach (GTK_GRID (details), heading_label, 0, top, 1, 1); + + value_label = gtk_label_new (value); + gtk_widget_set_halign (value_label, GTK_ALIGN_START); + gtk_widget_set_hexpand (value_label, TRUE); + gtk_label_set_selectable (GTK_LABEL (value_label), TRUE); + + gtk_label_set_mnemonic_widget (GTK_LABEL (heading_label), value_label); + + gtk_grid_attach (GTK_GRID (details), value_label, 1, top, 1, 1); +} + +static gchar * +get_last_used_string (NMConnection *connection) +{ + g_autoptr(GDateTime) now = NULL; + g_autoptr(GDateTime) then = NULL; + gint days; + GTimeSpan diff; + guint64 timestamp; + NMSettingConnection *s_con; + + s_con = nm_connection_get_setting_connection (connection); + if (s_con == NULL) + return NULL; + timestamp = nm_setting_connection_get_timestamp (s_con); + if (timestamp == 0) + return g_strdup (_("never")); + + /* calculate the amount of time that has elapsed */ + now = g_date_time_new_now_utc (); + then = g_date_time_new_from_unix_utc (timestamp); + diff = g_date_time_difference (now, then); + days = diff / G_TIME_SPAN_DAY; + if (days == 0) + return g_strdup (_("today")); + else if (days == 1) + return g_strdup (_("yesterday")); + else + return g_strdup_printf (ngettext ("%i day ago", "%i days ago", days), days); +} + +static void +add_details (GtkWidget *details, NMDevice *device, NMConnection *connection) +{ + NMIPConfig *ip4_config = NULL; + NMIPConfig *ip6_config = NULL; + const gchar *ip4_address = NULL; + const gchar *ip4_route = NULL; + g_autofree gchar *ip4_dns = NULL; + g_autofree gchar *ip6_addresses = NULL; + const gchar *ip6_route = NULL; + g_autofree gchar *ip6_dns = NULL; + gint i = 0; + + ip4_config = nm_device_get_ip4_config (device); + if (ip4_config) { + GPtrArray *addresses; + + addresses = nm_ip_config_get_addresses (ip4_config); + if (addresses->len > 0) + ip4_address = nm_ip_address_get_address (g_ptr_array_index (addresses, 0)); + + ip4_route = nm_ip_config_get_gateway (ip4_config); + ip4_dns = g_strjoinv (" ", (char **) nm_ip_config_get_nameservers (ip4_config)); + if (!*ip4_dns) + ip4_dns = NULL; + } + ip6_config = nm_device_get_ip6_config (device); + if (ip6_config) { + ip6_addresses = net_device_get_ip6_addresses (ip6_config); + ip6_route = nm_ip_config_get_gateway (ip6_config); + ip6_dns = g_strjoinv (" ", (char **) nm_ip_config_get_nameservers (ip6_config)); + if (!*ip6_dns) + ip6_dns = NULL; + } + + if (ip4_address && ip6_addresses) { + add_details_row (details, i++, _("IPv4 Address"), ip4_address); + gtk_widget_set_valign (details, GTK_ALIGN_START); + add_details_row (details, i++, _("IPv6 Address"), ip6_addresses); + gtk_widget_set_valign (details, GTK_ALIGN_START); + } else if (ip4_address) { + add_details_row (details, i++, _("IP Address"), ip4_address); + } else if (ip6_addresses) { + add_details_row (details, i++, _("IP Address"), ip6_addresses); + } + + add_details_row (details, i++, _("Hardware Address"), nm_device_get_hw_address (device)); + + if (ip4_route && ip6_route) { + g_autofree gchar *ip_routes = g_strjoin ("\n", ip4_route, ip6_route, NULL); + add_details_row (details, i++, _("Default Route"), ip_routes); + } else if (ip4_route) { + add_details_row (details, i++, _("Default Route"), ip4_route); + } else if (ip6_route) { + add_details_row (details, i++, _("Default Route"), ip6_route); + } + + if (ip4_dns && ip6_dns) { + add_details_row (details, i++, _("DNS4"), ip4_dns); + add_details_row (details, i++, _("DNS6"), ip6_dns); + } else if (ip4_dns) { + add_details_row (details, i++, _("DNS"), ip4_dns); + } else if (ip6_dns) { + add_details_row (details, i++, _("DNS"), ip6_dns); + } + + if (nm_device_get_state (device) != NM_DEVICE_STATE_ACTIVATED) { + g_autofree gchar *last_used = NULL; + last_used = get_last_used_string (connection); + add_details_row (details, i++, _("Last used"), last_used); + } +} + +static void populate_ui (NetDeviceEthernet *self); + +static gboolean +device_state_to_off_switch (NMDeviceState state) +{ + switch (state) { + case NM_DEVICE_STATE_UNMANAGED: + case NM_DEVICE_STATE_UNAVAILABLE: + case NM_DEVICE_STATE_DISCONNECTED: + case NM_DEVICE_STATE_DEACTIVATING: + case NM_DEVICE_STATE_FAILED: + return FALSE; + default: + return TRUE; + } +} + +static void +device_ethernet_refresh_ui (NetDeviceEthernet *self) +{ + NMDeviceState state; + g_autofree gchar *speed_text = NULL; + g_autofree gchar *status = NULL; + + state = nm_device_get_state (self->device); + gtk_widget_set_sensitive (GTK_WIDGET (self->device_off_switch), + state != NM_DEVICE_STATE_UNAVAILABLE + && state != NM_DEVICE_STATE_UNMANAGED); + self->updating_device = TRUE; + gtk_switch_set_active (self->device_off_switch, device_state_to_off_switch (state)); + self->updating_device = FALSE; + + if (state != NM_DEVICE_STATE_UNAVAILABLE) { + guint speed = nm_device_ethernet_get_speed (NM_DEVICE_ETHERNET (self->device)); + if (speed > 0) { + /* Translators: network device speed */ + speed_text = g_strdup_printf (_("%d Mb/s"), speed); + } + } + status = panel_device_status_to_localized_string (self->device, speed_text); + adw_preferences_row_set_title (ADW_PREFERENCES_ROW (self->details_row), status); + + populate_ui (self); +} + +static void +editor_done (NetDeviceEthernet *self) +{ + device_ethernet_refresh_ui (self); +} + +static void +show_details (NetDeviceEthernet *self, GtkButton *button, const gchar *title) +{ + GtkWidget *row; + NMConnection *connection; + NetConnectionEditor *editor; + + row = g_object_get_data (G_OBJECT (button), "row"); + connection = NM_CONNECTION (g_object_get_data (G_OBJECT (row), "connection")); + + editor = net_connection_editor_new (connection, self->device, NULL, self->client); + gtk_window_set_transient_for (GTK_WINDOW (editor), GTK_WINDOW (gtk_widget_get_native (GTK_WIDGET (self)))); + if (title) + net_connection_editor_set_title (editor, title); + g_signal_connect_object (editor, "done", G_CALLBACK (editor_done), self, G_CONNECT_SWAPPED); + gtk_window_present (GTK_WINDOW (editor)); +} + +static void +show_details_for_row (NetDeviceEthernet *self, GtkButton *button) +{ + show_details (self, button, NULL); +} + +static void +details_button_clicked_cb (NetDeviceEthernet *self) +{ + /* Translators: This is used as the title of the connection + * details window for ethernet, if there is only a single + * profile. It is also used to display ethernet in the + * device list. + */ + show_details (self, self->details_button, _("Wired")); +} + +static void +add_row (NetDeviceEthernet *self, NMConnection *connection) +{ + GtkWidget *row; + GtkWidget *widget; + GtkWidget *box; + GtkWidget *details; + NMActiveConnection *aconn; + gboolean active; + + active = FALSE; + + aconn = nm_device_get_active_connection (self->device); + if (aconn) { + const gchar *uuid1, *uuid2; + uuid1 = nm_active_connection_get_uuid (aconn); + uuid2 = nm_connection_get_uuid (connection); + active = g_strcmp0 (uuid1, uuid2) == 0; + } + + row = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0); + box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0); + gtk_box_append (GTK_BOX (row), box); + widget = gtk_label_new (nm_connection_get_id (connection)); + gtk_widget_set_margin_start (widget, 12); + gtk_widget_set_margin_end (widget, 12); + gtk_widget_set_margin_top (widget, 8); + gtk_widget_set_margin_bottom (widget, 8); + gtk_box_append (GTK_BOX (box), widget); + + if (active) { + widget = gtk_image_new_from_icon_name ("object-select-symbolic"); + gtk_widget_set_halign (widget, GTK_ALIGN_CENTER); + gtk_widget_set_valign (widget, GTK_ALIGN_CENTER); + gtk_box_append (GTK_BOX (box), widget); + + details = gtk_grid_new (); + gtk_grid_set_row_spacing (GTK_GRID (details), 10); + gtk_grid_set_column_spacing (GTK_GRID (details), 10); + + gtk_box_append (GTK_BOX (row), details); + + add_details (details, self->device, connection); + } + + /* filler */ + widget = gtk_label_new (""); + gtk_widget_set_hexpand (widget, TRUE); + gtk_box_append (GTK_BOX (box), widget); + + widget = gtk_button_new_from_icon_name ("emblem-system-symbolic"); + gtk_widget_set_margin_start (widget, 12); + gtk_widget_set_margin_end (widget, 12); + gtk_widget_set_margin_top (widget, 8); + gtk_widget_set_margin_bottom (widget, 8); + gtk_widget_set_halign (widget, GTK_ALIGN_CENTER); + gtk_widget_set_valign (widget, GTK_ALIGN_CENTER); + gtk_accessible_update_property (GTK_ACCESSIBLE (widget), + GTK_ACCESSIBLE_PROPERTY_LABEL, _("Options…"), + -1); + gtk_box_append (GTK_BOX (box), widget); + g_object_set_data (G_OBJECT (widget), "edit", widget); + g_object_set_data (G_OBJECT (widget), "row", row); + g_signal_connect_object (widget, "clicked", G_CALLBACK (show_details_for_row), self, G_CONNECT_SWAPPED); + + g_object_set_data (G_OBJECT (row), "connection", connection); + + gtk_list_box_append (self->connection_list, row); +} + +static void +connection_removed (NetDeviceEthernet *self, NMRemoteConnection *connection) +{ + if (g_hash_table_remove (self->connections, connection)) + device_ethernet_refresh_ui (self); +} + +static void +populate_ui (NetDeviceEthernet *self) +{ + GSList *connections, *l; + NMConnection *connection; + GtkWidget *child; + gint n_connections; + + while ((child = gtk_widget_get_first_child (GTK_WIDGET (self->connection_list))) != NULL) + gtk_list_box_remove (self->connection_list, child); + + connections = net_device_get_valid_connections (self->client, self->device); + for (l = connections; l; l = l->next) { + NMConnection *connection = l->data; + if (!g_hash_table_contains (self->connections, connection)) { + g_hash_table_add (self->connections, connection); + } + } + n_connections = g_slist_length (connections); + + if (n_connections > 1) { + for (l = connections; l; l = l->next) { + NMConnection *connection = l->data; + add_row (self, connection); + } + gtk_stack_set_visible_child (self->connection_stack, + GTK_WIDGET (self->connection_list)); + } else if (n_connections == 1) { + connection = connections->data; + gtk_stack_set_visible_child (self->connection_stack, + GTK_WIDGET (self->details_listbox)); + g_object_set_data (G_OBJECT (self->details_button), "row", self->details_button); + g_object_set_data (G_OBJECT (self->details_button), "connection", connection); + + } + + gtk_widget_set_visible (GTK_WIDGET (self->connection_stack), n_connections >= 1); + + g_slist_free (connections); +} + +static void +client_connection_added_cb (NetDeviceEthernet *self) +{ + device_ethernet_refresh_ui (self); +} + +static void +add_profile_button_clicked_cb (NetDeviceEthernet *self) +{ + NMConnection *connection; + NMSettingConnection *sc; + g_autofree gchar *uuid = NULL; + g_autofree gchar *id = NULL; + NetConnectionEditor *editor; + const GPtrArray *connections; + const char *iface; + + connection = nm_simple_connection_new (); + sc = NM_SETTING_CONNECTION (nm_setting_connection_new ()); + nm_connection_add_setting (connection, NM_SETTING (sc)); + + uuid = nm_utils_uuid_generate (); + + connections = nm_client_get_connections (self->client); + id = ce_page_get_next_available_name (connections, NAME_FORMAT_PROFILE, NULL); + + g_object_set (sc, + NM_SETTING_CONNECTION_UUID, uuid, + NM_SETTING_CONNECTION_ID, id, + NM_SETTING_CONNECTION_TYPE, NM_SETTING_WIRED_SETTING_NAME, + NM_SETTING_CONNECTION_AUTOCONNECT, TRUE, + NULL); + + iface = nm_device_get_iface (self->device); + g_object_set (sc, + NM_SETTING_CONNECTION_INTERFACE_NAME, iface, + NULL); + + nm_connection_add_setting (connection, nm_setting_wired_new ()); + + editor = net_connection_editor_new (connection, self->device, NULL, self->client); + gtk_window_set_transient_for (GTK_WINDOW (editor), GTK_WINDOW (gtk_widget_get_native (GTK_WIDGET (self)))); + g_signal_connect_object (editor, "done", G_CALLBACK (editor_done), self, G_CONNECT_SWAPPED); + gtk_window_present (GTK_WINDOW (editor)); +} + +static void +device_off_switch_changed_cb (NetDeviceEthernet *self) +{ + NMConnection *connection; + + if (self->updating_device) + return; + + if (gtk_switch_get_active (self->device_off_switch)) { + connection = net_device_get_find_connection (self->client, self->device); + if (connection != NULL) { + nm_client_activate_connection_async (self->client, + connection, + self->device, + NULL, NULL, NULL, NULL); + } + } else { + nm_device_disconnect_async (self->device, NULL, NULL, NULL); + } +} + +static void +connection_list_row_activated_cb (NetDeviceEthernet *self, GtkListBoxRow *row) +{ + NMConnection *connection; + GtkWidget *child; + + if (!NM_IS_DEVICE_ETHERNET (self->device) || + !nm_device_ethernet_get_carrier (NM_DEVICE_ETHERNET (self->device))) + return; + + child = gtk_list_box_row_get_child (GTK_LIST_BOX_ROW (row)); + connection = NM_CONNECTION (g_object_get_data (G_OBJECT (child), "connection")); + + nm_client_activate_connection_async (self->client, + connection, + self->device, + NULL, NULL, NULL, NULL); +} + +static void +device_ethernet_finalize (GObject *object) +{ + NetDeviceEthernet *self = NET_DEVICE_ETHERNET (object); + + g_clear_object (&self->client); + g_clear_object (&self->device); + g_hash_table_destroy (self->connections); + + G_OBJECT_CLASS (net_device_ethernet_parent_class)->finalize (object); +} + +static void +net_device_ethernet_class_init (NetDeviceEthernetClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + object_class->finalize = device_ethernet_finalize; + + gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/control-center/network/network-ethernet.ui"); + + gtk_widget_class_bind_template_child (widget_class, NetDeviceEthernet, connection_list); + gtk_widget_class_bind_template_child (widget_class, NetDeviceEthernet, connection_stack); + gtk_widget_class_bind_template_child (widget_class, NetDeviceEthernet, details_button); + gtk_widget_class_bind_template_child (widget_class, NetDeviceEthernet, details_listbox); + gtk_widget_class_bind_template_child (widget_class, NetDeviceEthernet, details_row); + gtk_widget_class_bind_template_child (widget_class, NetDeviceEthernet, device_off_switch); + + gtk_widget_class_bind_template_callback (widget_class, connection_list_row_activated_cb); + gtk_widget_class_bind_template_callback (widget_class, device_off_switch_changed_cb); + gtk_widget_class_bind_template_callback (widget_class, details_button_clicked_cb); + gtk_widget_class_bind_template_callback (widget_class, add_profile_button_clicked_cb); +} + +static void +net_device_ethernet_init (NetDeviceEthernet *self) +{ + gtk_widget_init_template (GTK_WIDGET (self)); + + self->connections = g_hash_table_new (NULL, NULL); +} + +NetDeviceEthernet * +net_device_ethernet_new (NMClient *client, NMDevice *device) +{ + NetDeviceEthernet *self; + + self = g_object_new (net_device_ethernet_get_type (), NULL); + self->client = g_object_ref (client); + self->device = g_object_ref (device); + + g_signal_connect_object (client, NM_CLIENT_CONNECTION_ADDED, + G_CALLBACK (client_connection_added_cb), self, G_CONNECT_SWAPPED); + g_signal_connect_object (client, NM_CLIENT_CONNECTION_REMOVED, + G_CALLBACK (connection_removed), self, G_CONNECT_SWAPPED); + + g_signal_connect_object (device, "state-changed", G_CALLBACK (device_ethernet_refresh_ui), self, G_CONNECT_SWAPPED); + + device_ethernet_refresh_ui (self); + + return self; +} + +NMDevice * +net_device_ethernet_get_device (NetDeviceEthernet *self) +{ + g_return_val_if_fail (NET_IS_DEVICE_ETHERNET (self), NULL); + return self->device; +} diff --git a/panels/network/net-device-ethernet.h b/panels/network/net-device-ethernet.h new file mode 100644 index 0000000..8a6acb8 --- /dev/null +++ b/panels/network/net-device-ethernet.h @@ -0,0 +1,37 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2012 Red Hat, Inc. + * + * 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. + */ + +#pragma once + +#include <adwaita.h> +#include <gtk/gtk.h> +#include <NetworkManager.h> + +G_BEGIN_DECLS + +G_DECLARE_FINAL_TYPE (NetDeviceEthernet, net_device_ethernet, NET, DEVICE_ETHERNET, AdwPreferencesGroup) + +NetDeviceEthernet *net_device_ethernet_new (NMClient *client, + NMDevice *device); + +NMDevice *net_device_ethernet_get_device (NetDeviceEthernet *device); + +G_END_DECLS diff --git a/panels/network/net-device-mobile.c b/panels/network/net-device-mobile.c new file mode 100644 index 0000000..2c4012c --- /dev/null +++ b/panels/network/net-device-mobile.c @@ -0,0 +1,912 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2011-2012 Richard Hughes <richard@hughsie.com> + * Copyright (C) 2013 Aleksander Morgado <aleksander@gnu.org> + * + * 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 <NetworkManager.h> +#include <libmm-glib.h> +#include <nma-mobile-providers.h> + +#include "panel-common.h" +#include "network-dialogs.h" +#include "net-device-mobile.h" + +static void nm_device_mobile_refresh_ui (NetDeviceMobile *self); + +struct _NetDeviceMobile +{ + GtkBox parent; + + GtkLabel *device_label; + GtkSwitch *device_off_switch; + GtkLabel *dns4_heading_label; + GtkLabel *dns4_label; + GtkLabel *dns6_heading_label; + GtkLabel *dns6_label; + GtkLabel *imei_heading_label; + GtkLabel *imei_label; + GtkLabel *ipv4_heading_label; + GtkLabel *ipv4_label; + GtkLabel *ipv6_heading_label; + GtkLabel *ipv6_label; + GtkListStore *mobile_connections_list_store; + GtkComboBox *network_combo; + GtkLabel *network_label; + GtkButton *options_button; + GtkLabel *provider_heading_label; + GtkLabel *provider_label; + GtkLabel *route_heading_label; + GtkLabel *route_label; + GtkLabel *status_label; + + NMClient *client; + NMDevice *device; + GDBusObject *modem; + GCancellable *cancellable; + + gboolean updating_device; + + /* Old MM < 0.7 support */ + GDBusProxy *gsm_proxy; + GDBusProxy *cdma_proxy; + + /* New MM >= 0.7 support */ + MMObject *mm_object; + + NMAMobileProvidersDatabase *mpd; +}; + +enum { + COLUMN_ID, + COLUMN_TITLE, + COLUMN_LAST +}; + +G_DEFINE_TYPE (NetDeviceMobile, net_device_mobile, GTK_TYPE_BOX) + +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)) { + /* failed to activate */ + nm_device_mobile_refresh_ui (user_data); + } +} + +static void +network_combo_changed_cb (NetDeviceMobile *self) +{ + gboolean ret; + g_autofree gchar *object_path = NULL; + GtkTreeIter iter; + GtkTreeModel *model; + NMConnection *connection; + + if (self->updating_device) + return; + + ret = gtk_combo_box_get_active_iter (self->network_combo, &iter); + if (!ret) + return; + + /* get entry */ + model = gtk_combo_box_get_model (self->network_combo); + gtk_tree_model_get (model, &iter, + COLUMN_ID, &object_path, + -1); + if (g_strcmp0 (object_path, NULL) == 0) { + GtkNative *native = gtk_widget_get_native (GTK_WIDGET (self)); + cc_network_panel_connect_to_3g_network (GTK_WIDGET (native), self->client, self->device); + return; + } + + /* activate the connection */ + g_debug ("try to switch to connection %s", object_path); + connection = (NMConnection*) nm_client_get_connection_by_path (self->client, object_path); + if (connection != NULL) { + nm_device_disconnect_async (self->device, NULL, NULL, NULL); + nm_client_activate_connection_async (self->client, + connection, + self->device, NULL, NULL, + connection_activate_cb, + self); + return; + } +} + +static void +mobilebb_enabled_toggled (NetDeviceMobile *self) +{ + gboolean enabled = FALSE; + + if (nm_client_wwan_get_enabled (self->client)) { + NMDeviceState state; + + state = nm_device_get_state (self->device); + if (state == NM_DEVICE_STATE_UNKNOWN || + state == NM_DEVICE_STATE_UNMANAGED || + state == NM_DEVICE_STATE_UNAVAILABLE || + state == NM_DEVICE_STATE_DISCONNECTED || + state == NM_DEVICE_STATE_DEACTIVATING || + state == NM_DEVICE_STATE_FAILED) { + enabled = FALSE; + } else { + enabled = TRUE; + } + } + + self->updating_device = TRUE; + gtk_switch_set_active (self->device_off_switch, enabled); + self->updating_device = FALSE; +} + +static void +device_add_device_connections (NetDeviceMobile *self, + NMDevice *nm_device, + GtkListStore *liststore, + GtkComboBox *combobox) +{ + GSList *list, *l; + GtkTreeIter treeiter; + NMActiveConnection *active_connection; + NMConnection *connection; + + /* get the list of available connections for this device */ + list = net_device_get_valid_connections (self->client, nm_device); + gtk_list_store_clear (liststore); + active_connection = nm_device_get_active_connection (nm_device); + for (l = list; l; l = g_slist_next (l)) { + connection = NM_CONNECTION (l->data); + gtk_list_store_append (liststore, &treeiter); + gtk_list_store_set (liststore, + &treeiter, + COLUMN_ID, nm_connection_get_uuid (connection), + COLUMN_TITLE, nm_connection_get_id (connection), + -1); + + /* is this already activated? */ + if (active_connection != NULL && + g_strcmp0 (nm_connection_get_uuid (connection), + nm_active_connection_get_uuid (active_connection)) == 0) { + self->updating_device = TRUE; + gtk_combo_box_set_active_iter (combobox, &treeiter); + self->updating_device = FALSE; + } + } + + /* add new connection entry */ + gtk_list_store_append (liststore, &treeiter); + gtk_list_store_set (liststore, + &treeiter, + COLUMN_ID, NULL, + COLUMN_TITLE, _("Add new connection"), + -1); + + g_slist_free (list); +} + +static void +device_mobile_refresh_equipment_id (NetDeviceMobile *self) +{ + const gchar *equipment_id = NULL; + + if (self->mm_object != NULL) { + MMModem *modem; + + /* Modem interface should always be present */ + modem = mm_object_peek_modem (self->mm_object); + equipment_id = mm_modem_get_equipment_identifier (modem); + + /* Set equipment ID */ + if (equipment_id != NULL) { + g_debug ("[%s] Equipment ID set to '%s'", + mm_object_get_path (self->mm_object), + equipment_id); + } + } else { + /* Assume old MM handling */ + equipment_id = g_object_get_data (G_OBJECT (self), + "ControlCenter::EquipmentIdentifier"); + } + + gtk_label_set_label (self->imei_label, equipment_id); + gtk_widget_set_visible (GTK_WIDGET (self->imei_heading_label), equipment_id != NULL); + gtk_widget_set_visible (GTK_WIDGET (self->imei_label), equipment_id != NULL); +} + +static gchar * +device_mobile_find_provider (NetDeviceMobile *self, + const gchar *mccmnc, + guint32 sid) +{ + NMAMobileProvider *provider; + GString *name = NULL; + + if (self->mpd == NULL) { + g_autoptr(GError) error = NULL; + + /* Use defaults */ + self->mpd = nma_mobile_providers_database_new_sync (NULL, NULL, NULL, &error); + if (self->mpd == NULL) { + g_debug ("Couldn't load mobile providers database: %s", + error ? error->message : ""); + return NULL; + } + } + + if (mccmnc != NULL) { + provider = nma_mobile_providers_database_lookup_3gpp_mcc_mnc (self->mpd, mccmnc); + if (provider != NULL) + name = g_string_new (nma_mobile_provider_get_name (provider)); + } + + if (sid != 0) { + provider = nma_mobile_providers_database_lookup_cdma_sid (self->mpd, sid); + if (provider != NULL) { + if (name == NULL) + name = g_string_new (nma_mobile_provider_get_name (provider)); + else + g_string_append_printf (name, ", %s", nma_mobile_provider_get_name (provider)); + } + } + + return (name != NULL ? g_string_free (name, FALSE) : NULL); +} + +static void +device_mobile_refresh_operator_name (NetDeviceMobile *self) +{ + g_autofree gchar *operator_name = NULL; + + if (self->mm_object != NULL) { + MMModem3gpp *modem_3gpp; + MMModemCdma *modem_cdma; + + modem_3gpp = mm_object_peek_modem_3gpp (self->mm_object); + modem_cdma = mm_object_peek_modem_cdma (self->mm_object); + + if (modem_3gpp != NULL) { + const gchar *operator_name_unsafe; + + operator_name_unsafe = mm_modem_3gpp_get_operator_name (modem_3gpp); + if (operator_name_unsafe != NULL && operator_name_unsafe[0] != '\0') + operator_name = g_strescape (operator_name_unsafe, NULL); + } + + /* If not directly given in the 3GPP interface, try to guess from + * MCCMNC/SID */ + if (operator_name == NULL) { + const gchar *mccmnc = NULL; + guint32 sid = 0; + + if (modem_3gpp != NULL) + mccmnc = mm_modem_3gpp_get_operator_code (modem_3gpp); + if (modem_cdma != NULL) + sid = mm_modem_cdma_get_sid (modem_cdma); + operator_name = device_mobile_find_provider (self, mccmnc, sid); + } + + /* Set operator name */ + if (operator_name != NULL) { + g_debug ("[%s] Operator name set to '%s'", + mm_object_get_path (self->mm_object), + operator_name); + } + + } else { + const gchar *gsm; + const gchar *cdma; + + /* Assume old MM handling */ + gsm = g_object_get_data (G_OBJECT (self), + "ControlCenter::OperatorNameGsm"); + cdma = g_object_get_data (G_OBJECT (self), + "ControlCenter::OperatorNameCdma"); + + if (gsm != NULL && cdma != NULL) + operator_name = g_strdup_printf ("%s, %s", gsm, cdma); + else if (gsm != NULL) + operator_name = g_strdup (gsm); + else if (cdma != NULL) + operator_name = g_strdup (cdma); + } + + gtk_label_set_label (self->provider_label, operator_name); + gtk_widget_set_visible (GTK_WIDGET (self->provider_heading_label), operator_name != NULL); + gtk_widget_set_visible (GTK_WIDGET (self->provider_label), operator_name != NULL); +} + +static void +nm_device_mobile_refresh_ui (NetDeviceMobile *self) +{ + gboolean is_connected; + NMDeviceModemCapabilities caps; + g_autofree gchar *status = NULL; + NMIPConfig *ipv4_config = NULL, *ipv6_config = NULL; + gboolean have_ipv4_address = FALSE, have_ipv6_address = FALSE; + gboolean have_dns4 = FALSE, have_dns6 = FALSE; + const gchar *route4_text = NULL, *route6_text = NULL; + + /* set up the device on/off switch */ + gtk_widget_show (GTK_WIDGET (self->device_off_switch)); + mobilebb_enabled_toggled (self); + + /* set device state, with status */ + status = panel_device_status_to_localized_string (self->device, NULL); + gtk_label_set_label (self->status_label, status); + + /* sensitive for other connection types if the device is currently connected */ + is_connected = net_device_get_find_connection (self->client, self->device) != NULL; + gtk_widget_set_sensitive (GTK_WIDGET (self->options_button), is_connected); + + caps = nm_device_modem_get_current_capabilities (NM_DEVICE_MODEM (self->device)); + if ((caps & NM_DEVICE_MODEM_CAPABILITY_GSM_UMTS) || + (caps & NM_DEVICE_MODEM_CAPABILITY_CDMA_EVDO) || + (caps & NM_DEVICE_MODEM_CAPABILITY_LTE)) { + device_mobile_refresh_operator_name (self); + device_mobile_refresh_equipment_id (self); + } + + /* add possible connections to device */ + device_add_device_connections (self, + self->device, + self->mobile_connections_list_store, + self->network_combo); + + ipv4_config = nm_device_get_ip4_config (self->device); + if (ipv4_config != NULL) { + GPtrArray *addresses; + const gchar *ipv4_text = NULL; + g_autofree gchar *ip4_dns = NULL; + + addresses = nm_ip_config_get_addresses (ipv4_config); + if (addresses->len > 0) + ipv4_text = nm_ip_address_get_address (g_ptr_array_index (addresses, 0)); + gtk_label_set_label (self->ipv4_label, ipv4_text); + gtk_widget_set_visible (GTK_WIDGET (self->ipv4_heading_label), ipv4_text != NULL); + gtk_widget_set_visible (GTK_WIDGET (self->ipv4_label), ipv4_text != NULL); + have_ipv4_address = ipv4_text != NULL; + + ip4_dns = g_strjoinv (" ", (char **) nm_ip_config_get_nameservers (ipv4_config)); + if (!*ip4_dns) + ip4_dns = NULL; + gtk_label_set_label (self->dns4_label, ip4_dns); + gtk_widget_set_visible (GTK_WIDGET (self->dns4_heading_label), ip4_dns != NULL); + gtk_widget_set_visible (GTK_WIDGET (self->dns4_label), ip4_dns != NULL); + have_dns4 = ip4_dns != NULL; + + route4_text = nm_ip_config_get_gateway (ipv4_config); + } else { + gtk_widget_hide (GTK_WIDGET (self->ipv4_heading_label)); + gtk_widget_hide (GTK_WIDGET (self->ipv4_label)); + gtk_widget_hide (GTK_WIDGET (self->dns4_heading_label)); + gtk_widget_hide (GTK_WIDGET (self->dns4_label)); + } + + ipv6_config = nm_device_get_ip6_config (self->device); + if (ipv6_config != NULL) { + g_autofree gchar *ipv6_text = NULL; + g_autofree gchar *ip6_dns = NULL; + + ipv6_text = net_device_get_ip6_addresses (ipv6_config); + gtk_label_set_label (self->ipv6_label, ipv6_text); + gtk_widget_set_visible (GTK_WIDGET (self->ipv6_heading_label), ipv6_text != NULL); + gtk_widget_set_valign (GTK_WIDGET (self->ipv6_heading_label), GTK_ALIGN_START); + gtk_widget_set_visible (GTK_WIDGET (self->ipv6_label), ipv6_text != NULL); + have_ipv6_address = ipv6_text != NULL; + + ip6_dns = g_strjoinv (" ", (char **) nm_ip_config_get_nameservers (ipv6_config)); + if (!*ip6_dns) + ip6_dns = NULL; + gtk_label_set_label (self->dns6_label, ip6_dns); + gtk_widget_set_visible (GTK_WIDGET (self->dns6_heading_label), ip6_dns != NULL); + gtk_widget_set_visible (GTK_WIDGET (self->dns6_label), ip6_dns != NULL); + have_dns6 = ip6_dns != NULL; + + route6_text = nm_ip_config_get_gateway (ipv6_config); + } else { + gtk_widget_hide (GTK_WIDGET (self->ipv6_heading_label)); + gtk_widget_hide (GTK_WIDGET (self->ipv6_label)); + gtk_widget_hide (GTK_WIDGET (self->dns6_heading_label)); + gtk_widget_hide (GTK_WIDGET (self->dns6_label)); + } + + if (have_ipv4_address && have_ipv6_address) { + gtk_label_set_label (self->ipv4_heading_label, _("IPv4 Address")); + gtk_label_set_label (self->ipv6_heading_label, _("IPv6 Address")); + } + else { + gtk_label_set_label (self->ipv4_heading_label, _("IP Address")); + gtk_label_set_label (self->ipv6_heading_label, _("IP Address")); + } + + if (have_dns4 && have_dns6) { + gtk_label_set_label (self->dns4_heading_label, _("DNS4")); + gtk_label_set_label (self->dns6_heading_label, _("DNS6")); + } else { + gtk_label_set_label (self->dns4_heading_label, _("DNS")); + gtk_label_set_label (self->dns6_heading_label, _("DNS")); + } + + if (route4_text != NULL || route6_text != NULL) { + g_autofree const gchar *routes_text = NULL; + + if (route4_text == NULL) { + routes_text = g_strdup (route6_text); + } else if (route6_text == NULL) { + routes_text = g_strdup (route4_text); + } else { + routes_text = g_strjoin ("\n", route4_text, route6_text, NULL); + } + gtk_label_set_label (self->route_label, routes_text); + gtk_widget_set_visible (GTK_WIDGET (self->route_heading_label), routes_text != NULL); + gtk_widget_set_valign (GTK_WIDGET (self->route_heading_label), GTK_ALIGN_START); + gtk_widget_set_visible (GTK_WIDGET (self->route_label), routes_text != NULL); + } else { + gtk_widget_hide (GTK_WIDGET (self->route_heading_label)); + gtk_widget_hide (GTK_WIDGET (self->route_label)); + } +} + +static void +device_off_switch_changed_cb (NetDeviceMobile *self) +{ + const GPtrArray *acs; + gboolean active; + gint i; + NMActiveConnection *a; + NMConnection *connection; + + if (self->updating_device) + return; + + connection = net_device_get_find_connection (self->client, self->device); + if (connection == NULL) + return; + + active = gtk_switch_get_active (self->device_off_switch); + if (active) { + nm_client_activate_connection_async (self->client, + connection, + self->device, + NULL, NULL, NULL, NULL); + } else { + const gchar *uuid; + + uuid = nm_connection_get_uuid (connection); + acs = nm_client_get_active_connections (self->client); + for (i = 0; acs && i < acs->len; i++) { + a = (NMActiveConnection*)acs->pdata[i]; + if (strcmp (nm_active_connection_get_uuid (a), uuid) == 0) { + nm_client_deactivate_connection_async (self->client, a, NULL, NULL, NULL); + break; + } + } + } +} + +static void +options_button_clicked_cb (NetDeviceMobile *self) +{ + const gchar *uuid; + g_autofree gchar *cmdline = NULL; + g_autoptr(GError) error = NULL; + NMConnection *connection; + + connection = net_device_get_find_connection (self->client, self->device); + uuid = nm_connection_get_uuid (connection); + cmdline = g_strdup_printf ("nm-connection-editor --edit %s", uuid); + g_debug ("Launching '%s'\n", cmdline); + if (!g_spawn_command_line_async (cmdline, &error)) + g_warning ("Failed to launch nm-connection-editor: %s", error->message); +} + +static void +device_mobile_device_got_modem_manager_cb (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + g_autoptr(GError) error = NULL; + g_autoptr(GVariant) result = NULL; + g_autoptr(GDBusProxy) proxy = NULL; + NetDeviceMobile *self = (NetDeviceMobile *)user_data; + + proxy = g_dbus_proxy_new_for_bus_finish (res, &error); + if (!proxy) { + g_warning ("Error creating ModemManager proxy: %s", + error->message); + return; + } + + /* get the IMEI */ + result = g_dbus_proxy_get_cached_property (proxy, + "EquipmentIdentifier"); + + /* save */ + if (result) + g_object_set_data_full (G_OBJECT (self), + "ControlCenter::EquipmentIdentifier", + g_variant_dup_string (result, NULL), + g_free); + + device_mobile_refresh_equipment_id (self); +} + +static void +device_mobile_save_operator_name (NetDeviceMobile *self, + const gchar *field, + const gchar *operator_name) +{ + gchar *operator_name_safe = NULL; + + if (operator_name != NULL && operator_name[0] != '\0') + operator_name_safe = g_strescape (operator_name, NULL); + + /* save */ + g_object_set_data_full (G_OBJECT (self), + field, + operator_name_safe, + g_free); + /* refresh */ + device_mobile_refresh_operator_name (self); +} + +static void +device_mobile_get_registration_info_cb (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + g_autofree gchar *operator_code = NULL; + g_autoptr(GError) error = NULL; + guint registration_status; + g_autoptr(GVariant) result = NULL; + g_autofree gchar *operator_name = NULL; + NetDeviceMobile *self = (NetDeviceMobile *)user_data; + + result = g_dbus_proxy_call_finish (G_DBUS_PROXY (source_object), res, &error); + if (result == NULL) { + g_warning ("Error getting registration info: %s\n", + error->message); + return; + } + + /* get values */ + g_variant_get (result, "((uss))", + ®istration_status, + &operator_code, + &operator_name); + + /* If none give, try to guess it */ + if (operator_name == NULL || operator_name[0] == '\0') { + g_free (operator_name); + operator_name = device_mobile_find_provider (self, operator_code, 0); + } + + /* save and refresh */ + device_mobile_save_operator_name (self, + "ControlCenter::OperatorNameGsm", + operator_name); +} + +static void +device_mobile_gsm_signal_cb (NetDeviceMobile *self, + const gchar *sender_name, + const gchar *signal_name, + GVariant *parameters) +{ + guint registration_status = 0; + g_autofree gchar *operator_code = NULL; + g_autofree gchar *operator_name = NULL; + + if (!g_str_equal (signal_name, "RegistrationInfo")) + return; + + g_variant_get (parameters, + "(uss)", + ®istration_status, + &operator_code, + &operator_name); + + /* If none given, try to guess it */ + if (operator_name == NULL || operator_name[0] == '\0') { + g_free (operator_name); + operator_name = device_mobile_find_provider (self, operator_code, 0); + } + + /* save and refresh */ + device_mobile_save_operator_name (self, + "ControlCenter::OperatorNameGsm", + operator_name); +} + +static void +device_mobile_device_got_modem_manager_gsm_cb (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + g_autoptr(GError) error = NULL; + NetDeviceMobile *self = (NetDeviceMobile *)user_data; + + self->gsm_proxy = g_dbus_proxy_new_for_bus_finish (res, &error); + if (self->gsm_proxy == NULL) { + g_warning ("Error creating ModemManager GSM proxy: %s\n", + error->message); + return; + } + + /* Setup value updates */ + g_signal_connect_object (self->gsm_proxy, + "g-signal", + G_CALLBACK (device_mobile_gsm_signal_cb), + self, + G_CONNECT_SWAPPED); + + /* Load initial value */ + g_dbus_proxy_call (self->gsm_proxy, + "GetRegistrationInfo", + NULL, + G_DBUS_CALL_FLAGS_NONE, + -1, + NULL, + device_mobile_get_registration_info_cb, + self); +} + +static void +device_mobile_get_serving_system_cb (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + NetDeviceMobile *self = (NetDeviceMobile *)user_data; + g_autoptr(GVariant) result = NULL; + g_autoptr(GError) error = NULL; + + guint32 band_class; + g_autofree gchar *band = NULL; + guint32 sid; + gchar *operator_name; + + result = g_dbus_proxy_call_finish (G_DBUS_PROXY (source_object), res, &error); + if (result == NULL) { + g_warning ("Error getting serving system: %s\n", + error->message); + return; + } + + /* get values */ + g_variant_get (result, "((usu))", + &band_class, + &band, + &sid); + + operator_name = device_mobile_find_provider (self, NULL, sid); + + /* save and refresh */ + device_mobile_save_operator_name (self, + "ControlCenter::OperatorNameCdma", + operator_name); +} + +static void +device_mobile_device_got_modem_manager_cdma_cb (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + g_autoptr(GError) error = NULL; + NetDeviceMobile *self = (NetDeviceMobile *)user_data; + + self->cdma_proxy = g_dbus_proxy_new_for_bus_finish (res, &error); + if (self->cdma_proxy == NULL) { + g_warning ("Error creating ModemManager CDMA proxy: %s\n", + error->message); + return; + } + + /* Load initial value */ + g_dbus_proxy_call (self->cdma_proxy, + "GetServingSystem", + NULL, + G_DBUS_CALL_FLAGS_NONE, + -1, + NULL, + device_mobile_get_serving_system_cb, + self); +} + +static void +operator_name_updated (NetDeviceMobile *self) +{ + device_mobile_refresh_operator_name (self); +} + +static void +net_device_mobile_dispose (GObject *object) +{ + NetDeviceMobile *self = NET_DEVICE_MOBILE (object); + + g_cancellable_cancel (self->cancellable); + + g_clear_object (&self->client); + g_clear_object (&self->device); + g_clear_object (&self->modem); + g_clear_object (&self->cancellable); + g_clear_object (&self->gsm_proxy); + g_clear_object (&self->cdma_proxy); + g_clear_object (&self->mm_object); + g_clear_object (&self->mpd); + + G_OBJECT_CLASS (net_device_mobile_parent_class)->dispose (object); +} + +static void +net_device_mobile_class_init (NetDeviceMobileClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + object_class->dispose = net_device_mobile_dispose; + + gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/control-center/network/network-mobile.ui"); + + gtk_widget_class_bind_template_child (widget_class, NetDeviceMobile, device_label); + gtk_widget_class_bind_template_child (widget_class, NetDeviceMobile, device_off_switch); + gtk_widget_class_bind_template_child (widget_class, NetDeviceMobile, dns4_heading_label); + gtk_widget_class_bind_template_child (widget_class, NetDeviceMobile, dns4_label); + gtk_widget_class_bind_template_child (widget_class, NetDeviceMobile, dns6_heading_label); + gtk_widget_class_bind_template_child (widget_class, NetDeviceMobile, dns6_label); + gtk_widget_class_bind_template_child (widget_class, NetDeviceMobile, imei_heading_label); + gtk_widget_class_bind_template_child (widget_class, NetDeviceMobile, imei_label); + gtk_widget_class_bind_template_child (widget_class, NetDeviceMobile, ipv4_heading_label); + gtk_widget_class_bind_template_child (widget_class, NetDeviceMobile, ipv4_label); + gtk_widget_class_bind_template_child (widget_class, NetDeviceMobile, ipv6_heading_label); + gtk_widget_class_bind_template_child (widget_class, NetDeviceMobile, ipv6_label); + gtk_widget_class_bind_template_child (widget_class, NetDeviceMobile, mobile_connections_list_store); + gtk_widget_class_bind_template_child (widget_class, NetDeviceMobile, network_combo); + gtk_widget_class_bind_template_child (widget_class, NetDeviceMobile, network_label); + gtk_widget_class_bind_template_child (widget_class, NetDeviceMobile, options_button); + gtk_widget_class_bind_template_child (widget_class, NetDeviceMobile, provider_heading_label); + gtk_widget_class_bind_template_child (widget_class, NetDeviceMobile, provider_label); + gtk_widget_class_bind_template_child (widget_class, NetDeviceMobile, route_heading_label); + gtk_widget_class_bind_template_child (widget_class, NetDeviceMobile, route_label); + gtk_widget_class_bind_template_child (widget_class, NetDeviceMobile, status_label); + + gtk_widget_class_bind_template_callback (widget_class, device_off_switch_changed_cb); + gtk_widget_class_bind_template_callback (widget_class, network_combo_changed_cb); + gtk_widget_class_bind_template_callback (widget_class, options_button_clicked_cb); +} + +static void +net_device_mobile_init (NetDeviceMobile *self) +{ + g_autofree gchar *path = NULL; + + gtk_widget_init_template (GTK_WIDGET (self)); + + self->cancellable = g_cancellable_new (); + + path = g_find_program_in_path ("nm-connection-editor"); + gtk_widget_set_visible (GTK_WIDGET (self->options_button), path != NULL); +} + +NetDeviceMobile * +net_device_mobile_new (NMClient *client, NMDevice *device, GDBusObject *modem) +{ + NetDeviceMobile *self; + NMDeviceModemCapabilities caps; + + self = g_object_new (net_device_mobile_get_type (), NULL); + self->client = g_object_ref (client); + self->device = g_object_ref (device); + + g_signal_connect_object (device, "state-changed", G_CALLBACK (nm_device_mobile_refresh_ui), self, G_CONNECT_SWAPPED); + + if (modem != NULL) { + MMModem3gpp *modem_3gpp; + + self->modem = g_object_ref (modem); + + /* Load equipment ID initially */ + device_mobile_refresh_equipment_id (self); + + /* Follow changes in operator name and load initial values */ + modem_3gpp = mm_object_peek_modem_3gpp (self->mm_object); + if (modem_3gpp != NULL) { + g_signal_connect_object (modem_3gpp, + "notify::operator-name", + G_CALLBACK (operator_name_updated), + self, + G_CONNECT_SWAPPED); + device_mobile_refresh_operator_name (self); + } + } + + caps = nm_device_modem_get_current_capabilities (NM_DEVICE_MODEM (device)); + + /* Only load proxies if we have broadband modems of the OLD ModemManager interface */ + if (g_str_has_prefix (nm_device_get_udi (device), "/org/freedesktop/ModemManager/") && + ((caps & NM_DEVICE_MODEM_CAPABILITY_GSM_UMTS) || + (caps & NM_DEVICE_MODEM_CAPABILITY_CDMA_EVDO) || + (caps & NM_DEVICE_MODEM_CAPABILITY_LTE))) { + g_dbus_proxy_new_for_bus (G_BUS_TYPE_SYSTEM, + G_DBUS_PROXY_FLAGS_NONE, + NULL, + "org.freedesktop.ModemManager", + nm_device_get_udi (device), + "org.freedesktop.ModemManager.Modem", + self->cancellable, + device_mobile_device_got_modem_manager_cb, + self); + + if ((caps & NM_DEVICE_MODEM_CAPABILITY_GSM_UMTS) || + (caps & NM_DEVICE_MODEM_CAPABILITY_LTE)) { + g_dbus_proxy_new_for_bus (G_BUS_TYPE_SYSTEM, + G_DBUS_PROXY_FLAGS_NONE, + NULL, + "org.freedesktop.ModemManager", + nm_device_get_udi (device), + "org.freedesktop.ModemManager.Modem.Gsm.Network", + self->cancellable, + device_mobile_device_got_modem_manager_gsm_cb, + self); + } + + if (caps & NM_DEVICE_MODEM_CAPABILITY_CDMA_EVDO) { + g_dbus_proxy_new_for_bus (G_BUS_TYPE_SYSTEM, + G_DBUS_PROXY_FLAGS_NONE, + NULL, + "org.freedesktop.ModemManager", + nm_device_get_udi (device), + "org.freedesktop.ModemManager.Modem.Cdma", + self->cancellable, + device_mobile_device_got_modem_manager_cdma_cb, + self); + } + } + + g_signal_connect_object (client, "notify::wwan-enabled", + G_CALLBACK (mobilebb_enabled_toggled), + self, G_CONNECT_SWAPPED); + nm_device_mobile_refresh_ui (self); + + return self; +} + +NMDevice * +net_device_mobile_get_device (NetDeviceMobile *self) +{ + g_return_val_if_fail (NET_IS_DEVICE_MOBILE (self), NULL); + return self->device; +} + +void +net_device_mobile_set_title (NetDeviceMobile *self, const gchar *title) +{ + g_return_if_fail (NET_IS_DEVICE_MOBILE (self)); + gtk_label_set_label (self->device_label, title); +} diff --git a/panels/network/net-device-mobile.h b/panels/network/net-device-mobile.h new file mode 100644 index 0000000..7574d3f --- /dev/null +++ b/panels/network/net-device-mobile.h @@ -0,0 +1,40 @@ +/* -*- 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. + */ + +#pragma once + +#include <gtk/gtk.h> +#include <NetworkManager.h> + +G_BEGIN_DECLS + +G_DECLARE_FINAL_TYPE (NetDeviceMobile, net_device_mobile, NET, DEVICE_MOBILE, GtkBox) + +NetDeviceMobile *net_device_mobile_new (NMClient *client, + NMDevice *device, + GDBusObject *modem); + +NMDevice *net_device_mobile_get_device (NetDeviceMobile *device); + +void net_device_mobile_set_title (NetDeviceMobile *device, + const gchar *title); + +G_END_DECLS 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); +} + diff --git a/panels/network/net-device-wifi.h b/panels/network/net-device-wifi.h new file mode 100644 index 0000000..1d4ae7d --- /dev/null +++ b/panels/network/net-device-wifi.h @@ -0,0 +1,47 @@ +/* -*- 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. + */ + +#pragma once + +#include <shell/cc-panel.h> +#include <NetworkManager.h> + +G_BEGIN_DECLS + +G_DECLARE_FINAL_TYPE (NetDeviceWifi, net_device_wifi, NET, DEVICE_WIFI, AdwBin) + +NetDeviceWifi *net_device_wifi_new (CcPanel *panel, + NMClient *client, + NMDevice *device); + +NMDevice *net_device_wifi_get_device (NetDeviceWifi *device); + +void net_device_wifi_set_title (NetDeviceWifi *device, + const gchar *title); + +GtkWidget *net_device_wifi_get_header_widget (NetDeviceWifi *device); + +GtkWidget *net_device_wifi_get_title_widget (NetDeviceWifi *device); + +void net_device_wifi_turn_off_hotspot (NetDeviceWifi *self); + +G_END_DECLS + diff --git a/panels/network/net-proxy.c b/panels/network/net-proxy.c new file mode 100644 index 0000000..fc6c744 --- /dev/null +++ b/panels/network/net-proxy.c @@ -0,0 +1,372 @@ +/* -*- 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 <gio/gio.h> + +#include "net-proxy.h" + +typedef enum +{ + MODE_DISABLED, + MODE_MANUAL, + MODE_AUTOMATIC +} ProxyMode; + +struct _NetProxy +{ + AdwBin parent; + + GtkCheckButton *automatic_radio; + GtkDialog *dialog; + GtkCheckButton *manual_radio; + GtkCheckButton *none_radio; + GtkEntry *proxy_ftp_entry; + GtkEntry *proxy_http_entry; + GtkEntry *proxy_https_entry; + GtkEntry *proxy_ignore_entry; + GtkAdjustment *proxy_port_ftp_adjustment; + GtkAdjustment *proxy_port_http_adjustment; + GtkAdjustment *proxy_port_https_adjustment; + GtkAdjustment *proxy_port_socks_adjustment; + GtkEntry *proxy_socks_entry; + GtkEntry *proxy_url_entry; + GtkLabel *proxy_warning_label; + GtkStack *stack; + GtkLabel *status_label; + + GSettings *settings; +}; + +G_DEFINE_TYPE (NetProxy, net_proxy, ADW_TYPE_BIN) + +static const gchar * +panel_get_string_for_value (ProxyMode mode) +{ + switch (mode) { + case MODE_DISABLED: + return _("Off"); + case MODE_MANUAL: + return _("Manual"); + case MODE_AUTOMATIC: + return _("Automatic"); + default: + g_assert_not_reached (); + } +} + +static inline void +panel_update_status_label (NetProxy *self, + ProxyMode mode) +{ + gtk_label_set_label (self->status_label, panel_get_string_for_value (mode)); +} + +static void +check_wpad_warning (NetProxy *self) +{ + g_autofree gchar *autoconfig_url = NULL; + GString *string = NULL; + gboolean ret = FALSE; + guint mode; + + string = g_string_new (""); + + /* check we're using 'Automatic' */ + mode = g_settings_get_enum (self->settings, "mode"); + if (mode != MODE_AUTOMATIC) + goto out; + + /* see if the PAC is blank */ + autoconfig_url = g_settings_get_string (self->settings, + "autoconfig-url"); + ret = autoconfig_url == NULL || + autoconfig_url[0] == '\0'; + if (!ret) + goto out; + + g_string_append (string, "<small>"); + + /* TRANSLATORS: this is when the use leaves the PAC textbox blank */ + g_string_append (string, _("Web Proxy Autodiscovery is used when a Configuration URL is not provided.")); + + g_string_append (string, "\n"); + + /* TRANSLATORS: WPAD is bad: if you enable it on an untrusted + * network, then anyone else on that network can tell your + * machine that it should proxy all of your web traffic + * through them. */ + g_string_append (string, _("This is not recommended for untrusted public networks.")); + g_string_append (string, "</small>"); +out: + gtk_label_set_markup (self->proxy_warning_label, string->str); + gtk_widget_set_visible (GTK_WIDGET (self->proxy_warning_label), (string->len > 0)); + + g_string_free (string, TRUE); +} + +static void +settings_changed_cb (NetProxy *self) +{ + check_wpad_warning (self); +} + +static void +panel_proxy_mode_setup_widgets (NetProxy *self, ProxyMode value) +{ + /* hide or show the PAC text box */ + switch (value) { + case MODE_DISABLED: + gtk_stack_set_visible_child_name (self->stack, "disabled"); + break; + case MODE_MANUAL: + gtk_stack_set_visible_child_name (self->stack, "manual"); + break; + case MODE_AUTOMATIC: + gtk_stack_set_visible_child_name (self->stack, "automatic"); + break; + default: + g_assert_not_reached (); + } + + /* perhaps show the wpad warning */ + check_wpad_warning (self); +} + +static void +panel_proxy_mode_radio_changed_cb (NetProxy *self, GtkCheckButton *radio) +{ + ProxyMode value; + + if (!gtk_check_button_get_active (GTK_CHECK_BUTTON (radio))) + return; + + /* get selected radio */ + if (radio == self->none_radio) + value = MODE_DISABLED; + else if (radio == self->manual_radio) + value = MODE_MANUAL; + else if (radio == self->automatic_radio) + value = MODE_AUTOMATIC; + else + g_assert_not_reached (); + + /* set */ + g_settings_set_enum (self->settings, "mode", value); + + /* hide or show the correct widgets */ + panel_proxy_mode_setup_widgets (self, value); + + /* status label */ + panel_update_status_label (self, value); +} + +static void +show_dialog_cb (NetProxy *self) +{ + gtk_window_set_transient_for (GTK_WINDOW (self->dialog), GTK_WINDOW (gtk_widget_get_native (GTK_WIDGET (self)))); + gtk_window_present (GTK_WINDOW (self->dialog)); +} + +static void +net_proxy_finalize (GObject *object) +{ + NetProxy *self = NET_PROXY (object); + + g_clear_object (&self->settings); + + G_OBJECT_CLASS (net_proxy_parent_class)->finalize (object); +} + +static void +net_proxy_class_init (NetProxyClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + object_class->finalize = net_proxy_finalize; + + gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/control-center/network/network-proxy.ui"); + + gtk_widget_class_bind_template_child (widget_class, NetProxy, automatic_radio); + gtk_widget_class_bind_template_child (widget_class, NetProxy, dialog); + gtk_widget_class_bind_template_child (widget_class, NetProxy, manual_radio); + gtk_widget_class_bind_template_child (widget_class, NetProxy, none_radio); + gtk_widget_class_bind_template_child (widget_class, NetProxy, proxy_ftp_entry); + gtk_widget_class_bind_template_child (widget_class, NetProxy, proxy_http_entry); + gtk_widget_class_bind_template_child (widget_class, NetProxy, proxy_https_entry); + gtk_widget_class_bind_template_child (widget_class, NetProxy, proxy_ignore_entry); + gtk_widget_class_bind_template_child (widget_class, NetProxy, proxy_port_ftp_adjustment); + gtk_widget_class_bind_template_child (widget_class, NetProxy, proxy_port_http_adjustment); + gtk_widget_class_bind_template_child (widget_class, NetProxy, proxy_port_https_adjustment); + gtk_widget_class_bind_template_child (widget_class, NetProxy, proxy_port_socks_adjustment); + gtk_widget_class_bind_template_child (widget_class, NetProxy, proxy_socks_entry); + gtk_widget_class_bind_template_child (widget_class, NetProxy, proxy_url_entry); + gtk_widget_class_bind_template_child (widget_class, NetProxy, proxy_warning_label); + gtk_widget_class_bind_template_child (widget_class, NetProxy, stack); + gtk_widget_class_bind_template_child (widget_class, NetProxy, status_label); + + gtk_widget_class_bind_template_callback (widget_class, panel_proxy_mode_radio_changed_cb); + gtk_widget_class_bind_template_callback (widget_class, show_dialog_cb); +} + +static gboolean +get_ignore_hosts (GValue *value, + GVariant *variant, + gpointer user_data) +{ + GVariantIter iter; + const gchar *s; + g_autofree gchar **av = NULL; + gchar **p; + gsize n; + + n = g_variant_iter_init (&iter, variant); + p = av = g_new0 (gchar *, n + 1); + + while (g_variant_iter_next (&iter, "&s", &s)) + if (s[0] != '\0') { + *p = (gchar *) s; + ++p; + } + + g_value_take_string (value, g_strjoinv (", ", av)); + + return TRUE; +} + +static GVariant * +set_ignore_hosts (const GValue *value, + const GVariantType *expected_type, + gpointer user_data) +{ + GVariantBuilder builder; + const gchar *sv; + gchar **av, **p; + + sv = g_value_get_string (value); + av = g_strsplit_set (sv, ", ", 0); + + g_variant_builder_init (&builder, G_VARIANT_TYPE_STRING_ARRAY); + for (p = av; *p; ++p) { + if (*p[0] != '\0') + g_variant_builder_add (&builder, "s", *p); + } + + g_strfreev (av); + + return g_variant_builder_end (&builder); +} + +static void +net_proxy_init (NetProxy *self) +{ + g_autoptr(GSettings) http_settings = NULL; + g_autoptr(GSettings) https_settings = NULL; + g_autoptr(GSettings) ftp_settings = NULL; + g_autoptr(GSettings) socks_settings = NULL; + ProxyMode value; + + gtk_widget_init_template (GTK_WIDGET (self)); + + self->settings = g_settings_new ("org.gnome.system.proxy"); + g_signal_connect_object (self->settings, + "changed", + G_CALLBACK (settings_changed_cb), + self, + G_CONNECT_SWAPPED); + + /* actions */ + value = g_settings_get_enum (self->settings, "mode"); + + /* bind the proxy values */ + g_settings_bind (self->settings, "autoconfig-url", + self->proxy_url_entry, "text", + G_SETTINGS_BIND_DEFAULT); + + /* bind the HTTP proxy values */ + http_settings = g_settings_get_child (self->settings, "http"); + g_settings_bind (http_settings, "host", + self->proxy_http_entry, "text", + G_SETTINGS_BIND_DEFAULT); + g_settings_bind (http_settings, "port", + self->proxy_port_http_adjustment, "value", + G_SETTINGS_BIND_DEFAULT); + + /* bind the HTTPS proxy values */ + https_settings = g_settings_get_child (self->settings, "https"); + g_settings_bind (https_settings, "host", + self->proxy_https_entry, "text", + G_SETTINGS_BIND_DEFAULT); + g_settings_bind (https_settings, "port", + self->proxy_port_https_adjustment, "value", + G_SETTINGS_BIND_DEFAULT); + + /* bind the FTP proxy values */ + ftp_settings = g_settings_get_child (self->settings, "ftp"); + g_settings_bind (ftp_settings, "host", + self->proxy_ftp_entry, "text", + G_SETTINGS_BIND_DEFAULT); + g_settings_bind (ftp_settings, "port", + self->proxy_port_ftp_adjustment, "value", + G_SETTINGS_BIND_DEFAULT); + + /* bind the SOCKS proxy values */ + socks_settings = g_settings_get_child (self->settings, "socks"); + g_settings_bind (socks_settings, "host", + self->proxy_socks_entry, "text", + G_SETTINGS_BIND_DEFAULT); + g_settings_bind (socks_settings, "port", + self->proxy_port_socks_adjustment, "value", + G_SETTINGS_BIND_DEFAULT); + + /* bind the proxy ignore hosts */ + g_settings_bind_with_mapping (self->settings, "ignore-hosts", + self->proxy_ignore_entry, "text", + G_SETTINGS_BIND_DEFAULT, get_ignore_hosts, set_ignore_hosts, + NULL, NULL); + + /* setup the radio before connecting to the :toggled signal */ + switch (value) { + case MODE_DISABLED: + gtk_check_button_set_active (GTK_CHECK_BUTTON (self->none_radio), TRUE); + break; + case MODE_MANUAL: + gtk_check_button_set_active (GTK_CHECK_BUTTON (self->manual_radio), TRUE); + break; + case MODE_AUTOMATIC: + gtk_check_button_set_active (GTK_CHECK_BUTTON (self->automatic_radio), TRUE); + break; + default: + g_assert_not_reached (); + } + panel_proxy_mode_setup_widgets (self, value); + panel_update_status_label (self, value); +} + +NetProxy * +net_proxy_new (void) +{ + return g_object_new (net_proxy_get_type (), NULL); +} diff --git a/panels/network/net-proxy.h b/panels/network/net-proxy.h new file mode 100644 index 0000000..5469c25 --- /dev/null +++ b/panels/network/net-proxy.h @@ -0,0 +1,32 @@ +/* -*- 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. + */ + +#pragma once + +#include <adwaita.h> + +G_BEGIN_DECLS + +G_DECLARE_FINAL_TYPE (NetProxy, net_proxy, NET, PROXY, AdwBin) + +NetProxy *net_proxy_new (void); + +G_END_DECLS diff --git a/panels/network/net-vpn.c b/panels/network/net-vpn.c new file mode 100644 index 0000000..74009d5 --- /dev/null +++ b/panels/network/net-vpn.c @@ -0,0 +1,228 @@ +/* -*- 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 <NetworkManager.h> + +#include "panel-common.h" + +#include "net-vpn.h" + +#include "connection-editor/net-connection-editor.h" + +struct _NetVpn +{ + AdwActionRow parent; + + GtkBox *box; + GtkSwitch *device_off_switch; + + NMClient *client; + NMConnection *connection; + NMActiveConnection *active_connection; + gboolean updating_device; +}; + +G_DEFINE_TYPE (NetVpn, net_vpn, ADW_TYPE_ACTION_ROW) + +static void +nm_device_refresh_vpn_ui (NetVpn *self) +{ + g_autofree char *title_escaped = NULL; + const GPtrArray *acs; + NMActiveConnection *a; + gint i; + NMVpnConnectionState state; + + /* update title */ + title_escaped = g_markup_escape_text (nm_connection_get_id (self->connection), + -1); + adw_preferences_row_set_title (ADW_PREFERENCES_ROW (self), + title_escaped); + + if (self->active_connection) { + g_signal_handlers_disconnect_by_func (self->active_connection, + nm_device_refresh_vpn_ui, + self); + g_clear_object (&self->active_connection); + } + + + /* Default to disconnected if there is no active connection */ + state = NM_VPN_CONNECTION_STATE_DISCONNECTED; + acs = nm_client_get_active_connections (self->client); + if (acs != NULL) { + const gchar *uuid; + uuid = nm_connection_get_uuid (self->connection); + + for (i = 0; i < acs->len; i++) { + const gchar *auuid; + + a = (NMActiveConnection*)acs->pdata[i]; + + auuid = nm_active_connection_get_uuid (a); + if (NM_IS_VPN_CONNECTION (a) && strcmp (auuid, uuid) == 0) { + self->active_connection = g_object_ref (a); + g_signal_connect_object (a, "notify::vpn-state", + G_CALLBACK (nm_device_refresh_vpn_ui), + self, G_CONNECT_SWAPPED); + state = nm_vpn_connection_get_vpn_state (NM_VPN_CONNECTION (a)); + break; + } + } + } + + self->updating_device = TRUE; + gtk_switch_set_active (self->device_off_switch, + state != NM_VPN_CONNECTION_STATE_FAILED && + state != NM_VPN_CONNECTION_STATE_DISCONNECTED); + self->updating_device = FALSE; +} + +static void +nm_active_connections_changed (NetVpn *self) +{ + nm_device_refresh_vpn_ui (self); +} + +static void +device_off_toggled (NetVpn *self) +{ + const GPtrArray *acs; + gboolean active; + gint i; + NMActiveConnection *a; + + if (self->updating_device) + return; + + active = gtk_switch_get_active (self->device_off_switch); + if (active) { + nm_client_activate_connection_async (self->client, + self->connection, NULL, NULL, + NULL, NULL, NULL); + } else { + const gchar *uuid; + + uuid = nm_connection_get_uuid (self->connection); + acs = nm_client_get_active_connections (self->client); + for (i = 0; acs && i < acs->len; i++) { + a = (NMActiveConnection*)acs->pdata[i]; + if (strcmp (nm_active_connection_get_uuid (a), uuid) == 0) { + nm_client_deactivate_connection_async (self->client, a, NULL, NULL, NULL); + break; + } + } + } +} + +static void +editor_done (NetVpn *self) +{ + nm_device_refresh_vpn_ui (self); +} + +static void +edit_connection (NetVpn *self) +{ + NetConnectionEditor *editor; + + editor = net_connection_editor_new (self->connection, NULL, NULL, self->client); + gtk_window_set_transient_for (GTK_WINDOW (editor), GTK_WINDOW (gtk_widget_get_native (GTK_WIDGET (self)))); + net_connection_editor_set_title (editor, nm_connection_get_id (self->connection)); + + g_signal_connect_object (editor, "done", G_CALLBACK (editor_done), self, G_CONNECT_SWAPPED); + gtk_window_present (GTK_WINDOW (editor)); +} + +static void +net_vpn_finalize (GObject *object) +{ + NetVpn *self = NET_VPN (object); + + if (self->active_connection) + g_signal_handlers_disconnect_by_func (self->active_connection, + nm_device_refresh_vpn_ui, + self); + + g_clear_object (&self->active_connection); + g_clear_object (&self->client); + g_clear_object (&self->connection); + + G_OBJECT_CLASS (net_vpn_parent_class)->finalize (object); +} + +static void +net_vpn_class_init (NetVpnClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + object_class->finalize = net_vpn_finalize; + + gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/control-center/network/network-vpn.ui"); + + gtk_widget_class_bind_template_child (widget_class, NetVpn, device_off_switch); + + gtk_widget_class_bind_template_callback (widget_class, device_off_toggled); + gtk_widget_class_bind_template_callback (widget_class, edit_connection); +} + +static void +net_vpn_init (NetVpn *self) +{ + gtk_widget_init_template (GTK_WIDGET (self)); +} + +NetVpn * +net_vpn_new (NMClient *client, + NMConnection *connection) +{ + NetVpn *self; + + self = g_object_new (net_vpn_get_type (), NULL); + self->client = g_object_ref (client); + self->connection = g_object_ref (connection); + + g_signal_connect_object (connection, + NM_CONNECTION_CHANGED, + G_CALLBACK (nm_device_refresh_vpn_ui), + self, G_CONNECT_SWAPPED); + + nm_device_refresh_vpn_ui (self); + + g_signal_connect_object (client, + "notify::active-connections", + G_CALLBACK (nm_active_connections_changed), + self, G_CONNECT_SWAPPED); + + return self; +} + +NMConnection * +net_vpn_get_connection (NetVpn *self) +{ + g_return_val_if_fail (NET_IS_VPN (self), NULL); + return self->connection; +} diff --git a/panels/network/net-vpn.h b/panels/network/net-vpn.h new file mode 100644 index 0000000..1835458 --- /dev/null +++ b/panels/network/net-vpn.h @@ -0,0 +1,37 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2011 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. + */ + +#pragma once + +#include <adwaita.h> +#include <gtk/gtk.h> +#include <NetworkManager.h> + +G_BEGIN_DECLS + +G_DECLARE_FINAL_TYPE (NetVpn, net_vpn, NET, VPN, AdwActionRow) + +NetVpn *net_vpn_new (NMClient *client, + NMConnection *connection); + +NMConnection *net_vpn_get_connection (NetVpn *vpn); + +G_END_DECLS diff --git a/panels/network/network-bluetooth.ui b/panels/network/network-bluetooth.ui new file mode 100644 index 0000000..fe3f8c1 --- /dev/null +++ b/panels/network/network-bluetooth.ui @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="UTF-8"?> +<interface> + <template class="NetDeviceBluetooth" parent="AdwActionRow"> + <property name="activatable_widget">device_off_switch</property> + <property name="title" translatable="yes">Wired</property> + <child type="suffix"> + <object class="GtkSwitch" id="device_off_switch"> + <property name="valign">center</property> + <signal name="notify::active" handler="device_off_switch_changed_cb" object="NetDeviceBluetooth" swapped="yes"/> + <accessibility> + <property name="label" translatable="yes">Turn device off</property> + </accessibility> + </object> + </child> + <child type="suffix"> + <object class="GtkButton" id="options_button"> + <property name="valign">center</property> + <property name="use_underline">True</property> + <property name="icon_name">emblem-system-symbolic</property> + <signal name="clicked" handler="options_button_clicked_cb" object="NetDeviceBluetooth" swapped="yes"/> + <accessibility> + <property name="label" translatable="yes">Options…</property> + </accessibility> + </object> + </child> + </template> +</interface> diff --git a/panels/network/network-dialogs.c b/panels/network/network-dialogs.c new file mode 100644 index 0000000..83df99a --- /dev/null +++ b/panels/network/network-dialogs.c @@ -0,0 +1,499 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2011 Giovanni Campagna <scampa.giovanni@gmail.com> + * + * 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. + * + * Portions of this code were taken from network-manager-applet. + * Copyright 2008 - 2011 Red Hat, Inc. + */ + +#include <NetworkManager.h> +#include <nma-wifi-dialog.h> +#include <nma-mobile-wizard.h> + +#include "network-dialogs.h" + +typedef struct { + NMClient *client; +} WirelessDialogClosure; + +typedef struct { + NMClient *client; + NMDevice *device; +} MobileDialogClosure; + +static void +wireless_dialog_closure_closure_notify (gpointer data, + GClosure *gclosure) +{ + WirelessDialogClosure *closure = data; + + g_clear_object (&closure->client); + g_slice_free (WirelessDialogClosure, data); +} + +static void +mobile_dialog_closure_free (gpointer data) +{ + MobileDialogClosure *closure = data; + + g_clear_object (&closure->client); + g_clear_object (&closure->device); + g_slice_free (MobileDialogClosure, data); +} + +static gboolean +wifi_can_create_wifi_network (NMClient *client) +{ + NMClientPermissionResult perm; + + /* FIXME: check WIFI_SHARE_PROTECTED too, and make the wireless dialog + * handle the permissions as well so that admins can restrict open network + * creation separately from protected network creation. + */ + perm = nm_client_get_permission_result (client, NM_CLIENT_PERMISSION_WIFI_SHARE_OPEN); + if (perm == NM_CLIENT_PERMISSION_RESULT_YES || perm == NM_CLIENT_PERMISSION_RESULT_AUTH) + return TRUE; + + return FALSE; +} + +static void +activate_existing_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)) + g_warning ("Failed to activate connection: (%d) %s", error->code, error->message); +} + +static void +activate_new_cb (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + g_autoptr(GError) error = NULL; + + if (!nm_client_add_and_activate_connection_finish (NM_CLIENT (source_object), res, &error)) + g_warning ("Failed to add new connection: (%d) %s", error->code, error->message); +} + +static void +wireless_dialog_response_cb (GtkDialog *foo, + gint response, + gpointer user_data) +{ + NMAWifiDialog *dialog = NMA_WIFI_DIALOG (foo); + WirelessDialogClosure *closure = user_data; + g_autoptr(NMConnection) connection = NULL; + NMConnection *fuzzy_match = NULL; + NMDevice *device; + NMAccessPoint *ap; + const GPtrArray *all; + guint i; + + if (response != GTK_RESPONSE_OK) + goto done; + + /* nma_wifi_dialog_get_connection() returns a connection with the + * refcount incremented, so the caller must remember to unref it. + */ + connection = nma_wifi_dialog_get_connection (dialog, &device, &ap); + g_assert (connection); + g_assert (device); + + /* Find a similar connection and use that instead */ + all = nm_client_get_connections (closure->client); + for (i = 0; i < all->len; i++) { + if (nm_connection_compare (connection, + NM_CONNECTION (g_ptr_array_index (all, i)), + (NM_SETTING_COMPARE_FLAG_FUZZY | NM_SETTING_COMPARE_FLAG_IGNORE_ID))) { + fuzzy_match = NM_CONNECTION (g_ptr_array_index (all, i)); + break; + } + } + + if (fuzzy_match) { + nm_client_activate_connection_async (closure->client, + fuzzy_match, + device, + ap ? nm_object_get_path (NM_OBJECT (ap)) : NULL, + NULL, + activate_existing_cb, + NULL); + } else { + NMSetting *s_con; + NMSettingWireless *s_wifi; + const char *mode = NULL; + + /* Entirely new connection */ + + /* Don't autoconnect adhoc networks by default for now */ + s_wifi = (NMSettingWireless *) nm_connection_get_setting (connection, NM_TYPE_SETTING_WIRELESS); + if (s_wifi) + mode = nm_setting_wireless_get_mode (s_wifi); + if (g_strcmp0 (mode, "adhoc") == 0) { + s_con = nm_connection_get_setting (connection, NM_TYPE_SETTING_CONNECTION); + if (!s_con) { + s_con = nm_setting_connection_new (); + nm_connection_add_setting (connection, s_con); + } + g_object_set (G_OBJECT (s_con), NM_SETTING_CONNECTION_AUTOCONNECT, FALSE, NULL); + } + + nm_client_add_and_activate_connection_async (closure->client, + connection, + device, + ap ? nm_object_get_path (NM_OBJECT (ap)) : NULL, + NULL, + activate_new_cb, + NULL); + } + +done: + gtk_widget_hide (GTK_WIDGET (dialog)); + gtk_window_destroy (GTK_WINDOW (dialog)); +} + +static void +show_wireless_dialog (GtkWidget *toplevel, + NMClient *client, + GtkWidget *dialog) +{ + WirelessDialogClosure *closure; + + g_debug ("About to parent and show a network dialog"); + + g_assert (GTK_IS_NATIVE (toplevel)); + g_object_set (G_OBJECT (dialog), + "modal", TRUE, + "transient-for", toplevel, + NULL); + + closure = g_slice_new (WirelessDialogClosure); + closure->client = g_object_ref (client); + g_signal_connect_data (dialog, "response", + G_CALLBACK (wireless_dialog_response_cb), + closure, wireless_dialog_closure_closure_notify, 0); + + g_object_bind_property (G_OBJECT (toplevel), "visible", + G_OBJECT (dialog), "visible", + G_BINDING_SYNC_CREATE); +} + +void +cc_network_panel_create_wifi_network (GtkWidget *toplevel, + NMClient *client) +{ + if (wifi_can_create_wifi_network (client)) { + show_wireless_dialog (toplevel, client, + nma_wifi_dialog_new_for_create (client)); + } +} + +void +cc_network_panel_connect_to_hidden_network (GtkWidget *toplevel, + NMClient *client) +{ + g_debug ("connect to hidden wifi"); + show_wireless_dialog (toplevel, client, + nma_wifi_dialog_new_for_hidden (client)); +} + +void +cc_network_panel_connect_to_8021x_network (GtkWidget *toplevel, + NMClient *client, + NMDevice *device, + const gchar *arg_access_point) +{ + NMConnection *connection; + NMSettingConnection *s_con; + NMSettingWireless *s_wifi; + NMSettingWirelessSecurity *s_wsec; + NMSetting8021x *s_8021x; + NM80211ApSecurityFlags wpa_flags, rsn_flags; + GtkWidget *dialog; + g_autofree gchar *uuid = NULL; + NMAccessPoint *ap; + + g_debug ("connect to 8021x wifi"); + ap = nm_device_wifi_get_access_point_by_path (NM_DEVICE_WIFI (device), arg_access_point); + if (ap == NULL) { + g_warning ("didn't find access point with path %s", arg_access_point); + return; + } + + /* If the AP is WPA[2]-Enterprise then we need to set up a minimal 802.1x + * setting and ask the user for more information. + */ + rsn_flags = nm_access_point_get_rsn_flags (ap); + wpa_flags = nm_access_point_get_wpa_flags (ap); + if (!(rsn_flags & NM_802_11_AP_SEC_KEY_MGMT_802_1X) + && !(wpa_flags & NM_802_11_AP_SEC_KEY_MGMT_802_1X)) { + g_warning ("Network panel loaded with connect-8021x-wifi but the " + "access point does not support 802.1x"); + return; + } + + connection = nm_simple_connection_new (); + + /* Need a UUID for the "always ask" stuff in the Dialog of Doom */ + s_con = (NMSettingConnection *) nm_setting_connection_new (); + uuid = nm_utils_uuid_generate (); + g_object_set (s_con, NM_SETTING_CONNECTION_UUID, uuid, NULL); + nm_connection_add_setting (connection, NM_SETTING (s_con)); + + s_wifi = (NMSettingWireless *) nm_setting_wireless_new (); + nm_connection_add_setting (connection, NM_SETTING (s_wifi)); + g_object_set (s_wifi, + NM_SETTING_WIRELESS_SSID, nm_access_point_get_ssid (ap), + NULL); + + s_wsec = (NMSettingWirelessSecurity *) nm_setting_wireless_security_new (); + g_object_set (s_wsec, NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, "wpa-eap", NULL); + nm_connection_add_setting (connection, NM_SETTING (s_wsec)); + + s_8021x = (NMSetting8021x *) nm_setting_802_1x_new (); + nm_setting_802_1x_add_eap_method (s_8021x, "ttls"); + g_object_set (s_8021x, NM_SETTING_802_1X_PHASE2_AUTH, "mschapv2", NULL); + nm_connection_add_setting (connection, NM_SETTING (s_8021x)); + + dialog = nma_wifi_dialog_new (client, connection, device, ap, FALSE); + show_wireless_dialog (toplevel, client, dialog); +} + +static void +connect_3g (NMConnection *connection, + gboolean canceled, + gpointer user_data) +{ + MobileDialogClosure *closure = user_data; + + if (canceled == FALSE) { + g_return_if_fail (connection != NULL); + + /* Ask NM to add the new connection and activate it; NM will fill in the + * missing details based on the specific object and the device. + */ + nm_client_add_and_activate_connection_async (closure->client, + connection, + closure->device, + "/", + NULL, + activate_new_cb, + NULL); + } + + mobile_dialog_closure_free (closure); +} + +static void +cdma_mobile_wizard_done (NMAMobileWizard *wizard, + gboolean canceled, + NMAMobileWizardAccessMethod *method, + gpointer user_data) +{ + NMConnection *connection = NULL; + + if (!canceled && method) { + NMSetting *setting; + g_autofree gchar *uuid = NULL; + g_autofree gchar *id = NULL; + + if (method->devtype != NM_DEVICE_MODEM_CAPABILITY_CDMA_EVDO) { + g_warning ("Unexpected device type (not CDMA)."); + canceled = TRUE; + goto done; + } + + connection = nm_simple_connection_new (); + + setting = nm_setting_cdma_new (); + g_object_set (setting, + NM_SETTING_CDMA_NUMBER, "#777", + NM_SETTING_CDMA_USERNAME, method->username, + NM_SETTING_CDMA_PASSWORD, method->password, + NULL); + nm_connection_add_setting (connection, setting); + + /* Serial setting */ + setting = nm_setting_serial_new (); + g_object_set (setting, + NM_SETTING_SERIAL_BAUD, 115200, + NM_SETTING_SERIAL_BITS, 8, + NM_SETTING_SERIAL_PARITY, 'n', + NM_SETTING_SERIAL_STOPBITS, 1, + NULL); + nm_connection_add_setting (connection, setting); + + nm_connection_add_setting (connection, nm_setting_ppp_new ()); + + setting = nm_setting_connection_new (); + if (method->plan_name) + id = g_strdup_printf ("%s %s", method->provider_name, method->plan_name); + else + id = g_strdup_printf ("%s connection", method->provider_name); + uuid = nm_utils_uuid_generate (); + g_object_set (setting, + NM_SETTING_CONNECTION_ID, id, + NM_SETTING_CONNECTION_TYPE, NM_SETTING_CDMA_SETTING_NAME, + NM_SETTING_CONNECTION_AUTOCONNECT, FALSE, + NM_SETTING_CONNECTION_UUID, uuid, + NULL); + nm_connection_add_setting (connection, setting); + } + + done: + connect_3g (connection, canceled, user_data); + nma_mobile_wizard_destroy (wizard); +} + +static void +gsm_mobile_wizard_done (NMAMobileWizard *wizard, + gboolean canceled, + NMAMobileWizardAccessMethod *method, + gpointer user_data) +{ + NMConnection *connection = NULL; + + if (!canceled && method) { + NMSetting *setting; + g_autofree gchar *uuid = NULL; + g_autofree gchar *id = NULL; + + if (method->devtype != NM_DEVICE_MODEM_CAPABILITY_GSM_UMTS) { + g_warning ("Unexpected device type (not GSM)."); + canceled = TRUE; + goto done; + } + + connection = nm_simple_connection_new (); + + setting = nm_setting_gsm_new (); + g_object_set (setting, + NM_SETTING_GSM_NUMBER, "*99#", + NM_SETTING_GSM_USERNAME, method->username, + NM_SETTING_GSM_PASSWORD, method->password, + NM_SETTING_GSM_APN, method->gsm_apn, + NULL); + nm_connection_add_setting (connection, setting); + + /* Serial setting */ + setting = nm_setting_serial_new (); + g_object_set (setting, + NM_SETTING_SERIAL_BAUD, 115200, + NM_SETTING_SERIAL_BITS, 8, + NM_SETTING_SERIAL_PARITY, 'n', + NM_SETTING_SERIAL_STOPBITS, 1, + NULL); + nm_connection_add_setting (connection, setting); + + nm_connection_add_setting (connection, nm_setting_ppp_new ()); + + setting = nm_setting_connection_new (); + if (method->plan_name) + id = g_strdup_printf ("%s %s", method->provider_name, method->plan_name); + else + id = g_strdup_printf ("%s connection", method->provider_name); + uuid = nm_utils_uuid_generate (); + g_object_set (setting, + NM_SETTING_CONNECTION_ID, id, + NM_SETTING_CONNECTION_TYPE, NM_SETTING_GSM_SETTING_NAME, + NM_SETTING_CONNECTION_AUTOCONNECT, FALSE, + NM_SETTING_CONNECTION_UUID, uuid, + NULL); + nm_connection_add_setting (connection, setting); + } + +done: + connect_3g (connection, canceled, user_data); + nma_mobile_wizard_destroy (wizard); +} + +static void +toplevel_shown (GtkWindow *toplevel, + GParamSpec *pspec, + NMAMobileWizard *wizard) +{ + gboolean visible = FALSE; + + g_object_get (G_OBJECT (toplevel), "visible", &visible, NULL); + if (visible) + nma_mobile_wizard_present (wizard); +} + +static gboolean +show_wizard_idle_cb (NMAMobileWizard *wizard) +{ + nma_mobile_wizard_present (wizard); + return FALSE; +} + +void +cc_network_panel_connect_to_3g_network (GtkWidget *toplevel, + NMClient *client, + NMDevice *device) +{ + MobileDialogClosure *closure; + NMAMobileWizard *wizard; + NMDeviceModemCapabilities caps; + gboolean visible = FALSE; + + g_debug ("connect to 3g"); + if (!NM_IS_DEVICE_MODEM (device)) { + g_warning ("Network panel loaded with connect-3g but the selected device" + " is not a modem"); + return; + } + + closure = g_slice_new (MobileDialogClosure); + closure->client = g_object_ref (client); + closure->device = g_object_ref (device); + + caps = nm_device_modem_get_current_capabilities (NM_DEVICE_MODEM (device)); + if (caps & NM_DEVICE_MODEM_CAPABILITY_GSM_UMTS) { + wizard = nma_mobile_wizard_new (GTK_WINDOW (toplevel), NULL, NM_DEVICE_MODEM_CAPABILITY_GSM_UMTS, FALSE, + gsm_mobile_wizard_done, closure); + if (wizard == NULL) { + g_warning ("failed to construct GSM wizard"); + return; + } + } else if (caps & NM_DEVICE_MODEM_CAPABILITY_CDMA_EVDO) { + wizard = nma_mobile_wizard_new (GTK_WINDOW (toplevel), NULL, NM_DEVICE_MODEM_CAPABILITY_CDMA_EVDO, FALSE, + cdma_mobile_wizard_done, closure); + if (wizard == NULL) { + g_warning ("failed to construct CDMA wizard"); + return; + } + } else { + g_warning ("Network panel loaded with connect-3g but the selected device" + " does not support GSM or CDMA"); + mobile_dialog_closure_free (closure); + return; + } + + g_object_get (G_OBJECT (toplevel), "visible", &visible, NULL); + if (visible) { + g_debug ("Scheduling showing the Mobile wizard"); + g_idle_add ((GSourceFunc) show_wizard_idle_cb, wizard); + } else { + g_debug ("Will show wizard a bit later, toplevel is not visible"); + g_signal_connect (G_OBJECT (toplevel), "notify::visible", + G_CALLBACK (toplevel_shown), wizard); + } +} diff --git a/panels/network/network-dialogs.h b/panels/network/network-dialogs.h new file mode 100644 index 0000000..54251c3 --- /dev/null +++ b/panels/network/network-dialogs.h @@ -0,0 +1,38 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2011 Giovanni Campagna <scampa.giovanni@gmail.com> + * + * 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. + */ + +#pragma once + +#include <NetworkManager.h> +#include <gtk/gtk.h> + +void cc_network_panel_create_wifi_network (GtkWidget *toplevel, + NMClient *client); + +void cc_network_panel_connect_to_hidden_network (GtkWidget *toplevel, + NMClient *client); + +void cc_network_panel_connect_to_8021x_network (GtkWidget *toplevel, + NMClient *client, + NMDevice *device, + const gchar *arg_access_point); + +void cc_network_panel_connect_to_3g_network (GtkWidget *toplevel, + NMClient *client, + NMDevice *device); diff --git a/panels/network/network-ethernet.ui b/panels/network/network-ethernet.ui new file mode 100644 index 0000000..3651fa8 --- /dev/null +++ b/panels/network/network-ethernet.ui @@ -0,0 +1,70 @@ +<?xml version="1.0" encoding="UTF-8"?> +<interface> + <!-- interface-requires gtk+ 3.0 --> + <template class="NetDeviceEthernet" parent="AdwPreferencesGroup"> + <property name="title" translatable="yes">Wired</property> + <property name="header-suffix"> + <object class="GtkButton"> + <property name="icon_name">list-add-symbolic</property> + <accessibility> + <property name="label" translatable="Yes">Add Ethernet connection</property> + </accessibility> + <signal name="clicked" handler="add_profile_button_clicked_cb" object="NetDeviceEthernet" swapped="yes"/> + <style> + <class name="flat" /> + </style> + </object> + </property> + + <child> + <object class="GtkStack" id="connection_stack"> + <child> + <object class="GtkListBox" id="connection_list"> + <property name="selection-mode">none</property> + <signal name="row-activated" handler="connection_list_row_activated_cb" object="NetDeviceEthernet" swapped="yes"/> + <style> + <class name="boxed-list" /> + </style> + </object> + </child> + + <!-- Single profile row --> + <child> + <object class="GtkListBox" id="details_listbox"> + <property name="selection_mode">none</property> + <style> + <class name="boxed-list" /> + </style> + <child> + <object class="AdwActionRow" id="details_row"> + <property name="activatable-widget">device_off_switch</property> + <child> + <object class="GtkSwitch" id="device_off_switch"> + <property name="valign">center</property> + <accessibility> + <property name="label" translatable="yes">Active</property> + </accessibility> + <signal name="notify::active" handler="device_off_switch_changed_cb" object="NetDeviceEthernet" swapped="yes"/> + </object> + </child> + <child> + <object class="GtkButton" id="details_button"> + <property name="valign">center</property> + <property name="icon-name">emblem-system-symbolic</property> + <signal name="clicked" handler="details_button_clicked_cb" object="NetDeviceEthernet" swapped="yes"/> + <accessibility> + <property name="label" translatable="yes">Options…</property> + </accessibility> + <style> + <class name="flat"/> + </style> + </object> + </child> + </object> + </child> + </object> + </child> + </object> + </child> + </template> +</interface> diff --git a/panels/network/network-mobile.ui b/panels/network/network-mobile.ui new file mode 100644 index 0000000..e5d7f55 --- /dev/null +++ b/panels/network/network-mobile.ui @@ -0,0 +1,334 @@ +<?xml version="1.0" encoding="UTF-8"?> +<interface> + <!-- interface-requires gtk+ 3.0 --> + <object class="GtkListStore" id="mobile_connections_list_store"> + <columns> + <!-- column-name id --> + <column type="gchararray"/> + <!-- column-name title --> + <column type="gchararray"/> + </columns> + </object> + <template class="NetDeviceMobile" parent="GtkBox"> + <property name="margin-top">12</property> + <property name="margin-bottom">12</property> + <property name="margin-start">12</property> + <property name="margin-end">12</property> + <property name="orientation">vertical</property> + <property name="spacing">6</property> + <child> + <object class="GtkGrid"> + <property name="valign">start</property> + <property name="row_spacing">10</property> + <property name="column_spacing">6</property> + <child> + <object class="GtkLabel" id="imei_heading_label"> + <property name="xalign">1</property> + <property name="label" translatable="yes">IMEI</property> + <property name="mnemonic_widget">imei_label</property> + <layout> + <property name="column">0</property> + <property name="row">1</property> + </layout> + <style> + <class name="dim-label"/> + </style> + </object> + </child> + <child> + <object class="GtkLabel" id="provider_heading_label"> + <property name="xalign">1</property> + <property name="label" translatable="yes">Provider</property> + <property name="mnemonic_widget">provider_label</property> + <layout> + <property name="column">0</property> + <property name="row">3</property> + </layout> + <style> + <class name="dim-label"/> + </style> + </object> + </child> + <child> + <object class="GtkLabel" id="imei_label"> + <property name="xalign">0</property> + <property name="label">1234567890</property> + <property name="selectable">True</property> + <property name="max-width-chars">50</property> + <property name="ellipsize">end</property> + <layout> + <property name="column">1</property> + <property name="row">1</property> + <property name="column-span">2</property> + </layout> + </object> + </child> + <child> + <object class="GtkLabel" id="provider_label"> + <property name="xalign">0</property> + <property name="label">SuperTel Supremo </property> + <property name="selectable">True</property> + <property name="max-width-chars">50</property> + <property name="ellipsize">end</property> + <layout> + <property name="column">1</property> + <property name="row">3</property> + <property name="column-span">2</property> + </layout> + </object> + </child> + <child> + <object class="GtkLabel" id="ipv4_label"> + <property name="xalign">0</property> + <property name="label">127.0.0.1</property> + <property name="selectable">True</property> + <property name="max-width-chars">50</property> + <property name="ellipsize">end</property> + <layout> + <property name="column">1</property> + <property name="row">4</property> + <property name="column-span">2</property> + </layout> + </object> + </child> + <child> + <object class="GtkLabel" id="ipv6_label"> + <property name="xalign">0</property> + <property name="selectable">True</property> + <property name="max-width-chars">50</property> + <property name="ellipsize">end</property> + <layout> + <property name="column">1</property> + <property name="row">5</property> + <property name="column-span">2</property> + </layout> + </object> + </child> + <child> + <object class="GtkLabel" id="route_label"> + <property name="xalign">0</property> + <property name="selectable">True</property> + <property name="max-width-chars">50</property> + <property name="ellipsize">end</property> + <layout> + <property name="column">1</property> + <property name="row">6</property> + <property name="column-span">2</property> + </layout> + </object> + </child> + <child> + <object class="GtkLabel" id="dns4_label"> + <property name="xalign">0</property> + <property name="yalign">0</property> + <property name="wrap">True</property> + <property name="selectable">True</property> + <property name="max-width-chars">50</property> + <property name="ellipsize">end</property> + <layout> + <property name="column">1</property> + <property name="row">7</property> + <property name="column-span">2</property> + </layout> + </object> + </child> + <child> + <object class="GtkLabel" id="dns6_label"> + <property name="xalign">0</property> + <property name="yalign">0</property> + <property name="wrap">True</property> + <property name="selectable">True</property> + <property name="max-width-chars">50</property> + <property name="ellipsize">end</property> + <layout> + <property name="column">1</property> + <property name="row">8</property> + <property name="column-span">2</property> + </layout> + </object> + </child> + <child> + <object class="GtkAlignment"> + <property name="halign">end</property> + <property name="valign">start</property> + <layout> + <property name="column">2</property> + <property name="row">0</property> + </layout> + </object> + </child> + <child> + <object class="GtkLabel" id="ipv4_heading_label"> + <property name="xalign">1</property> + <property name="label" translatable="yes">IP Address</property> + <property name="mnemonic_widget">ipv4_label</property> + <layout> + <property name="column">0</property> + <property name="row">4</property> + </layout> + <style> + <class name="dim-label"/> + </style> + </object> + </child> + <child> + <object class="GtkLabel" id="ipv6_heading_label"> + <property name="xalign">1</property> + <property name="label" translatable="yes">IPv6 Address</property> + <property name="mnemonic_label">ipv6_label</property> + <layout> + <property name="column">0</property> + <property name="row">5</property> + </layout> + <style> + <class name="dim-label"/> + </style> + </object> + </child> + <child> + <object class="GtkLabel" id="route_heading_label"> + <property name="xalign">1</property> + <property name="label" translatable="yes">Default Route</property> + <property name="mnemonic_label">route_label</property> + <layout> + <property name="column">0</property> + <property name="row">6</property> + </layout> + <style> + <class name="dim-label"/> + </style> + </object> + </child> + <child> + <object class="GtkLabel" id="dns4_heading_label"> + <property name="xalign">1</property> + <property name="yalign">0</property> + <property name="label" translatable="yes">DNS</property> + <property name="mnemonic_widget">dns4_label</property> + <layout> + <property name="column">0</property> + <property name="row">7</property> + </layout> + <style> + <class name="dim-label"/> + </style> + </object> + </child> + <child> + <object class="GtkLabel" id="dns6_heading_label"> + <property name="xalign">1</property> + <property name="yalign">0</property> + <property name="label" translatable="yes">DNS</property> + <property name="mnemonic_widget">dns6_label</property> + <layout> + <property name="column">0</property> + <property name="row">8</property> + </layout> + <style> + <class name="dim-label"/> + </style> + </object> + </child> + <child> + <object class="GtkLabel" id="network_label"> + <property name="xalign">1</property> + <property name="label" translatable="yes">Network</property> + <layout> + <property name="column">0</property> + <property name="row">2</property> + </layout> + <style> + <class name="dim-label"/> + </style> + </object> + </child> + <child> + <object class="GtkComboBox" id="network_combo"> + <property name="model">mobile_connections_list_store</property> + <property name="entry_text_column">1</property> + <signal name="changed" handler="network_combo_changed_cb" object="NetDeviceMobile" swapped="yes"/> + <layout> + <property name="column">1</property> + <property name="row">2</property> + </layout> + <child> + <object class="GtkCellRendererText"/> + <attributes> + <attribute name="text">1</attribute> + </attributes> + </child> + </object> + </child> + <child> + <object class="GtkBox"> + <property name="spacing">6</property> + <layout> + <property name="column">0</property> + <property name="row">0</property> + <property name="column-span">3</property> + </layout> + <child> + <object class="GtkImage"> + <property name="halign">end</property> + <property name="valign">start</property> + <property name="xalign">1</property> + <property name="pixel_size">48</property> + <property name="icon_name">network-cellular-connected</property> + <property name="icon-size">6</property> + </object> + </child> + <child> + <object class="GtkBox"> + <property name="valign">start</property> + <property name="hexpand">True</property> + <property name="spacing">3</property> + <property name="orientation">vertical</property> + <child> + <object class="GtkLabel" id="device_label"> + <property name="xalign">0</property> + <property name="label">Mobile Broadband</property> + <property name="ellipsize">end</property> + <attributes> + <attribute name="weight" value="bold"/> + <attribute name="scale" value="1.2"/> + </attributes> + </object> + </child> + <child> + <object class="GtkLabel" id="status_label"> + <property name="xalign">0</property> + <property name="label">Not connected</property> + </object> + </child> + </object> + </child> + <child> + <object class="GtkSwitch" id="device_off_switch"> + <property name="halign">end</property> + <property name="valign">center</property> + <accessibility> + <property name="label" translatable="yes">Active</property> + </accessibility> + <signal name="notify::active" handler="device_off_switch_changed_cb" object="NetDeviceMobile" swapped="yes"/> + </object> + </child> + </object> + </child> + </object> + </child> + <child> + <object class="GtkButton" id="options_button"> + <property name="halign">end</property> + <property name="valign">end</property> + <property name="hexpand">True</property> + <property name="vexpand">True</property> + <property name="margin-top">12</property> + <property name="icon_name">emblem-system-symbolic</property> + <signal name="clicked" handler="options_button_clicked_cb" object="NetDeviceMobile" swapped="yes"/> + <accessibility> + <property name="label" translatable="yes">Options…</property> + </accessibility> + </object> + </child> + </template> +</interface> diff --git a/panels/network/network-proxy.ui b/panels/network/network-proxy.ui new file mode 100644 index 0000000..f89fa04 --- /dev/null +++ b/panels/network/network-proxy.ui @@ -0,0 +1,404 @@ +<?xml version="1.0" encoding="UTF-8"?> +<interface> + <!-- interface-requires gtk+ 3.0 --> + <object class="GtkAdjustment" id="proxy_port_ftp_adjustment"> + <property name="upper">65535</property> + <property name="step_increment">1</property> + </object> + <object class="GtkAdjustment" id="proxy_port_http_adjustment"> + <property name="upper">65535</property> + <property name="step_increment">1</property> + </object> + <object class="GtkAdjustment" id="proxy_port_https_adjustment"> + <property name="upper">65535</property> + <property name="step_increment">1</property> + </object> + <object class="GtkAdjustment" id="proxy_port_socks_adjustment"> + <property name="upper">65535</property> + <property name="step_increment">1</property> + </object> + <template class="NetProxy" parent="AdwBin"> + <child> + <object class="GtkListBox"> + <property name="selection_mode">none</property> + <style> + <class name="boxed-list" /> + </style> + <child> + <object class="GtkListBoxRow"> + <property name="activatable">False</property> + <child> + <object class="GtkBox"> + <property name="spacing">12</property> + <property name="margin_top">8</property> + <property name="margin_bottom">8</property> + <property name="margin_start">12</property> + <property name="margin_end">12</property> + <child> + <object class="GtkLabel"> + <property name="hexpand">True</property> + <property name="label" translatable="yes">Network Proxy</property> + <property name="ellipsize">end</property> + <property name="xalign">0.0</property> + <attributes> + <attribute name="weight" value="bold" /> + </attributes> + </object> + </child> + <child> + <object class="GtkLabel" id="status_label"> + <property name="margin_top">8</property> + <property name="margin_bottom">8</property> + <style> + <class name="dim-label" /> + </style> + </object> + </child> + <child> + <object class="GtkButton"> + <signal name="clicked" handler="show_dialog_cb" object="NetProxy" swapped="yes"/> + <property name="icon_name">emblem-system-symbolic</property> + <accessibility> + <property name="label" translatable="yes">Options…</property> + </accessibility> + <style> + <class name="flat"/> + </style> + </object> + </child> + </object> + </child> + </object> + </child> + </object> + </child> + </template> + <object class="GtkDialog" id="dialog"> + <property name="use_header_bar">1</property> + <property name="default_height">350</property> + <property name="modal">True</property> + <property name="destroy_with_parent">True</property> + <property name="hide-on-close">True</property> + <property name="title" translatable="yes">Network Proxy</property> + <child> + <object class="GtkBox"> + <property name="margin-top">18</property> + <property name="margin-bottom">18</property> + <property name="margin-start">18</property> + <property name="margin-end">18</property> + <property name="orientation">vertical</property> + <property name="spacing">6</property> + <child> + <object class="GtkCheckButton" id="automatic_radio"> + <property name="label" translatable="yes">Automatic</property> + <property name="group">none_radio</property> + <signal name="toggled" handler="panel_proxy_mode_radio_changed_cb" object="NetProxy" swapped="yes"/> + </object> + </child> + <child> + <object class="GtkCheckButton" id="manual_radio"> + <property name="label" translatable="yes">Manual</property> + <property name="group">none_radio</property> + <signal name="toggled" handler="panel_proxy_mode_radio_changed_cb" object="NetProxy" swapped="yes"/> + </object> + </child> + <child> + <object class="GtkCheckButton" id="none_radio"> + <property name="label" translatable="yes">Disabled</property> + <signal name="toggled" handler="panel_proxy_mode_radio_changed_cb" object="NetProxy" swapped="yes"/> + </object> + </child> + <child> + <object class="GtkStack" id="stack"> + <property name="transition_type">crossfade</property> + + <!-- Disabled (empty box) --> + <child> + <object class="GtkStackPage"> + <property name="name">disabled</property> + <property name="child"> + <object class="GtkBox" /> + </property> + </object> + </child> + + <!-- Manual --> + <child> + <object class="GtkStackPage"> + <property name="name">manual</property> + <property name="child"> + <object class="GtkGrid"> + <property name="valign">start</property> + <property name="margin-top">12</property> + <property name="margin-bottom">12</property> + <property name="margin-start">12</property> + <property name="margin-end">12</property> + <property name="row_spacing">10</property> + <property name="column_spacing">6</property> + <child> + <object class="GtkLabel"> + <property name="xalign">1</property> + <property name="label" translatable="yes">_HTTP Proxy</property> + <property name="use_underline">True</property> + <property name="mnemonic_widget">proxy_http_entry</property> + <layout> + <property name="column">0</property> + <property name="row">3</property> + <property name="column-span">1</property> + <property name="row-span">1</property> + </layout> + <style> + <class name="dim-label"/> + </style> + </object> + </child> + <child> + <object class="GtkLabel"> + <property name="xalign">1</property> + <property name="label" translatable="yes">H_TTPS Proxy</property> + <property name="use_underline">True</property> + <property name="mnemonic_widget">proxy_https_entry</property> + <layout> + <property name="column">0</property> + <property name="row">4</property> + <property name="column-span">1</property> + <property name="row-span">1</property> + </layout> + <style> + <class name="dim-label"/> + </style> + </object> + </child> + <child> + <object class="GtkLabel"> + <property name="xalign">1</property> + <property name="label" translatable="yes">_FTP Proxy</property> + <property name="use_underline">True</property> + <property name="mnemonic_widget">proxy_ftp_entry</property> + <layout> + <property name="column">0</property> + <property name="row">5</property> + <property name="column-span">1</property> + <property name="row-span">1</property> + </layout> + <style> + <class name="dim-label"/> + </style> + </object> + </child> + <child> + <object class="GtkLabel"> + <property name="xalign">1</property> + <property name="label" translatable="yes">_Socks Host</property> + <property name="use_underline">True</property> + <property name="mnemonic_widget">proxy_socks_entry</property> + <layout> + <property name="column">0</property> + <property name="row">6</property> + <property name="column-span">1</property> + <property name="row-span">1</property> + </layout> + <style> + <class name="dim-label"/> + </style> + </object> + </child> + <child> + <object class="GtkLabel"> + <property name="xalign">1</property> + <property name="label" translatable="yes">_Ignore Hosts</property> + <property name="use_underline">True</property> + <property name="mnemonic_widget">proxy_ignore_entry</property> + <layout> + <property name="column">0</property> + <property name="row">7</property> + <property name="column-span">1</property> + <property name="row-span">1</property> + </layout> + <style> + <class name="dim-label"/> + </style> + </object> + </child> + <child> + <object class="GtkEntry" id="proxy_http_entry"> + <property name="invisible_char">●</property> + <property name="invisible_char_set">True</property> + <layout> + <property name="column">1</property> + <property name="row">3</property> + <property name="column-span">1</property> + <property name="row-span">1</property> + </layout> + </object> + </child> + <child> + <object class="GtkSpinButton"> + <property name="xalign">1</property> + <property name="adjustment">proxy_port_http_adjustment</property> + <layout> + <property name="column">2</property> + <property name="row">3</property> + <property name="column-span">1</property> + <property name="row-span">1</property> + </layout> + <accessibility> + <property name="label" translatable="yes">HTTP proxy port</property> + </accessibility> + </object> + </child> + <child> + <object class="GtkEntry" id="proxy_https_entry"> + <property name="invisible_char">●</property> + <property name="invisible_char_set">True</property> + <layout> + <property name="column">1</property> + <property name="row">4</property> + <property name="column-span">1</property> + <property name="row-span">1</property> + </layout> + </object> + </child> + <child> + <object class="GtkEntry" id="proxy_ftp_entry"> + <property name="invisible_char">●</property> + <property name="invisible_char_set">True</property> + <layout> + <property name="column">1</property> + <property name="row">5</property> + <property name="column-span">1</property> + <property name="row-span">1</property> + </layout> + </object> + </child> + <child> + <object class="GtkEntry" id="proxy_socks_entry"> + <property name="invisible_char">●</property> + <property name="invisible_char_set">True</property> + <layout> + <property name="column">1</property> + <property name="row">6</property> + <property name="column-span">1</property> + <property name="row-span">1</property> + </layout> + </object> + </child> + <child> + <object class="GtkEntry" id="proxy_ignore_entry"> + <property name="invisible_char">●</property> + <property name="invisible_char_set">True</property> + <layout> + <property name="column">1</property> + <property name="row">7</property> + <property name="column-span">2</property> + <property name="row-span">1</property> + </layout> + </object> + </child> + <child> + <object class="GtkSpinButton"> + <property name="xalign">1</property> + <property name="adjustment">proxy_port_https_adjustment</property> + <layout> + <property name="column">2</property> + <property name="row">4</property> + <property name="column-span">1</property> + <property name="row-span">1</property> + </layout> + <accessibility> + <property name="label" translatable="yes">HTTPS proxy port</property> + </accessibility> + </object> + </child> + <child> + <object class="GtkSpinButton"> + <property name="xalign">1</property> + <property name="adjustment">proxy_port_ftp_adjustment</property> + <layout> + <property name="column">2</property> + <property name="row">5</property> + <property name="column-span">1</property> + <property name="row-span">1</property> + </layout> + <accessibility> + <property name="label" translatable="yes">FTP proxy port</property> + </accessibility> + </object> + </child> + <child> + <object class="GtkSpinButton"> + <property name="xalign">1</property> + <property name="adjustment">proxy_port_socks_adjustment</property> + <layout> + <property name="column">2</property> + <property name="row">6</property> + <property name="column-span">1</property> + <property name="row-span">1</property> + </layout> + <accessibility> + <property name="label" translatable="yes">Socks proxy port</property> + </accessibility> + </object> + </child> + </object> + </property> + </object> + </child> + + <!-- Automatic --> + <child> + <object class="GtkStackPage"> + <property name="name">automatic</property> + <property name="child"> + <object class="GtkGrid"> + <property name="row_spacing">12</property> + <property name="column_spacing">12</property> + <child> + <object class="GtkLabel"> + <property name="xalign">1</property> + <property name="label" translatable="yes">_Configuration URL</property> + <property name="use_underline">True</property> + <property name="mnemonic_widget">proxy_url_entry</property> + <layout> + <property name="row">0</property> + <property name="column">0</property> + </layout> + <style> + <class name="dim-label"/> + </style> + </object> + </child> + <child> + <object class="GtkEntry" id="proxy_url_entry"> + <property name="invisible_char">●</property> + <property name="invisible_char_set">True</property> + <layout> + <property name="row">0</property> + <property name="column">1</property> + </layout> + </object> + </child> + <child> + <object class="GtkLabel" id="proxy_warning_label"> + <property name="visible">False</property> + <property name="xalign">0</property> + <property name="wrap">True</property> + <property name="width_chars">50</property> + <layout> + <property name="row">1</property> + <property name="column">0</property> + <property name="column-span">2</property> + </layout> + </object> + </child> + </object> + </property> + </object> + </child> + + </object> + </child> + </object> + </child> + </object> +</interface> diff --git a/panels/network/network-vpn.ui b/panels/network/network-vpn.ui new file mode 100644 index 0000000..c0c2913 --- /dev/null +++ b/panels/network/network-vpn.ui @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="UTF-8"?> +<interface> + <!-- interface-requires gtk+ 3.0 --> + <template class="NetVpn" parent="AdwActionRow"> + <property name="activatable-widget">device_off_switch</property> + <child type="suffix"> + <object class="GtkSwitch" id="device_off_switch"> + <property name="valign">center</property> + <signal name="notify::active" handler="device_off_toggled" object="NetVpn" swapped="yes"/> + <accessibility> + <property name="label" translatable="yes">Turn VPN connection off</property> + </accessibility> + </object> + </child> + <child type="suffix"> + <object class="GtkButton" id="options_button"> + <property name="valign">center</property> + <property name="icon_name">emblem-system-symbolic</property> + <signal name="clicked" handler="edit_connection" object="NetVpn" swapped="yes"/> + <accessibility> + <property name="label" translatable="yes">Options…</property> + </accessibility> + <style> + <class name="flat"/> + </style> + </object> + </child> + </template> +</interface> diff --git a/panels/network/network-wifi.ui b/panels/network/network-wifi.ui new file mode 100644 index 0000000..be6500d --- /dev/null +++ b/panels/network/network-wifi.ui @@ -0,0 +1,153 @@ +<?xml version="1.0" encoding="UTF-8"?> +<interface> + <!-- interface-requires gtk+ 3.0 --> + <template class="NetDeviceWifi" parent="AdwBin"> + + <child> + <object class="GtkStack" id="stack"> + <property name="valign">start</property> + <property name="vhomogeneous">False</property> + + <child> + <object class="GtkStackPage"> + <property name="child"> + <object class="GtkBox" id="listbox_box"> + <property name="orientation">vertical</property> + <property name="height-request">140</property> + </object> + </property> + </object> + </child> + + <!-- Wi-Fi Hotspot deails --> + <child> + <object class="GtkStackPage"> + <property name="child"> + <object class="GtkListBox" id="hotspot_box"> + <property name="selection-mode">none</property> + <style> + <class name="boxed-list" /> + </style> + <child> + <object class="CcListRow" id="hotspot_name_row"> + <property name="activatable">False</property> + <property name="title" context="Wi-Fi Hotspot" translatable="yes">Network Name</property> + </object> + </child> + <child> + <object class="CcListRow" id="hotspot_security_row"> + <property name="activatable">False</property> + <property name="title" context="Wi-Fi Hotspot" translatable="yes">Security type</property> + </object> + </child> + <child> + <object class="CcListRow" id="hotspot_password_row"> + <property name="activatable">False</property> + <property name="title" context="Wi-Fi Hotspot" translatable="yes">Password</property> + </object> + </child> + </object> + </property> + </object> + </child> + + </object> + </child> + + </template> + + <!-- Center widget --> + <object class="GtkBox" id="center_box"> + <property name="orientation">vertical</property> + <property name="halign">center</property> + <property name="valign">center</property> + <child> + <object class="GtkLabel" id="title_label"> + <property name="label" translatable="yes">Wi-Fi</property> + <style> + <class name="title" /> + </style> + </object> + </child> + <child> + <object class="GtkLabel" id="status_label"> + <style> + <class name="subtitle" /> + </style> + </object> + </child> + </object> + + <!-- Box with the On/Off switch + menu button --> + <object class="GtkBox" id="header_box"> + <property name="spacing">6</property> + <property name="halign">end</property> + <child> + <object class="GtkSwitch" id="device_off_switch"> + <property name="valign">center</property> + <signal name="notify::active" handler="device_off_switch_changed_cb" object="NetDeviceWifi" swapped="yes"/> + <accessibility> + <property name="label" translatable="yes">Turn Wi-Fi off</property> + </accessibility> + </object> + </child> + <child> + <object class="GtkMenuButton" id="header_button"> + <property name="popover">header_button_popover</property> + <property name="icon_name">view-more-symbolic</property> + <accessibility> + <property name="label" translatable="yes">More options…</property> + </accessibility> + </object> + </child> + </object> + + <!-- Menu Popover --> + <object class="GtkPopover" id="header_button_popover"> + <style> + <class name="menu" /> + </style> + <child> + <object class="GtkListBox"> + <property name="selection_mode">none</property> + <signal name="row_activated" handler="on_popover_row_activated_cb" /> + <child> + <object class="GtkListBoxRow" id="connect_hidden_row"> + <property name="activatable">True</property> + <child> + <object class="GtkLabel"> + <property name="label" translatable="yes">_Connect to Hidden Network…</property> + <property name="use_underline">True</property> + <property name="xalign">0</property> + </object> + </child> + </object> + </child> + <child> + <object class="GtkListBoxRow" id="start_hotspot_row"> + <property name="activatable">True</property> + <child> + <object class="GtkLabel"> + <property name="label" translatable="yes">_Turn On Wi-Fi Hotspot…</property> + <property name="use_underline">True</property> + <property name="xalign">0</property> + </object> + </child> + </object> + </child> + <child> + <object class="GtkListBoxRow"> + <property name="activatable">True</property> + <child> + <object class="GtkLabel"> + <property name="label" translatable="yes">_Known Wi-Fi Networks</property> + <property name="use_underline">True</property> + <property name="xalign">0</property> + </object> + </child> + </object> + </child> + </object> + </child> + </object> +</interface> diff --git a/panels/network/network.gresource.xml b/panels/network/network.gresource.xml new file mode 100644 index 0000000..d014387 --- /dev/null +++ b/panels/network/network.gresource.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="UTF-8"?> +<gresources> + <gresource prefix="/org/gnome/control-center/network"> + <!-- Network panel --> + <file preprocess="xml-stripblanks">cc-network-panel.ui</file> + <file preprocess="xml-stripblanks">cc-wifi-connection-row.ui</file> + <file preprocess="xml-stripblanks">cc-wifi-hotspot-dialog.ui</file> + <file preprocess="xml-stripblanks">network-bluetooth.ui</file> + <file preprocess="xml-stripblanks">network-proxy.ui</file> + <file preprocess="xml-stripblanks">network-vpn.ui</file> + <file preprocess="xml-stripblanks">network-wifi.ui</file> + <file preprocess="xml-stripblanks">network-mobile.ui</file> + <file preprocess="xml-stripblanks">network-ethernet.ui</file> + + <!-- Wi-Fi panel --> + <file preprocess="xml-stripblanks">cc-wifi-panel.ui</file> + <file>wifi-panel.css</file> + </gresource> + <gresource prefix="/org/gnome/Settings/icons/scalable/actions"> + <!-- Wi-Fi panel icons --> + <file preprocess="xml-stripblanks">lock-small-symbolic.svg</file> + <file preprocess="xml-stripblanks">warning-small-symbolic.svg</file> + </gresource> +</gresources> diff --git a/panels/network/panel-common.c b/panels/network/panel-common.c new file mode 100644 index 0000000..7654a56 --- /dev/null +++ b/panels/network/panel-common.c @@ -0,0 +1,449 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2010 Richard Hughes <richard@hughsie.com> + * Copyright (C) 2012 Thomas Bechtold <thomasbechtold@jpberlin.de> + * + * 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/gi18n.h> + +#include "panel-common.h" + +static const gchar * +device_state_to_localized_string (NMDeviceState state) +{ + const gchar *value = NULL; + + switch (state) { + case NM_DEVICE_STATE_UNKNOWN: + /* TRANSLATORS: device status */ + value = _("Status unknown"); + break; + case NM_DEVICE_STATE_UNMANAGED: + /* TRANSLATORS: device status */ + value = _("Unmanaged"); + break; + case NM_DEVICE_STATE_UNAVAILABLE: + /* TRANSLATORS: device status */ + value = _("Unavailable"); + break; + case NM_DEVICE_STATE_DISCONNECTED: + value = NULL; + break; + case NM_DEVICE_STATE_PREPARE: + case NM_DEVICE_STATE_CONFIG: + case NM_DEVICE_STATE_IP_CONFIG: + case NM_DEVICE_STATE_IP_CHECK: + /* TRANSLATORS: device status */ + value = _("Connecting"); + break; + case NM_DEVICE_STATE_NEED_AUTH: + /* TRANSLATORS: device status */ + value = _("Authentication required"); + break; + case NM_DEVICE_STATE_ACTIVATED: + /* TRANSLATORS: device status */ + value = _("Connected"); + break; + case NM_DEVICE_STATE_DEACTIVATING: + /* TRANSLATORS: device status */ + value = _("Disconnecting"); + break; + case NM_DEVICE_STATE_FAILED: + /* TRANSLATORS: device status */ + value = _("Connection failed"); + break; + default: + /* TRANSLATORS: device status */ + value = _("Status unknown (missing)"); + break; + } + return value; +} + +static const gchar * +device_state_reason_to_localized_string (NMDevice *device) +{ + const gchar *value = NULL; + NMDeviceStateReason state_reason; + + /* This only covers NMDeviceStateReasons that explain why a connection + * failed / can't be attempted, and aren't redundant with the state + * (eg, NM_DEVICE_STATE_REASON_CARRIER). + */ + + state_reason = nm_device_get_state_reason (device); + switch (state_reason) { + case NM_DEVICE_STATE_REASON_CONFIG_FAILED: + /* TRANSLATORS: device status reason */ + value = _("Configuration failed"); + break; + case NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE: + /* TRANSLATORS: device status reason */ + value = _("IP configuration failed"); + break; + case NM_DEVICE_STATE_REASON_IP_CONFIG_EXPIRED: + /* TRANSLATORS: device status reason */ + value = _("IP configuration expired"); + break; + case NM_DEVICE_STATE_REASON_NO_SECRETS: + /* TRANSLATORS: device status reason */ + value = _("Secrets were required, but not provided"); + break; + case NM_DEVICE_STATE_REASON_SUPPLICANT_DISCONNECT: + /* TRANSLATORS: device status reason */ + value = _("802.1x supplicant disconnected"); + break; + case NM_DEVICE_STATE_REASON_SUPPLICANT_CONFIG_FAILED: + /* TRANSLATORS: device status reason */ + value = _("802.1x supplicant configuration failed"); + break; + case NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED: + /* TRANSLATORS: device status reason */ + value = _("802.1x supplicant failed"); + break; + case NM_DEVICE_STATE_REASON_SUPPLICANT_TIMEOUT: + /* TRANSLATORS: device status reason */ + value = _("802.1x supplicant took too long to authenticate"); + break; + case NM_DEVICE_STATE_REASON_PPP_START_FAILED: + /* TRANSLATORS: device status reason */ + value = _("PPP service failed to start"); + break; + case NM_DEVICE_STATE_REASON_PPP_DISCONNECT: + /* TRANSLATORS: device status reason */ + value = _("PPP service disconnected"); + break; + case NM_DEVICE_STATE_REASON_PPP_FAILED: + /* TRANSLATORS: device status reason */ + value = _("PPP failed"); + break; + case NM_DEVICE_STATE_REASON_DHCP_START_FAILED: + /* TRANSLATORS: device status reason */ + value = _("DHCP client failed to start"); + break; + case NM_DEVICE_STATE_REASON_DHCP_ERROR: + /* TRANSLATORS: device status reason */ + value = _("DHCP client error"); + break; + case NM_DEVICE_STATE_REASON_DHCP_FAILED: + /* TRANSLATORS: device status reason */ + value = _("DHCP client failed"); + break; + case NM_DEVICE_STATE_REASON_SHARED_START_FAILED: + /* TRANSLATORS: device status reason */ + value = _("Shared connection service failed to start"); + break; + case NM_DEVICE_STATE_REASON_SHARED_FAILED: + /* TRANSLATORS: device status reason */ + value = _("Shared connection service failed"); + break; + case NM_DEVICE_STATE_REASON_AUTOIP_START_FAILED: + /* TRANSLATORS: device status reason */ + value = _("AutoIP service failed to start"); + break; + case NM_DEVICE_STATE_REASON_AUTOIP_ERROR: + /* TRANSLATORS: device status reason */ + value = _("AutoIP service error"); + break; + case NM_DEVICE_STATE_REASON_AUTOIP_FAILED: + /* TRANSLATORS: device status reason */ + value = _("AutoIP service failed"); + break; + case NM_DEVICE_STATE_REASON_MODEM_BUSY: + /* TRANSLATORS: device status reason */ + value = _("Line busy"); + break; + case NM_DEVICE_STATE_REASON_MODEM_NO_DIAL_TONE: + /* TRANSLATORS: device status reason */ + value = _("No dial tone"); + break; + case NM_DEVICE_STATE_REASON_MODEM_NO_CARRIER: + /* TRANSLATORS: device status reason */ + value = _("No carrier could be established"); + break; + case NM_DEVICE_STATE_REASON_MODEM_DIAL_TIMEOUT: + /* TRANSLATORS: device status reason */ + value = _("Dialing request timed out"); + break; + case NM_DEVICE_STATE_REASON_MODEM_DIAL_FAILED: + /* TRANSLATORS: device status reason */ + value = _("Dialing attempt failed"); + break; + case NM_DEVICE_STATE_REASON_MODEM_INIT_FAILED: + /* TRANSLATORS: device status reason */ + value = _("Modem initialization failed"); + break; + case NM_DEVICE_STATE_REASON_GSM_APN_FAILED: + /* TRANSLATORS: device status reason */ + value = _("Failed to select the specified APN"); + break; + case NM_DEVICE_STATE_REASON_GSM_REGISTRATION_NOT_SEARCHING: + /* TRANSLATORS: device status reason */ + value = _("Not searching for networks"); + break; + case NM_DEVICE_STATE_REASON_GSM_REGISTRATION_DENIED: + /* TRANSLATORS: device status reason */ + value = _("Network registration denied"); + break; + case NM_DEVICE_STATE_REASON_GSM_REGISTRATION_TIMEOUT: + /* TRANSLATORS: device status reason */ + value = _("Network registration timed out"); + break; + case NM_DEVICE_STATE_REASON_GSM_REGISTRATION_FAILED: + /* TRANSLATORS: device status reason */ + value = _("Failed to register with the requested network"); + break; + case NM_DEVICE_STATE_REASON_GSM_PIN_CHECK_FAILED: + /* TRANSLATORS: device status reason */ + value = _("PIN check failed"); + break; + case NM_DEVICE_STATE_REASON_FIRMWARE_MISSING: + /* TRANSLATORS: device status reason */ + value = _("Firmware for the device may be missing"); + break; + case NM_DEVICE_STATE_REASON_CONNECTION_REMOVED: + /* TRANSLATORS: device status reason */ + value = _("Connection disappeared"); + break; + case NM_DEVICE_STATE_REASON_CONNECTION_ASSUMED: + /* TRANSLATORS: device status reason */ + value = _("Existing connection was assumed"); + break; + case NM_DEVICE_STATE_REASON_MODEM_NOT_FOUND: + /* TRANSLATORS: device status reason */ + value = _("Modem not found"); + break; + case NM_DEVICE_STATE_REASON_BT_FAILED: + /* TRANSLATORS: device status reason */ + value = _("Bluetooth connection failed"); + break; + case NM_DEVICE_STATE_REASON_GSM_SIM_NOT_INSERTED: + /* TRANSLATORS: device status reason */ + value = _("SIM Card not inserted"); + break; + case NM_DEVICE_STATE_REASON_GSM_SIM_PIN_REQUIRED: + /* TRANSLATORS: device status reason */ + value = _("SIM Pin required"); + break; + case NM_DEVICE_STATE_REASON_GSM_SIM_PUK_REQUIRED: + /* TRANSLATORS: device status reason */ + value = _("SIM Puk required"); + break; + case NM_DEVICE_STATE_REASON_GSM_SIM_WRONG: + /* TRANSLATORS: device status reason */ + value = _("SIM wrong"); + break; + case NM_DEVICE_STATE_REASON_DEPENDENCY_FAILED: + /* TRANSLATORS: device status reason */ + value = _("Connection dependency failed"); + break; + default: + /* no StateReason to show */ + value = ""; + break; + } + + return value; +} + +static gchar * +get_mac_address_of_connection (NMConnection *connection) +{ + if (!connection) + return NULL; + + /* check the connection type */ + if (nm_connection_is_type (connection, + NM_SETTING_WIRELESS_SETTING_NAME)) { + /* check wireless settings */ + NMSettingWireless *s_wireless = nm_connection_get_setting_wireless (connection); + if (!s_wireless) + return NULL; + return g_strdup (nm_setting_wireless_get_mac_address (s_wireless)); + } else if (nm_connection_is_type (connection, + NM_SETTING_WIRED_SETTING_NAME)) { + /* check wired settings */ + NMSettingWired *s_wired = nm_connection_get_setting_wired (connection); + if (!s_wired) + return NULL; + return g_strdup (nm_setting_wired_get_mac_address (s_wired)); + } + /* no MAC address found */ + return NULL; +} + +/* returns TRUE if both MACs are equal */ +static gboolean +compare_mac_device_with_mac_connection (NMDevice *device, + NMConnection *connection) +{ + const gchar *mac_dev = NULL; + g_autofree gchar *mac_conn = NULL; + + mac_dev = nm_device_get_hw_address (device); + if (mac_dev == NULL) + return FALSE; + + mac_conn = get_mac_address_of_connection (connection); + if (mac_conn == NULL) + return FALSE; + + /* compare both MACs */ + return g_strcmp0 (mac_dev, mac_conn) == 0; +} + +gchar * +panel_device_status_to_localized_string (NMDevice *nm_device, + const gchar *speed) +{ + NMDeviceState state; + GString *string; + const gchar *state_str = NULL, *reason_str = NULL; + + string = g_string_new (NULL); + + state = nm_device_get_state (nm_device); + if (state == NM_DEVICE_STATE_UNAVAILABLE) { + if (nm_device_get_firmware_missing (nm_device)) { + /* TRANSLATORS: device status */ + state_str = _("Firmware missing"); + } else if (NM_IS_DEVICE_ETHERNET (nm_device) && + !nm_device_ethernet_get_carrier (NM_DEVICE_ETHERNET (nm_device))) { + /* TRANSLATORS: device status */ + state_str = _("Cable unplugged"); + } + } + if (!state_str) + state_str = device_state_to_localized_string (state); + if (state_str) + g_string_append (string, state_str); + + if (state > NM_DEVICE_STATE_UNAVAILABLE && speed) { + if (string->len) + g_string_append (string, " - "); + g_string_append (string, speed); + } else if (state == NM_DEVICE_STATE_UNAVAILABLE || + state == NM_DEVICE_STATE_DISCONNECTED || + state == NM_DEVICE_STATE_DEACTIVATING || + state == NM_DEVICE_STATE_FAILED) { + reason_str = device_state_reason_to_localized_string (nm_device); + if (*reason_str) { + if (string->len) + g_string_append (string, " - "); + g_string_append (string, reason_str); + } + } + + return g_string_free (string, FALSE); +} + +NMConnection * +net_device_get_find_connection (NMClient *client, NMDevice *device) +{ + GSList *list, *iterator; + NMConnection *connection = NULL; + NMActiveConnection *ac; + + /* is the device available in a active connection? */ + ac = nm_device_get_active_connection (device); + if (ac) + return (NMConnection*) nm_active_connection_get_connection (ac); + + /* not found in active connections - check all available connections */ + list = net_device_get_valid_connections (client, device); + if (list != NULL) { + /* if list has only one connection, use this connection */ + if (g_slist_length (list) == 1) { + connection = list->data; + goto out; + } + + /* is there connection with the MAC address of the device? */ + for (iterator = list; iterator; iterator = iterator->next) { + connection = iterator->data; + if (compare_mac_device_with_mac_connection (device, + connection)) { + goto out; + } + } + } + + /* no connection found for the given device */ + connection = NULL; +out: + g_slist_free (list); + return connection; +} + +GSList * +net_device_get_valid_connections (NMClient *client, NMDevice *device) +{ + GSList *valid; + NMConnection *connection; + NMSettingConnection *s_con; + NMActiveConnection *active_connection; + const char *active_uuid; + const GPtrArray *all; + GPtrArray *filtered; + guint i; + + all = nm_client_get_connections (client); + filtered = nm_device_filter_connections (device, all); + + active_connection = nm_device_get_active_connection (device); + active_uuid = active_connection ? nm_active_connection_get_uuid (active_connection) : NULL; + + valid = NULL; + for (i = 0; i < filtered->len; i++) { + connection = g_ptr_array_index (filtered, i); + s_con = nm_connection_get_setting_connection (connection); + if (!s_con) + continue; + + if (nm_setting_connection_get_master (s_con) && + g_strcmp0 (nm_setting_connection_get_uuid (s_con), active_uuid) != 0) + continue; + + valid = g_slist_prepend (valid, connection); + } + g_ptr_array_free (filtered, FALSE); + + return g_slist_reverse (valid); +} + +gchar * +net_device_get_ip6_addresses (NMIPConfig *ipv6_config) +{ + g_autoptr(GPtrArray) ipv6 = NULL; + GPtrArray *addresses; + + addresses = nm_ip_config_get_addresses (ipv6_config); + if (addresses->len == 0) { + return NULL; + } + ipv6 = g_ptr_array_sized_new (addresses->len + 1); + + for (int i = 0; i < addresses->len; i++) { + g_ptr_array_add (ipv6, (char *) nm_ip_address_get_address (g_ptr_array_index (addresses, i))); + } + g_ptr_array_add (ipv6, NULL); + return g_strjoinv ("\n", (char **) ipv6->pdata); +} diff --git a/panels/network/panel-common.h b/panels/network/panel-common.h new file mode 100644 index 0000000..32aed09 --- /dev/null +++ b/panels/network/panel-common.h @@ -0,0 +1,39 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2010 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. + */ + +#pragma once + +#include <NetworkManager.h> + +G_BEGIN_DECLS + +gchar *panel_device_status_to_localized_string (NMDevice *nm_device, + const gchar *speed); + +NMConnection *net_device_get_find_connection (NMClient *client, + NMDevice *device); + +GSList *net_device_get_valid_connections (NMClient *client, + NMDevice *device); + +gchar *net_device_get_ip6_addresses (NMIPConfig *ipv6_config); + +G_END_DECLS diff --git a/panels/network/qrcodegen.c b/panels/network/qrcodegen.c new file mode 100644 index 0000000..7cda965 --- /dev/null +++ b/panels/network/qrcodegen.c @@ -0,0 +1,1009 @@ +/* + * QR Code generator library (C) + * + * Copyright (c) Project Nayuki. (MIT License) + * https://www.nayuki.io/page/qr-code-generator-library + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * - The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * - The Software is provided "as is", without warranty of any kind, express or + * implied, including but not limited to the warranties of merchantability, + * fitness for a particular purpose and noninfringement. In no event shall the + * authors or copyright holders be liable for any claim, damages or other + * liability, whether in an action of contract, tort or otherwise, arising from, + * out of or in connection with the Software or the use or other dealings in the + * Software. + */ + +#include <assert.h> +#include <limits.h> +#include <stdlib.h> +#include <string.h> +#include "qrcodegen.h" + +#ifndef QRCODEGEN_TEST + #define testable static // Keep functions private +#else + #define testable // Expose private functions +#endif + + +/*---- Forward declarations for private functions ----*/ + +// Regarding all public and private functions defined in this source file: +// - They require all pointer/array arguments to be not null unless the array length is zero. +// - They only read input scalar/array arguments, write to output pointer/array +// arguments, and return scalar values; they are "pure" functions. +// - They don't read mutable global variables or write to any global variables. +// - They don't perform I/O, read the clock, print to console, etc. +// - They allocate a small and constant amount of stack memory. +// - They don't allocate or free any memory on the heap. +// - They don't recurse or mutually recurse. All the code +// could be inlined into the top-level public functions. +// - They run in at most quadratic time with respect to input arguments. +// Most functions run in linear time, and some in constant time. +// There are no unbounded loops or non-obvious termination conditions. +// - They are completely thread-safe if the caller does not give the +// same writable buffer to concurrent calls to these functions. + +testable void appendBitsToBuffer(unsigned int val, int numBits, uint8_t buffer[], int *bitLen); + +testable void addEccAndInterleave(uint8_t data[], int version, enum qrcodegen_Ecc ecl, uint8_t result[]); +testable int getNumDataCodewords(int version, enum qrcodegen_Ecc ecl); +testable int getNumRawDataModules(int ver); + +testable void calcReedSolomonGenerator(int degree, uint8_t result[]); +testable void calcReedSolomonRemainder(const uint8_t data[], int dataLen, + const uint8_t generator[], int degree, uint8_t result[]); +testable uint8_t finiteFieldMultiply(uint8_t x, uint8_t y); + +testable void initializeFunctionModules(int version, uint8_t qrcode[]); +static void drawWhiteFunctionModules(uint8_t qrcode[], int version); +static void drawFormatBits(enum qrcodegen_Ecc ecl, enum qrcodegen_Mask mask, uint8_t qrcode[]); +testable int getAlignmentPatternPositions(int version, uint8_t result[7]); +static void fillRectangle(int left, int top, int width, int height, uint8_t qrcode[]); + +static void drawCodewords(const uint8_t data[], int dataLen, uint8_t qrcode[]); +static void applyMask(const uint8_t functionModules[], uint8_t qrcode[], enum qrcodegen_Mask mask); +static long getPenaltyScore(const uint8_t qrcode[]); +static void addRunToHistory(unsigned char run, unsigned char history[7]); +static bool hasFinderLikePattern(unsigned char runHistory[7]); + +testable bool getModule(const uint8_t qrcode[], int x, int y); +testable void setModule(uint8_t qrcode[], int x, int y, bool isBlack); +testable void setModuleBounded(uint8_t qrcode[], int x, int y, bool isBlack); +static bool getBit(int x, int i); + +testable int calcSegmentBitLength(enum qrcodegen_Mode mode, size_t numChars); +testable int getTotalBits(const struct qrcodegen_Segment segs[], size_t len, int version); +static int numCharCountBits(enum qrcodegen_Mode mode, int version); + + + +/*---- Private tables of constants ----*/ + +// The set of all legal characters in alphanumeric mode, where each character +// value maps to the index in the string. For checking text and encoding segments. +static const char *ALPHANUMERIC_CHARSET = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:"; + +// For generating error correction codes. +testable const int8_t ECC_CODEWORDS_PER_BLOCK[4][41] = { + // Version: (note that index 0 is for padding, and is set to an illegal value) + //0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40 Error correction level + {-1, 7, 10, 15, 20, 26, 18, 20, 24, 30, 18, 20, 24, 26, 30, 22, 24, 28, 30, 28, 28, 28, 28, 30, 30, 26, 28, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30}, // Low + {-1, 10, 16, 26, 18, 24, 16, 18, 22, 22, 26, 30, 22, 22, 24, 24, 28, 28, 26, 26, 26, 26, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28}, // Medium + {-1, 13, 22, 18, 26, 18, 24, 18, 22, 20, 24, 28, 26, 24, 20, 30, 24, 28, 28, 26, 30, 28, 30, 30, 30, 30, 28, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30}, // Quartile + {-1, 17, 28, 22, 16, 22, 28, 26, 26, 24, 28, 24, 28, 22, 24, 24, 30, 28, 28, 26, 28, 30, 24, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30}, // High +}; + +#define qrcodegen_REED_SOLOMON_DEGREE_MAX 30 // Based on the table above + +// For generating error correction codes. +testable const int8_t NUM_ERROR_CORRECTION_BLOCKS[4][41] = { + // Version: (note that index 0 is for padding, and is set to an illegal value) + //0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40 Error correction level + {-1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 4, 4, 4, 4, 4, 6, 6, 6, 6, 7, 8, 8, 9, 9, 10, 12, 12, 12, 13, 14, 15, 16, 17, 18, 19, 19, 20, 21, 22, 24, 25}, // Low + {-1, 1, 1, 1, 2, 2, 4, 4, 4, 5, 5, 5, 8, 9, 9, 10, 10, 11, 13, 14, 16, 17, 17, 18, 20, 21, 23, 25, 26, 28, 29, 31, 33, 35, 37, 38, 40, 43, 45, 47, 49}, // Medium + {-1, 1, 1, 2, 2, 4, 4, 6, 6, 8, 8, 8, 10, 12, 16, 12, 17, 16, 18, 21, 20, 23, 23, 25, 27, 29, 34, 34, 35, 38, 40, 43, 45, 48, 51, 53, 56, 59, 62, 65, 68}, // Quartile + {-1, 1, 1, 2, 4, 4, 4, 5, 6, 8, 8, 11, 11, 16, 16, 18, 16, 19, 21, 25, 25, 25, 34, 30, 32, 35, 37, 40, 42, 45, 48, 51, 54, 57, 60, 63, 66, 70, 74, 77, 81}, // High +}; + +// For automatic mask pattern selection. +static const int PENALTY_N1 = 3; +static const int PENALTY_N2 = 3; +static const int PENALTY_N3 = 40; +static const int PENALTY_N4 = 10; + + + +/*---- High-level QR Code encoding functions ----*/ + +// Public function - see documentation comment in header file. +bool qrcodegen_encodeText(const char *text, uint8_t tempBuffer[], uint8_t qrcode[], + enum qrcodegen_Ecc ecl, int minVersion, int maxVersion, enum qrcodegen_Mask mask, bool boostEcl) { + + size_t textLen = strlen(text); + if (textLen == 0) + return qrcodegen_encodeSegmentsAdvanced(NULL, 0, ecl, minVersion, maxVersion, mask, boostEcl, tempBuffer, qrcode); + size_t bufLen = qrcodegen_BUFFER_LEN_FOR_VERSION(maxVersion); + + struct qrcodegen_Segment seg; + if (qrcodegen_isNumeric(text)) { + if (qrcodegen_calcSegmentBufferSize(qrcodegen_Mode_NUMERIC, textLen) > bufLen) + goto fail; + seg = qrcodegen_makeNumeric(text, tempBuffer); + } else if (qrcodegen_isAlphanumeric(text)) { + if (qrcodegen_calcSegmentBufferSize(qrcodegen_Mode_ALPHANUMERIC, textLen) > bufLen) + goto fail; + seg = qrcodegen_makeAlphanumeric(text, tempBuffer); + } else { + if (textLen > bufLen) + goto fail; + for (size_t i = 0; i < textLen; i++) + tempBuffer[i] = (uint8_t)text[i]; + seg.mode = qrcodegen_Mode_BYTE; + seg.bitLength = calcSegmentBitLength(seg.mode, textLen); + if (seg.bitLength == -1) + goto fail; + seg.numChars = (int)textLen; + seg.data = tempBuffer; + } + return qrcodegen_encodeSegmentsAdvanced(&seg, 1, ecl, minVersion, maxVersion, mask, boostEcl, tempBuffer, qrcode); + +fail: + qrcode[0] = 0; // Set size to invalid value for safety + return false; +} + + +// Public function - see documentation comment in header file. +bool qrcodegen_encodeBinary(uint8_t dataAndTemp[], size_t dataLen, uint8_t qrcode[], + enum qrcodegen_Ecc ecl, int minVersion, int maxVersion, enum qrcodegen_Mask mask, bool boostEcl) { + + struct qrcodegen_Segment seg; + seg.mode = qrcodegen_Mode_BYTE; + seg.bitLength = calcSegmentBitLength(seg.mode, dataLen); + if (seg.bitLength == -1) { + qrcode[0] = 0; // Set size to invalid value for safety + return false; + } + seg.numChars = (int)dataLen; + seg.data = dataAndTemp; + return qrcodegen_encodeSegmentsAdvanced(&seg, 1, ecl, minVersion, maxVersion, mask, boostEcl, dataAndTemp, qrcode); +} + + +// Appends the given number of low-order bits of the given value to the given byte-based +// bit buffer, increasing the bit length. Requires 0 <= numBits <= 16 and val < 2^numBits. +testable void appendBitsToBuffer(unsigned int val, int numBits, uint8_t buffer[], int *bitLen) { + assert(0 <= numBits && numBits <= 16 && (unsigned long)val >> numBits == 0); + for (int i = numBits - 1; i >= 0; i--, (*bitLen)++) + buffer[*bitLen >> 3] |= ((val >> i) & 1) << (7 - (*bitLen & 7)); +} + + + +/*---- Low-level QR Code encoding functions ----*/ + +// Public function - see documentation comment in header file. +bool qrcodegen_encodeSegments(const struct qrcodegen_Segment segs[], size_t len, + enum qrcodegen_Ecc ecl, uint8_t tempBuffer[], uint8_t qrcode[]) { + return qrcodegen_encodeSegmentsAdvanced(segs, len, ecl, + qrcodegen_VERSION_MIN, qrcodegen_VERSION_MAX, -1, true, tempBuffer, qrcode); +} + + +// Public function - see documentation comment in header file. +bool qrcodegen_encodeSegmentsAdvanced(const struct qrcodegen_Segment segs[], size_t len, enum qrcodegen_Ecc ecl, + int minVersion, int maxVersion, int mask, bool boostEcl, uint8_t tempBuffer[], uint8_t qrcode[]) { + assert(segs != NULL || len == 0); + assert(qrcodegen_VERSION_MIN <= minVersion && minVersion <= maxVersion && maxVersion <= qrcodegen_VERSION_MAX); + assert(0 <= (int)ecl && (int)ecl <= 3 && -1 <= (int)mask && (int)mask <= 7); + + // Find the minimal version number to use + int version, dataUsedBits; + for (version = minVersion; ; version++) { + int dataCapacityBits = getNumDataCodewords(version, ecl) * 8; // Number of data bits available + dataUsedBits = getTotalBits(segs, len, version); + if (dataUsedBits != -1 && dataUsedBits <= dataCapacityBits) + break; // This version number is found to be suitable + if (version >= maxVersion) { // All versions in the range could not fit the given data + qrcode[0] = 0; // Set size to invalid value for safety + return false; + } + } + assert(dataUsedBits != -1); + + // Increase the error correction level while the data still fits in the current version number + for (int i = (int)qrcodegen_Ecc_MEDIUM; i <= (int)qrcodegen_Ecc_HIGH; i++) { // From low to high + if (boostEcl && dataUsedBits <= getNumDataCodewords(version, (enum qrcodegen_Ecc)i) * 8) + ecl = (enum qrcodegen_Ecc)i; + } + + // Concatenate all segments to create the data bit string + memset(qrcode, 0, qrcodegen_BUFFER_LEN_FOR_VERSION(version) * sizeof(qrcode[0])); + int bitLen = 0; + for (size_t i = 0; i < len; i++) { + const struct qrcodegen_Segment *seg = &segs[i]; + appendBitsToBuffer((int)seg->mode, 4, qrcode, &bitLen); + appendBitsToBuffer(seg->numChars, numCharCountBits(seg->mode, version), qrcode, &bitLen); + for (int j = 0; j < seg->bitLength; j++) + appendBitsToBuffer((seg->data[j >> 3] >> (7 - (j & 7))) & 1, 1, qrcode, &bitLen); + } + assert(bitLen == dataUsedBits); + + // Add terminator and pad up to a byte if applicable + int dataCapacityBits = getNumDataCodewords(version, ecl) * 8; + assert(bitLen <= dataCapacityBits); + int terminatorBits = dataCapacityBits - bitLen; + if (terminatorBits > 4) + terminatorBits = 4; + appendBitsToBuffer(0, terminatorBits, qrcode, &bitLen); + appendBitsToBuffer(0, (8 - bitLen % 8) % 8, qrcode, &bitLen); + assert(bitLen % 8 == 0); + + // Pad with alternating bytes until data capacity is reached + for (uint8_t padByte = 0xEC; bitLen < dataCapacityBits; padByte ^= 0xEC ^ 0x11) + appendBitsToBuffer(padByte, 8, qrcode, &bitLen); + + // Draw function and data codeword modules + addEccAndInterleave(qrcode, version, ecl, tempBuffer); + initializeFunctionModules(version, qrcode); + drawCodewords(tempBuffer, getNumRawDataModules(version) / 8, qrcode); + drawWhiteFunctionModules(qrcode, version); + initializeFunctionModules(version, tempBuffer); + + // Handle masking + if (mask == qrcodegen_Mask_AUTO) { // Automatically choose best mask + long minPenalty = LONG_MAX; + for (int i = 0; i < 8; i++) { + enum qrcodegen_Mask msk = (enum qrcodegen_Mask)i; + drawFormatBits(ecl, msk, qrcode); + applyMask(tempBuffer, qrcode, msk); + long penalty = getPenaltyScore(qrcode); + if (penalty < minPenalty) { + mask = msk; + minPenalty = penalty; + } + applyMask(tempBuffer, qrcode, msk); // Undoes the mask due to XOR + } + } + assert(0 <= (int)mask && (int)mask <= 7); + drawFormatBits(ecl, mask, qrcode); + applyMask(tempBuffer, qrcode, mask); + return true; +} + + + +/*---- Error correction code generation functions ----*/ + +// Appends error correction bytes to each block of the given data array, then interleaves +// bytes from the blocks and stores them in the result array. data[0 : dataLen] contains +// the input data. data[dataLen : rawCodewords] is used as a temporary work area and will +// be clobbered by this function. The final answer is stored in result[0 : rawCodewords]. +testable void addEccAndInterleave(uint8_t data[], int version, enum qrcodegen_Ecc ecl, uint8_t result[]) { + // Calculate parameter numbers + assert(0 <= (int)ecl && (int)ecl < 4 && qrcodegen_VERSION_MIN <= version && version <= qrcodegen_VERSION_MAX); + int numBlocks = NUM_ERROR_CORRECTION_BLOCKS[(int)ecl][version]; + int blockEccLen = ECC_CODEWORDS_PER_BLOCK [(int)ecl][version]; + int rawCodewords = getNumRawDataModules(version) / 8; + int dataLen = getNumDataCodewords(version, ecl); + int numShortBlocks = numBlocks - rawCodewords % numBlocks; + int shortBlockDataLen = rawCodewords / numBlocks - blockEccLen; + + // Split data into blocks, calculate ECC, and interleave + // (not concatenate) the bytes into a single sequence + uint8_t generator[qrcodegen_REED_SOLOMON_DEGREE_MAX]; + calcReedSolomonGenerator(blockEccLen, generator); + const uint8_t *dat = data; + for (int i = 0; i < numBlocks; i++) { + int datLen = shortBlockDataLen + (i < numShortBlocks ? 0 : 1); + uint8_t *ecc = &data[dataLen]; // Temporary storage + calcReedSolomonRemainder(dat, datLen, generator, blockEccLen, ecc); + for (int j = 0, k = i; j < datLen; j++, k += numBlocks) { // Copy data + if (j == shortBlockDataLen) + k -= numShortBlocks; + result[k] = dat[j]; + } + for (int j = 0, k = dataLen + i; j < blockEccLen; j++, k += numBlocks) // Copy ECC + result[k] = ecc[j]; + dat += datLen; + } +} + + +// Returns the number of 8-bit codewords that can be used for storing data (not ECC), +// for the given version number and error correction level. The result is in the range [9, 2956]. +testable int getNumDataCodewords(int version, enum qrcodegen_Ecc ecl) { + int v = version, e = (int)ecl; + assert(0 <= e && e < 4); + return getNumRawDataModules(v) / 8 + - ECC_CODEWORDS_PER_BLOCK [e][v] + * NUM_ERROR_CORRECTION_BLOCKS[e][v]; +} + + +// Returns the number of data bits that can be stored in a QR Code of the given version number, after +// all function modules are excluded. This includes remainder bits, so it might not be a multiple of 8. +// The result is in the range [208, 29648]. This could be implemented as a 40-entry lookup table. +testable int getNumRawDataModules(int ver) { + assert(qrcodegen_VERSION_MIN <= ver && ver <= qrcodegen_VERSION_MAX); + int result = (16 * ver + 128) * ver + 64; + if (ver >= 2) { + int numAlign = ver / 7 + 2; + result -= (25 * numAlign - 10) * numAlign - 55; + if (ver >= 7) + result -= 36; + } + return result; +} + + + +/*---- Reed-Solomon ECC generator functions ----*/ + +// Calculates the Reed-Solomon generator polynomial of the given degree, storing in result[0 : degree]. +testable void calcReedSolomonGenerator(int degree, uint8_t result[]) { + // Start with the monomial x^0 + assert(1 <= degree && degree <= qrcodegen_REED_SOLOMON_DEGREE_MAX); + memset(result, 0, degree * sizeof(result[0])); + result[degree - 1] = 1; + + // Compute the product polynomial (x - r^0) * (x - r^1) * (x - r^2) * ... * (x - r^{degree-1}), + // drop the highest term, and store the rest of the coefficients in order of descending powers. + // Note that r = 0x02, which is a generator element of this field GF(2^8/0x11D). + uint8_t root = 1; + for (int i = 0; i < degree; i++) { + // Multiply the current product by (x - r^i) + for (int j = 0; j < degree; j++) { + result[j] = finiteFieldMultiply(result[j], root); + if (j + 1 < degree) + result[j] ^= result[j + 1]; + } + root = finiteFieldMultiply(root, 0x02); + } +} + + +// Calculates the remainder of the polynomial data[0 : dataLen] when divided by the generator[0 : degree], where all +// polynomials are in big endian and the generator has an implicit leading 1 term, storing the result in result[0 : degree]. +testable void calcReedSolomonRemainder(const uint8_t data[], int dataLen, + const uint8_t generator[], int degree, uint8_t result[]) { + + // Perform polynomial division + assert(1 <= degree && degree <= qrcodegen_REED_SOLOMON_DEGREE_MAX); + memset(result, 0, degree * sizeof(result[0])); + for (int i = 0; i < dataLen; i++) { + uint8_t factor = data[i] ^ result[0]; + memmove(&result[0], &result[1], (degree - 1) * sizeof(result[0])); + result[degree - 1] = 0; + for (int j = 0; j < degree; j++) + result[j] ^= finiteFieldMultiply(generator[j], factor); + } +} + +#undef qrcodegen_REED_SOLOMON_DEGREE_MAX + + +// Returns the product of the two given field elements modulo GF(2^8/0x11D). +// All inputs are valid. This could be implemented as a 256*256 lookup table. +testable uint8_t finiteFieldMultiply(uint8_t x, uint8_t y) { + // Russian peasant multiplication + uint8_t z = 0; + for (int i = 7; i >= 0; i--) { + z = (z << 1) ^ ((z >> 7) * 0x11D); + z ^= ((y >> i) & 1) * x; + } + return z; +} + + + +/*---- Drawing function modules ----*/ + +// Clears the given QR Code grid with white modules for the given +// version's size, then marks every function module as black. +testable void initializeFunctionModules(int version, uint8_t qrcode[]) { + // Initialize QR Code + int qrsize = version * 4 + 17; + memset(qrcode, 0, ((qrsize * qrsize + 7) / 8 + 1) * sizeof(qrcode[0])); + qrcode[0] = (uint8_t)qrsize; + + // Fill horizontal and vertical timing patterns + fillRectangle(6, 0, 1, qrsize, qrcode); + fillRectangle(0, 6, qrsize, 1, qrcode); + + // Fill 3 finder patterns (all corners except bottom right) and format bits + fillRectangle(0, 0, 9, 9, qrcode); + fillRectangle(qrsize - 8, 0, 8, 9, qrcode); + fillRectangle(0, qrsize - 8, 9, 8, qrcode); + + // Fill numerous alignment patterns + uint8_t alignPatPos[7]; + int numAlign = getAlignmentPatternPositions(version, alignPatPos); + for (int i = 0; i < numAlign; i++) { + for (int j = 0; j < numAlign; j++) { + // Don't draw on the three finder corners + if (!((i == 0 && j == 0) || (i == 0 && j == numAlign - 1) || (i == numAlign - 1 && j == 0))) + fillRectangle(alignPatPos[i] - 2, alignPatPos[j] - 2, 5, 5, qrcode); + } + } + + // Fill version blocks + if (version >= 7) { + fillRectangle(qrsize - 11, 0, 3, 6, qrcode); + fillRectangle(0, qrsize - 11, 6, 3, qrcode); + } +} + + +// Draws white function modules and possibly some black modules onto the given QR Code, without changing +// non-function modules. This does not draw the format bits. This requires all function modules to be previously +// marked black (namely by initializeFunctionModules()), because this may skip redrawing black function modules. +static void drawWhiteFunctionModules(uint8_t qrcode[], int version) { + // Draw horizontal and vertical timing patterns + int qrsize = qrcodegen_getSize(qrcode); + for (int i = 7; i < qrsize - 7; i += 2) { + setModule(qrcode, 6, i, false); + setModule(qrcode, i, 6, false); + } + + // Draw 3 finder patterns (all corners except bottom right; overwrites some timing modules) + for (int dy = -4; dy <= 4; dy++) { + for (int dx = -4; dx <= 4; dx++) { + int dist = abs(dx); + if (abs(dy) > dist) + dist = abs(dy); + if (dist == 2 || dist == 4) { + setModuleBounded(qrcode, 3 + dx, 3 + dy, false); + setModuleBounded(qrcode, qrsize - 4 + dx, 3 + dy, false); + setModuleBounded(qrcode, 3 + dx, qrsize - 4 + dy, false); + } + } + } + + // Draw numerous alignment patterns + uint8_t alignPatPos[7]; + int numAlign = getAlignmentPatternPositions(version, alignPatPos); + for (int i = 0; i < numAlign; i++) { + for (int j = 0; j < numAlign; j++) { + if ((i == 0 && j == 0) || (i == 0 && j == numAlign - 1) || (i == numAlign - 1 && j == 0)) + continue; // Don't draw on the three finder corners + for (int dy = -1; dy <= 1; dy++) { + for (int dx = -1; dx <= 1; dx++) + setModule(qrcode, alignPatPos[i] + dx, alignPatPos[j] + dy, dx == 0 && dy == 0); + } + } + } + + // Draw version blocks + if (version >= 7) { + // Calculate error correction code and pack bits + int rem = version; // version is uint6, in the range [7, 40] + for (int i = 0; i < 12; i++) + rem = (rem << 1) ^ ((rem >> 11) * 0x1F25); + long bits = (long)version << 12 | rem; // uint18 + assert(bits >> 18 == 0); + + // Draw two copies + for (int i = 0; i < 6; i++) { + for (int j = 0; j < 3; j++) { + int k = qrsize - 11 + j; + setModule(qrcode, k, i, (bits & 1) != 0); + setModule(qrcode, i, k, (bits & 1) != 0); + bits >>= 1; + } + } + } +} + + +// Draws two copies of the format bits (with its own error correction code) based +// on the given mask and error correction level. This always draws all modules of +// the format bits, unlike drawWhiteFunctionModules() which might skip black modules. +static void drawFormatBits(enum qrcodegen_Ecc ecl, enum qrcodegen_Mask mask, uint8_t qrcode[]) { + // Calculate error correction code and pack bits + assert(0 <= (int)mask && (int)mask <= 7); + static const int table[] = {1, 0, 3, 2}; + int data = table[(int)ecl] << 3 | (int)mask; // errCorrLvl is uint2, mask is uint3 + int rem = data; + for (int i = 0; i < 10; i++) + rem = (rem << 1) ^ ((rem >> 9) * 0x537); + int bits = (data << 10 | rem) ^ 0x5412; // uint15 + assert(bits >> 15 == 0); + + // Draw first copy + for (int i = 0; i <= 5; i++) + setModule(qrcode, 8, i, getBit(bits, i)); + setModule(qrcode, 8, 7, getBit(bits, 6)); + setModule(qrcode, 8, 8, getBit(bits, 7)); + setModule(qrcode, 7, 8, getBit(bits, 8)); + for (int i = 9; i < 15; i++) + setModule(qrcode, 14 - i, 8, getBit(bits, i)); + + // Draw second copy + int qrsize = qrcodegen_getSize(qrcode); + for (int i = 0; i < 8; i++) + setModule(qrcode, qrsize - 1 - i, 8, getBit(bits, i)); + for (int i = 8; i < 15; i++) + setModule(qrcode, 8, qrsize - 15 + i, getBit(bits, i)); + setModule(qrcode, 8, qrsize - 8, true); // Always black +} + + +// Calculates and stores an ascending list of positions of alignment patterns +// for this version number, returning the length of the list (in the range [0,7]). +// Each position is in the range [0,177), and are used on both the x and y axes. +// This could be implemented as lookup table of 40 variable-length lists of unsigned bytes. +testable int getAlignmentPatternPositions(int version, uint8_t result[7]) { + if (version == 1) + return 0; + int numAlign = version / 7 + 2; + int step = (version == 32) ? 26 : + (version*4 + numAlign*2 + 1) / (numAlign*2 - 2) * 2; + for (int i = numAlign - 1, pos = version * 4 + 10; i >= 1; i--, pos -= step) + result[i] = pos; + result[0] = 6; + return numAlign; +} + + +// Sets every pixel in the range [left : left + width] * [top : top + height] to black. +static void fillRectangle(int left, int top, int width, int height, uint8_t qrcode[]) { + for (int dy = 0; dy < height; dy++) { + for (int dx = 0; dx < width; dx++) + setModule(qrcode, left + dx, top + dy, true); + } +} + + + +/*---- Drawing data modules and masking ----*/ + +// Draws the raw codewords (including data and ECC) onto the given QR Code. This requires the initial state of +// the QR Code to be black at function modules and white at codeword modules (including unused remainder bits). +static void drawCodewords(const uint8_t data[], int dataLen, uint8_t qrcode[]) { + int qrsize = qrcodegen_getSize(qrcode); + int i = 0; // Bit index into the data + // Do the funny zigzag scan + for (int right = qrsize - 1; right >= 1; right -= 2) { // Index of right column in each column pair + if (right == 6) + right = 5; + for (int vert = 0; vert < qrsize; vert++) { // Vertical counter + for (int j = 0; j < 2; j++) { + int x = right - j; // Actual x coordinate + bool upward = ((right + 1) & 2) == 0; + int y = upward ? qrsize - 1 - vert : vert; // Actual y coordinate + if (!getModule(qrcode, x, y) && i < dataLen * 8) { + bool black = getBit(data[i >> 3], 7 - (i & 7)); + setModule(qrcode, x, y, black); + i++; + } + // If this QR Code has any remainder bits (0 to 7), they were assigned as + // 0/false/white by the constructor and are left unchanged by this method + } + } + } + assert(i == dataLen * 8); +} + + +// XORs the codeword modules in this QR Code with the given mask pattern. +// The function modules must be marked and the codeword bits must be drawn +// before masking. Due to the arithmetic of XOR, calling applyMask() with +// the same mask value a second time will undo the mask. A final well-formed +// QR Code needs exactly one (not zero, two, etc.) mask applied. +static void applyMask(const uint8_t functionModules[], uint8_t qrcode[], enum qrcodegen_Mask mask) { + assert(0 <= (int)mask && (int)mask <= 7); // Disallows qrcodegen_Mask_AUTO + int qrsize = qrcodegen_getSize(qrcode); + for (int y = 0; y < qrsize; y++) { + for (int x = 0; x < qrsize; x++) { + if (getModule(functionModules, x, y)) + continue; + bool invert; + switch ((int)mask) { + case 0: invert = (x + y) % 2 == 0; break; + case 1: invert = y % 2 == 0; break; + case 2: invert = x % 3 == 0; break; + case 3: invert = (x + y) % 3 == 0; break; + case 4: invert = (x / 3 + y / 2) % 2 == 0; break; + case 5: invert = x * y % 2 + x * y % 3 == 0; break; + case 6: invert = (x * y % 2 + x * y % 3) % 2 == 0; break; + case 7: invert = ((x + y) % 2 + x * y % 3) % 2 == 0; break; + default: assert(false); return; + } + bool val = getModule(qrcode, x, y); + setModule(qrcode, x, y, val ^ invert); + } + } +} + + +// Calculates and returns the penalty score based on state of the given QR Code's current modules. +// This is used by the automatic mask choice algorithm to find the mask pattern that yields the lowest score. +static long getPenaltyScore(const uint8_t qrcode[]) { + int qrsize = qrcodegen_getSize(qrcode); + long result = 0; + + // Adjacent modules in row having same color, and finder-like patterns + for (int y = 0; y < qrsize; y++) { + unsigned char runHistory[7] = {0}; + bool color = false; + unsigned char runX = 0; + for (int x = 0; x < qrsize; x++) { + if (getModule(qrcode, x, y) == color) { + runX++; + if (runX == 5) + result += PENALTY_N1; + else if (runX > 5) + result++; + } else { + addRunToHistory(runX, runHistory); + if (!color && hasFinderLikePattern(runHistory)) + result += PENALTY_N3; + color = getModule(qrcode, x, y); + runX = 1; + } + } + addRunToHistory(runX, runHistory); + if (color) + addRunToHistory(0, runHistory); // Dummy run of white + if (hasFinderLikePattern(runHistory)) + result += PENALTY_N3; + } + // Adjacent modules in column having same color, and finder-like patterns + for (int x = 0; x < qrsize; x++) { + unsigned char runHistory[7] = {0}; + bool color = false; + unsigned char runY = 0; + for (int y = 0; y < qrsize; y++) { + if (getModule(qrcode, x, y) == color) { + runY++; + if (runY == 5) + result += PENALTY_N1; + else if (runY > 5) + result++; + } else { + addRunToHistory(runY, runHistory); + if (!color && hasFinderLikePattern(runHistory)) + result += PENALTY_N3; + color = getModule(qrcode, x, y); + runY = 1; + } + } + addRunToHistory(runY, runHistory); + if (color) + addRunToHistory(0, runHistory); // Dummy run of white + if (hasFinderLikePattern(runHistory)) + result += PENALTY_N3; + } + + // 2*2 blocks of modules having same color + for (int y = 0; y < qrsize - 1; y++) { + for (int x = 0; x < qrsize - 1; x++) { + bool color = getModule(qrcode, x, y); + if ( color == getModule(qrcode, x + 1, y) && + color == getModule(qrcode, x, y + 1) && + color == getModule(qrcode, x + 1, y + 1)) + result += PENALTY_N2; + } + } + + // Balance of black and white modules + int black = 0; + for (int y = 0; y < qrsize; y++) { + for (int x = 0; x < qrsize; x++) { + if (getModule(qrcode, x, y)) + black++; + } + } + int total = qrsize * qrsize; // Note that size is odd, so black/total != 1/2 + // Compute the smallest integer k >= 0 such that (45-5k)% <= black/total <= (55+5k)% + int k = (int)((labs(black * 20L - total * 10L) + total - 1) / total) - 1; + result += k * PENALTY_N4; + return result; +} + + +// Inserts the given value to the front of the given array, which shifts over the +// existing values and deletes the last value. A helper function for getPenaltyScore(). +static void addRunToHistory(unsigned char run, unsigned char history[7]) { + memmove(&history[1], &history[0], 6 * sizeof(history[0])); + history[0] = run; +} + + +// Tests whether the given run history has the pattern of ratio 1:1:3:1:1 in the middle, and +// surrounded by at least 4 on either or both ends. A helper function for getPenaltyScore(). +// Must only be called immediately after a run of white modules has ended. +static bool hasFinderLikePattern(unsigned char runHistory[7]) { + unsigned char n = runHistory[1]; + // The maximum QR Code size is 177, hence the run length n <= 177. + // Arithmetic is promoted to int, so n*4 will not overflow. + return n > 0 && runHistory[2] == n && runHistory[4] == n && runHistory[5] == n + && runHistory[3] == n * 3 && (runHistory[0] >= n * 4 || runHistory[6] >= n * 4); +} + + + +/*---- Basic QR Code information ----*/ + +// Public function - see documentation comment in header file. +int qrcodegen_getSize(const uint8_t qrcode[]) { + assert(qrcode != NULL); + int result = qrcode[0]; + assert((qrcodegen_VERSION_MIN * 4 + 17) <= result + && result <= (qrcodegen_VERSION_MAX * 4 + 17)); + return result; +} + + +// Public function - see documentation comment in header file. +bool qrcodegen_getModule(const uint8_t qrcode[], int x, int y) { + assert(qrcode != NULL); + int qrsize = qrcode[0]; + return (0 <= x && x < qrsize && 0 <= y && y < qrsize) && getModule(qrcode, x, y); +} + + +// Gets the module at the given coordinates, which must be in bounds. +testable bool getModule(const uint8_t qrcode[], int x, int y) { + int qrsize = qrcode[0]; + assert(21 <= qrsize && qrsize <= 177 && 0 <= x && x < qrsize && 0 <= y && y < qrsize); + int index = y * qrsize + x; + return getBit(qrcode[(index >> 3) + 1], index & 7); +} + + +// Sets the module at the given coordinates, which must be in bounds. +testable void setModule(uint8_t qrcode[], int x, int y, bool isBlack) { + int qrsize = qrcode[0]; + assert(21 <= qrsize && qrsize <= 177 && 0 <= x && x < qrsize && 0 <= y && y < qrsize); + int index = y * qrsize + x; + int bitIndex = index & 7; + int byteIndex = (index >> 3) + 1; + if (isBlack) + qrcode[byteIndex] |= 1 << bitIndex; + else + qrcode[byteIndex] &= (1 << bitIndex) ^ 0xFF; +} + + +// Sets the module at the given coordinates, doing nothing if out of bounds. +testable void setModuleBounded(uint8_t qrcode[], int x, int y, bool isBlack) { + int qrsize = qrcode[0]; + if (0 <= x && x < qrsize && 0 <= y && y < qrsize) + setModule(qrcode, x, y, isBlack); +} + + +// Returns true iff the i'th bit of x is set to 1. Requires x >= 0 and 0 <= i <= 14. +static bool getBit(int x, int i) { + return ((x >> i) & 1) != 0; +} + + + +/*---- Segment handling ----*/ + +// Public function - see documentation comment in header file. +bool qrcodegen_isAlphanumeric(const char *text) { + assert(text != NULL); + for (; *text != '\0'; text++) { + if (strchr(ALPHANUMERIC_CHARSET, *text) == NULL) + return false; + } + return true; +} + + +// Public function - see documentation comment in header file. +bool qrcodegen_isNumeric(const char *text) { + assert(text != NULL); + for (; *text != '\0'; text++) { + if (*text < '0' || *text > '9') + return false; + } + return true; +} + + +// Public function - see documentation comment in header file. +size_t qrcodegen_calcSegmentBufferSize(enum qrcodegen_Mode mode, size_t numChars) { + int temp = calcSegmentBitLength(mode, numChars); + if (temp == -1) + return SIZE_MAX; + assert(0 <= temp && temp <= INT16_MAX); + return ((size_t)temp + 7) / 8; +} + + +// Returns the number of data bits needed to represent a segment +// containing the given number of characters using the given mode. Notes: +// - Returns -1 on failure, i.e. numChars > INT16_MAX or +// the number of needed bits exceeds INT16_MAX (i.e. 32767). +// - Otherwise, all valid results are in the range [0, INT16_MAX]. +// - For byte mode, numChars measures the number of bytes, not Unicode code points. +// - For ECI mode, numChars must be 0, and the worst-case number of bits is returned. +// An actual ECI segment can have shorter data. For non-ECI modes, the result is exact. +testable int calcSegmentBitLength(enum qrcodegen_Mode mode, size_t numChars) { + // All calculations are designed to avoid overflow on all platforms + if (numChars > (unsigned int)INT16_MAX) + return -1; + long result = (long)numChars; + if (mode == qrcodegen_Mode_NUMERIC) + result = (result * 10 + 2) / 3; // ceil(10/3 * n) + else if (mode == qrcodegen_Mode_ALPHANUMERIC) + result = (result * 11 + 1) / 2; // ceil(11/2 * n) + else if (mode == qrcodegen_Mode_BYTE) + result *= 8; + else if (mode == qrcodegen_Mode_KANJI) + result *= 13; + else if (mode == qrcodegen_Mode_ECI && numChars == 0) + result = 3 * 8; + else { // Invalid argument + assert(false); + return -1; + } + assert(result >= 0); + if (result > (unsigned int)INT16_MAX) + return -1; + return (int)result; +} + + +// Public function - see documentation comment in header file. +struct qrcodegen_Segment qrcodegen_makeBytes(const uint8_t data[], size_t len, uint8_t buf[]) { + assert(data != NULL || len == 0); + struct qrcodegen_Segment result; + result.mode = qrcodegen_Mode_BYTE; + result.bitLength = calcSegmentBitLength(result.mode, len); + assert(result.bitLength != -1); + result.numChars = (int)len; + if (len > 0) + memcpy(buf, data, len * sizeof(buf[0])); + result.data = buf; + return result; +} + + +// Public function - see documentation comment in header file. +struct qrcodegen_Segment qrcodegen_makeNumeric(const char *digits, uint8_t buf[]) { + assert(digits != NULL); + struct qrcodegen_Segment result; + size_t len = strlen(digits); + result.mode = qrcodegen_Mode_NUMERIC; + int bitLen = calcSegmentBitLength(result.mode, len); + assert(bitLen != -1); + result.numChars = (int)len; + if (bitLen > 0) + memset(buf, 0, ((size_t)bitLen + 7) / 8 * sizeof(buf[0])); + result.bitLength = 0; + + unsigned int accumData = 0; + int accumCount = 0; + for (; *digits != '\0'; digits++) { + char c = *digits; + assert('0' <= c && c <= '9'); + accumData = accumData * 10 + (unsigned int)(c - '0'); + accumCount++; + if (accumCount == 3) { + appendBitsToBuffer(accumData, 10, buf, &result.bitLength); + accumData = 0; + accumCount = 0; + } + } + if (accumCount > 0) // 1 or 2 digits remaining + appendBitsToBuffer(accumData, accumCount * 3 + 1, buf, &result.bitLength); + assert(result.bitLength == bitLen); + result.data = buf; + return result; +} + + +// Public function - see documentation comment in header file. +struct qrcodegen_Segment qrcodegen_makeAlphanumeric(const char *text, uint8_t buf[]) { + assert(text != NULL); + struct qrcodegen_Segment result; + size_t len = strlen(text); + result.mode = qrcodegen_Mode_ALPHANUMERIC; + int bitLen = calcSegmentBitLength(result.mode, len); + assert(bitLen != -1); + result.numChars = (int)len; + if (bitLen > 0) + memset(buf, 0, ((size_t)bitLen + 7) / 8 * sizeof(buf[0])); + result.bitLength = 0; + + unsigned int accumData = 0; + int accumCount = 0; + for (; *text != '\0'; text++) { + const char *temp = strchr(ALPHANUMERIC_CHARSET, *text); + assert(temp != NULL); + accumData = accumData * 45 + (unsigned int)(temp - ALPHANUMERIC_CHARSET); + accumCount++; + if (accumCount == 2) { + appendBitsToBuffer(accumData, 11, buf, &result.bitLength); + accumData = 0; + accumCount = 0; + } + } + if (accumCount > 0) // 1 character remaining + appendBitsToBuffer(accumData, 6, buf, &result.bitLength); + assert(result.bitLength == bitLen); + result.data = buf; + return result; +} + + +// Public function - see documentation comment in header file. +struct qrcodegen_Segment qrcodegen_makeEci(long assignVal, uint8_t buf[]) { + struct qrcodegen_Segment result; + result.mode = qrcodegen_Mode_ECI; + result.numChars = 0; + result.bitLength = 0; + if (assignVal < 0) + assert(false); + else if (assignVal < (1 << 7)) { + memset(buf, 0, 1 * sizeof(buf[0])); + appendBitsToBuffer(assignVal, 8, buf, &result.bitLength); + } else if (assignVal < (1 << 14)) { + memset(buf, 0, 2 * sizeof(buf[0])); + appendBitsToBuffer(2, 2, buf, &result.bitLength); + appendBitsToBuffer(assignVal, 14, buf, &result.bitLength); + } else if (assignVal < 1000000L) { + memset(buf, 0, 3 * sizeof(buf[0])); + appendBitsToBuffer(6, 3, buf, &result.bitLength); + appendBitsToBuffer(assignVal >> 10, 11, buf, &result.bitLength); + appendBitsToBuffer(assignVal & 0x3FF, 10, buf, &result.bitLength); + } else + assert(false); + result.data = buf; + return result; +} + + +// Calculates the number of bits needed to encode the given segments at the given version. +// Returns a non-negative number if successful. Otherwise returns -1 if a segment has too +// many characters to fit its length field, or the total bits exceeds INT16_MAX. +testable int getTotalBits(const struct qrcodegen_Segment segs[], size_t len, int version) { + assert(segs != NULL || len == 0); + long result = 0; + for (size_t i = 0; i < len; i++) { + int numChars = segs[i].numChars; + int bitLength = segs[i].bitLength; + assert(0 <= numChars && numChars <= INT16_MAX); + assert(0 <= bitLength && bitLength <= INT16_MAX); + int ccbits = numCharCountBits(segs[i].mode, version); + assert(0 <= ccbits && ccbits <= 16); + if (numChars >= (1L << ccbits)) + return -1; // The segment's length doesn't fit the field's bit width + result += 4L + ccbits + bitLength; + if (result > INT16_MAX) + return -1; // The sum might overflow an int type + } + assert(0 <= result && result <= INT16_MAX); + return (int)result; +} + + +// Returns the bit width of the character count field for a segment in the given mode +// in a QR Code at the given version number. The result is in the range [0, 16]. +static int numCharCountBits(enum qrcodegen_Mode mode, int version) { + assert(qrcodegen_VERSION_MIN <= version && version <= qrcodegen_VERSION_MAX); + int i = (version + 7) / 17; + switch (mode) { + case qrcodegen_Mode_NUMERIC : { static const int temp[] = {10, 12, 14}; return temp[i]; } + case qrcodegen_Mode_ALPHANUMERIC: { static const int temp[] = { 9, 11, 13}; return temp[i]; } + case qrcodegen_Mode_BYTE : { static const int temp[] = { 8, 16, 16}; return temp[i]; } + case qrcodegen_Mode_KANJI : { static const int temp[] = { 8, 10, 12}; return temp[i]; } + case qrcodegen_Mode_ECI : return 0; + default: assert(false); return -1; // Dummy value + } +} diff --git a/panels/network/qrcodegen.h b/panels/network/qrcodegen.h new file mode 100644 index 0000000..55e2bfe --- /dev/null +++ b/panels/network/qrcodegen.h @@ -0,0 +1,311 @@ +/* + * QR Code generator library (C) + * + * Copyright (c) Project Nayuki. (MIT License) + * https://www.nayuki.io/page/qr-code-generator-library + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * - The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * - The Software is provided "as is", without warranty of any kind, express or + * implied, including but not limited to the warranties of merchantability, + * fitness for a particular purpose and noninfringement. In no event shall the + * authors or copyright holders be liable for any claim, damages or other + * liability, whether in an action of contract, tort or otherwise, arising from, + * out of or in connection with the Software or the use or other dealings in the + * Software. + */ + +#pragma once + +#include <stdbool.h> +#include <stddef.h> +#include <stdint.h> + + +#ifdef __cplusplus +extern "C" { +#endif + + +/* + * This library creates QR Code symbols, which is a type of two-dimension barcode. + * Invented by Denso Wave and described in the ISO/IEC 18004 standard. + * A QR Code structure is an immutable square grid of black and white cells. + * The library provides functions to create a QR Code from text or binary data. + * The library covers the QR Code Model 2 specification, supporting all versions (sizes) + * from 1 to 40, all 4 error correction levels, and 4 character encoding modes. + * + * Ways to create a QR Code object: + * - High level: Take the payload data and call qrcodegen_encodeText() or qrcodegen_encodeBinary(). + * - Low level: Custom-make the list of segments and call + * qrcodegen_encodeSegments() or qrcodegen_encodeSegmentsAdvanced(). + * (Note that all ways require supplying the desired error correction level and various byte buffers.) + */ + + +/*---- Enum and struct types----*/ + +/* + * The error correction level in a QR Code symbol. + */ +enum qrcodegen_Ecc { + // Must be declared in ascending order of error protection + // so that an internal qrcodegen function works properly + qrcodegen_Ecc_LOW = 0 , // The QR Code can tolerate about 7% erroneous codewords + qrcodegen_Ecc_MEDIUM , // The QR Code can tolerate about 15% erroneous codewords + qrcodegen_Ecc_QUARTILE, // The QR Code can tolerate about 25% erroneous codewords + qrcodegen_Ecc_HIGH , // The QR Code can tolerate about 30% erroneous codewords +}; + + +/* + * The mask pattern used in a QR Code symbol. + */ +enum qrcodegen_Mask { + // A special value to tell the QR Code encoder to + // automatically select an appropriate mask pattern + qrcodegen_Mask_AUTO = -1, + // The eight actual mask patterns + qrcodegen_Mask_0 = 0, + qrcodegen_Mask_1, + qrcodegen_Mask_2, + qrcodegen_Mask_3, + qrcodegen_Mask_4, + qrcodegen_Mask_5, + qrcodegen_Mask_6, + qrcodegen_Mask_7, +}; + + +/* + * Describes how a segment's data bits are interpreted. + */ +enum qrcodegen_Mode { + qrcodegen_Mode_NUMERIC = 0x1, + qrcodegen_Mode_ALPHANUMERIC = 0x2, + qrcodegen_Mode_BYTE = 0x4, + qrcodegen_Mode_KANJI = 0x8, + qrcodegen_Mode_ECI = 0x7, +}; + + +/* + * A segment of character/binary/control data in a QR Code symbol. + * The mid-level way to create a segment is to take the payload data + * and call a factory function such as qrcodegen_makeNumeric(). + * The low-level way to create a segment is to custom-make the bit buffer + * and initialize a qrcodegen_Segment struct with appropriate values. + * Even in the most favorable conditions, a QR Code can only hold 7089 characters of data. + * Any segment longer than this is meaningless for the purpose of generating QR Codes. + * Moreover, the maximum allowed bit length is 32767 because + * the largest QR Code (version 40) has 31329 modules. + */ +struct qrcodegen_Segment { + // The mode indicator of this segment. + enum qrcodegen_Mode mode; + + // The length of this segment's unencoded data. Measured in characters for + // numeric/alphanumeric/kanji mode, bytes for byte mode, and 0 for ECI mode. + // Always zero or positive. Not the same as the data's bit length. + int numChars; + + // The data bits of this segment, packed in bitwise big endian. + // Can be null if the bit length is zero. + uint8_t *data; + + // The number of valid data bits used in the buffer. Requires + // 0 <= bitLength <= 32767, and bitLength <= (capacity of data array) * 8. + // The character count (numChars) must agree with the mode and the bit buffer length. + int bitLength; +}; + + + +/*---- Macro constants and functions ----*/ + +#define qrcodegen_VERSION_MIN 1 // The minimum version number supported in the QR Code Model 2 standard +#define qrcodegen_VERSION_MAX 40 // The maximum version number supported in the QR Code Model 2 standard + +// Calculates the number of bytes needed to store any QR Code up to and including the given version number, +// as a compile-time constant. For example, 'uint8_t buffer[qrcodegen_BUFFER_LEN_FOR_VERSION(25)];' +// can store any single QR Code from version 1 to 25 (inclusive). The result fits in an int (or int16). +// Requires qrcodegen_VERSION_MIN <= n <= qrcodegen_VERSION_MAX. +#define qrcodegen_BUFFER_LEN_FOR_VERSION(n) ((((n) * 4 + 17) * ((n) * 4 + 17) + 7) / 8 + 1) + +// The worst-case number of bytes needed to store one QR Code, up to and including +// version 40. This value equals 3918, which is just under 4 kilobytes. +// Use this more convenient value to avoid calculating tighter memory bounds for buffers. +#define qrcodegen_BUFFER_LEN_MAX qrcodegen_BUFFER_LEN_FOR_VERSION(qrcodegen_VERSION_MAX) + + + +/*---- Functions (high level) to generate QR Codes ----*/ + +/* + * Encodes the given text string to a QR Code, returning true if encoding succeeded. + * If the data is too long to fit in any version in the given range + * at the given ECC level, then false is returned. + * - The input text must be encoded in UTF-8 and contain no NULs. + * - The variables ecl and mask must correspond to enum constant values. + * - Requires 1 <= minVersion <= maxVersion <= 40. + * - The arrays tempBuffer and qrcode must each have a length + * of at least qrcodegen_BUFFER_LEN_FOR_VERSION(maxVersion). + * - After the function returns, tempBuffer contains no useful data. + * - If successful, the resulting QR Code may use numeric, + * alphanumeric, or byte mode to encode the text. + * - In the most optimistic case, a QR Code at version 40 with low ECC + * can hold any UTF-8 string up to 2953 bytes, or any alphanumeric string + * up to 4296 characters, or any digit string up to 7089 characters. + * These numbers represent the hard upper limit of the QR Code standard. + * - Please consult the QR Code specification for information on + * data capacities per version, ECC level, and text encoding mode. + */ +bool qrcodegen_encodeText(const char *text, uint8_t tempBuffer[], uint8_t qrcode[], + enum qrcodegen_Ecc ecl, int minVersion, int maxVersion, enum qrcodegen_Mask mask, bool boostEcl); + + +/* + * Encodes the given binary data to a QR Code, returning true if encoding succeeded. + * If the data is too long to fit in any version in the given range + * at the given ECC level, then false is returned. + * - The input array range dataAndTemp[0 : dataLen] should normally be + * valid UTF-8 text, but is not required by the QR Code standard. + * - The variables ecl and mask must correspond to enum constant values. + * - Requires 1 <= minVersion <= maxVersion <= 40. + * - The arrays dataAndTemp and qrcode must each have a length + * of at least qrcodegen_BUFFER_LEN_FOR_VERSION(maxVersion). + * - After the function returns, the contents of dataAndTemp may have changed, + * and does not represent useful data anymore. + * - If successful, the resulting QR Code will use byte mode to encode the data. + * - In the most optimistic case, a QR Code at version 40 with low ECC can hold any byte + * sequence up to length 2953. This is the hard upper limit of the QR Code standard. + * - Please consult the QR Code specification for information on + * data capacities per version, ECC level, and text encoding mode. + */ +bool qrcodegen_encodeBinary(uint8_t dataAndTemp[], size_t dataLen, uint8_t qrcode[], + enum qrcodegen_Ecc ecl, int minVersion, int maxVersion, enum qrcodegen_Mask mask, bool boostEcl); + + +/*---- Functions (low level) to generate QR Codes ----*/ + +/* + * Renders a QR Code representing the given segments at the given error correction level. + * The smallest possible QR Code version is automatically chosen for the output. Returns true if + * QR Code creation succeeded, or false if the data is too long to fit in any version. The ECC level + * of the result may be higher than the ecl argument if it can be done without increasing the version. + * This function allows the user to create a custom sequence of segments that switches + * between modes (such as alphanumeric and byte) to encode text in less space. + * This is a low-level API; the high-level API is qrcodegen_encodeText() and qrcodegen_encodeBinary(). + * To save memory, the segments' data buffers can alias/overlap tempBuffer, and will + * result in them being clobbered, but the QR Code output will still be correct. + * But the qrcode array must not overlap tempBuffer or any segment's data buffer. + */ +bool qrcodegen_encodeSegments(const struct qrcodegen_Segment segs[], size_t len, + enum qrcodegen_Ecc ecl, uint8_t tempBuffer[], uint8_t qrcode[]); + + +/* + * Renders a QR Code representing the given segments with the given encoding parameters. + * Returns true if QR Code creation succeeded, or false if the data is too long to fit in the range of versions. + * The smallest possible QR Code version within the given range is automatically + * chosen for the output. Iff boostEcl is true, then the ECC level of the result + * may be higher than the ecl argument if it can be done without increasing the + * version. The mask number is either between 0 to 7 (inclusive) to force that + * mask, or -1 to automatically choose an appropriate mask (which may be slow). + * This function allows the user to create a custom sequence of segments that switches + * between modes (such as alphanumeric and byte) to encode text in less space. + * This is a low-level API; the high-level API is qrcodegen_encodeText() and qrcodegen_encodeBinary(). + * To save memory, the segments' data buffers can alias/overlap tempBuffer, and will + * result in them being clobbered, but the QR Code output will still be correct. + * But the qrcode array must not overlap tempBuffer or any segment's data buffer. + */ +bool qrcodegen_encodeSegmentsAdvanced(const struct qrcodegen_Segment segs[], size_t len, enum qrcodegen_Ecc ecl, + int minVersion, int maxVersion, int mask, bool boostEcl, uint8_t tempBuffer[], uint8_t qrcode[]); + + +/* + * Tests whether the given string can be encoded as a segment in alphanumeric mode. + * A string is encodable iff each character is in the following set: 0 to 9, A to Z + * (uppercase only), space, dollar, percent, asterisk, plus, hyphen, period, slash, colon. + */ +bool qrcodegen_isAlphanumeric(const char *text); + + +/* + * Tests whether the given string can be encoded as a segment in numeric mode. + * A string is encodable iff each character is in the range 0 to 9. + */ +bool qrcodegen_isNumeric(const char *text); + + +/* + * Returns the number of bytes (uint8_t) needed for the data buffer of a segment + * containing the given number of characters using the given mode. Notes: + * - Returns SIZE_MAX on failure, i.e. numChars > INT16_MAX or + * the number of needed bits exceeds INT16_MAX (i.e. 32767). + * - Otherwise, all valid results are in the range [0, ceil(INT16_MAX / 8)], i.e. at most 4096. + * - It is okay for the user to allocate more bytes for the buffer than needed. + * - For byte mode, numChars measures the number of bytes, not Unicode code points. + * - For ECI mode, numChars must be 0, and the worst-case number of bytes is returned. + * An actual ECI segment can have shorter data. For non-ECI modes, the result is exact. + */ +size_t qrcodegen_calcSegmentBufferSize(enum qrcodegen_Mode mode, size_t numChars); + + +/* + * Returns a segment representing the given binary data encoded in + * byte mode. All input byte arrays are acceptable. Any text string + * can be converted to UTF-8 bytes and encoded as a byte mode segment. + */ +struct qrcodegen_Segment qrcodegen_makeBytes(const uint8_t data[], size_t len, uint8_t buf[]); + + +/* + * Returns a segment representing the given string of decimal digits encoded in numeric mode. + */ +struct qrcodegen_Segment qrcodegen_makeNumeric(const char *digits, uint8_t buf[]); + + +/* + * Returns a segment representing the given text string encoded in alphanumeric mode. + * The characters allowed are: 0 to 9, A to Z (uppercase only), space, + * dollar, percent, asterisk, plus, hyphen, period, slash, colon. + */ +struct qrcodegen_Segment qrcodegen_makeAlphanumeric(const char *text, uint8_t buf[]); + + +/* + * Returns a segment representing an Extended Channel Interpretation + * (ECI) designator with the given assignment value. + */ +struct qrcodegen_Segment qrcodegen_makeEci(long assignVal, uint8_t buf[]); + + +/*---- Functions to extract raw data from QR Codes ----*/ + +/* + * Returns the side length of the given QR Code, assuming that encoding succeeded. + * The result is in the range [21, 177]. Note that the length of the array buffer + * is related to the side length - every 'uint8_t qrcode[]' must have length at least + * qrcodegen_BUFFER_LEN_FOR_VERSION(version), which equals ceil(size^2 / 8 + 1). + */ +int qrcodegen_getSize(const uint8_t qrcode[]); + + +/* + * Returns the color of the module (pixel) at the given coordinates, which is false + * for white or true for black. The top left corner has the coordinates (x=0, y=0). + * If the given coordinates are out of bounds, then false (white) is returned. + */ +bool qrcodegen_getModule(const uint8_t qrcode[], int x, int y); + + +#ifdef __cplusplus +} +#endif diff --git a/panels/network/ui-helpers.c b/panels/network/ui-helpers.c new file mode 100644 index 0000000..c90ed09 --- /dev/null +++ b/panels/network/ui-helpers.c @@ -0,0 +1,38 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * 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. + * + * (C) Copyright 2014 Red Hat, Inc. + */ + +#include "config.h" + +#include "ui-helpers.h" + +void +widget_set_error (GtkWidget *widget) +{ + g_return_if_fail (GTK_IS_WIDGET (widget)); + + gtk_style_context_add_class (gtk_widget_get_style_context (widget), "error"); +} + +void +widget_unset_error (GtkWidget *widget) +{ + g_return_if_fail (GTK_IS_WIDGET (widget)); + + gtk_style_context_remove_class (gtk_widget_get_style_context (widget), "error"); +} diff --git a/panels/network/ui-helpers.h b/panels/network/ui-helpers.h new file mode 100644 index 0000000..c9754a4 --- /dev/null +++ b/panels/network/ui-helpers.h @@ -0,0 +1,27 @@ +/* + * 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. + * + * (C) Copyright 2014 Red Hat, Inc. + */ + +#ifndef _UI_HELPERS_H_ +#define _UI_HELPERS_H_ + +#include <gtk/gtk.h> + +void widget_set_error (GtkWidget *widget); +void widget_unset_error (GtkWidget *widget); + +#endif /* _UI_HELPERS_H_ */ diff --git a/panels/network/warning-small-symbolic.svg b/panels/network/warning-small-symbolic.svg new file mode 100644 index 0000000..b4f7d35 --- /dev/null +++ b/panels/network/warning-small-symbolic.svg @@ -0,0 +1 @@ +<svg width="10" height="10" xmlns="http://www.w3.org/2000/svg"><path d="M5.014 0a1 1 0 0 0-.909.553l-4 8A1 1 0 0 0 1 10h8a1 1 0 0 0 .895-1.447l-4-8A1 1 0 0 0 5.014 0ZM4 3h2v2.5s0 .5-.5.5h-1C4 6 4 5.5 4 5.5Zm.5 4h1c.277 0 .5.223.5.5v1c0 .277-.223.5-.5.5h-1a.499.499 0 0 1-.5-.5v-1c0-.277.223-.5.5-.5Z" style="fill:#ff7800"/></svg> diff --git a/panels/network/wifi-panel.css b/panels/network/wifi-panel.css new file mode 100644 index 0000000..de7d80c --- /dev/null +++ b/panels/network/wifi-panel.css @@ -0,0 +1,5 @@ +.qr-image { + background: #fff; + padding: 12px; +} + diff --git a/panels/network/wireless-security/eap-method-fast.c b/panels/network/wireless-security/eap-method-fast.c new file mode 100644 index 0000000..c0f4ad0 --- /dev/null +++ b/panels/network/wireless-security/eap-method-fast.c @@ -0,0 +1,399 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* vim: set ft=c ts=4 sts=4 sw=4 noexpandtab smartindent: */ + +/* EAP-FAST authentication method (RFC4851) + * + * 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. + * + * Copyright 2012 - 2014 Red Hat, Inc. + */ + +#include <glib/gi18n.h> + +#include "eap-method.h" +#include "eap-method-fast.h" +#include "eap-method-simple.h" +#include "helpers.h" +#include "ui-helpers.h" +#include "ws-file-chooser-button.h" + +#define I_NAME_COLUMN 0 +#define I_ID_COLUMN 1 + +struct _EAPMethodFAST { + GtkGrid parent; + + GtkEntry *anon_identity_entry; + GtkLabel *anon_identity_label; + GtkComboBox *inner_auth_combo; + GtkLabel *inner_auth_label; + GtkListStore *inner_auth_model; + GtkBox *inner_auth_box; + WsFileChooserButton *pac_file_button; + GtkLabel *pac_file_label; + GtkCheckButton *pac_provision_check; + GtkComboBox *pac_provision_combo; + + EAPMethodSimple *em_gtc; + EAPMethodSimple *em_mschap_v2; +}; + +static void eap_method_iface_init (EAPMethodInterface *); + +G_DEFINE_TYPE_WITH_CODE (EAPMethodFAST, eap_method_fast, GTK_TYPE_GRID, + G_IMPLEMENT_INTERFACE (eap_method_get_type (), eap_method_iface_init)) + +static EAPMethod * +get_inner_method (EAPMethodFAST *self) +{ + GtkTreeIter iter; + g_autofree gchar *id = NULL; + + if (!gtk_combo_box_get_active_iter (self->inner_auth_combo, &iter)) + return NULL; + gtk_tree_model_get (GTK_TREE_MODEL (self->inner_auth_model), &iter, I_ID_COLUMN, &id, -1); + + if (strcmp (id, "gtc") == 0) + return EAP_METHOD (self->em_gtc); + if (strcmp (id, "mschapv2") == 0) + return EAP_METHOD (self->em_mschap_v2); + + return NULL; +} + +static gboolean +validate (EAPMethod *parent, GError **error) +{ + EAPMethodFAST *self = (EAPMethodFAST *) parent; + g_autoptr(GFile) file = NULL; + gboolean provisioning; + gboolean valid = TRUE; + + provisioning = gtk_check_button_get_active (GTK_CHECK_BUTTON (self->pac_provision_check)); + file = ws_file_chooser_button_get_file (self->pac_file_button); + if (!provisioning && !file) { + widget_set_error (GTK_WIDGET (self->pac_file_button)); + g_set_error_literal (error, NMA_ERROR, NMA_ERROR_GENERIC, _("missing EAP-FAST PAC file")); + valid = FALSE; + } else + widget_unset_error (GTK_WIDGET (self->pac_file_button)); + + return eap_method_validate (get_inner_method (self), valid ? error : NULL) && valid; +} + +static void +add_to_size_group (EAPMethod *parent, GtkSizeGroup *group) +{ + EAPMethodFAST *self = (EAPMethodFAST *) parent; + + gtk_size_group_add_widget (group, GTK_WIDGET (self->anon_identity_label)); + gtk_size_group_add_widget (group, GTK_WIDGET (self->pac_file_label)); + gtk_size_group_add_widget (group, GTK_WIDGET (self->pac_provision_check)); + gtk_size_group_add_widget (group, GTK_WIDGET (self->inner_auth_label)); + + eap_method_add_to_size_group (EAP_METHOD (self->em_gtc), group); + eap_method_add_to_size_group (EAP_METHOD (self->em_mschap_v2), group); +} + +static void +fill_connection (EAPMethod *parent, NMConnection *connection, NMSettingSecretFlags flags) +{ + EAPMethodFAST *self = (EAPMethodFAST *) parent; + g_autofree gchar *filename = NULL; + g_autoptr(GFile) file = NULL; + NMSetting8021x *s_8021x; + const char *text; + gboolean enabled; + int pac_provisioning = 0; + + s_8021x = nm_connection_get_setting_802_1x (connection); + g_assert (s_8021x); + + nm_setting_802_1x_add_eap_method (s_8021x, "fast"); + + text = gtk_editable_get_text (GTK_EDITABLE (self->anon_identity_entry)); + if (text && strlen (text)) + g_object_set (s_8021x, NM_SETTING_802_1X_ANONYMOUS_IDENTITY, text, NULL); + + file = ws_file_chooser_button_get_file (self->pac_file_button); + filename = file ? g_file_get_path (file) : NULL; + g_object_set (s_8021x, NM_SETTING_802_1X_PAC_FILE, filename, NULL); + + enabled = gtk_check_button_get_active (GTK_CHECK_BUTTON (self->pac_provision_check)); + + if (!enabled) + g_object_set (G_OBJECT (s_8021x), NM_SETTING_802_1X_PHASE1_FAST_PROVISIONING, "0", NULL); + else { + pac_provisioning = gtk_combo_box_get_active (self->pac_provision_combo); + + switch (pac_provisioning) { + case 0: /* Anonymous */ + g_object_set (G_OBJECT (s_8021x), NM_SETTING_802_1X_PHASE1_FAST_PROVISIONING, "1", NULL); + break; + case 1: /* Authenticated */ + g_object_set (G_OBJECT (s_8021x), NM_SETTING_802_1X_PHASE1_FAST_PROVISIONING, "2", NULL); + break; + case 2: /* Both - anonymous and authenticated */ + g_object_set (G_OBJECT (s_8021x), NM_SETTING_802_1X_PHASE1_FAST_PROVISIONING, "3", NULL); + break; + default: /* Should not happen */ + g_object_set (G_OBJECT (s_8021x), NM_SETTING_802_1X_PHASE1_FAST_PROVISIONING, "1", NULL); + break; + } + } + + eap_method_fill_connection (get_inner_method (self), connection, flags); +} + +static void +inner_auth_combo_changed_cb (EAPMethodFAST *self) +{ + EAPMethod *inner_method; + GtkWidget *child; + + inner_method = get_inner_method (self); + + /* Remove the previous method and migrate username/password across */ + child = gtk_widget_get_first_child (GTK_WIDGET (self->inner_auth_box)); + if (child != NULL) { + EAPMethod *old_eap = EAP_METHOD (child); + eap_method_set_username (inner_method, eap_method_get_username (old_eap)); + eap_method_set_password (inner_method, eap_method_get_password (old_eap)); + eap_method_set_show_password (inner_method, eap_method_get_show_password (old_eap)); + gtk_box_remove (self->inner_auth_box, child); + } + + gtk_box_append (self->inner_auth_box, g_object_ref (GTK_WIDGET (inner_method))); + + eap_method_emit_changed (EAP_METHOD (self)); +} + +static void +update_secrets (EAPMethod *parent, NMConnection *connection) +{ + EAPMethodFAST *self = (EAPMethodFAST *) parent; + + eap_method_update_secrets (EAP_METHOD (self->em_gtc), connection); + eap_method_update_secrets (EAP_METHOD (self->em_mschap_v2), connection); +} + +static GtkWidget * +get_default_field (EAPMethod *parent) +{ + EAPMethodFAST *self = (EAPMethodFAST *) parent; + return GTK_WIDGET (self->anon_identity_entry); +} + +static const gchar * +get_password_flags_name (EAPMethod *parent) +{ + return NM_SETTING_802_1X_PASSWORD; +} + +static const gchar * +get_username (EAPMethod *method) +{ + EAPMethodFAST *self = EAP_METHOD_FAST (method); + return eap_method_get_username (get_inner_method (self)); +} + +static void +set_username (EAPMethod *method, const gchar *username) +{ + EAPMethodFAST *self = EAP_METHOD_FAST (method); + return eap_method_set_username (get_inner_method (self), username); +} + +static const gchar * +get_password (EAPMethod *method) +{ + EAPMethodFAST *self = EAP_METHOD_FAST (method); + return eap_method_get_password (get_inner_method (self)); +} + +static void +set_password (EAPMethod *method, const gchar *password) +{ + EAPMethodFAST *self = EAP_METHOD_FAST (method); + return eap_method_set_password (get_inner_method (self), password); +} + +static gboolean +get_show_password (EAPMethod *method) +{ + EAPMethodFAST *self = EAP_METHOD_FAST (method); + return eap_method_get_show_password (get_inner_method (self)); +} + +static void +set_show_password (EAPMethod *method, gboolean show_password) +{ + EAPMethodFAST *self = EAP_METHOD_FAST (method); + return eap_method_set_show_password (get_inner_method (self), show_password); +} + +static void +pac_toggled_cb (EAPMethodFAST *self) +{ + gboolean enabled = FALSE; + + enabled = gtk_check_button_get_active (GTK_CHECK_BUTTON (self->pac_provision_check)); + gtk_widget_set_sensitive (GTK_WIDGET (self->pac_provision_combo), enabled); + + eap_method_emit_changed (EAP_METHOD (self)); +} + +static void +changed_cb (EAPMethodFAST *self) +{ + eap_method_emit_changed (EAP_METHOD (self)); +} + +static void +eap_method_fast_init (EAPMethodFAST *self) +{ + gtk_widget_init_template (GTK_WIDGET (self)); +} + +static void +eap_method_fast_class_init (EAPMethodFASTClass *klass) +{ + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + g_type_ensure (WS_TYPE_FILE_CHOOSER_BUTTON); + + gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/Settings/network/eap-method-fast.ui"); + + gtk_widget_class_bind_template_child (widget_class, EAPMethodFAST, anon_identity_entry); + gtk_widget_class_bind_template_child (widget_class, EAPMethodFAST, anon_identity_label); + gtk_widget_class_bind_template_child (widget_class, EAPMethodFAST, inner_auth_combo); + gtk_widget_class_bind_template_child (widget_class, EAPMethodFAST, inner_auth_label); + gtk_widget_class_bind_template_child (widget_class, EAPMethodFAST, inner_auth_model); + gtk_widget_class_bind_template_child (widget_class, EAPMethodFAST, inner_auth_box); + gtk_widget_class_bind_template_child (widget_class, EAPMethodFAST, pac_file_button); + gtk_widget_class_bind_template_child (widget_class, EAPMethodFAST, pac_file_label); + gtk_widget_class_bind_template_child (widget_class, EAPMethodFAST, pac_provision_check); + gtk_widget_class_bind_template_child (widget_class, EAPMethodFAST, pac_provision_combo); +} + +static void +eap_method_iface_init (EAPMethodInterface *iface) +{ + iface->validate = validate; + iface->add_to_size_group = add_to_size_group; + iface->fill_connection = fill_connection; + iface->update_secrets = update_secrets; + iface->get_default_field = get_default_field; + iface->get_password_flags_name = get_password_flags_name; + iface->get_username = get_username; + iface->set_username = set_username; + iface->get_password = get_password; + iface->set_password = set_password; + iface->get_show_password = get_show_password; + iface->set_show_password = set_show_password; +} + +EAPMethodFAST * +eap_method_fast_new (NMConnection *connection) +{ + EAPMethodFAST *self; + GtkFileFilter *filter; + NMSetting8021x *s_8021x = NULL; + gboolean provisioning_enabled = TRUE; + const gchar *phase2_auth = NULL; + GtkTreeIter iter; + + self = g_object_new (eap_method_fast_get_type (), NULL); + + if (connection) + s_8021x = nm_connection_get_setting_802_1x (connection); + + gtk_combo_box_set_active (self->pac_provision_combo, 0); + if (s_8021x) { + const char *fast_prov; + + fast_prov = nm_setting_802_1x_get_phase1_fast_provisioning (s_8021x); + if (fast_prov) { + if (!strcmp (fast_prov, "0")) + provisioning_enabled = FALSE; + else if (!strcmp (fast_prov, "1")) + gtk_combo_box_set_active (self->pac_provision_combo, 0); + else if (!strcmp (fast_prov, "2")) + gtk_combo_box_set_active (self->pac_provision_combo, 1); + else if (!strcmp (fast_prov, "3")) + gtk_combo_box_set_active (self->pac_provision_combo, 2); + } + } + gtk_widget_set_sensitive (GTK_WIDGET (self->pac_provision_combo), provisioning_enabled); + g_signal_connect_swapped (self->pac_provision_combo, "changed", G_CALLBACK (changed_cb), self); + + gtk_check_button_set_active (self->pac_provision_check, provisioning_enabled); + g_signal_connect_swapped (self->pac_provision_check, "toggled", G_CALLBACK (pac_toggled_cb), self); + + if (s_8021x && nm_setting_802_1x_get_anonymous_identity (s_8021x)) + gtk_editable_set_text (GTK_EDITABLE (self->anon_identity_entry), nm_setting_802_1x_get_anonymous_identity (s_8021x)); + g_signal_connect_swapped (self->anon_identity_entry, "changed", G_CALLBACK (changed_cb), self); + + g_signal_connect_swapped (self->pac_file_button, "notify::file", G_CALLBACK (changed_cb), self); + + filter = gtk_file_filter_new (); + gtk_file_filter_add_pattern (filter, "*.pac"); + gtk_file_filter_set_name (filter, _("PAC files (*.pac)")); + gtk_file_chooser_add_filter (ws_file_chooser_button_get_filechooser (self->pac_file_button), filter); + filter = gtk_file_filter_new (); + gtk_file_filter_add_pattern (filter, "*"); + gtk_file_filter_set_name (filter, _("All files")); + gtk_file_chooser_add_filter (ws_file_chooser_button_get_filechooser (self->pac_file_button), filter); + + if (connection && s_8021x) { + const char *filename = nm_setting_802_1x_get_pac_file (s_8021x); + if (filename) { + g_autoptr(GFile) file = g_file_new_for_path (filename); + ws_file_chooser_button_set_file (self->pac_file_button, file); + } + } + + self->em_gtc = eap_method_simple_new (connection, "gtc", TRUE, FALSE); + gtk_widget_show (GTK_WIDGET (self->em_gtc)); + g_signal_connect_object (self->em_gtc, "changed", G_CALLBACK (eap_method_emit_changed), self, G_CONNECT_SWAPPED); + + self->em_mschap_v2 = eap_method_simple_new (connection, "mschapv2", TRUE, FALSE); + gtk_widget_show (GTK_WIDGET (self->em_mschap_v2)); + g_signal_connect_object (self->em_mschap_v2, "changed", G_CALLBACK (eap_method_emit_changed), self, G_CONNECT_SWAPPED); + + if (s_8021x) { + if (nm_setting_802_1x_get_phase2_auth (s_8021x)) + phase2_auth = nm_setting_802_1x_get_phase2_auth (s_8021x); + else if (nm_setting_802_1x_get_phase2_autheap (s_8021x)) + phase2_auth = nm_setting_802_1x_get_phase2_autheap (s_8021x); + } + if (phase2_auth == NULL) + phase2_auth = "gtc"; + + if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (self->inner_auth_model), &iter)) { + do { + g_autofree gchar *id = NULL; + gtk_tree_model_get (GTK_TREE_MODEL (self->inner_auth_model), &iter, I_ID_COLUMN, &id, -1); + if (strcmp (id, phase2_auth) == 0) + gtk_combo_box_set_active_iter (self->inner_auth_combo, &iter); + } while (gtk_tree_model_iter_next (GTK_TREE_MODEL (self->inner_auth_model), &iter)); + } + + g_signal_connect_swapped (self->inner_auth_combo, "changed", G_CALLBACK (inner_auth_combo_changed_cb), self); + inner_auth_combo_changed_cb (self); + + return self; +} + diff --git a/panels/network/wireless-security/eap-method-fast.h b/panels/network/wireless-security/eap-method-fast.h new file mode 100644 index 0000000..5528ab1 --- /dev/null +++ b/panels/network/wireless-security/eap-method-fast.h @@ -0,0 +1,34 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* vim: set ft=c ts=4 sts=4 sw=4 noexpandtab smartindent: */ + +/* EAP-FAST authentication method (RFC4851) + * + * 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. + * + * (C) Copyright 2012 Red Hat, Inc. + */ + +#pragma once + +#include <gtk/gtk.h> +#include <NetworkManager.h> + +G_BEGIN_DECLS + +G_DECLARE_FINAL_TYPE (EAPMethodFAST, eap_method_fast, EAP, METHOD_FAST, GtkGrid) + +EAPMethodFAST *eap_method_fast_new (NMConnection *connection); + +G_END_DECLS diff --git a/panels/network/wireless-security/eap-method-fast.ui b/panels/network/wireless-security/eap-method-fast.ui new file mode 100644 index 0000000..ad63b18 --- /dev/null +++ b/panels/network/wireless-security/eap-method-fast.ui @@ -0,0 +1,151 @@ +<?xml version="1.0" encoding="UTF-8"?> +<interface> + <requires lib="gtk+" version="3.4"/> + <object class="GtkListStore" id="inner_auth_model"> + <columns> + <!-- column-name label --> + <column type="gchararray"/> + <!-- column-name id --> + <column type="gchararray"/> + </columns> + <data> + <row> + <col id="0" translatable="yes">GTC</col> + <col id="1">gtc</col> + </row> + <row> + <col id="0" translatable="yes">MSCHAPv2</col> + <col id="1">mschapv2</col> + </row> + </data> + </object> + <object class="GtkListStore" id="pac_provision_model"> + <columns> + <!-- column-name gchararray --> + <column type="gchararray"/> + </columns> + <data> + <row> + <col id="0" translatable="yes">Anonymous</col> + </row> + <row> + <col id="0" translatable="yes">Authenticated</col> + </row> + <row> + <col id="0" translatable="yes">Both</col> + </row> + </data> + </object> + <template class="EAPMethodFAST" parent="GtkGrid"> + <property name="valign">start</property> + <property name="column_spacing">6</property> + <property name="row_spacing">6</property> + <child> + <object class="GtkLabel" id="anon_identity_label"> + <property name="xalign">1</property> + <property name="label" translatable="yes">Anony_mous identity</property> + <property name="use_underline">True</property> + <property name="mnemonic_widget">anon_identity_entry</property> + <layout> + <property name="column">0</property> + <property name="row">0</property> + </layout> + </object> + </child> + <child> + <object class="GtkEntry" id="anon_identity_entry"> + <property name="hexpand">True</property> + <layout> + <property name="column">1</property> + <property name="row">0</property> + </layout> + </object> + </child> + <child> + <object class="GtkLabel" id="pac_file_label"> + <property name="xalign">1</property> + <property name="label" translatable="yes">PAC _file</property> + <property name="use_underline">True</property> + <property name="mnemonic_widget">pac_file_button</property> + <layout> + <property name="column">0</property> + <property name="row">2</property> + </layout> + </object> + </child> + <child> + <object class="WsFileChooserButton" id="pac_file_button"> + <property name="hexpand">True</property> + <property name="title" translatable="yes">Choose a PAC file</property> + <layout> + <property name="column">1</property> + <property name="row">2</property> + </layout> + </object> + </child> + <child> + <object class="GtkBox" id="inner_auth_box"> + <property name="orientation">vertical</property> + <layout> + <property name="column">0</property> + <property name="column-span">2</property> + <property name="row">4</property> + </layout> + </object> + </child> + <child> + <object class="GtkLabel" id="inner_auth_label"> + <property name="xalign">1</property> + <property name="label" translatable="yes">_Inner authentication</property> + <property name="use_underline">True</property> + <property name="mnemonic_widget">inner_auth_combo</property> + <layout> + <property name="column">0</property> + <property name="row">3</property> + </layout> + </object> + </child> + <child> + <object class="GtkComboBox" id="inner_auth_combo"> + <property name="hexpand">True</property> + <property name="model">inner_auth_model</property> + <layout> + <property name="column">1</property> + <property name="row">3</property> + </layout> + <child> + <object class="GtkCellRendererText"/> + <attributes> + <attribute name="text">0</attribute> + </attributes> + </child> + </object> + </child> + <child> + <object class="GtkCheckButton" id="pac_provision_check"> + <property name="label" translatable="yes">Allow automatic PAC pro_visioning</property> + <property name="use_underline">True</property> + <layout> + <property name="column">0</property> + <property name="row">1</property> + </layout> + </object> + </child> + <child> + <object class="GtkComboBox" id="pac_provision_combo"> + <property name="hexpand">True</property> + <property name="model">pac_provision_model</property> + <layout> + <property name="column">1</property> + <property name="row">1</property> + </layout> + <child> + <object class="GtkCellRendererText"/> + <attributes> + <attribute name="text">0</attribute> + </attributes> + </child> + </object> + </child> + </template> +</interface> diff --git a/panels/network/wireless-security/eap-method-leap.c b/panels/network/wireless-security/eap-method-leap.c new file mode 100644 index 0000000..95e7a8e --- /dev/null +++ b/panels/network/wireless-security/eap-method-leap.c @@ -0,0 +1,266 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* NetworkManager Applet -- allow user control over networking + * + * Dan Williams <dcbw@redhat.com> + * + * 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. + * + * Copyright 2007 - 2014 Red Hat, Inc. + */ + +#include <glib/gi18n.h> + +#include "eap-method.h" +#include "eap-method-leap.h" +#include "helpers.h" +#include "nma-ui-utils.h" +#include "ui-helpers.h" + +struct _EAPMethodLEAP { + GtkGrid parent; + + GtkEntry *password_entry; + GtkLabel *password_label; + GtkCheckButton *show_password_check; + GtkEntry *username_entry; + GtkLabel *username_label; +}; + +static void eap_method_iface_init (EAPMethodInterface *); + +G_DEFINE_TYPE_WITH_CODE (EAPMethodLEAP, eap_method_leap, GTK_TYPE_GRID, + G_IMPLEMENT_INTERFACE (eap_method_get_type (), eap_method_iface_init)) + +static void +show_toggled_cb (EAPMethodLEAP *self) +{ + gboolean visible; + visible = gtk_check_button_get_active (GTK_CHECK_BUTTON (self->show_password_check)); + gtk_entry_set_visibility (self->password_entry, visible); +} + +static gboolean +validate (EAPMethod *parent, GError **error) +{ + EAPMethodLEAP *self = (EAPMethodLEAP *)parent; + NMSettingSecretFlags secret_flags; + const char *text; + gboolean ret = TRUE; + + text = gtk_editable_get_text (GTK_EDITABLE (self->username_entry)); + if (!text || !strlen (text)) { + widget_set_error (GTK_WIDGET (self->username_entry)); + g_set_error_literal (error, NMA_ERROR, NMA_ERROR_GENERIC, _("missing EAP-LEAP username")); + ret = FALSE; + } else + widget_unset_error (GTK_WIDGET (self->username_entry)); + + secret_flags = nma_utils_menu_to_secret_flags (GTK_WIDGET (self->password_entry)); + if (secret_flags & NM_SETTING_SECRET_FLAG_NOT_SAVED) { + widget_unset_error (GTK_WIDGET (self->password_entry)); + return TRUE; + } + + text = gtk_editable_get_text (GTK_EDITABLE (self->password_entry)); + if (!text || !strlen (text)) { + widget_set_error (GTK_WIDGET (self->password_entry)); + if (ret) { + g_set_error_literal (error, NMA_ERROR, NMA_ERROR_GENERIC, _("missing EAP-LEAP password")); + ret = FALSE; + } + } else + widget_unset_error (GTK_WIDGET (self->password_entry)); + + return ret; +} + +static void +add_to_size_group (EAPMethod *parent, GtkSizeGroup *group) +{ + EAPMethodLEAP *self = (EAPMethodLEAP *) parent; + gtk_size_group_add_widget (group, GTK_WIDGET (self->username_label)); + gtk_size_group_add_widget (group, GTK_WIDGET (self->password_label)); +} + +static void +fill_connection (EAPMethod *parent, NMConnection *connection, NMSettingSecretFlags flags) +{ + EAPMethodLEAP *self = (EAPMethodLEAP *) parent; + NMSetting8021x *s_8021x; + NMSettingSecretFlags secret_flags; + + s_8021x = nm_connection_get_setting_802_1x (connection); + g_assert (s_8021x); + + nm_setting_802_1x_add_eap_method (s_8021x, "leap"); + + g_object_set (s_8021x, NM_SETTING_802_1X_IDENTITY, gtk_editable_get_text (GTK_EDITABLE (self->username_entry)), NULL); + g_object_set (s_8021x, NM_SETTING_802_1X_PASSWORD, gtk_editable_get_text (GTK_EDITABLE (self->password_entry)), NULL); + + /* Save 802.1X password flags to the connection */ + secret_flags = nma_utils_menu_to_secret_flags (GTK_WIDGET (self->password_entry)); + nm_setting_set_secret_flags (NM_SETTING (s_8021x), NM_SETTING_802_1X_PASSWORD, + secret_flags, NULL); + + /* Update secret flags and popup when editing the connection */ + nma_utils_update_password_storage (GTK_WIDGET (self->password_entry), secret_flags, + NM_SETTING (s_8021x), NM_SETTING_802_1X_PASSWORD); +} + +static void +update_secrets (EAPMethod *parent, NMConnection *connection) +{ + EAPMethodLEAP *self = (EAPMethodLEAP *) parent; + helper_fill_secret_entry (connection, + self->password_entry, + NM_TYPE_SETTING_802_1X, + (HelperSecretFunc) nm_setting_802_1x_get_password); +} + +static GtkWidget * +get_default_field (EAPMethod *parent) +{ + EAPMethodLEAP *self = (EAPMethodLEAP *) parent; + return GTK_WIDGET (self->username_entry); +} + +static const gchar * +get_password_flags_name (EAPMethod *parent) +{ + return NM_SETTING_802_1X_PASSWORD; +} + +static const gchar * +get_username (EAPMethod *method) +{ + EAPMethodLEAP *self = EAP_METHOD_LEAP (method); + return gtk_editable_get_text (GTK_EDITABLE (self->username_entry)); +} + +static void +set_username (EAPMethod *method, const gchar *username) +{ + EAPMethodLEAP *self = EAP_METHOD_LEAP (method); + gtk_editable_set_text (GTK_EDITABLE (self->username_entry), username); +} + +static const gchar * +get_password (EAPMethod *method) +{ + EAPMethodLEAP *self = EAP_METHOD_LEAP (method); + return gtk_editable_get_text (GTK_EDITABLE (self->password_entry)); +} + +static void +set_password (EAPMethod *method, const gchar *password) +{ + EAPMethodLEAP *self = EAP_METHOD_LEAP (method); + gtk_editable_set_text (GTK_EDITABLE (self->password_entry), password); +} + +static gboolean +get_show_password (EAPMethod *method) +{ + EAPMethodLEAP *self = EAP_METHOD_LEAP (method); + return gtk_check_button_get_active (GTK_CHECK_BUTTON (self->show_password_check)); +} + +static void +set_show_password (EAPMethod *method, gboolean show_password) +{ + EAPMethodLEAP *self = EAP_METHOD_LEAP (method); + gtk_check_button_set_active (self->show_password_check, show_password); +} + +static void +eap_method_leap_dispose (GObject *object) +{ + EAPMethodLEAP *self = EAP_METHOD_LEAP (object); + + g_signal_handlers_disconnect_by_data (self, self); + g_signal_handlers_disconnect_by_data (self->show_password_check, self); + + G_OBJECT_CLASS (eap_method_leap_parent_class)->dispose (object); +} + +static void +changed_cb (EAPMethodLEAP *self) +{ + eap_method_emit_changed (EAP_METHOD (self)); +} + +static void +eap_method_leap_init (EAPMethodLEAP *self) +{ + gtk_widget_init_template (GTK_WIDGET (self)); +} + +static void +eap_method_leap_class_init (EAPMethodLEAPClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + object_class->dispose = eap_method_leap_dispose; + + gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/Settings/network/eap-method-leap.ui"); + + gtk_widget_class_bind_template_child (widget_class, EAPMethodLEAP, password_entry); + gtk_widget_class_bind_template_child (widget_class, EAPMethodLEAP, password_label); + gtk_widget_class_bind_template_child (widget_class, EAPMethodLEAP, show_password_check); + gtk_widget_class_bind_template_child (widget_class, EAPMethodLEAP, username_entry); + gtk_widget_class_bind_template_child (widget_class, EAPMethodLEAP, username_label); +} + +static void +eap_method_iface_init (EAPMethodInterface *iface) +{ + iface->validate = validate; + iface->add_to_size_group = add_to_size_group; + iface->fill_connection = fill_connection; + iface->update_secrets = update_secrets; + iface->get_default_field = get_default_field; + iface->get_password_flags_name = get_password_flags_name; + iface->get_username = get_username; + iface->set_username = set_username; + iface->get_password = get_password; + iface->set_password = set_password; + iface->get_show_password = get_show_password; + iface->set_show_password = set_show_password; +} + +EAPMethodLEAP * +eap_method_leap_new (NMConnection *connection) +{ + EAPMethodLEAP *self; + NMSetting8021x *s_8021x = NULL; + + self = g_object_new (eap_method_leap_get_type (), NULL); + + g_signal_connect_swapped (self->username_entry, "changed", G_CALLBACK (changed_cb), self); + + g_signal_connect_swapped (self->password_entry, "changed", G_CALLBACK (changed_cb), self); + + /* Create password-storage popup menu for password entry under entry's secondary icon */ + if (connection) + s_8021x = nm_connection_get_setting_802_1x (connection); + nma_utils_setup_password_storage (GTK_WIDGET (self->password_entry), 0, (NMSetting *) s_8021x, NM_SETTING_802_1X_PASSWORD, + FALSE, FALSE); + + g_signal_connect_swapped (self->show_password_check, "toggled", G_CALLBACK (show_toggled_cb), self); + + return self; +} + diff --git a/panels/network/wireless-security/eap-method-leap.h b/panels/network/wireless-security/eap-method-leap.h new file mode 100644 index 0000000..18248a6 --- /dev/null +++ b/panels/network/wireless-security/eap-method-leap.h @@ -0,0 +1,34 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* NetworkManager Applet -- allow user control over networking + * + * Dan Williams <dcbw@redhat.com> + * + * 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. + * + * (C) Copyright 2007 - 2010 Red Hat, Inc. + */ + +#pragma once + +#include <gtk/gtk.h> +#include <NetworkManager.h> + +G_BEGIN_DECLS + +G_DECLARE_FINAL_TYPE (EAPMethodLEAP, eap_method_leap, EAP, METHOD_LEAP, GtkGrid) + +EAPMethodLEAP *eap_method_leap_new (NMConnection *connection); + +G_END_DECLS diff --git a/panels/network/wireless-security/eap-method-leap.ui b/panels/network/wireless-security/eap-method-leap.ui new file mode 100644 index 0000000..3f945bd --- /dev/null +++ b/panels/network/wireless-security/eap-method-leap.ui @@ -0,0 +1,63 @@ +<?xml version="1.0" encoding="UTF-8"?> +<interface> + <requires lib="gtk+" version="3.4"/> + <template class="EAPMethodLEAP" parent="GtkGrid"> + <property name="valign">start</property> + <property name="column_spacing">6</property> + <property name="row_spacing">6</property> + <child> + <object class="GtkLabel" id="username_label"> + <property name="xalign">1</property> + <property name="label" translatable="yes">_Username</property> + <property name="use_underline">True</property> + <property name="mnemonic_widget">username_entry</property> + <layout> + <property name="column">0</property> + <property name="row">0</property> + </layout> + </object> + </child> + <child> + <object class="GtkLabel" id="password_label"> + <property name="xalign">1</property> + <property name="label" translatable="yes">_Password</property> + <property name="use_underline">True</property> + <property name="mnemonic_widget">password_entry</property> + <layout> + <property name="column">0</property> + <property name="row">1</property> + </layout> + </object> + </child> + <child> + <object class="GtkEntry" id="password_entry"> + <property name="hexpand">True</property> + <property name="visibility">False</property> + <layout> + <property name="column">1</property> + <property name="row">1</property> + </layout> + </object> + </child> + <child> + <object class="GtkCheckButton" id="show_password_check"> + <property name="label" translatable="yes">Sho_w password</property> + <property name="hexpand">True</property> + <property name="use_underline">True</property> + <layout> + <property name="column">1</property> + <property name="row">2</property> + </layout> + </object> + </child> + <child> + <object class="GtkEntry" id="username_entry"> + <property name="hexpand">True</property> + <layout> + <property name="column">1</property> + <property name="row">0</property> + </layout> + </object> + </child> + </template> +</interface> diff --git a/panels/network/wireless-security/eap-method-peap.c b/panels/network/wireless-security/eap-method-peap.c new file mode 100644 index 0000000..17eee12 --- /dev/null +++ b/panels/network/wireless-security/eap-method-peap.c @@ -0,0 +1,400 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* NetworkManager Applet -- allow user control over networking + * + * Dan Williams <dcbw@redhat.com> + * + * 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. + * + * Copyright 2007 - 2014 Red Hat, Inc. + */ + +#include <glib/gi18n.h> + +#include "eap-method.h" +#include "eap-method-peap.h" +#include "eap-method-simple.h" +#include "helpers.h" +#include "ui-helpers.h" +#include "ws-file-chooser-button.h" + +#define I_NAME_COLUMN 0 +#define I_ID_COLUMN 1 + +struct _EAPMethodPEAP { + GtkGrid parent; + + GtkEntry *anon_identity_entry; + GtkLabel *anon_identity_label; + WsFileChooserButton *ca_cert_button; + GtkLabel *ca_cert_label; + GtkCheckButton *ca_cert_not_required_check; + GtkBox *inner_auth_box; + GtkComboBox *inner_auth_combo; + GtkLabel *inner_auth_label; + GtkListStore *inner_auth_model; + GtkComboBox *version_combo; + GtkLabel *version_label; + + EAPMethodSimple *em_gtc; + EAPMethodSimple *em_md5; + EAPMethodSimple *em_mschap_v2; +}; + +static void eap_method_iface_init (EAPMethodInterface *); + +G_DEFINE_TYPE_WITH_CODE (EAPMethodPEAP, eap_method_peap, GTK_TYPE_GRID, + G_IMPLEMENT_INTERFACE (eap_method_get_type (), eap_method_iface_init)) + +static EAPMethod * +get_inner_method (EAPMethodPEAP *self) +{ + GtkTreeIter iter; + g_autofree gchar *id = NULL; + + if (!gtk_combo_box_get_active_iter (self->inner_auth_combo, &iter)) + return NULL; + gtk_tree_model_get (GTK_TREE_MODEL (self->inner_auth_model), &iter, I_ID_COLUMN, &id, -1); + + if (strcmp (id, "gtc") == 0) + return EAP_METHOD (self->em_gtc); + if (strcmp (id, "md5") == 0) + return EAP_METHOD (self->em_md5); + if (strcmp (id, "mschapv2") == 0) + return EAP_METHOD (self->em_mschap_v2); + + return NULL; +} + +static gboolean +validate (EAPMethod *method, GError **error) +{ + EAPMethodPEAP *self = EAP_METHOD_PEAP (method); + g_autoptr(GError) local_error = NULL; + + if (!eap_method_validate_filepicker (ws_file_chooser_button_get_filechooser (self->ca_cert_button), + TYPE_CA_CERT, NULL, NULL, &local_error)) { + g_set_error (error, NMA_ERROR, NMA_ERROR_GENERIC, _("invalid EAP-PEAP CA certificate: %s"), local_error->message); + return FALSE; + } + + if (!gtk_check_button_get_active (GTK_CHECK_BUTTON (self->ca_cert_not_required_check))) { + g_autoptr(GFile) file = NULL; + + file = ws_file_chooser_button_get_file (self->ca_cert_button); + if (file == NULL) { + g_set_error_literal (error, NMA_ERROR, NMA_ERROR_GENERIC, _("invalid EAP-PEAP CA certificate: no certificate specified")); + return FALSE; + } + } + + return eap_method_validate (get_inner_method (self), error); +} + +static void +ca_cert_not_required_toggled (EAPMethodPEAP *self) +{ + eap_method_ca_cert_not_required_toggled (self->ca_cert_not_required_check, + ws_file_chooser_button_get_filechooser (self->ca_cert_button)); + eap_method_emit_changed (EAP_METHOD (self)); +} + +static void +add_to_size_group (EAPMethod *method, GtkSizeGroup *group) +{ + EAPMethodPEAP *self = EAP_METHOD_PEAP (method); + + gtk_size_group_add_widget (group, GTK_WIDGET (self->ca_cert_not_required_check)); + gtk_size_group_add_widget (group, GTK_WIDGET (self->anon_identity_label)); + gtk_size_group_add_widget (group, GTK_WIDGET (self->ca_cert_label)); + gtk_size_group_add_widget (group, GTK_WIDGET (self->version_label)); + gtk_size_group_add_widget (group, GTK_WIDGET (self->inner_auth_label)); + + eap_method_add_to_size_group (EAP_METHOD (self->em_gtc), group); + eap_method_add_to_size_group (EAP_METHOD (self->em_md5), group); + eap_method_add_to_size_group (EAP_METHOD (self->em_mschap_v2), group); +} + +static void +fill_connection (EAPMethod *method, NMConnection *connection, NMSettingSecretFlags flags) +{ + EAPMethodPEAP *self = EAP_METHOD_PEAP (method); + NMSetting8021x *s_8021x; + NMSetting8021xCKFormat format = NM_SETTING_802_1X_CK_FORMAT_UNKNOWN; + const char *text; + g_autofree gchar *filename = NULL; + g_autoptr(GFile) file = NULL; + int peapver_active = 0; + g_autoptr(GError) error = NULL; + gboolean ca_cert_error = FALSE; + + s_8021x = nm_connection_get_setting_802_1x (connection); + g_assert (s_8021x); + + nm_setting_802_1x_add_eap_method (s_8021x, "peap"); + + text = gtk_editable_get_text (GTK_EDITABLE (self->anon_identity_entry)); + if (text && strlen (text)) + g_object_set (s_8021x, NM_SETTING_802_1X_ANONYMOUS_IDENTITY, text, NULL); + + file = ws_file_chooser_button_get_file (self->ca_cert_button); + filename = file ? g_file_get_path (file) : NULL; + if (!nm_setting_802_1x_set_ca_cert (s_8021x, filename, NM_SETTING_802_1X_CK_SCHEME_PATH, &format, &error)) { + g_warning ("Couldn't read CA certificate '%s': %s", filename, error ? error->message : "(unknown)"); + ca_cert_error = TRUE; + } + eap_method_ca_cert_ignore_set (method, connection, filename, ca_cert_error); + + peapver_active = gtk_combo_box_get_active (self->version_combo); + switch (peapver_active) { + case 1: /* PEAP v0 */ + g_object_set (G_OBJECT (s_8021x), NM_SETTING_802_1X_PHASE1_PEAPVER, "0", NULL); + break; + case 2: /* PEAP v1 */ + g_object_set (G_OBJECT (s_8021x), NM_SETTING_802_1X_PHASE1_PEAPVER, "1", NULL); + break; + default: /* Automatic */ + g_object_set (G_OBJECT (s_8021x), NM_SETTING_802_1X_PHASE1_PEAPVER, NULL, NULL); + break; + } + + eap_method_fill_connection (get_inner_method (self), connection, flags); +} + +static void +inner_auth_combo_changed_cb (EAPMethodPEAP *self) +{ + EAPMethod *inner_method; + GtkWidget *child; + + inner_method = get_inner_method (self); + + /* Remove the previous method and migrate username/password across */ + child = gtk_widget_get_first_child (GTK_WIDGET (self->inner_auth_box)); + if (child != NULL) { + EAPMethod *old_eap = EAP_METHOD (child); + eap_method_set_username (inner_method, eap_method_get_username (old_eap)); + eap_method_set_password (inner_method, eap_method_get_password (old_eap)); + eap_method_set_show_password (inner_method, eap_method_get_show_password (old_eap)); + gtk_box_remove (self->inner_auth_box, GTK_WIDGET (old_eap)); + } + + gtk_box_append (self->inner_auth_box, g_object_ref (GTK_WIDGET (inner_method))); + + eap_method_emit_changed (EAP_METHOD (self)); +} + +static void +update_secrets (EAPMethod *method, NMConnection *connection) +{ + EAPMethodPEAP *self = EAP_METHOD_PEAP (method); + + eap_method_update_secrets (EAP_METHOD (self->em_gtc), connection); + eap_method_update_secrets (EAP_METHOD (self->em_md5), connection); + eap_method_update_secrets (EAP_METHOD (self->em_mschap_v2), connection); +} + +static GtkWidget * +get_default_field (EAPMethod *method) +{ + EAPMethodPEAP *self = EAP_METHOD_PEAP (method); + return GTK_WIDGET (self->anon_identity_entry); +} + +static const gchar * +get_password_flags_name (EAPMethod *method) +{ + return NM_SETTING_802_1X_PASSWORD; +} + +static const gchar * +get_username (EAPMethod *method) +{ + EAPMethodPEAP *self = EAP_METHOD_PEAP (method); + return eap_method_get_username (get_inner_method (self)); +} + +static void +set_username (EAPMethod *method, const gchar *username) +{ + EAPMethodPEAP *self = EAP_METHOD_PEAP (method); + return eap_method_set_username (get_inner_method (self), username); +} + +static const gchar * +get_password (EAPMethod *method) +{ + EAPMethodPEAP *self = EAP_METHOD_PEAP (method); + return eap_method_get_password (get_inner_method (self)); +} + +static void +set_password (EAPMethod *method, const gchar *password) +{ + EAPMethodPEAP *self = EAP_METHOD_PEAP (method); + return eap_method_set_password (get_inner_method (self), password); +} + +static gboolean +get_show_password (EAPMethod *method) +{ + EAPMethodPEAP *self = EAP_METHOD_PEAP (method); + return eap_method_get_show_password (get_inner_method (self)); +} + +static void +set_show_password (EAPMethod *method, gboolean show_password) +{ + EAPMethodPEAP *self = EAP_METHOD_PEAP (method); + return eap_method_set_show_password (get_inner_method (self), show_password); +} + +static void +changed_cb (EAPMethodPEAP *self) +{ + eap_method_emit_changed (EAP_METHOD (self)); +} + +static void +eap_method_peap_init (EAPMethodPEAP *self) +{ + gtk_widget_init_template (GTK_WIDGET (self)); +} + +static void +eap_method_peap_class_init (EAPMethodPEAPClass *klass) +{ + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/Settings/network/eap-method-peap.ui"); + + gtk_widget_class_bind_template_child (widget_class, EAPMethodPEAP, anon_identity_entry); + gtk_widget_class_bind_template_child (widget_class, EAPMethodPEAP, anon_identity_label); + gtk_widget_class_bind_template_child (widget_class, EAPMethodPEAP, ca_cert_button); + gtk_widget_class_bind_template_child (widget_class, EAPMethodPEAP, ca_cert_label); + gtk_widget_class_bind_template_child (widget_class, EAPMethodPEAP, ca_cert_not_required_check); + gtk_widget_class_bind_template_child (widget_class, EAPMethodPEAP, inner_auth_box); + gtk_widget_class_bind_template_child (widget_class, EAPMethodPEAP, inner_auth_combo); + gtk_widget_class_bind_template_child (widget_class, EAPMethodPEAP, inner_auth_label); + gtk_widget_class_bind_template_child (widget_class, EAPMethodPEAP, inner_auth_model); + gtk_widget_class_bind_template_child (widget_class, EAPMethodPEAP, version_combo); + gtk_widget_class_bind_template_child (widget_class, EAPMethodPEAP, version_label); +} + +static void +eap_method_iface_init (EAPMethodInterface *iface) +{ + iface->validate = validate; + iface->add_to_size_group = add_to_size_group; + iface->fill_connection = fill_connection; + iface->update_secrets = update_secrets; + iface->get_default_field = get_default_field; + iface->get_password_flags_name = get_password_flags_name; + iface->get_username = get_username; + iface->set_username = set_username; + iface->get_password = get_password; + iface->set_password = set_password; + iface->get_show_password = get_show_password; + iface->set_show_password = set_show_password; +} + +EAPMethodPEAP * +eap_method_peap_new (NMConnection *connection) +{ + EAPMethodPEAP *self; + GtkFileFilter *filter; + NMSetting8021x *s_8021x = NULL; + const char *filename; + const gchar *phase2_auth = NULL; + GtkTreeIter iter; + + self = g_object_new (eap_method_peap_get_type (), NULL); + + if (connection) + s_8021x = nm_connection_get_setting_802_1x (connection); + + g_signal_connect_swapped (self->ca_cert_not_required_check, "toggled", G_CALLBACK (ca_cert_not_required_toggled), self); + + g_signal_connect_swapped (self->ca_cert_button, "notify::file", G_CALLBACK (changed_cb), self); + filter = eap_method_default_file_chooser_filter_new (FALSE); + gtk_file_chooser_add_filter (ws_file_chooser_button_get_filechooser (self->ca_cert_button), + filter); + if (connection && s_8021x) { + filename = NULL; + if (nm_setting_802_1x_get_ca_cert_scheme (s_8021x) == NM_SETTING_802_1X_CK_SCHEME_PATH) { + filename = nm_setting_802_1x_get_ca_cert_path (s_8021x); + if (filename) { + g_autoptr(GFile) file = g_file_new_for_path (filename); + ws_file_chooser_button_set_file (self->ca_cert_button, file); + } + } + gtk_check_button_set_active (self->ca_cert_not_required_check, + !filename && eap_method_ca_cert_ignore_get (EAP_METHOD (self), connection)); + } + + self->em_mschap_v2 = eap_method_simple_new (connection, "mschapv2", TRUE, FALSE); + gtk_widget_show (GTK_WIDGET (self->em_mschap_v2)); + g_signal_connect_object (self->em_mschap_v2, "changed", G_CALLBACK (eap_method_emit_changed), self, G_CONNECT_SWAPPED); + + self->em_md5 = eap_method_simple_new (connection, "md5", TRUE, FALSE); + gtk_widget_show (GTK_WIDGET (self->em_md5)); + g_signal_connect_object (self->em_md5, "changed", G_CALLBACK (eap_method_emit_changed), self, G_CONNECT_SWAPPED); + + self->em_gtc = eap_method_simple_new (connection, "gtc", TRUE, FALSE); + gtk_widget_show (GTK_WIDGET (self->em_gtc)); + g_signal_connect_object (self->em_gtc, "changed", G_CALLBACK (eap_method_emit_changed), self, G_CONNECT_SWAPPED); + + if (s_8021x) { + if (nm_setting_802_1x_get_phase2_auth (s_8021x)) + phase2_auth = nm_setting_802_1x_get_phase2_auth (s_8021x); + else if (nm_setting_802_1x_get_phase2_autheap (s_8021x)) + phase2_auth = nm_setting_802_1x_get_phase2_autheap (s_8021x); + } + if (phase2_auth == NULL) + phase2_auth = "mschapv2"; + + if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (self->inner_auth_model), &iter)) { + do { + g_autofree gchar *id = NULL; + gtk_tree_model_get (GTK_TREE_MODEL (self->inner_auth_model), &iter, I_ID_COLUMN, &id, -1); + if (strcmp (id, phase2_auth) == 0) + gtk_combo_box_set_active_iter (self->inner_auth_combo, &iter); + } while (gtk_tree_model_iter_next (GTK_TREE_MODEL (self->inner_auth_model), &iter)); + } + + g_signal_connect_swapped (self->inner_auth_combo, "changed", G_CALLBACK (inner_auth_combo_changed_cb), self); + inner_auth_combo_changed_cb (self); + + gtk_combo_box_set_active (self->version_combo, 0); + if (s_8021x) { + const char *peapver; + + peapver = nm_setting_802_1x_get_phase1_peapver (s_8021x); + if (peapver) { + /* Index 0 is "Automatic" */ + if (!strcmp (peapver, "0")) + gtk_combo_box_set_active (self->version_combo, 1); + else if (!strcmp (peapver, "1")) + gtk_combo_box_set_active (self->version_combo, 2); + } + } + g_signal_connect_swapped (self->version_combo, "changed", G_CALLBACK (changed_cb), self); + + if (s_8021x && nm_setting_802_1x_get_anonymous_identity (s_8021x)) + gtk_editable_set_text (GTK_EDITABLE (self->anon_identity_entry), nm_setting_802_1x_get_anonymous_identity (s_8021x)); + g_signal_connect_swapped (self->anon_identity_entry, "changed", G_CALLBACK (changed_cb), self); + + return self; +} + diff --git a/panels/network/wireless-security/eap-method-peap.h b/panels/network/wireless-security/eap-method-peap.h new file mode 100644 index 0000000..53b2f30 --- /dev/null +++ b/panels/network/wireless-security/eap-method-peap.h @@ -0,0 +1,34 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* NetworkManager Applet -- allow user control over networking + * + * Dan Williams <dcbw@redhat.com> + * + * 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. + * + * (C) Copyright 2007 - 2010 Red Hat, Inc. + */ + +#pragma once + +#include <gtk/gtk.h> +#include <NetworkManager.h> + +G_BEGIN_DECLS + +G_DECLARE_FINAL_TYPE (EAPMethodPEAP, eap_method_peap, EAP, METHOD_PEAP, GtkGrid) + +EAPMethodPEAP *eap_method_peap_new (NMConnection *connection); + +G_END_DECLS diff --git a/panels/network/wireless-security/eap-method-peap.ui b/panels/network/wireless-security/eap-method-peap.ui new file mode 100644 index 0000000..ac04783 --- /dev/null +++ b/panels/network/wireless-security/eap-method-peap.ui @@ -0,0 +1,167 @@ +<?xml version="1.0" encoding="UTF-8"?> +<interface> + <requires lib="gtk+" version="3.4"/> + <object class="GtkListStore" id="inner_auth_model"> + <columns> + <!-- column-name label --> + <column type="gchararray"/> + <!-- column-name id --> + <column type="gchararray"/> + </columns> + <data> + <row> + <col id="0" translatable="yes">MSCHAPv2</col> + <col id="1">mschapv2</col> + </row> + <row> + <col id="0" translatable="yes">MD5</col> + <col id="1">md5</col> + </row> + <row> + <col id="0" translatable="yes">GTC</col> + <col id="1">gtc</col> + </row> + </data> + </object> + <object class="GtkListStore" id="version_model"> + <columns> + <!-- column-name gchararray --> + <column type="gchararray"/> + </columns> + <data> + <row> + <col id="0" translatable="yes">Automatic</col> + </row> + <row> + <col id="0" translatable="yes">Version 0</col> + </row> + <row> + <col id="0" translatable="yes">Version 1</col> + </row> + </data> + </object> + <template class="EAPMethodPEAP" parent="GtkGrid"> + <property name="column_spacing">6</property> + <property name="row_spacing">6</property> + <child> + <object class="GtkLabel" id="anon_identity_label"> + <property name="xalign">1</property> + <property name="label" translatable="yes">Anony_mous identity</property> + <property name="use_underline">True</property> + <property name="mnemonic_widget">anon_identity_entry</property> + <layout> + <property name="column">0</property> + <property name="row">0</property> + </layout> + </object> + </child> + <child> + <object class="GtkEntry" id="anon_identity_entry"> + <property name="hexpand">True</property> + <layout> + <property name="column">1</property> + <property name="row">0</property> + </layout> + </object> + </child> + <child> + <object class="GtkLabel" id="ca_cert_label"> + <property name="xalign">1</property> + <property name="label" translatable="yes">C_A certificate</property> + <property name="use_underline">True</property> + <property name="mnemonic_widget">ca_cert_button</property> + <layout> + <property name="column">0</property> + <property name="row">1</property> + </layout> + </object> + </child> + <child> + <object class="WsFileChooserButton" id="ca_cert_button"> + <property name="hexpand">True</property> + <property name="title" translatable="yes">Choose a Certificate Authority certificate</property> + <layout> + <property name="column">1</property> + <property name="row">1</property> + </layout> + </object> + </child> + <child> + <object class="GtkCheckButton" id="ca_cert_not_required_check"> + <property name="label" translatable="yes">No CA certificate is _required</property> + <property name="hexpand">True</property> + <property name="use_underline">True</property> + <layout> + <property name="column">1</property> + <property name="row">2</property> + </layout> + </object> + </child> + <child> + <object class="GtkLabel" id="version_label"> + <property name="xalign">1</property> + <property name="label" translatable="yes">PEAP _version</property> + <property name="use_underline">True</property> + <property name="mnemonic_widget">version_combo</property> + <layout> + <property name="column">0</property> + <property name="row">3</property> + </layout> + </object> + </child> + <child> + <object class="GtkComboBox" id="version_combo"> + <property name="hexpand">True</property> + <property name="model">version_model</property> + <layout> + <property name="column">1</property> + <property name="row">3</property> + </layout> + <child> + <object class="GtkCellRendererText"/> + <attributes> + <attribute name="text">0</attribute> + </attributes> + </child> + </object> + </child> + <child> + <object class="GtkLabel" id="inner_auth_label"> + <property name="xalign">1</property> + <property name="label" translatable="yes">_Inner authentication</property> + <property name="use_underline">True</property> + <property name="mnemonic_widget">inner_auth_combo</property> + <layout> + <property name="column">0</property> + <property name="row">4</property> + </layout> + </object> + </child> + <child> + <object class="GtkComboBox" id="inner_auth_combo"> + <property name="hexpand">True</property> + <property name="model">inner_auth_model</property> + <layout> + <property name="column">1</property> + <property name="row">4</property> + </layout> + <child> + <object class="GtkCellRendererText"/> + <attributes> + <attribute name="text">0</attribute> + </attributes> + </child> + </object> + </child> + <child> + <object class="GtkBox" id="inner_auth_box"> + <property name="orientation">vertical</property> + <layout> + <property name="column">0</property> + <property name="row">5</property> + <property name="column-span">2</property> + </layout> + </object> + </child> + </template> +</interface> diff --git a/panels/network/wireless-security/eap-method-simple.c b/panels/network/wireless-security/eap-method-simple.c new file mode 100644 index 0000000..7bb29ec --- /dev/null +++ b/panels/network/wireless-security/eap-method-simple.c @@ -0,0 +1,356 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* NetworkManager Applet -- allow user control over networking + * + * Dan Williams <dcbw@redhat.com> + * + * 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. + * + * Copyright 2007 - 2014 Red Hat, Inc. + */ + +#include <glib/gi18n.h> + +#include "eap-method.h" +#include "eap-method-simple.h" +#include "helpers.h" +#include "nma-ui-utils.h" +#include "ui-helpers.h" + +struct _EAPMethodSimple { + GtkGrid parent; + + GtkEntry *password_entry; + GtkLabel *password_label; + GtkToggleButton *show_password_check; + GtkEntry *username_entry; + GtkLabel *username_label; + + gchar *name; + gboolean phase2; + gboolean autheap_allowed; + + guint idle_func_id; +}; + +static void eap_method_iface_init (EAPMethodInterface *); + +G_DEFINE_TYPE_WITH_CODE (EAPMethodSimple, eap_method_simple, GTK_TYPE_GRID, + G_IMPLEMENT_INTERFACE (eap_method_get_type (), eap_method_iface_init)) + +static void +show_toggled_cb (EAPMethodSimple *self) +{ + gboolean visible; + + visible = gtk_check_button_get_active (GTK_CHECK_BUTTON (self->show_password_check)); + gtk_entry_set_visibility (self->password_entry, visible); +} + +static gboolean +always_ask_selected (GtkEntry *passwd_entry) +{ + return !!( nma_utils_menu_to_secret_flags (GTK_WIDGET (passwd_entry)) + & NM_SETTING_SECRET_FLAG_NOT_SAVED); +} + +static gboolean +validate (EAPMethod *method, GError **error) +{ + EAPMethodSimple *self = EAP_METHOD_SIMPLE (method); + const char *text; + gboolean ret = TRUE; + + text = gtk_editable_get_text (GTK_EDITABLE (self->username_entry)); + if (!text || !strlen (text)) { + widget_set_error (GTK_WIDGET (self->username_entry)); + g_set_error_literal (error, NMA_ERROR, NMA_ERROR_GENERIC, _("missing EAP username")); + ret = FALSE; + } else + widget_unset_error (GTK_WIDGET (self->username_entry)); + + /* Check if the password should always be requested */ + if (always_ask_selected (self->password_entry)) + widget_unset_error (GTK_WIDGET (self->password_entry)); + else { + text = gtk_editable_get_text (GTK_EDITABLE (self->password_entry)); + if (!text || !strlen (text)) { + widget_set_error (GTK_WIDGET (self->password_entry)); + if (ret) { + g_set_error_literal (error, NMA_ERROR, NMA_ERROR_GENERIC, _("missing EAP password")); + ret = FALSE; + } + } else + widget_unset_error (GTK_WIDGET (self->password_entry)); + } + + return ret; +} + +static void +add_to_size_group (EAPMethod *method, GtkSizeGroup *group) +{ + EAPMethodSimple *self = EAP_METHOD_SIMPLE (method); + gtk_size_group_add_widget (group, GTK_WIDGET (self->username_label)); + gtk_size_group_add_widget (group, GTK_WIDGET (self->password_label)); +} + +static void +fill_connection (EAPMethod *method, NMConnection *connection, NMSettingSecretFlags prev_flags) +{ + EAPMethodSimple *self = EAP_METHOD_SIMPLE (method); + NMSetting8021x *s_8021x; + gboolean not_saved = FALSE; + NMSettingSecretFlags flags; + + s_8021x = nm_connection_get_setting_802_1x (connection); + g_assert (s_8021x); + + /* If this is the main EAP method, clear any existing methods because the + * user-selected on will replace it. + */ + if (eap_method_get_phase2 (method) == FALSE) + nm_setting_802_1x_clear_eap_methods (s_8021x); + + if (eap_method_get_phase2 (method)) { + /* If the outer EAP method (TLS, TTLS, PEAP, etc) allows inner/phase2 + * EAP methods (which only TTLS allows) *and* the inner/phase2 method + * supports being an inner EAP method, then set PHASE2_AUTHEAP. + * Otherwise the inner/phase2 method goes into PHASE2_AUTH. + */ + if (self->autheap_allowed) { + g_object_set (s_8021x, NM_SETTING_802_1X_PHASE2_AUTHEAP, self->name, NULL); + g_object_set (s_8021x, NM_SETTING_802_1X_PHASE2_AUTH, NULL, NULL); + } else { + g_object_set (s_8021x, NM_SETTING_802_1X_PHASE2_AUTH, self->name, NULL); + g_object_set (s_8021x, NM_SETTING_802_1X_PHASE2_AUTHEAP, NULL, NULL); + } + } else + nm_setting_802_1x_add_eap_method (s_8021x, self->name); + + g_object_set (s_8021x, NM_SETTING_802_1X_IDENTITY, gtk_editable_get_text (GTK_EDITABLE (self->username_entry)), NULL); + + /* Save the password always ask setting */ + not_saved = always_ask_selected (self->password_entry); + flags = nma_utils_menu_to_secret_flags (GTK_WIDGET (self->password_entry)); + nm_setting_set_secret_flags (NM_SETTING (s_8021x), NM_SETTING_802_1X_PASSWORD, flags, NULL); + + /* Fill the connection's password if we're in the applet so that it'll get + * back to NM. From the editor though, since the connection isn't going + * back to NM in response to a GetSecrets() call, we don't save it if the + * user checked "Always Ask". + */ + if (not_saved == FALSE) + g_object_set (s_8021x, NM_SETTING_802_1X_PASSWORD, gtk_editable_get_text (GTK_EDITABLE (self->password_entry)), NULL); + + /* Update secret flags and popup when editing the connection */ + nma_utils_update_password_storage (GTK_WIDGET (self->password_entry), flags, + NM_SETTING (s_8021x), NM_SETTING_802_1X_PASSWORD); +} + +static void +update_secrets (EAPMethod *method, NMConnection *connection) +{ + EAPMethodSimple *self = EAP_METHOD_SIMPLE (method); + helper_fill_secret_entry (connection, + self->password_entry, + NM_TYPE_SETTING_802_1X, + (HelperSecretFunc) nm_setting_802_1x_get_password); +} + +static GtkWidget * +get_default_field (EAPMethod *method) +{ + EAPMethodSimple *self = EAP_METHOD_SIMPLE (method); + return GTK_WIDGET (self->username_entry); +} + +static const gchar * +get_password_flags_name (EAPMethod *method) +{ + return NM_SETTING_802_1X_PASSWORD; +} + +static const gboolean +get_phase2 (EAPMethod *method) +{ + EAPMethodSimple *self = EAP_METHOD_SIMPLE (method); + return self->phase2; +} + +static const gchar * +get_username (EAPMethod *method) +{ + EAPMethodSimple *self = EAP_METHOD_SIMPLE (method); + return gtk_editable_get_text (GTK_EDITABLE (self->username_entry)); +} + +static void +set_username (EAPMethod *method, const gchar *username) +{ + EAPMethodSimple *self = EAP_METHOD_SIMPLE (method); + if (username) + gtk_editable_set_text (GTK_EDITABLE (self->username_entry), username); +} + +static const gchar * +get_password (EAPMethod *method) +{ + EAPMethodSimple *self = EAP_METHOD_SIMPLE (method); + return gtk_editable_get_text (GTK_EDITABLE (self->password_entry)); +} + +static void +set_password (EAPMethod *method, const gchar *password) +{ + EAPMethodSimple *self = EAP_METHOD_SIMPLE (method); + if (password) + gtk_editable_set_text (GTK_EDITABLE (self->password_entry), password); +} + +static gboolean +get_show_password (EAPMethod *method) +{ + EAPMethodSimple *self = EAP_METHOD_SIMPLE (method); + return gtk_check_button_get_active (GTK_CHECK_BUTTON (self->show_password_check)); +} + +static void +set_show_password (EAPMethod *method, gboolean show_password) +{ + EAPMethodSimple *self = EAP_METHOD_SIMPLE (method); + gtk_check_button_set_active (GTK_CHECK_BUTTON (self->show_password_check), show_password); +} + +static gboolean +stuff_changed (EAPMethodSimple *self) +{ + eap_method_emit_changed (EAP_METHOD (self)); + self->idle_func_id = 0; + return FALSE; +} + +static void +password_storage_changed (EAPMethodSimple *self) +{ + gboolean always_ask; + + always_ask = always_ask_selected (self->password_entry); + + if (always_ask) { + /* we always clear this button and do not restore it + * (because we want to hide the password). */ + gtk_check_button_set_active (GTK_CHECK_BUTTON (self->show_password_check), FALSE); + } + + gtk_widget_set_sensitive (GTK_WIDGET (self->show_password_check), !always_ask); + + if (!self->idle_func_id) + self->idle_func_id = g_idle_add ((GSourceFunc) stuff_changed, self); +} + +static void +eap_method_simple_dispose (GObject *object) +{ + EAPMethodSimple *self = EAP_METHOD_SIMPLE (object); + + g_clear_pointer (&self->name, g_free); + + g_signal_handlers_disconnect_by_data (self, self); + g_signal_handlers_disconnect_by_data (self->password_entry, self); + g_signal_handlers_disconnect_by_data (self->show_password_check, self); + + if (self->idle_func_id != 0) { + g_source_remove (self->idle_func_id); + self->idle_func_id = 0; + } + + G_OBJECT_CLASS (eap_method_simple_parent_class)->dispose (object); +} + +static void +changed_cb (EAPMethodSimple *self) +{ + eap_method_emit_changed (EAP_METHOD (self)); +} + +static void +eap_method_simple_init (EAPMethodSimple *self) +{ + gtk_widget_init_template (GTK_WIDGET (self)); +} + +static void +eap_method_simple_class_init (EAPMethodSimpleClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + object_class->dispose = eap_method_simple_dispose; + + gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/Settings/network/eap-method-simple.ui"); + + gtk_widget_class_bind_template_child (widget_class, EAPMethodSimple, password_label); + gtk_widget_class_bind_template_child (widget_class, EAPMethodSimple, username_label); + gtk_widget_class_bind_template_child (widget_class, EAPMethodSimple, password_entry); + gtk_widget_class_bind_template_child (widget_class, EAPMethodSimple, show_password_check); + gtk_widget_class_bind_template_child (widget_class, EAPMethodSimple, username_entry); +} + +static void +eap_method_iface_init (EAPMethodInterface *iface) +{ + iface->validate = validate; + iface->add_to_size_group = add_to_size_group; + iface->fill_connection = fill_connection; + iface->update_secrets = update_secrets; + iface->get_default_field = get_default_field; + iface->get_password_flags_name = get_password_flags_name; + iface->get_phase2 = get_phase2; + iface->get_username = get_username; + iface->set_username = set_username; + iface->get_password = get_password; + iface->set_password = set_password; + iface->get_show_password = get_show_password; + iface->set_show_password = set_show_password; +} + +EAPMethodSimple * +eap_method_simple_new (NMConnection *connection, const gchar *name, gboolean phase2, gboolean autheap_allowed) +{ + EAPMethodSimple *self; + NMSetting8021x *s_8021x = NULL; + + self = g_object_new (eap_method_simple_get_type (), NULL); + self->name = g_strdup (name); + self->phase2 = phase2; + self->autheap_allowed = autheap_allowed; + + g_signal_connect_swapped (self->username_entry, "changed", G_CALLBACK (changed_cb), self); + + g_signal_connect_swapped (self->password_entry, "changed", G_CALLBACK (changed_cb), self); + + /* Create password-storage popup menu for password entry under entry's secondary icon */ + if (connection) + s_8021x = nm_connection_get_setting_802_1x (connection); + nma_utils_setup_password_storage (GTK_WIDGET (self->password_entry), 0, (NMSetting *) s_8021x, NM_SETTING_802_1X_PASSWORD, + FALSE, FALSE); + + g_signal_connect_swapped (self->password_entry, "notify::secondary-icon-name", G_CALLBACK (password_storage_changed), self); + + g_signal_connect_swapped (self->show_password_check, "toggled", G_CALLBACK (show_toggled_cb), self); + + return self; +} + diff --git a/panels/network/wireless-security/eap-method-simple.h b/panels/network/wireless-security/eap-method-simple.h new file mode 100644 index 0000000..8645fb1 --- /dev/null +++ b/panels/network/wireless-security/eap-method-simple.h @@ -0,0 +1,52 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* NetworkManager Applet -- allow user control over networking + * + * Dan Williams <dcbw@redhat.com> + * + * 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. + * + * (C) Copyright 2007 - 2010 Red Hat, Inc. + */ + +#pragma once + +#include <gtk/gtk.h> +#include <NetworkManager.h> + +G_BEGIN_DECLS + +G_DECLARE_FINAL_TYPE (EAPMethodSimple, eap_method_simple, EAP, METHOD_SIMPLE, GtkGrid) + +typedef enum { + /* NOTE: when updating this table, also update eap_methods[] */ + EAP_METHOD_SIMPLE_TYPE_PAP = 0, + EAP_METHOD_SIMPLE_TYPE_MSCHAP, + EAP_METHOD_SIMPLE_TYPE_MSCHAP_V2, + EAP_METHOD_SIMPLE_TYPE_PLAIN_MSCHAP_V2, + EAP_METHOD_SIMPLE_TYPE_MD5, + EAP_METHOD_SIMPLE_TYPE_PWD, + EAP_METHOD_SIMPLE_TYPE_CHAP, + EAP_METHOD_SIMPLE_TYPE_GTC, + + /* Boundary value, do not use */ + EAP_METHOD_SIMPLE_TYPE_LAST +} EAPMethodSimpleType; + +EAPMethodSimple *eap_method_simple_new (NMConnection *connection, + const gchar *name, + gboolean phase2, + gboolean autheap_allowed); + +G_END_DECLS diff --git a/panels/network/wireless-security/eap-method-simple.ui b/panels/network/wireless-security/eap-method-simple.ui new file mode 100644 index 0000000..e5c37c9 --- /dev/null +++ b/panels/network/wireless-security/eap-method-simple.ui @@ -0,0 +1,68 @@ +<?xml version="1.0" encoding="UTF-8"?> +<interface> + <requires lib="gtk+" version="3.4"/> + <template class="EAPMethodSimple" parent="GtkGrid"> + <property name="valign">start</property> + <property name="column_spacing">6</property> + <property name="row_spacing">6</property> + <child> + <object class="GtkLabel" id="username_label"> + <property name="xalign">1</property> + <property name="label" translatable="yes">_Username</property> + <property name="use_underline">True</property> + <property name="mnemonic_widget">username_entry</property> + <layout> + <property name="column">0</property> + <property name="row">0</property> + </layout> + </object> + </child> + <child> + <object class="GtkLabel" id="password_label"> + <property name="xalign">1</property> + <property name="label" translatable="yes">_Password</property> + <property name="use_underline">True</property> + <property name="mnemonic_widget">password_entry</property> + <layout> + <property name="column">0</property> + <property name="row">1</property> + </layout> + </object> + </child> + <child> + <object class="GtkEntry" id="password_entry"> + <property name="hexpand">True</property> + <property name="visibility">False</property> + <layout> + <property name="column">1</property> + <property name="row">1</property> + </layout> + </object> + </child> + <child> + <object class="GtkEntry" id="username_entry"> + <property name="hexpand">True</property> + <layout> + <property name="column">1</property> + <property name="row">0</property> + </layout> + </object> + </child> + <child> + <object class="GtkBox"> + <property name="hexpand">True</property> + <property name="orientation">vertical</property> + <layout> + <property name="column">1</property> + <property name="row">2</property> + </layout> + <child> + <object class="GtkCheckButton" id="show_password_check"> + <property name="label" translatable="yes">Sho_w password</property> + <property name="use_underline">True</property> + </object> + </child> + </object> + </child> + </template> +</interface> diff --git a/panels/network/wireless-security/eap-method-tls.c b/panels/network/wireless-security/eap-method-tls.c new file mode 100644 index 0000000..5038915 --- /dev/null +++ b/panels/network/wireless-security/eap-method-tls.c @@ -0,0 +1,557 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* NetworkManager Applet -- allow user control over networking + * + * Dan Williams <dcbw@redhat.com> + * + * 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. + * + * Copyright 2007 - 2014 Red Hat, Inc. + */ + +#include <glib/gi18n.h> + +#include "eap-method.h" +#include "eap-method-tls.h" +#include "helpers.h" +#include "nma-ui-utils.h" +#include "ui-helpers.h" +#include "ws-file-chooser-button.h" + +struct _EAPMethodTLS { + GtkGrid parent; + + WsFileChooserButton *ca_cert_button; + GtkLabel *ca_cert_label; + GtkCheckButton *ca_cert_not_required_check; + GtkEntry *identity_entry; + GtkLabel *identity_label; + WsFileChooserButton *private_key_button; + GtkLabel *private_key_label; + GtkEntry *private_key_password_entry; + GtkLabel *private_key_password_label; + GtkCheckButton *show_password_check; + WsFileChooserButton *user_cert_button; + GtkLabel *user_cert_label; + + gchar *username; + gchar *password; + gboolean show_password; +}; + +static void eap_method_iface_init (EAPMethodInterface *); + +G_DEFINE_TYPE_WITH_CODE (EAPMethodTLS, eap_method_tls, GTK_TYPE_GRID, + G_IMPLEMENT_INTERFACE (eap_method_get_type (), eap_method_iface_init)) + +static void +eap_method_tls_dispose (GObject *object) +{ + EAPMethodTLS *self = EAP_METHOD_TLS (object); + + g_clear_pointer (&self->username, g_free); + g_clear_pointer (&self->password, g_free); + + G_OBJECT_CLASS (eap_method_tls_parent_class)->dispose (object); +} + +static void +show_toggled_cb (EAPMethodTLS *self) +{ + gboolean visible; + + visible = gtk_check_button_get_active (GTK_CHECK_BUTTON (self->show_password_check)); + gtk_entry_set_visibility (self->private_key_password_entry, visible); +} + +static gboolean +validate (EAPMethod *method, GError **error) +{ + EAPMethodTLS *self = EAP_METHOD_TLS (method); + NMSetting8021xCKFormat format = NM_SETTING_802_1X_CK_FORMAT_UNKNOWN; + NMSettingSecretFlags secret_flags; + const char *password, *identity; + g_autoptr(GError) ca_cert_error = NULL; + g_autoptr(GError) private_key_error = NULL; + g_autoptr(GError) user_cert_error = NULL; + gboolean ret = TRUE; + + identity = gtk_editable_get_text (GTK_EDITABLE (self->identity_entry)); + if (!identity || !strlen (identity)) { + widget_set_error (GTK_WIDGET (self->identity_entry)); + g_set_error_literal (error, NMA_ERROR, NMA_ERROR_GENERIC, _("missing EAP-TLS identity")); + ret = FALSE; + } else { + widget_unset_error (GTK_WIDGET (self->identity_entry)); + } + + if (!eap_method_validate_filepicker (ws_file_chooser_button_get_filechooser (self->ca_cert_button), + TYPE_CA_CERT, NULL, NULL, &ca_cert_error)) { + widget_set_error (GTK_WIDGET (self->ca_cert_button)); + if (ret) { + g_set_error (error, NMA_ERROR, NMA_ERROR_GENERIC, _("invalid EAP-TLS CA certificate: %s"), ca_cert_error->message); + ret = FALSE; + } + } else if (!gtk_check_button_get_active (GTK_CHECK_BUTTON (self->ca_cert_not_required_check))) { + g_autoptr(GFile) file = NULL; + + file = ws_file_chooser_button_get_file (self->ca_cert_button); + if (file == NULL) { + widget_set_error (GTK_WIDGET (self->ca_cert_button)); + if (ret) { + g_set_error_literal (error, NMA_ERROR, NMA_ERROR_GENERIC, _("invalid EAP-TLS CA certificate: no certificate specified")); + ret = FALSE; + } + } + } + + password = gtk_editable_get_text (GTK_EDITABLE (self->private_key_password_entry)); + secret_flags = nma_utils_menu_to_secret_flags (GTK_WIDGET (self->private_key_password_entry)); + if (secret_flags & NM_SETTING_SECRET_FLAG_NOT_SAVED) + password = NULL; + + if (!eap_method_validate_filepicker (ws_file_chooser_button_get_filechooser (self->private_key_button), + TYPE_PRIVATE_KEY, + password, + &format, + &private_key_error)) { + if (ret) { + g_set_error (error, NMA_ERROR, NMA_ERROR_GENERIC, _("invalid EAP-TLS private-key: %s"), private_key_error->message); + ret = FALSE; + } + widget_set_error (GTK_WIDGET (self->private_key_button)); + } + + if (format != NM_SETTING_802_1X_CK_FORMAT_PKCS12) { + if (!eap_method_validate_filepicker (ws_file_chooser_button_get_filechooser (self->user_cert_button), + TYPE_CLIENT_CERT, NULL, NULL, &user_cert_error)) { + if (ret) { + g_set_error (error, NMA_ERROR, NMA_ERROR_GENERIC, _("invalid EAP-TLS user-certificate: %s"), user_cert_error->message); + ret = FALSE; + } + widget_set_error (GTK_WIDGET (self->user_cert_button)); + } + } + + return ret; +} + +static void +ca_cert_not_required_toggled (EAPMethodTLS *self) +{ + eap_method_ca_cert_not_required_toggled (self->ca_cert_not_required_check, + ws_file_chooser_button_get_filechooser (self->ca_cert_button)); + eap_method_emit_changed (EAP_METHOD (self)); +} + +static void +add_to_size_group (EAPMethod *method, GtkSizeGroup *group) +{ + EAPMethodTLS *self = EAP_METHOD_TLS (method); + + gtk_size_group_add_widget (group, GTK_WIDGET (self->ca_cert_not_required_check)); + gtk_size_group_add_widget (group, GTK_WIDGET (self->identity_label)); + gtk_size_group_add_widget (group, GTK_WIDGET (self->user_cert_label)); + gtk_size_group_add_widget (group, GTK_WIDGET (self->ca_cert_label)); + gtk_size_group_add_widget (group, GTK_WIDGET (self->private_key_label)); + gtk_size_group_add_widget (group, GTK_WIDGET (self->private_key_password_label)); +} + +static void +fill_connection (EAPMethod *method, NMConnection *connection, NMSettingSecretFlags flags) +{ + EAPMethodTLS *self = EAP_METHOD_TLS (method); + NMSetting8021xCKFormat format = NM_SETTING_802_1X_CK_FORMAT_UNKNOWN; + NMSetting8021x *s_8021x; + NMSettingSecretFlags secret_flags; + g_autofree gchar *ca_filename = NULL; + g_autoptr(GFile) ca_file = NULL; + g_autofree gchar *pk_filename = NULL; + g_autoptr(GFile) pk_file = NULL; + const char *password = NULL; + gboolean ca_cert_error = FALSE; + g_autoptr(GError) error = NULL; + g_autoptr(GError) error2 = NULL; + + s_8021x = nm_connection_get_setting_802_1x (connection); + g_assert (s_8021x); + + nm_setting_802_1x_add_eap_method (s_8021x, "tls"); + + g_object_set (s_8021x, NM_SETTING_802_1X_IDENTITY, gtk_editable_get_text (GTK_EDITABLE (self->identity_entry)), NULL); + + /* TLS private key */ + password = gtk_editable_get_text (GTK_EDITABLE (self->private_key_password_entry)); + secret_flags = nma_utils_menu_to_secret_flags (GTK_WIDGET (self->private_key_password_entry)); + if (secret_flags & NM_SETTING_SECRET_FLAG_NOT_SAVED) + password = NULL; + + pk_file = ws_file_chooser_button_get_file (self->private_key_button); + g_assert (pk_file); + pk_filename = g_file_get_path (pk_file); + + if (!nm_setting_802_1x_set_private_key (s_8021x, pk_filename, password, NM_SETTING_802_1X_CK_SCHEME_PATH, &format, &error)) + g_warning ("Couldn't read private key '%s': %s", pk_filename, error ? error->message : "(unknown)"); + + /* Save 802.1X password flags to the connection */ + nm_setting_set_secret_flags (NM_SETTING (s_8021x), NM_SETTING_802_1X_PRIVATE_KEY_PASSWORD, + secret_flags, NULL); + + /* Update secret flags and popup when editing the connection */ + nma_utils_update_password_storage (GTK_WIDGET (self->private_key_password_entry), secret_flags, + NM_SETTING (s_8021x), NM_SETTING_802_1X_PRIVATE_KEY_PASSWORD); + + /* TLS client certificate */ + if (format != NM_SETTING_802_1X_CK_FORMAT_PKCS12) { + g_autofree gchar *cc_filename = NULL; + g_autoptr(GFile) cc_file = NULL; + g_autoptr(GError) error = NULL; + + /* If the key is pkcs#12 nm_setting_802_1x_set_private_key() already + * set the client certificate for us. + */ + cc_file = ws_file_chooser_button_get_file (self->private_key_button); + g_assert (cc_file); + cc_filename = g_file_get_path (cc_file); + + format = NM_SETTING_802_1X_CK_FORMAT_UNKNOWN; + if (!nm_setting_802_1x_set_client_cert (s_8021x, cc_filename, NM_SETTING_802_1X_CK_SCHEME_PATH, &format, &error)) + g_warning ("Couldn't read client certificate '%s': %s", cc_filename, error ? error->message : "(unknown)"); + } + + /* TLS CA certificate */ + ca_file = ws_file_chooser_button_get_file (self->private_key_button); + ca_filename = ca_file ? g_file_get_path (ca_file) : NULL; + + format = NM_SETTING_802_1X_CK_FORMAT_UNKNOWN; + if (!nm_setting_802_1x_set_ca_cert (s_8021x, ca_filename, NM_SETTING_802_1X_CK_SCHEME_PATH, &format, &error2)) { + g_warning ("Couldn't read CA certificate '%s': %s", ca_filename, error2 ? error2->message : "(unknown)"); + ca_cert_error = TRUE; + } + eap_method_ca_cert_ignore_set (method, connection, ca_filename, ca_cert_error); +} + +static void +private_key_picker_helper (EAPMethodTLS *self, const char *filename, gboolean changed) +{ + g_autoptr(NMSetting8021x) setting = NULL; + NMSetting8021xCKFormat cert_format = NM_SETTING_802_1X_CK_FORMAT_UNKNOWN; + const char *password; + + password = gtk_editable_get_text (GTK_EDITABLE (self->private_key_password_entry)); + + setting = (NMSetting8021x *) nm_setting_802_1x_new (); + nm_setting_802_1x_set_private_key (setting, filename, password, NM_SETTING_802_1X_CK_SCHEME_PATH, &cert_format, NULL); + + /* With PKCS#12, the client cert must be the same as the private key */ + if (cert_format == NM_SETTING_802_1X_CK_FORMAT_PKCS12) { + ws_file_chooser_button_set_file (self->user_cert_button, NULL); + gtk_widget_set_sensitive (GTK_WIDGET (self->user_cert_button), FALSE); + } else if (changed) + gtk_widget_set_sensitive (GTK_WIDGET (self->user_cert_button), TRUE); + + /* Warn the user if the private key is unencrypted */ + if (!eap_method_is_encrypted_private_key (filename)) { + GtkWidget *dialog; + GtkNative *native; + GtkWindow *parent_window = NULL; + + native = gtk_widget_get_native (GTK_WIDGET (self)); + if (GTK_IS_WINDOW (native)) + parent_window = GTK_WINDOW (native); + + dialog = gtk_message_dialog_new (parent_window, + GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_MESSAGE_WARNING, + GTK_BUTTONS_OK, + "%s", + _("Unencrypted private keys are insecure")); + gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog), + "%s", + _("The selected private key does not appear to be protected by a password. This could allow your security credentials to be compromised. Please select a password-protected private key.\n\n(You can password-protect your private key with openssl)")); + gtk_window_present (GTK_WINDOW (dialog)); + } +} + +static void +private_key_picker_file_set_cb (WsFileChooserButton *chooser, gpointer user_data) +{ + EAPMethodTLS *self = user_data; + g_autoptr(GFile) file = NULL; + g_autofree gchar *filename = NULL; + + file = ws_file_chooser_button_get_file (chooser); + filename = file ? g_file_get_path (file) : NULL; + if (filename) + private_key_picker_helper (self, filename, TRUE); +} + +static void reset_filter (GtkWidget *widget, GParamSpec *spec, gpointer user_data) +{ + if (!gtk_file_chooser_get_filter (GTK_FILE_CHOOSER (widget))) { + g_signal_handlers_block_by_func (widget, reset_filter, user_data); + gtk_file_chooser_set_filter (GTK_FILE_CHOOSER (widget), GTK_FILE_FILTER (user_data)); + g_signal_handlers_unblock_by_func (widget, reset_filter, user_data); + } +} + +typedef const char * (*PathFunc) (NMSetting8021x *setting); +typedef NMSetting8021xCKScheme (*SchemeFunc) (NMSetting8021x *setting); + +static void +changed_cb (EAPMethodTLS *self) +{ + eap_method_emit_changed (EAP_METHOD (self)); +} + +static void +setup_filepicker (EAPMethodTLS *self, + WsFileChooserButton *button, + const char *title, + NMSetting8021x *s_8021x, + SchemeFunc scheme_func, + PathFunc path_func, + gboolean privkey, + gboolean client_cert) +{ + GtkFileFilter *filter; + const char *filename = NULL; + + if (s_8021x && path_func && scheme_func) { + if (scheme_func (s_8021x) == NM_SETTING_802_1X_CK_SCHEME_PATH) { + filename = path_func (s_8021x); + if (filename) { + g_autoptr(GFile) file = g_file_new_for_path (filename); + ws_file_chooser_button_set_file (button, file); + } + } + } + + /* Connect a special handler for private keys to intercept PKCS#12 key types + * and desensitize the user cert button. + */ + if (privkey) { + g_signal_connect (button, "notify::file", + G_CALLBACK (private_key_picker_file_set_cb), + self); + if (filename) + private_key_picker_helper (self, filename, FALSE); + } + + g_signal_connect_swapped (button, "notify::file", G_CALLBACK (changed_cb), self); + + filter = eap_method_default_file_chooser_filter_new (privkey); + gtk_file_chooser_add_filter (ws_file_chooser_button_get_filechooser (button), filter); + + /* For some reason, GTK+ calls set_current_filter (..., NULL) from + * gtkfilechooserdefault.c::show_and_select_files_finished_loading() on our + * dialog; so force-reset the filter to what we want it to be whenever + * it gets cleared. + */ + if (client_cert) + g_signal_connect (button, "notify::filter", (GCallback) reset_filter, filter); +} + +static void +update_secrets (EAPMethod *method, NMConnection *connection) +{ + EAPMethodTLS *self = EAP_METHOD_TLS (method); + NMSetting8021x *s_8021x; + const char *filename; + + helper_fill_secret_entry (connection, + self->private_key_password_entry, + NM_TYPE_SETTING_802_1X, + (HelperSecretFunc) nm_setting_802_1x_get_private_key_password); + + /* Set the private key filepicker button path if we have a private key */ + s_8021x = nm_connection_get_setting_802_1x (connection); + if (s_8021x && (nm_setting_802_1x_get_private_key_scheme (s_8021x) == NM_SETTING_802_1X_CK_SCHEME_PATH)) { + filename = nm_setting_802_1x_get_private_key_path (s_8021x); + if (filename) { + g_autoptr(GFile) file = g_file_new_for_path (filename); + ws_file_chooser_button_set_file (self->private_key_button, file); + } + } +} + +static GtkWidget * +get_default_field (EAPMethod *method) +{ + EAPMethodTLS *self = EAP_METHOD_TLS (method); + return GTK_WIDGET (self->identity_entry); +} + +static const gchar * +get_password_flags_name (EAPMethod *method) +{ + return NM_SETTING_802_1X_PRIVATE_KEY_PASSWORD; +} + +static const gchar * +get_username (EAPMethod *method) +{ + EAPMethodTLS *self = EAP_METHOD_TLS (method); + return self->username; +} + +static void +set_username (EAPMethod *method, const gchar *username) +{ + EAPMethodTLS *self = EAP_METHOD_TLS (method); + g_free (self->username); + self->username = g_strdup (username); +} + +static const gchar * +get_password (EAPMethod *method) +{ + EAPMethodTLS *self = EAP_METHOD_TLS (method); + return self->password; +} + +static void +set_password (EAPMethod *method, const gchar *password) +{ + EAPMethodTLS *self = EAP_METHOD_TLS (method); + g_free (self->password); + self->password = g_strdup (password); +} + +static const gboolean +get_show_password (EAPMethod *method) +{ + EAPMethodTLS *self = EAP_METHOD_TLS (method); + return self->show_password; +} + +static void +set_show_password (EAPMethod *method, gboolean show_password) +{ + EAPMethodTLS *self = EAP_METHOD_TLS (method); + self->show_password = show_password; +} + +static void +eap_method_tls_init (EAPMethodTLS *self) +{ + gtk_widget_init_template (GTK_WIDGET (self)); + self->username = g_strdup (""); + self->password = g_strdup (""); +} + +static void +eap_method_tls_class_init (EAPMethodTLSClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + object_class->dispose = eap_method_tls_dispose; + + g_type_ensure (WS_TYPE_FILE_CHOOSER_BUTTON); + + gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/Settings/network/eap-method-tls.ui"); + + gtk_widget_class_bind_template_child (widget_class, EAPMethodTLS, ca_cert_button); + gtk_widget_class_bind_template_child (widget_class, EAPMethodTLS, ca_cert_label); + gtk_widget_class_bind_template_child (widget_class, EAPMethodTLS, ca_cert_not_required_check); + gtk_widget_class_bind_template_child (widget_class, EAPMethodTLS, identity_entry); + gtk_widget_class_bind_template_child (widget_class, EAPMethodTLS, identity_label); + gtk_widget_class_bind_template_child (widget_class, EAPMethodTLS, private_key_button); + gtk_widget_class_bind_template_child (widget_class, EAPMethodTLS, private_key_label); + gtk_widget_class_bind_template_child (widget_class, EAPMethodTLS, private_key_password_entry); + gtk_widget_class_bind_template_child (widget_class, EAPMethodTLS, private_key_password_label); + gtk_widget_class_bind_template_child (widget_class, EAPMethodTLS, show_password_check); + gtk_widget_class_bind_template_child (widget_class, EAPMethodTLS, user_cert_button); + gtk_widget_class_bind_template_child (widget_class, EAPMethodTLS, user_cert_label); +} + +static void +eap_method_iface_init (EAPMethodInterface *iface) +{ + iface->validate = validate; + iface->add_to_size_group = add_to_size_group; + iface->fill_connection = fill_connection; + iface->update_secrets = update_secrets; + iface->get_default_field = get_default_field; + iface->get_password_flags_name = get_password_flags_name; + iface->get_username = get_username; + iface->set_username = set_username; + iface->get_password = get_password; + iface->set_password = set_password; + iface->get_show_password = get_show_password; + iface->set_show_password = set_show_password; +} + +EAPMethodTLS * +eap_method_tls_new (NMConnection *connection) +{ + EAPMethodTLS *self; + NMSetting8021x *s_8021x = NULL; + gboolean ca_not_required = FALSE; + + self = g_object_new (eap_method_tls_get_type (), NULL); + + if (connection) + s_8021x = nm_connection_get_setting_802_1x (connection); + + g_signal_connect_swapped (self->ca_cert_not_required_check, "toggled", G_CALLBACK (ca_cert_not_required_toggled), self); + + g_signal_connect_swapped (self->identity_entry, "changed", G_CALLBACK (changed_cb), self); + if (s_8021x && nm_setting_802_1x_get_identity (s_8021x)) + gtk_editable_set_text (GTK_EDITABLE (self->identity_entry), nm_setting_802_1x_get_identity (s_8021x)); + + setup_filepicker (self, + self->user_cert_button, + _("Choose your personal certificate"), + s_8021x, + nm_setting_802_1x_get_client_cert_scheme, + nm_setting_802_1x_get_client_cert_path, + FALSE, TRUE); + setup_filepicker (self, + self->ca_cert_button, + _("Choose a Certificate Authority certificate"), + s_8021x, + nm_setting_802_1x_get_ca_cert_scheme, + nm_setting_802_1x_get_ca_cert_path, + FALSE, FALSE); + setup_filepicker (self, + self->private_key_button, + _("Choose your private key"), + s_8021x, + nm_setting_802_1x_get_private_key_scheme, + nm_setting_802_1x_get_private_key_path, + TRUE, FALSE); + + if (connection && eap_method_ca_cert_ignore_get (EAP_METHOD (self), connection)) { + g_autoptr(GFile) file = ws_file_chooser_button_get_file (self->ca_cert_button); + ca_not_required = !file; + } + gtk_check_button_set_active (self->ca_cert_not_required_check, ca_not_required); + + /* Fill secrets, if any */ + if (connection) + update_secrets (EAP_METHOD (self), connection); + + g_signal_connect_swapped (self->private_key_password_entry, "changed", G_CALLBACK (changed_cb), self); + + /* Create password-storage popup menu for password entry under entry's secondary icon */ + nma_utils_setup_password_storage (GTK_WIDGET (self->private_key_password_entry), 0, (NMSetting *) s_8021x, NM_SETTING_802_1X_PRIVATE_KEY_PASSWORD, + FALSE, FALSE); + + g_signal_connect_swapped (self->show_password_check, "toggled", G_CALLBACK (show_toggled_cb), self); + + return self; +} + diff --git a/panels/network/wireless-security/eap-method-tls.h b/panels/network/wireless-security/eap-method-tls.h new file mode 100644 index 0000000..27f3843 --- /dev/null +++ b/panels/network/wireless-security/eap-method-tls.h @@ -0,0 +1,34 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* NetworkManager Applet -- allow user control over networking + * + * Dan Williams <dcbw@redhat.com> + * + * 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. + * + * (C) Copyright 2007 - 2010 Red Hat, Inc. + */ + +#pragma once + +#include <gtk/gtk.h> +#include <NetworkManager.h> + +G_BEGIN_DECLS + +G_DECLARE_FINAL_TYPE (EAPMethodTLS, eap_method_tls, EAP, METHOD_TLS, GtkGrid) + +EAPMethodTLS *eap_method_tls_new (NMConnection *connection); + +G_END_DECLS diff --git a/panels/network/wireless-security/eap-method-tls.ui b/panels/network/wireless-security/eap-method-tls.ui new file mode 100644 index 0000000..69c2610 --- /dev/null +++ b/panels/network/wireless-security/eap-method-tls.ui @@ -0,0 +1,139 @@ +<?xml version="1.0" encoding="UTF-8"?> +<interface> + <requires lib="gtk+" version="3.4"/> + <template class="EAPMethodTLS" parent="GtkGrid"> + <property name="valign">start</property> + <property name="column_spacing">6</property> + <property name="row_spacing">6</property> + <child> + <object class="GtkLabel" id="identity_label"> + <property name="xalign">1</property> + <property name="label" translatable="yes">I_dentity</property> + <property name="use_underline">True</property> + <property name="mnemonic_widget">identity_entry</property> + <layout> + <property name="column">0</property> + <property name="row">0</property> + </layout> + </object> + </child> + <child> + <object class="GtkEntry" id="identity_entry"> + <property name="hexpand">True</property> + <layout> + <property name="column">1</property> + <property name="row">0</property> + </layout> + </object> + </child> + <child> + <object class="GtkLabel" id="user_cert_label"> + <property name="xalign">1</property> + <property name="label" translatable="yes">_User certificate</property> + <property name="use_underline">True</property> + <property name="mnemonic_widget">user_cert_button</property> + <layout> + <property name="column">0</property> + <property name="row">1</property> + </layout> + </object> + </child> + <child> + <object class="WsFileChooserButton" id="user_cert_button"> + <property name="hexpand">True</property> + <property name="title" translatable="yes">Choose a Certificate Authority certificate</property> + <layout> + <property name="column">1</property> + <property name="row">1</property> + </layout> + </object> + </child> + <child> + <object class="GtkLabel" id="ca_cert_label"> + <property name="xalign">1</property> + <property name="label" translatable="yes">C_A certificate</property> + <property name="use_underline">True</property> + <property name="mnemonic_widget">ca_cert_button</property> + <layout> + <property name="column">0</property> + <property name="row">2</property> + </layout> + </object> + </child> + <child> + <object class="WsFileChooserButton" id="ca_cert_button"> + <property name="hexpand">True</property> + <property name="title" translatable="yes">Choose a Certificate Authority certificate</property> + <layout> + <property name="column">1</property> + <property name="row">2</property> + </layout> + </object> + </child> + <child> + <object class="GtkCheckButton" id="ca_cert_not_required_check"> + <property name="label" translatable="yes">No CA certificate is _required</property> + <property name="hexpand">True</property> + <property name="use_underline">True</property> + <layout> + <property name="column">1</property> + <property name="row">3</property> + </layout> + </object> + </child> + <child> + <object class="GtkLabel" id="private_key_label"> + <property name="xalign">1</property> + <property name="label" translatable="yes">Private _key</property> + <property name="use_underline">True</property> + <property name="mnemonic_widget">private_key_button</property> + <layout> + <property name="column">0</property> + <property name="row">4</property> + </layout> + </object> + </child> + <child> + <object class="WsFileChooserButton" id="private_key_button"> + <property name="hexpand">True</property> + <layout> + <property name="column">1</property> + <property name="row">4</property> + </layout> + </object> + </child> + <child> + <object class="GtkLabel" id="private_key_password_label"> + <property name="xalign">1</property> + <property name="label" translatable="yes">_Private key password</property> + <property name="use_underline">True</property> + <property name="mnemonic_widget">private_key_password_entry</property> + <layout> + <property name="column">0</property> + <property name="row">5</property> + </layout> + </object> + </child> + <child> + <object class="GtkEntry" id="private_key_password_entry"> + <property name="hexpand">True</property> + <property name="visibility">False</property> + <layout> + <property name="column">1</property> + <property name="row">5</property> + </layout> + </object> + </child> + <child> + <object class="GtkCheckButton" id="show_password_check"> + <property name="label" translatable="yes">Sho_w password</property> + <property name="hexpand">True</property> + <property name="use_underline">True</property> + <layout> + <property name="column">1</property> + <property name="row">6</property> + </layout> + </object> + </child> + </template> +</interface> diff --git a/panels/network/wireless-security/eap-method-ttls.c b/panels/network/wireless-security/eap-method-ttls.c new file mode 100644 index 0000000..b41a57f --- /dev/null +++ b/panels/network/wireless-security/eap-method-ttls.c @@ -0,0 +1,415 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* NetworkManager Applet -- allow user control over networking + * + * Dan Williams <dcbw@redhat.com> + * + * 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. + * + * Copyright 2007 - 2014 Red Hat, Inc. + */ + +#include <glib/gi18n.h> + +#include "eap-method.h" +#include "eap-method-simple.h" +#include "eap-method-ttls.h" +#include "helpers.h" +#include "ws-file-chooser-button.h" + +#define I_NAME_COLUMN 0 +#define I_ID_COLUMN 1 + +struct _EAPMethodTTLS { + GtkGrid parent; + + GtkEntry *anon_identity_entry; + GtkLabel *anon_identity_label; + WsFileChooserButton *ca_cert_button; + GtkLabel *ca_cert_label; + GtkCheckButton *ca_cert_not_required_check; + GtkEntry *domain_match_entry; + GtkLabel *domain_match_label; + GtkComboBox *inner_auth_combo; + GtkLabel *inner_auth_label; + GtkListStore *inner_auth_model; + GtkBox *inner_auth_box; + + EAPMethodSimple *em_chap; + EAPMethodSimple *em_gtc; + EAPMethodSimple *em_md5; + EAPMethodSimple *em_mschap; + EAPMethodSimple *em_mschap_v2; + EAPMethodSimple *em_pap; + EAPMethodSimple *em_plain_mschap_v2; +}; + +static void eap_method_iface_init (EAPMethodInterface *); + +G_DEFINE_TYPE_WITH_CODE (EAPMethodTTLS, eap_method_ttls, GTK_TYPE_GRID, + G_IMPLEMENT_INTERFACE (eap_method_get_type (), eap_method_iface_init)) + +static EAPMethod * +get_inner_method (EAPMethodTTLS *self) +{ + GtkTreeIter iter; + g_autofree gchar *id = NULL; + + if (!gtk_combo_box_get_active_iter (self->inner_auth_combo, &iter)) + return NULL; + gtk_tree_model_get (GTK_TREE_MODEL (self->inner_auth_model), &iter, I_ID_COLUMN, &id, -1); + + if (strcmp (id, "chap") == 0) + return EAP_METHOD (self->em_chap); + if (strcmp (id, "gtc") == 0) + return EAP_METHOD (self->em_gtc); + if (strcmp (id, "md5") == 0) + return EAP_METHOD (self->em_md5); + if (strcmp (id, "mschap") == 0) + return EAP_METHOD (self->em_mschap); + if (strcmp (id, "mschapv2") == 0) + return EAP_METHOD (self->em_mschap_v2); + if (strcmp (id, "pap") == 0) + return EAP_METHOD (self->em_pap); + if (strcmp (id, "plain_mschapv2") == 0) + return EAP_METHOD (self->em_plain_mschap_v2); + + return NULL; +} + +static gboolean +validate (EAPMethod *method, GError **error) +{ + EAPMethodTTLS *self = EAP_METHOD_TTLS (method); + g_autoptr(GError) local_error = NULL; + + if (!eap_method_validate_filepicker (ws_file_chooser_button_get_filechooser (self->ca_cert_button), + TYPE_CA_CERT, NULL, NULL, &local_error)) { + g_set_error (error, NMA_ERROR, NMA_ERROR_GENERIC, _("invalid EAP-TTLS CA certificate: %s"), local_error->message); + return FALSE; + } + if (!gtk_check_button_get_active (GTK_CHECK_BUTTON (self->ca_cert_not_required_check))) { + g_autoptr(GFile) file = NULL; + + file = ws_file_chooser_button_get_file (self->ca_cert_button); + if (file == NULL) { + g_set_error_literal (error, NMA_ERROR, NMA_ERROR_GENERIC, _("invalid EAP-TTLS CA certificate: no certificate specified")); + return FALSE; + } + } + + return eap_method_validate (get_inner_method (self), error); +} + +static void +ca_cert_not_required_toggled (EAPMethodTTLS *self) +{ + eap_method_ca_cert_not_required_toggled (self->ca_cert_not_required_check, + ws_file_chooser_button_get_filechooser (self->ca_cert_button)); + eap_method_emit_changed (EAP_METHOD (self)); +} + +static void +add_to_size_group (EAPMethod *method, GtkSizeGroup *group) +{ + EAPMethodTTLS *self = EAP_METHOD_TTLS (method); + + gtk_size_group_add_widget (group, GTK_WIDGET (self->ca_cert_not_required_check)); + gtk_size_group_add_widget (group, GTK_WIDGET (self->anon_identity_label)); + gtk_size_group_add_widget (group, GTK_WIDGET (self->domain_match_label)); + gtk_size_group_add_widget (group, GTK_WIDGET (self->ca_cert_label)); + gtk_size_group_add_widget (group, GTK_WIDGET (self->inner_auth_label)); + + eap_method_add_to_size_group (EAP_METHOD (self->em_chap), group); + eap_method_add_to_size_group (EAP_METHOD (self->em_gtc), group); + eap_method_add_to_size_group (EAP_METHOD (self->em_md5), group); + eap_method_add_to_size_group (EAP_METHOD (self->em_mschap), group); + eap_method_add_to_size_group (EAP_METHOD (self->em_mschap_v2), group); + eap_method_add_to_size_group (EAP_METHOD (self->em_pap), group); + eap_method_add_to_size_group (EAP_METHOD (self->em_plain_mschap_v2), group); +} + +static void +fill_connection (EAPMethod *method, NMConnection *connection, NMSettingSecretFlags flags) +{ + EAPMethodTTLS *self = EAP_METHOD_TTLS (method); + NMSetting8021x *s_8021x; + NMSetting8021xCKFormat format = NM_SETTING_802_1X_CK_FORMAT_UNKNOWN; + const char *text; + g_autofree gchar *filename = NULL; + g_autoptr(GError) error = NULL; + g_autoptr(GFile) file = NULL; + gboolean ca_cert_error = FALSE; + + s_8021x = nm_connection_get_setting_802_1x (connection); + g_assert (s_8021x); + + nm_setting_802_1x_add_eap_method (s_8021x, "ttls"); + + text = gtk_editable_get_text (GTK_EDITABLE (self->anon_identity_entry)); + if (text && strlen (text)) + g_object_set (s_8021x, NM_SETTING_802_1X_ANONYMOUS_IDENTITY, text, NULL); + + text = gtk_editable_get_text (GTK_EDITABLE (self->domain_match_entry)); + if (text && strlen (text)) + g_object_set (s_8021x, NM_SETTING_802_1X_DOMAIN_SUFFIX_MATCH, text, NULL); + + file = ws_file_chooser_button_get_file (self->ca_cert_button); + filename = file ? g_file_get_path (file) : NULL; + if (!nm_setting_802_1x_set_ca_cert (s_8021x, filename, NM_SETTING_802_1X_CK_SCHEME_PATH, &format, &error)) { + g_warning ("Couldn't read CA certificate '%s': %s", filename, error ? error->message : "(unknown)"); + ca_cert_error = TRUE; + } + eap_method_ca_cert_ignore_set (method, connection, filename, ca_cert_error); + + eap_method_fill_connection (get_inner_method (self), connection, flags); +} + +static void +inner_auth_combo_changed_cb (EAPMethodTTLS *self) +{ + EAPMethod *inner_method; + GtkWidget *child; + + inner_method = get_inner_method (self); + + /* Remove the previous method and migrate username/password across */ + child = gtk_widget_get_first_child (GTK_WIDGET (self->inner_auth_box)); + if (child != NULL) { + EAPMethod *old_eap = EAP_METHOD (child); + eap_method_set_username (inner_method, eap_method_get_username (old_eap)); + eap_method_set_password (inner_method, eap_method_get_password (old_eap)); + eap_method_set_show_password (inner_method, eap_method_get_show_password (old_eap)); + gtk_box_remove (self->inner_auth_box, child); + } + + gtk_box_append (self->inner_auth_box, g_object_ref (GTK_WIDGET (inner_method))); + + eap_method_emit_changed (EAP_METHOD (self)); +} + +static void +update_secrets (EAPMethod *method, NMConnection *connection) +{ + EAPMethodTTLS *self = EAP_METHOD_TTLS (method); + + eap_method_update_secrets (EAP_METHOD (self->em_chap), connection); + eap_method_update_secrets (EAP_METHOD (self->em_gtc), connection); + eap_method_update_secrets (EAP_METHOD (self->em_md5), connection); + eap_method_update_secrets (EAP_METHOD (self->em_mschap), connection); + eap_method_update_secrets (EAP_METHOD (self->em_mschap_v2), connection); + eap_method_update_secrets (EAP_METHOD (self->em_pap), connection); + eap_method_update_secrets (EAP_METHOD (self->em_plain_mschap_v2), connection); +} + +static GtkWidget * +get_default_field (EAPMethod *method) +{ + EAPMethodTTLS *self = EAP_METHOD_TTLS (method); + return GTK_WIDGET (self->anon_identity_entry); +} + +static const gchar * +get_password_flags_name (EAPMethod *method) +{ + return NM_SETTING_802_1X_PASSWORD; +} + +static const gchar * +get_username (EAPMethod *method) +{ + EAPMethodTTLS *self = EAP_METHOD_TTLS (method); + return eap_method_get_username (get_inner_method (self)); +} + +static void +set_username (EAPMethod *method, const gchar *username) +{ + EAPMethodTTLS *self = EAP_METHOD_TTLS (method); + return eap_method_set_username (get_inner_method (self), username); +} + +static const gchar * +get_password (EAPMethod *method) +{ + EAPMethodTTLS *self = EAP_METHOD_TTLS (method); + return eap_method_get_password (get_inner_method (self)); +} + +static void +set_password (EAPMethod *method, const gchar *password) +{ + EAPMethodTTLS *self = EAP_METHOD_TTLS (method); + return eap_method_set_password (get_inner_method (self), password); +} + +static gboolean +get_show_password (EAPMethod *method) +{ + EAPMethodTTLS *self = EAP_METHOD_TTLS (method); + return eap_method_get_show_password (get_inner_method (self)); +} + +static void +set_show_password (EAPMethod *method, gboolean show_password) +{ + EAPMethodTTLS *self = EAP_METHOD_TTLS (method); + return eap_method_set_show_password (get_inner_method (self), show_password); +} + +static void +changed_cb (EAPMethodTTLS *self) +{ + eap_method_emit_changed (EAP_METHOD (self)); +} + +static void +eap_method_ttls_init (EAPMethodTTLS *self) +{ + gtk_widget_init_template (GTK_WIDGET (self)); +} + +static void +eap_method_ttls_class_init (EAPMethodTTLSClass *klass) +{ + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/Settings/network/eap-method-ttls.ui"); + + gtk_widget_class_bind_template_child (widget_class, EAPMethodTTLS, anon_identity_entry); + gtk_widget_class_bind_template_child (widget_class, EAPMethodTTLS, anon_identity_label); + gtk_widget_class_bind_template_child (widget_class, EAPMethodTTLS, ca_cert_button); + gtk_widget_class_bind_template_child (widget_class, EAPMethodTTLS, ca_cert_label); + gtk_widget_class_bind_template_child (widget_class, EAPMethodTTLS, ca_cert_not_required_check); + gtk_widget_class_bind_template_child (widget_class, EAPMethodTTLS, domain_match_entry); + gtk_widget_class_bind_template_child (widget_class, EAPMethodTTLS, domain_match_label); + gtk_widget_class_bind_template_child (widget_class, EAPMethodTTLS, inner_auth_combo); + gtk_widget_class_bind_template_child (widget_class, EAPMethodTTLS, inner_auth_label); + gtk_widget_class_bind_template_child (widget_class, EAPMethodTTLS, inner_auth_model); + gtk_widget_class_bind_template_child (widget_class, EAPMethodTTLS, inner_auth_box); +} + +static void +eap_method_iface_init (EAPMethodInterface *iface) +{ + iface->validate = validate; + iface->add_to_size_group = add_to_size_group; + iface->fill_connection = fill_connection; + iface->update_secrets = update_secrets; + iface->get_default_field = get_default_field; + iface->get_password_flags_name = get_password_flags_name; + iface->get_username = get_username; + iface->set_username = set_username; + iface->get_password = get_password; + iface->set_password = set_password; + iface->get_show_password = get_show_password; + iface->set_show_password = set_show_password; +} + +EAPMethodTTLS * +eap_method_ttls_new (NMConnection *connection) +{ + EAPMethodTTLS *self; + GtkFileFilter *filter; + NMSetting8021x *s_8021x = NULL; + const char *filename; + const char *phase2_auth = NULL; + GtkTreeIter iter; + + self = g_object_new (eap_method_ttls_get_type (), NULL); + + if (connection) + s_8021x = nm_connection_get_setting_802_1x (connection); + + g_signal_connect_swapped (self->ca_cert_not_required_check, "toggled", G_CALLBACK (ca_cert_not_required_toggled), self); + + g_signal_connect_swapped (self->ca_cert_button, "notify::file", G_CALLBACK (changed_cb), self); + filter = eap_method_default_file_chooser_filter_new (FALSE); + gtk_file_chooser_add_filter (ws_file_chooser_button_get_filechooser (self->ca_cert_button), + filter); + if (connection && s_8021x) { + filename = NULL; + if (nm_setting_802_1x_get_ca_cert_scheme (s_8021x) == NM_SETTING_802_1X_CK_SCHEME_PATH) { + filename = nm_setting_802_1x_get_ca_cert_path (s_8021x); + if (filename) { + g_autoptr(GFile) file = g_file_new_for_path (filename); + ws_file_chooser_button_set_file (self->ca_cert_button, file); + } + } + gtk_check_button_set_active (self->ca_cert_not_required_check, + !filename && eap_method_ca_cert_ignore_get (EAP_METHOD (self), connection)); + } + + if (s_8021x && nm_setting_802_1x_get_anonymous_identity (s_8021x)) + gtk_editable_set_text (GTK_EDITABLE (self->anon_identity_entry), nm_setting_802_1x_get_anonymous_identity (s_8021x)); + g_signal_connect_swapped (self->anon_identity_entry, "changed", G_CALLBACK (changed_cb), self); + if (s_8021x && nm_setting_802_1x_get_domain_suffix_match (s_8021x)) + gtk_editable_set_text (GTK_EDITABLE (self->domain_match_entry), nm_setting_802_1x_get_domain_suffix_match (s_8021x)); + g_signal_connect_swapped (self->domain_match_entry, "changed", G_CALLBACK (changed_cb), self); + + self->em_pap = eap_method_simple_new (connection, "pap", TRUE, FALSE); + gtk_widget_show (GTK_WIDGET (self->em_pap)); + g_signal_connect_object (self->em_pap, "changed", G_CALLBACK (eap_method_emit_changed), self, G_CONNECT_SWAPPED); + + self->em_mschap = eap_method_simple_new (connection, "mschap", TRUE, FALSE); + gtk_widget_show (GTK_WIDGET (self->em_mschap)); + g_signal_connect_object (self->em_mschap, "changed", G_CALLBACK (eap_method_emit_changed), self, G_CONNECT_SWAPPED); + + self->em_mschap_v2 = eap_method_simple_new (connection, "mschapv2", TRUE, TRUE); + gtk_widget_show (GTK_WIDGET (self->em_mschap_v2)); + g_signal_connect_object (self->em_mschap_v2, "changed", G_CALLBACK (eap_method_emit_changed), self, G_CONNECT_SWAPPED); + + self->em_plain_mschap_v2 = eap_method_simple_new (connection, "mschapv2", TRUE, FALSE); + gtk_widget_show (GTK_WIDGET (self->em_plain_mschap_v2)); + g_signal_connect_object (self->em_plain_mschap_v2, "changed", G_CALLBACK (eap_method_emit_changed), self, G_CONNECT_SWAPPED); + + self->em_chap = eap_method_simple_new (connection, "chap", TRUE, FALSE); + gtk_widget_show (GTK_WIDGET (self->em_chap)); + g_signal_connect_object (self->em_chap, "changed", G_CALLBACK (eap_method_emit_changed), self, G_CONNECT_SWAPPED); + + self->em_md5 = eap_method_simple_new (connection, "md5", TRUE, TRUE); + gtk_widget_show (GTK_WIDGET (self->em_md5)); + g_signal_connect_object (self->em_md5, "changed", G_CALLBACK (eap_method_emit_changed), self, G_CONNECT_SWAPPED); + + self->em_gtc = eap_method_simple_new (connection, "gtc", TRUE, TRUE); + gtk_widget_show (GTK_WIDGET (self->em_gtc)); + g_signal_connect_object (self->em_gtc, "changed", G_CALLBACK (eap_method_emit_changed), self, G_CONNECT_SWAPPED); + + if (s_8021x) { + if (nm_setting_802_1x_get_phase2_auth (s_8021x)) + phase2_auth = nm_setting_802_1x_get_phase2_auth (s_8021x); + else if (nm_setting_802_1x_get_phase2_autheap (s_8021x)) + phase2_auth = nm_setting_802_1x_get_phase2_autheap (s_8021x); + } + if (phase2_auth == NULL) + phase2_auth = "pap"; + + if (strcmp (phase2_auth, "mschapv2") == 0 && nm_setting_802_1x_get_phase2_auth (s_8021x) != NULL) + phase2_auth = "plain_mschapv2"; + + if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (self->inner_auth_model), &iter)) { + do { + g_autofree gchar *id = NULL; + gtk_tree_model_get (GTK_TREE_MODEL (self->inner_auth_model), &iter, I_ID_COLUMN, &id, -1); + if (strcmp (id, phase2_auth) == 0) + gtk_combo_box_set_active_iter (self->inner_auth_combo, &iter); + } while (gtk_tree_model_iter_next (GTK_TREE_MODEL (self->inner_auth_model), &iter)); + } + + g_signal_connect_swapped (self->inner_auth_combo, "changed", G_CALLBACK (inner_auth_combo_changed_cb), self); + inner_auth_combo_changed_cb (self); + + return self; +} + diff --git a/panels/network/wireless-security/eap-method-ttls.h b/panels/network/wireless-security/eap-method-ttls.h new file mode 100644 index 0000000..56b7085 --- /dev/null +++ b/panels/network/wireless-security/eap-method-ttls.h @@ -0,0 +1,34 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* NetworkManager Applet -- allow user control over networking + * + * Dan Williams <dcbw@redhat.com> + * + * 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. + * + * (C) Copyright 2007 - 2010 Red Hat, Inc. + */ + +#pragma once + +#include <gtk/gtk.h> +#include <NetworkManager.h> + +G_BEGIN_DECLS + +G_DECLARE_FINAL_TYPE (EAPMethodTTLS, eap_method_ttls, EAP, METHOD_TTLS, GtkGrid) + +EAPMethodTTLS *eap_method_ttls_new (NMConnection *connection); + +G_END_DECLS diff --git a/panels/network/wireless-security/eap-method-ttls.ui b/panels/network/wireless-security/eap-method-ttls.ui new file mode 100644 index 0000000..b10dac2 --- /dev/null +++ b/panels/network/wireless-security/eap-method-ttls.ui @@ -0,0 +1,160 @@ +<?xml version="1.0" encoding="UTF-8"?> +<interface> + <requires lib="gtk+" version="3.4"/> + <object class="GtkListStore" id="inner_auth_model"> + <columns> + <!-- column-name label --> + <column type="gchararray"/> + <!-- column-name id --> + <column type="gchararray"/> + </columns> + <data> + <row> + <col id="0" translatable="yes">PAP</col> + <col id="1">pap</col> + </row> + <row> + <col id="0" translatable="yes">MSCHAP</col> + <col id="1">mschap</col> + </row> + <row> + <col id="0" translatable="yes">MSCHAPv2</col> + <col id="1">mschapv2</col> + </row> + <row> + <col id="0" translatable="yes">MSCHAPv2 (no EAP)</col> + <col id="1">plain_mschapv2</col> + </row> + <row> + <col id="0" translatable="yes">CHAP</col> + <col id="1">chap</col> + </row> + <row> + <col id="0" translatable="yes">MD5</col> + <col id="1">md5</col> + </row> + <row> + <col id="0" translatable="yes">GTC</col> + <col id="1">gtc</col> + </row> + </data> + </object> + <template class="EAPMethodTTLS" parent="GtkGrid"> + <property name="column_spacing">6</property> + <property name="row_spacing">6</property> + <child> + <object class="GtkLabel" id="anon_identity_label"> + <property name="xalign">1</property> + <property name="label" translatable="yes">Anony_mous identity</property> + <property name="use_underline">True</property> + <property name="mnemonic_widget">anon_identity_entry</property> + <layout> + <property name="column">0</property> + <property name="row">0</property> + </layout> + </object> + </child> + <child> + <object class="GtkEntry" id="anon_identity_entry"> + <property name="hexpand">True</property> + <layout> + <property name="column">1</property> + <property name="row">0</property> + </layout> + </object> + </child> + <child> + <object class="GtkLabel" id="domain_match_label"> + <property name="label" translatable="yes">_Domain</property> + <property name="use_underline">True</property> + <property name="mnemonic_widget">domain_match_entry</property> + <property name="xalign">1</property> + <layout> + <property name="column">0</property> + <property name="row">1</property> + </layout> + </object> + </child> + <child> + <object class="GtkEntry" id="domain_match_entry"> + <property name="hexpand">True</property> + <layout> + <property name="column">1</property> + <property name="row">1</property> + </layout> + </object> + </child> + <child> + <object class="GtkLabel" id="ca_cert_label"> + <property name="label" translatable="yes">C_A certificate</property> + <property name="use_underline">True</property> + <property name="mnemonic_widget">ca_cert_button</property> + <property name="xalign">1</property> + <layout> + <property name="column">0</property> + <property name="row">2</property> + </layout> + </object> + </child> + <child> + <object class="WsFileChooserButton" id="ca_cert_button"> + <property name="hexpand">True</property> + <layout> + <property name="column">1</property> + <property name="row">2</property> + </layout> + </object> + </child> + <child> + <object class="GtkCheckButton" id="ca_cert_not_required_check"> + <property name="label" translatable="yes">No CA certificate is _required</property> + <property name="halign">start</property> + <property name="hexpand">True</property> + <property name="use_underline">True</property> + <layout> + <property name="column">1</property> + <property name="row">3</property> + </layout> + </object> + </child> + <child> + <object class="GtkLabel" id="inner_auth_label"> + <property name="label" translatable="yes">_Inner authentication</property> + <property name="use_underline">True</property> + <property name="mnemonic_widget">inner_auth_combo</property> + <property name="xalign">1</property> + <layout> + <property name="column">0</property> + <property name="row">4</property> + </layout> + </object> + </child> + <child> + <object class="GtkComboBox" id="inner_auth_combo"> + <property name="hexpand">True</property> + <property name="model">inner_auth_model</property> + <layout> + <property name="column">1</property> + <property name="row">4</property> + </layout> + <child> + <object class="GtkCellRendererText"/> + <attributes> + <attribute name="text">0</attribute> + </attributes> + </child> + </object> + </child> + <child> + <object class="GtkBox" id="inner_auth_box"> + <property name="orientation">vertical</property> + <property name="spacing">6</property> + <layout> + <property name="column">0</property> + <property name="row">5</property> + <property name="column-span">2</property> + </layout> + </object> + </child> + </template> +</interface> diff --git a/panels/network/wireless-security/eap-method.c b/panels/network/wireless-security/eap-method.c new file mode 100644 index 0000000..a9d11c7 --- /dev/null +++ b/panels/network/wireless-security/eap-method.c @@ -0,0 +1,588 @@ +/* -*- Mode: C; tab-width: 5; indent-tabs-mode: t; c-basic-offset: 5 -*- */ + +/* NetworkManager Applet -- allow user control over networking + * + * Dan Williams <dcbw@redhat.com> + * + * 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. + * + * Copyright 2007 - 2014 Red Hat, Inc. + */ + +#include <fcntl.h> +#include <glib/gi18n.h> + +#include "eap-method.h" +#include "helpers.h" +#include "ui-helpers.h" + +G_DEFINE_INTERFACE (EAPMethod, eap_method, G_TYPE_OBJECT) + +enum { + CHANGED, + LAST_SIGNAL +}; + +static guint signals[LAST_SIGNAL] = { 0 }; + +static void +eap_method_default_init (EAPMethodInterface *iface) +{ + signals[CHANGED] = + g_signal_new ("changed", + G_TYPE_FROM_INTERFACE (iface), + G_SIGNAL_RUN_FIRST, + 0, + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); +} + +GtkWidget * +eap_method_get_default_field (EAPMethod *self) +{ + g_return_val_if_fail (self != NULL, NULL); + + return EAP_METHOD_GET_IFACE (self)->get_default_field (self); +} + +const gchar * +eap_method_get_password_flags_name (EAPMethod *self) +{ + g_return_val_if_fail (self != NULL, NULL); + + if (EAP_METHOD_GET_IFACE (self)->get_password_flags_name) + return EAP_METHOD_GET_IFACE (self)->get_password_flags_name (self); + else + return NULL; +} + +gboolean +eap_method_get_phase2 (EAPMethod *self) +{ + g_return_val_if_fail (self != NULL, FALSE); + + if (EAP_METHOD_GET_IFACE (self)->get_phase2) + return EAP_METHOD_GET_IFACE (self)->get_phase2 (self); + else + return FALSE; +} + +gboolean +eap_method_validate (EAPMethod *self, GError **error) +{ + gboolean result; + + g_return_val_if_fail (self != NULL, FALSE); + + result = (*(EAP_METHOD_GET_IFACE (self)->validate)) (self, error); + if (!result && error && !*error) + g_set_error_literal (error, NMA_ERROR, NMA_ERROR_GENERIC, _("undefined error in 802.1X security (wpa-eap)")); + return result; +} + +void +eap_method_update_secrets (EAPMethod *self, NMConnection *connection) +{ + g_return_if_fail (self != NULL); + + if (EAP_METHOD_GET_IFACE (self)->update_secrets) + EAP_METHOD_GET_IFACE (self)->update_secrets (self, connection); +} + +void +eap_method_add_to_size_group (EAPMethod *self, GtkSizeGroup *group) +{ + g_return_if_fail (self != NULL); + g_return_if_fail (group != NULL); + + return (*(EAP_METHOD_GET_IFACE (self)->add_to_size_group)) (self, group); +} + +void +eap_method_fill_connection (EAPMethod *self, + NMConnection *connection, + NMSettingSecretFlags flags) +{ + g_return_if_fail (self != NULL); + g_return_if_fail (connection != NULL); + + return (*(EAP_METHOD_GET_IFACE (self)->fill_connection)) (self, connection, flags); +} + +void +eap_method_emit_changed (EAPMethod *self) +{ + g_return_if_fail (EAP_IS_METHOD (self)); + + g_signal_emit (self, signals[CHANGED], 0); +} + +const gchar * +eap_method_get_username (EAPMethod *self) +{ + g_return_val_if_fail (EAP_IS_METHOD (self), NULL); + return EAP_METHOD_GET_IFACE (self)->get_username (self); +} + +void +eap_method_set_username (EAPMethod *self, const gchar *username) +{ + g_return_if_fail (EAP_IS_METHOD (self)); + EAP_METHOD_GET_IFACE (self)->set_username (self, username); +} + +const gchar * +eap_method_get_password (EAPMethod *self) +{ + g_return_val_if_fail (EAP_IS_METHOD (self), NULL); + return EAP_METHOD_GET_IFACE (self)->get_password (self); +} + +void +eap_method_set_password (EAPMethod *self, const gchar *password) +{ + g_return_if_fail (EAP_IS_METHOD (self)); + EAP_METHOD_GET_IFACE (self)->set_password (self, password); +} + +gboolean +eap_method_get_show_password (EAPMethod *self) +{ + g_return_val_if_fail (EAP_IS_METHOD (self), FALSE); + return EAP_METHOD_GET_IFACE (self)->get_show_password (self); +} + +void +eap_method_set_show_password (EAPMethod *self, gboolean show_password) +{ + g_return_if_fail (EAP_IS_METHOD (self)); + EAP_METHOD_GET_IFACE (self)->set_show_password (self, show_password); +} + +gboolean +eap_method_validate_filepicker (GtkFileChooser *chooser, + guint32 item_type, + const char *password, + NMSetting8021xCKFormat *out_format, + GError **error) +{ + g_autofree gchar *filename = NULL; + g_autoptr(NMSetting8021x) setting = NULL; + g_autoptr(GFile) file = NULL; + gboolean success = TRUE; + + if (item_type == TYPE_PRIVATE_KEY) { + if (!password || *password == '\0') + success = FALSE; + } + + file = gtk_file_chooser_get_file (chooser); + if (!file) { + if (item_type != TYPE_CA_CERT) { + success = FALSE; + g_set_error_literal (error, NMA_ERROR, NMA_ERROR_GENERIC, _("no file selected")); + } + goto out; + } + + filename = g_file_get_path (file); + if (!g_file_test (filename, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_REGULAR)) { + success = FALSE; + goto out; + } + + setting = (NMSetting8021x *) nm_setting_802_1x_new (); + + success = FALSE; + if (item_type == TYPE_PRIVATE_KEY) { + if (nm_setting_802_1x_set_private_key (setting, filename, password, NM_SETTING_802_1X_CK_SCHEME_PATH, out_format, error)) + success = TRUE; + } else if (item_type == TYPE_CLIENT_CERT) { + if (nm_setting_802_1x_set_client_cert (setting, filename, NM_SETTING_802_1X_CK_SCHEME_PATH, out_format, error)) + success = TRUE; + } else if (item_type == TYPE_CA_CERT) { + if (nm_setting_802_1x_set_ca_cert (setting, filename, NM_SETTING_802_1X_CK_SCHEME_PATH, out_format, error)) + success = TRUE; + } else + g_warning ("%s: invalid item type %d.", __func__, item_type); + +out: + if (!success && error && !*error) + g_set_error_literal (error, NMA_ERROR, NMA_ERROR_GENERIC, _("unspecified error validating eap-method file")); + + if (success) + widget_unset_error (GTK_WIDGET (chooser)); + else + widget_set_error (GTK_WIDGET (chooser)); + return success; +} + +static gboolean +file_is_mime_type (const char *path, const char *mime_types[]) +{ + g_autoptr(GFile) file = NULL; + g_autoptr(GFileInfo) info = NULL; + const char *content_type; + int i = 0; + gboolean found = FALSE; + + file = g_file_new_for_path (path); + info = g_file_query_info (file, + G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE, + G_FILE_QUERY_INFO_NONE, + NULL, + NULL); + if (info) { + content_type = g_file_info_get_content_type (info); + if (content_type) { + while (mime_types[i]) { + if (strcmp (content_type, mime_types[i++]) == 0) { + found = TRUE; + break; + } + } + } + } + + return found; +} + +#if !LIBNM_BUILD +static const char * +find_tag (const char *tag, const char *buf, gsize len) +{ + gsize i, taglen; + + taglen = strlen (tag); + if (len < taglen) + return NULL; + + for (i = 0; i < len - taglen + 1; i++) { + if (memcmp (buf + i, tag, taglen) == 0) + return buf + i; + } + return NULL; +} + +static const char *pem_rsa_key_begin = "-----BEGIN RSA PRIVATE KEY-----"; +static const char *pem_dsa_key_begin = "-----BEGIN DSA PRIVATE KEY-----"; +static const char *pem_pkcs8_enc_key_begin = "-----BEGIN ENCRYPTED PRIVATE KEY-----"; +static const char *pem_pkcs8_dec_key_begin = "-----BEGIN PRIVATE KEY-----"; +static const char *pem_cert_begin = "-----BEGIN CERTIFICATE-----"; +static const char *proc_type_tag = "Proc-Type: 4,ENCRYPTED"; +static const char *dek_info_tag = "DEK-Info:"; + +static gboolean +pem_file_is_encrypted (const char *buffer, gsize bytes_read) +{ + /* Check if the private key is encrypted or not by looking for the + * old OpenSSL-style proc-type and dec-info tags. + */ + if (find_tag (proc_type_tag, (const char *) buffer, bytes_read)) { + if (find_tag (dek_info_tag, (const char *) buffer, bytes_read)) + return TRUE; + } + return FALSE; +} + +static gboolean +file_is_der_or_pem (const char *filename, + gboolean privkey, + gboolean *out_privkey_encrypted) +{ + int fd; + unsigned char buffer[8192]; + ssize_t bytes_read; + gboolean success = FALSE; + + fd = open (filename, O_RDONLY); + if (fd < 0) + return FALSE; + + bytes_read = read (fd, buffer, sizeof (buffer) - 1); + if (bytes_read < 400) /* needs to be lower? */ + goto out; + buffer[bytes_read] = '\0'; + + /* Check for DER signature */ + if (bytes_read > 2 && buffer[0] == 0x30 && buffer[1] == 0x82) { + success = TRUE; + goto out; + } + + /* Check for PEM signatures */ + if (privkey) { + if (find_tag (pem_rsa_key_begin, (const char *) buffer, bytes_read)) { + success = TRUE; + if (out_privkey_encrypted) + *out_privkey_encrypted = pem_file_is_encrypted ((const char *) buffer, bytes_read); + goto out; + } + + if (find_tag (pem_dsa_key_begin, (const char *) buffer, bytes_read)) { + success = TRUE; + if (out_privkey_encrypted) + *out_privkey_encrypted = pem_file_is_encrypted ((const char *) buffer, bytes_read); + goto out; + } + + if (find_tag (pem_pkcs8_enc_key_begin, (const char *) buffer, bytes_read)) { + success = TRUE; + if (out_privkey_encrypted) + *out_privkey_encrypted = TRUE; + goto out; + } + + if (find_tag (pem_pkcs8_dec_key_begin, (const char *) buffer, bytes_read)) { + success = TRUE; + if (out_privkey_encrypted) + *out_privkey_encrypted = FALSE; + goto out; + } + } else { + if (find_tag (pem_cert_begin, (const char *) buffer, bytes_read)) { + success = TRUE; + goto out; + } + } + +out: + close (fd); + return success; +} +#endif + +static const char *privkey_mime_types[] = { + "application/x-x509-ca-cert", "application/pkcs12", "application/x-pkcs12", "application/pgp-keys", NULL +}; +static const char *cert_mime_types[] = { + "application/x-x509-ca-cert", NULL +}; + +static void +add_mime_types_to_filter (GtkFileFilter *filter, + const char **mime_types) +{ + int i; + + for (i = 0; mime_types[i] != NULL; i++) + gtk_file_filter_add_mime_type (filter, mime_types[i]); +} + + +GtkFileFilter * +eap_method_default_file_chooser_filter_new (gboolean privkey) +{ + GtkFileFilter *filter; + + filter = gtk_file_filter_new (); + if (privkey) { + add_mime_types_to_filter (filter, privkey_mime_types); + gtk_file_filter_set_name (filter, _("DER, PEM, PKCS#12, or PGP private keys")); + } else { + add_mime_types_to_filter (filter, cert_mime_types); + gtk_file_filter_set_name (filter, _("DER or PEM certificates")); + } + return filter; +} + +gboolean +eap_method_is_encrypted_private_key (const char *path) +{ + gboolean is_encrypted; + + if (!file_is_mime_type (path, privkey_mime_types)) + return FALSE; + +#if LIBNM_BUILD + is_encrypted = FALSE; + if (!nm_utils_file_is_private_key (path, &is_encrypted)) + return FALSE; +#else + is_encrypted = TRUE; + if ( !file_is_der_or_pem (path, TRUE, &is_encrypted) + && !nm_utils_file_is_pkcs12 (path)) + return FALSE; +#endif + return is_encrypted; +} + +void +eap_method_ca_cert_not_required_toggled (GtkCheckButton *id_ca_cert_not_required_checkbutton, GtkFileChooser *id_ca_cert_chooser) +{ + g_autoptr(GFile) file = NULL; + g_autoptr(GFile) file_old = NULL; + gboolean is_not_required; + + g_assert (id_ca_cert_not_required_checkbutton && id_ca_cert_chooser); + + is_not_required = gtk_check_button_get_active (id_ca_cert_not_required_checkbutton); + + file = gtk_file_chooser_get_file (id_ca_cert_chooser); + file_old = g_object_steal_data (G_OBJECT (id_ca_cert_chooser), "filename-old"); + if (is_not_required) { + g_clear_object (&file_old); + file_old = g_steal_pointer (&file); + } else { + g_clear_object (&file); + file = g_steal_pointer (&file_old); + } + gtk_widget_set_sensitive (GTK_WIDGET (id_ca_cert_chooser), !is_not_required); + if (file) + gtk_file_chooser_set_file (id_ca_cert_chooser, file, NULL); + g_object_set_data_full (G_OBJECT (id_ca_cert_chooser), "filename-old", g_steal_pointer (&file_old), g_free); +} + +/* Used as both GSettings keys and GObject data tags */ +#define IGNORE_CA_CERT_TAG "ignore-ca-cert" +#define IGNORE_PHASE2_CA_CERT_TAG "ignore-phase2-ca-cert" + +/** + * eap_method_ca_cert_ignore_set: + * @method: the #EAPMethod object + * @connection: the #NMConnection + * @filename: the certificate file, if any + * @ca_cert_error: %TRUE if an error was encountered loading the given CA + * certificate, %FALSE if not or if a CA certificate is not present + * + * Updates the connection's CA cert ignore value to %TRUE if the "CA certificate + * not required" checkbox is checked. If @ca_cert_error is %TRUE, then the + * connection's CA cert ignore value will always be set to %FALSE, because it + * means that the user selected an invalid certificate (thus he does not want to + * ignore the CA cert).. + */ +void +eap_method_ca_cert_ignore_set (EAPMethod *self, + NMConnection *connection, + const char *filename, + gboolean ca_cert_error) +{ + NMSetting8021x *s_8021x; + gboolean ignore; + + s_8021x = nm_connection_get_setting_802_1x (connection); + if (s_8021x) { + ignore = !ca_cert_error && filename == NULL; + g_object_set_data (G_OBJECT (s_8021x), + eap_method_get_phase2 (self) ? IGNORE_PHASE2_CA_CERT_TAG : IGNORE_CA_CERT_TAG, + GUINT_TO_POINTER (ignore)); + } +} + +/** + * eap_method_ca_cert_ignore_get: + * @method: the #EAPMethod object + * @connection: the #NMConnection + * + * Returns: %TRUE if a missing CA certificate can be ignored, %FALSE if a CA + * certificate should be required for the connection to be valid. + */ +gboolean +eap_method_ca_cert_ignore_get (EAPMethod *self, NMConnection *connection) +{ + NMSetting8021x *s_8021x; + + s_8021x = nm_connection_get_setting_802_1x (connection); + if (s_8021x) { + return !!g_object_get_data (G_OBJECT (s_8021x), + eap_method_get_phase2 (self) ? IGNORE_PHASE2_CA_CERT_TAG : IGNORE_CA_CERT_TAG); + } + return FALSE; +} + +static GSettings * +_get_ca_ignore_settings (NMConnection *connection) +{ + GSettings *settings; + g_autofree gchar *path = NULL; + const char *uuid; + + g_return_val_if_fail (connection, NULL); + + uuid = nm_connection_get_uuid (connection); + g_return_val_if_fail (uuid && *uuid, NULL); + + path = g_strdup_printf ("/org/gnome/nm-applet/eap/%s/", uuid); + settings = g_settings_new_with_path ("org.gnome.nm-applet.eap", path); + + return settings; +} + +/** + * eap_method_ca_cert_ignore_save: + * @connection: the connection for which to save CA cert ignore values to GSettings + * + * Reads the CA cert ignore tags from the 802.1x setting GObject data and saves + * then to GSettings if present, using the connection UUID as the index. + */ +void +eap_method_ca_cert_ignore_save (NMConnection *connection) +{ + NMSetting8021x *s_8021x; + g_autoptr(GSettings) settings = NULL; + gboolean ignore = FALSE, phase2_ignore = FALSE; + + g_return_if_fail (connection); + + s_8021x = nm_connection_get_setting_802_1x (connection); + if (s_8021x) { + ignore = !!g_object_get_data (G_OBJECT (s_8021x), IGNORE_CA_CERT_TAG); + phase2_ignore = !!g_object_get_data (G_OBJECT (s_8021x), IGNORE_PHASE2_CA_CERT_TAG); + } + + settings = _get_ca_ignore_settings (connection); + if (!settings) + return; + + g_settings_set_boolean (settings, IGNORE_CA_CERT_TAG, ignore); + g_settings_set_boolean (settings, IGNORE_PHASE2_CA_CERT_TAG, phase2_ignore); +} + +/** + * eap_method_ca_cert_ignore_load: + * @connection: the connection for which to load CA cert ignore values to GSettings + * + * Reads the CA cert ignore tags from the 802.1x setting GObject data and saves + * then to GSettings if present, using the connection UUID as the index. + */ +void +eap_method_ca_cert_ignore_load (NMConnection *connection) +{ + g_autoptr(GSettings) settings = NULL; + NMSetting8021x *s_8021x; + gboolean ignore, phase2_ignore; + + g_return_if_fail (connection); + + s_8021x = nm_connection_get_setting_802_1x (connection); + if (!s_8021x) + return; + + settings = _get_ca_ignore_settings (connection); + if (!settings) + return; + + ignore = g_settings_get_boolean (settings, IGNORE_CA_CERT_TAG); + phase2_ignore = g_settings_get_boolean (settings, IGNORE_PHASE2_CA_CERT_TAG); + + g_object_set_data (G_OBJECT (s_8021x), + IGNORE_CA_CERT_TAG, + GUINT_TO_POINTER (ignore)); + g_object_set_data (G_OBJECT (s_8021x), + IGNORE_PHASE2_CA_CERT_TAG, + GUINT_TO_POINTER (phase2_ignore)); +} + diff --git a/panels/network/wireless-security/eap-method.h b/panels/network/wireless-security/eap-method.h new file mode 100644 index 0000000..d5a8908 --- /dev/null +++ b/panels/network/wireless-security/eap-method.h @@ -0,0 +1,108 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* NetworkManager Applet -- allow user control over networking + * + * Dan Williams <dcbw@redhat.com> + * + * 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. + * + * Copyright 2007 - 2014 Red Hat, Inc. + */ + +#pragma once + +#include <gtk/gtk.h> +#include <NetworkManager.h> + +G_BEGIN_DECLS + +G_DECLARE_INTERFACE (EAPMethod, eap_method, EAP, METHOD, GObject) + +struct _EAPMethodInterface { + GTypeInterface g_iface; + + void (*add_to_size_group) (EAPMethod *method, GtkSizeGroup *group); + void (*fill_connection) (EAPMethod *method, NMConnection *connection, NMSettingSecretFlags flags); + void (*update_secrets) (EAPMethod *method, NMConnection *connection); + gboolean (*validate) (EAPMethod *method, GError **error); + GtkWidget* (*get_default_field) (EAPMethod *method); + const gchar* (*get_password_flags_name) (EAPMethod *method); + gboolean (*get_phase2) (EAPMethod *method); + const gchar* (*get_username) (EAPMethod *method); + void (*set_username) (EAPMethod *method, const gchar *username); + const gchar* (*get_password) (EAPMethod *method); + void (*set_password) (EAPMethod *method, const gchar *password); + gboolean (*get_show_password) (EAPMethod *method); + void (*set_show_password) (EAPMethod *method, gboolean show_password); +}; + +GtkWidget *eap_method_get_default_field (EAPMethod *method); + +const gchar *eap_method_get_password_flags_name (EAPMethod *method); + +gboolean eap_method_get_phase2 (EAPMethod *method); + +void eap_method_update_secrets (EAPMethod *method, NMConnection *connection); + +gboolean eap_method_validate (EAPMethod *method, GError **error); + +void eap_method_add_to_size_group (EAPMethod *method, GtkSizeGroup *group); + +void eap_method_fill_connection (EAPMethod *method, + NMConnection *connection, + NMSettingSecretFlags flags); + +void eap_method_emit_changed (EAPMethod *method); + +const gchar *eap_method_get_username (EAPMethod *method); + +void eap_method_set_username (EAPMethod *method, const gchar *username); + +const gchar *eap_method_get_password (EAPMethod *method); + +void eap_method_set_password (EAPMethod *method, const gchar *password); + +gboolean eap_method_get_show_password (EAPMethod *method); + +void eap_method_set_show_password (EAPMethod *method, gboolean show_password); + +/* Below for internal use only */ + +GtkFileFilter * eap_method_default_file_chooser_filter_new (gboolean privkey); + +gboolean eap_method_is_encrypted_private_key (const char *path); + +#define TYPE_CLIENT_CERT 0 +#define TYPE_CA_CERT 1 +#define TYPE_PRIVATE_KEY 2 + +gboolean eap_method_validate_filepicker (GtkFileChooser *chooser, + guint32 item_type, + const char *password, + NMSetting8021xCKFormat *out_format, + GError **error); + +void eap_method_ca_cert_not_required_toggled (GtkCheckButton *id_ca_cert_is_not_required_checkbox, + GtkFileChooser *id_ca_cert_chooser); + +void eap_method_ca_cert_ignore_set (EAPMethod *method, + NMConnection *connection, + const char *filename, + gboolean ca_cert_error); +gboolean eap_method_ca_cert_ignore_get (EAPMethod *method, NMConnection *connection); + +void eap_method_ca_cert_ignore_save (NMConnection *connection); +void eap_method_ca_cert_ignore_load (NMConnection *connection); + +G_END_DECLS diff --git a/panels/network/wireless-security/helpers.c b/panels/network/wireless-security/helpers.c new file mode 100644 index 0000000..fc8446b --- /dev/null +++ b/panels/network/wireless-security/helpers.c @@ -0,0 +1,60 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* NetworkManager Applet -- allow user control over networking + * + * Dan Williams <dcbw@redhat.com> + * + * 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. + * + * Copyright 2009 - 2014 Red Hat, Inc. + */ + +#include "helpers.h" + +void +helper_fill_secret_entry (NMConnection *connection, + GtkEntry *entry, + GType setting_type, + HelperSecretFunc func) +{ + NMSetting *setting; + const char *tmp; + + g_return_if_fail (connection != NULL); + g_return_if_fail (entry != NULL); + g_return_if_fail (func != NULL); + + setting = nm_connection_get_setting (connection, setting_type); + if (setting) { + tmp = (*func) (setting); + if (tmp) { + gtk_editable_set_text (GTK_EDITABLE (entry), tmp); + } + } +} + +void +wireless_security_clear_ciphers (NMConnection *connection) +{ + NMSettingWirelessSecurity *s_wireless_sec; + + g_return_if_fail (connection != NULL); + + s_wireless_sec = nm_connection_get_setting_wireless_security (connection); + g_assert (s_wireless_sec); + + nm_setting_wireless_security_clear_protos (s_wireless_sec); + nm_setting_wireless_security_clear_pairwise (s_wireless_sec); + nm_setting_wireless_security_clear_groups (s_wireless_sec); +} diff --git a/panels/network/wireless-security/helpers.h b/panels/network/wireless-security/helpers.h new file mode 100644 index 0000000..6a402d8 --- /dev/null +++ b/panels/network/wireless-security/helpers.h @@ -0,0 +1,45 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* NetworkManager Applet -- allow user control over networking + * + * Dan Williams <dcbw@redhat.com> + * + * 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. + * + * Copyright 2009 - 2014 Red Hat, Inc. + */ + +#ifndef _HELPERS_H_ +#define _HELPERS_H_ + +#include <NetworkManager.h> +#include <gtk/gtk.h> + +#define NMA_ERROR (g_quark_from_static_string ("nma-error-quark")) + +typedef enum { + NMA_ERROR_GENERIC +} NMAError; + +typedef const char * (*HelperSecretFunc)(NMSetting *); + +void helper_fill_secret_entry (NMConnection *connection, + GtkEntry *entry, + GType setting_type, + HelperSecretFunc func); + +void wireless_security_clear_ciphers (NMConnection *connection); + +#endif /* _HELPERS_H_ */ + diff --git a/panels/network/wireless-security/meson.build b/panels/network/wireless-security/meson.build new file mode 100644 index 0000000..7f43efc --- /dev/null +++ b/panels/network/wireless-security/meson.build @@ -0,0 +1,73 @@ +name = 'wireless-security' + +wireless_security_inc = include_directories('.') + +nm_applet_headers = [ + 'eap-method.h', + 'eap-method-fast.h', + 'eap-method-leap.h', + 'eap-method-peap.h', + 'eap-method-simple.h', + 'eap-method-tls.h', + 'eap-method-ttls.h', + 'helpers.h', + 'wireless-security.h', + 'ws-leap.h', + 'ws-dynamic-wep.h', + 'ws-file-chooser-button.h', + 'ws-sae.h', + 'ws-wep-key.h', + 'ws-wpa-eap.h', + 'ws-wpa-psk.h' +] + +nm_applet_sources = [ + 'eap-method.c', + 'eap-method-fast.c', + 'eap-method-leap.c', + 'eap-method-peap.c', + 'eap-method-simple.c', + 'eap-method-tls.c', + 'eap-method-ttls.c', + 'helpers.c', + 'wireless-security.c', + 'ws-leap.c', + 'ws-dynamic-wep.c', + 'ws-file-chooser-button.c', + 'ws-sae.c', + 'ws-wep-key.c', + 'ws-wpa-eap.c', + 'ws-wpa-psk.c' +] + +sources = files(nm_applet_sources) + +nm_resource_data = [ + 'eap-method-fast.ui', + 'eap-method-leap.ui', + 'eap-method-peap.ui', + 'eap-method-simple.ui', + 'eap-method-tls.ui', + 'eap-method-ttls.ui', + 'ws-dynamic-wep.ui', + 'ws-leap.ui', + 'ws-sae.ui', + 'ws-wep-key.ui', + 'ws-wpa-eap.ui', + 'ws-wpa-psk.ui' +] + +sources += gnome.compile_resources( + name + '-resources', + name + '.gresource.xml', + c_name: name.underscorify(), + dependencies: files(nm_resource_data), + export: true +) + +libwireless_security = static_library( + name, + sources: sources, + include_directories: [top_inc, network_inc], + dependencies: deps +) diff --git a/panels/network/wireless-security/wireless-security.c b/panels/network/wireless-security/wireless-security.c new file mode 100644 index 0000000..9f083f6 --- /dev/null +++ b/panels/network/wireless-security/wireless-security.c @@ -0,0 +1,101 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* NetworkManager Applet -- allow user control over networking + * + * Dan Williams <dcbw@redhat.com> + * + * 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. + * + * Copyright 2007 - 2014 Red Hat, Inc. + */ + +#include <glib/gi18n.h> + +#include "helpers.h" +#include "wireless-security.h" +#include "wireless-security-resources.h" + +G_DEFINE_INTERFACE (WirelessSecurity, wireless_security, G_TYPE_OBJECT) + +enum { + CHANGED, + LAST_SIGNAL +}; + +static guint signals[LAST_SIGNAL] = { 0 }; + +static void +wireless_security_default_init (WirelessSecurityInterface *iface) +{ + g_resources_register (wireless_security_get_resource ()); + + signals[CHANGED] = + g_signal_new ("changed", + G_TYPE_FROM_INTERFACE (iface), + G_SIGNAL_RUN_FIRST, + 0, + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); +} + +void +wireless_security_notify_changed (WirelessSecurity *self) +{ + g_return_if_fail (WIRELESS_IS_SECURITY (self)); + + g_signal_emit (self, signals[CHANGED], 0); +} + +gboolean +wireless_security_validate (WirelessSecurity *self, GError **error) +{ + gboolean result; + + g_return_val_if_fail (WIRELESS_IS_SECURITY (self), FALSE); + g_return_val_if_fail (!error || !*error, FALSE); + + result = WIRELESS_SECURITY_GET_IFACE (self)->validate (self, error); + if (!result && error && !*error) + g_set_error_literal (error, NMA_ERROR, NMA_ERROR_GENERIC, _("Unknown error validating 802.1X security")); + return result; +} + +void +wireless_security_add_to_size_group (WirelessSecurity *self, GtkSizeGroup *group) +{ + g_return_if_fail (WIRELESS_IS_SECURITY (self)); + g_return_if_fail (GTK_IS_SIZE_GROUP (group)); + + return WIRELESS_SECURITY_GET_IFACE (self)->add_to_size_group (self, group); +} + +void +wireless_security_fill_connection (WirelessSecurity *self, + NMConnection *connection) +{ + g_return_if_fail (WIRELESS_IS_SECURITY (self)); + g_return_if_fail (connection != NULL); + + return WIRELESS_SECURITY_GET_IFACE (self)->fill_connection (self, connection); +} + +gboolean +wireless_security_adhoc_compatible (WirelessSecurity *self) +{ + if (WIRELESS_SECURITY_GET_IFACE (self)->adhoc_compatible) + return WIRELESS_SECURITY_GET_IFACE (self)->adhoc_compatible (self); + else + return TRUE; +} diff --git a/panels/network/wireless-security/wireless-security.gresource.xml b/panels/network/wireless-security/wireless-security.gresource.xml new file mode 100644 index 0000000..c82860c --- /dev/null +++ b/panels/network/wireless-security/wireless-security.gresource.xml @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="UTF-8"?> +<gresources> + <gresource prefix="/org/gnome/Settings/network/"> + <file preprocess="xml-stripblanks">eap-method-leap.ui</file> + <file preprocess="xml-stripblanks">eap-method-fast.ui</file> + <file preprocess="xml-stripblanks">eap-method-peap.ui</file> + <file preprocess="xml-stripblanks">eap-method-simple.ui</file> + <file preprocess="xml-stripblanks">eap-method-tls.ui</file> + <file preprocess="xml-stripblanks">eap-method-ttls.ui</file> + <file preprocess="xml-stripblanks">ws-dynamic-wep.ui</file> + <file preprocess="xml-stripblanks">ws-leap.ui</file> + <file preprocess="xml-stripblanks">ws-sae.ui</file> + <file preprocess="xml-stripblanks">ws-wep-key.ui</file> + <file preprocess="xml-stripblanks">ws-wpa-eap.ui</file> + <file preprocess="xml-stripblanks">ws-wpa-psk.ui</file> + </gresource> +</gresources> diff --git a/panels/network/wireless-security/wireless-security.h b/panels/network/wireless-security/wireless-security.h new file mode 100644 index 0000000..7f1cbed --- /dev/null +++ b/panels/network/wireless-security/wireless-security.h @@ -0,0 +1,53 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* NetworkManager Applet -- allow user control over networking + * + * Dan Williams <dcbw@redhat.com> + * + * 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. + * + * Copyright 2007 - 2014 Red Hat, Inc. + */ + +#pragma once + +#include <gtk/gtk.h> +#include <NetworkManager.h> + +G_BEGIN_DECLS + +G_DECLARE_INTERFACE (WirelessSecurity, wireless_security, WIRELESS, SECURITY, GObject) + +struct _WirelessSecurityInterface { + GTypeInterface g_iface; + + void (*add_to_size_group) (WirelessSecurity *sec, GtkSizeGroup *group); + void (*fill_connection) (WirelessSecurity *sec, NMConnection *connection); + gboolean (*validate) (WirelessSecurity *sec, GError **error); + gboolean (*adhoc_compatible) (WirelessSecurity *sec); +}; + +gboolean wireless_security_validate (WirelessSecurity *sec, GError **error); + +void wireless_security_add_to_size_group (WirelessSecurity *sec, + GtkSizeGroup *group); + +void wireless_security_fill_connection (WirelessSecurity *sec, + NMConnection *connection); + +gboolean wireless_security_adhoc_compatible (WirelessSecurity *sec); + +void wireless_security_notify_changed (WirelessSecurity *sec); + +G_END_DECLS diff --git a/panels/network/wireless-security/ws-dynamic-wep.c b/panels/network/wireless-security/ws-dynamic-wep.c new file mode 100644 index 0000000..87a5bb9 --- /dev/null +++ b/panels/network/wireless-security/ws-dynamic-wep.c @@ -0,0 +1,262 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* NetworkManager Applet -- allow user control over networking + * + * Dan Williams <dcbw@redhat.com> + * + * 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. + * + * Copyright 2007 - 2014 Red Hat, Inc. + */ + +#include <glib/gi18n.h> + +#include "eap-method.h" +#include "eap-method-fast.h" +#include "eap-method-leap.h" +#include "eap-method-peap.h" +#include "eap-method-simple.h" +#include "eap-method-tls.h" +#include "eap-method-ttls.h" +#include "wireless-security.h" +#include "ws-dynamic-wep.h" + +struct _WirelessSecurityDynamicWEP { + GtkGrid parent; + + GtkComboBox *auth_combo; + GtkLabel *auth_label; + GtkListStore *auth_model; + GtkBox *method_box; + + EAPMethodTLS *em_tls; + EAPMethodLEAP *em_leap; + EAPMethodSimple *em_pwd; + EAPMethodFAST *em_fast; + EAPMethodTTLS *em_ttls; + EAPMethodPEAP *em_peap; +}; + +static void wireless_security_iface_init (WirelessSecurityInterface *); + +G_DEFINE_TYPE_WITH_CODE (WirelessSecurityDynamicWEP, ws_dynamic_wep, GTK_TYPE_GRID, + G_IMPLEMENT_INTERFACE (wireless_security_get_type (), wireless_security_iface_init)); + +#define AUTH_NAME_COLUMN 0 +#define AUTH_ID_COLUMN 1 + +static EAPMethod * +get_eap (WirelessSecurityDynamicWEP *self) +{ + GtkTreeIter iter; + g_autofree gchar *id = NULL; + + if (!gtk_combo_box_get_active_iter (self->auth_combo, &iter)) + return NULL; + gtk_tree_model_get (GTK_TREE_MODEL (self->auth_model), &iter, AUTH_ID_COLUMN, &id, -1); + + if (strcmp (id, "tls") == 0) + return EAP_METHOD (self->em_tls); + if (strcmp (id, "leap") == 0) + return EAP_METHOD (self->em_leap); + if (strcmp (id, "pwd") == 0) + return EAP_METHOD (self->em_pwd); + if (strcmp (id, "fast") == 0) + return EAP_METHOD (self->em_fast); + if (strcmp (id, "ttls") == 0) + return EAP_METHOD (self->em_ttls); + if (strcmp (id, "peap") == 0) + return EAP_METHOD (self->em_peap); + + return NULL; +} + +static gboolean +validate (WirelessSecurity *security, GError **error) +{ + WirelessSecurityDynamicWEP *self = WS_DYNAMIC_WEP (security); + return eap_method_validate (get_eap (self), error); +} + +static void +add_to_size_group (WirelessSecurity *security, GtkSizeGroup *group) +{ + WirelessSecurityDynamicWEP *self = WS_DYNAMIC_WEP (security); + + gtk_size_group_add_widget (group, GTK_WIDGET (self->auth_label)); + eap_method_add_to_size_group (EAP_METHOD (self->em_tls), group); + eap_method_add_to_size_group (EAP_METHOD (self->em_leap), group); + eap_method_add_to_size_group (EAP_METHOD (self->em_pwd), group); + eap_method_add_to_size_group (EAP_METHOD (self->em_fast), group); + eap_method_add_to_size_group (EAP_METHOD (self->em_ttls), group); + eap_method_add_to_size_group (EAP_METHOD (self->em_peap), group); +} + +static void +fill_connection (WirelessSecurity *security, NMConnection *connection) +{ + WirelessSecurityDynamicWEP *self = WS_DYNAMIC_WEP (security); + NMSettingWirelessSecurity *s_wireless_sec; + NMSetting8021x *s_8021x; + NMSettingSecretFlags secret_flags = NM_SETTING_SECRET_FLAG_NONE; + EAPMethod *eap; + + /* Get the EAPMethod object */ + eap = get_eap (self); + + /* Get previous pasword flags, if any. Otherwise default to agent-owned secrets */ + s_8021x = nm_connection_get_setting_802_1x (connection); + if (s_8021x) + nm_setting_get_secret_flags (NM_SETTING (s_8021x), eap_method_get_password_flags_name (eap), &secret_flags, NULL); + else + secret_flags = NM_SETTING_SECRET_FLAG_AGENT_OWNED; + + /* Blow away the old wireless security setting by adding a clear one */ + s_wireless_sec = (NMSettingWirelessSecurity *) nm_setting_wireless_security_new (); + nm_connection_add_setting (connection, (NMSetting *) s_wireless_sec); + + /* Blow away the old 802.1x setting by adding a clear one */ + s_8021x = (NMSetting8021x *) nm_setting_802_1x_new (); + nm_connection_add_setting (connection, (NMSetting *) s_8021x); + + eap_method_fill_connection (eap, connection, secret_flags); + + g_object_set (s_wireless_sec, NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, "ieee8021x", NULL); +} + +static gboolean +adhoc_compatible (WirelessSecurity *security) +{ + return FALSE; +} + +static void +auth_combo_changed_cb (WirelessSecurityDynamicWEP *self) +{ + EAPMethod *eap; + GtkWidget *eap_default_field; + GtkWidget *child; + + eap = get_eap (self); + + /* Remove the previous method and migrate username/password across */ + child = gtk_widget_get_first_child (GTK_WIDGET (self->method_box)); + if (child != NULL) { + EAPMethod *old_eap = EAP_METHOD (child); + eap_method_set_username (eap, eap_method_get_username (old_eap)); + eap_method_set_password (eap, eap_method_get_password (old_eap)); + eap_method_set_show_password (eap, eap_method_get_show_password (old_eap)); + gtk_box_remove (self->method_box, child); + } + + gtk_box_append (self->method_box, g_object_ref (GTK_WIDGET (eap))); + eap_default_field = eap_method_get_default_field (eap); + if (eap_default_field) + gtk_widget_grab_focus (eap_default_field); + + wireless_security_notify_changed (WIRELESS_SECURITY (self)); +} + +void +ws_dynamic_wep_init (WirelessSecurityDynamicWEP *self) +{ + gtk_widget_init_template (GTK_WIDGET (self)); +} + +void +ws_dynamic_wep_class_init (WirelessSecurityDynamicWEPClass *klass) +{ + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/Settings/network/ws-dynamic-wep.ui"); + + gtk_widget_class_bind_template_child (widget_class, WirelessSecurityDynamicWEP, auth_combo); + gtk_widget_class_bind_template_child (widget_class, WirelessSecurityDynamicWEP, auth_label); + gtk_widget_class_bind_template_child (widget_class, WirelessSecurityDynamicWEP, auth_model); + gtk_widget_class_bind_template_child (widget_class, WirelessSecurityDynamicWEP, method_box); +} + +static void +wireless_security_iface_init (WirelessSecurityInterface *iface) +{ + iface->validate = validate; + iface->add_to_size_group = add_to_size_group; + iface->fill_connection = fill_connection; + iface->adhoc_compatible = adhoc_compatible; +} + +WirelessSecurityDynamicWEP * +ws_dynamic_wep_new (NMConnection *connection) +{ + WirelessSecurityDynamicWEP *self; + const gchar *default_method = NULL; + GtkTreeIter iter; + + self = g_object_new (ws_dynamic_wep_get_type (), NULL); + + /* Grab the default EAP method out of the security object */ + if (connection) { + NMSetting8021x *s_8021x; + + s_8021x = nm_connection_get_setting_802_1x (connection); + if (s_8021x && nm_setting_802_1x_get_num_eap_methods (s_8021x)) + default_method = nm_setting_802_1x_get_eap_method (s_8021x, 0); + } + if (default_method == NULL) + default_method = "tls"; + + self->em_tls = eap_method_tls_new (connection); + gtk_widget_show (GTK_WIDGET (self->em_tls)); + g_signal_connect_object (self->em_tls, "changed", G_CALLBACK (wireless_security_notify_changed), self, G_CONNECT_SWAPPED); + self->em_leap = eap_method_leap_new (connection); + gtk_widget_show (GTK_WIDGET (self->em_leap)); + g_signal_connect_object (self->em_leap, "changed", G_CALLBACK (wireless_security_notify_changed), self, G_CONNECT_SWAPPED); + self->em_pwd = eap_method_simple_new (connection, "pwd", FALSE, FALSE); + gtk_widget_show (GTK_WIDGET (self->em_pwd)); + g_signal_connect_object (self->em_pwd, "changed", G_CALLBACK (wireless_security_notify_changed), self, G_CONNECT_SWAPPED); + self->em_fast = eap_method_fast_new (connection); + gtk_widget_show (GTK_WIDGET (self->em_fast)); + g_signal_connect_object (self->em_fast, "changed", G_CALLBACK (wireless_security_notify_changed), self, G_CONNECT_SWAPPED); + self->em_ttls = eap_method_ttls_new (connection); + gtk_widget_show (GTK_WIDGET (self->em_ttls)); + g_signal_connect_object (self->em_ttls, "changed", G_CALLBACK (wireless_security_notify_changed), self, G_CONNECT_SWAPPED); + self->em_peap = eap_method_peap_new (connection); + gtk_widget_show (GTK_WIDGET (self->em_peap)); + g_signal_connect_object (self->em_peap, "changed", G_CALLBACK (wireless_security_notify_changed), self, G_CONNECT_SWAPPED); + + if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (self->auth_model), &iter)) { + do { + g_autofree gchar *id = NULL; + gtk_tree_model_get (GTK_TREE_MODEL (self->auth_model), &iter, AUTH_ID_COLUMN, &id, -1); + if (strcmp (id, default_method) == 0) + gtk_combo_box_set_active_iter (self->auth_combo, &iter); + } while (gtk_tree_model_iter_next (GTK_TREE_MODEL (self->auth_model), &iter)); + } + + if (connection) { + NMSetting8021x *setting; + + setting = nm_connection_get_setting_802_1x (connection); + if (setting) { + eap_method_set_username (get_eap (self), nm_setting_802_1x_get_identity (setting)); + eap_method_set_password (get_eap (self), nm_setting_802_1x_get_password (setting)); + } + } + + g_signal_connect_object (G_OBJECT (self->auth_combo), "changed", G_CALLBACK (auth_combo_changed_cb), self, G_CONNECT_SWAPPED); + auth_combo_changed_cb (self); + + return self; +} + diff --git a/panels/network/wireless-security/ws-dynamic-wep.h b/panels/network/wireless-security/ws-dynamic-wep.h new file mode 100644 index 0000000..80c914b --- /dev/null +++ b/panels/network/wireless-security/ws-dynamic-wep.h @@ -0,0 +1,34 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* NetworkManager Applet -- allow user control over networking + * + * Dan Williams <dcbw@redhat.com> + * + * 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. + * + * Copyright 2007 - 2014 Red Hat, Inc. + */ + +#pragma once + +#include <gtk/gtk.h> +#include <NetworkManager.h> + +G_BEGIN_DECLS + +G_DECLARE_FINAL_TYPE (WirelessSecurityDynamicWEP, ws_dynamic_wep, WS, DYNAMIC_WEP, GtkGrid) + +WirelessSecurityDynamicWEP *ws_dynamic_wep_new (NMConnection *connection); + +G_END_DECLS diff --git a/panels/network/wireless-security/ws-dynamic-wep.ui b/panels/network/wireless-security/ws-dynamic-wep.ui new file mode 100644 index 0000000..85d6389 --- /dev/null +++ b/panels/network/wireless-security/ws-dynamic-wep.ui @@ -0,0 +1,89 @@ +<?xml version="1.0" encoding="UTF-8"?> +<interface> + <requires lib="gtk+" version="3.4"/> + <object class="GtkListStore" id="auth_model"> + <columns> + <!-- column-name label --> + <column type="gchararray"/> + <!-- column-name id --> + <column type="gchararray"/> + <!-- column-name visible --> + <column type="gboolean"/> + </columns> + <data> + <row> + <col id="0" translatable="yes">TLS</col> + <col id="1">tls</col> + <col id="2">True</col> + </row> + <row> + <col id="0" translatable="yes">LEAP</col> + <col id="1">leap</col> + <col id="2">True</col> + </row> + <row> + <col id="0" translatable="yes">PWD</col> + <col id="1">pwd</col> + <col id="2">True</col> + </row> + <row> + <col id="0" translatable="yes">FAST</col> + <col id="1">fast</col> + <col id="2">True</col> + </row> + <row> + <col id="0" translatable="yes">Tunneled TLS</col> + <col id="1">ttls</col> + <col id="2">True</col> + </row> + <row> + <col id="0" translatable="yes">Protected EAP (PEAP)</col> + <col id="1">peap</col> + <col id="2">True</col> + </row> + </data> + </object> + <template class="WirelessSecurityDynamicWEP" parent="GtkGrid"> + <property name="column_spacing">6</property> + <property name="row_spacing">6</property> + <child> + <object class="GtkLabel" id="auth_label"> + <property name="xalign">1</property> + <property name="label" translatable="yes">Au_thentication</property> + <property name="use_underline">True</property> + <property name="mnemonic_widget">auth_combo</property> + <layout> + <property name="column">0</property> + <property name="row">0</property> + </layout> + </object> + </child> + <child> + <object class="GtkComboBox" id="auth_combo"> + <property name="hexpand">True</property> + <property name="model">auth_model</property> + <layout> + <property name="column">1</property> + <property name="row">0</property> + </layout> + <child> + <object class="GtkCellRendererText"/> + <attributes> + <attribute name="text">0</attribute> + </attributes> + </child> + </object> + </child> + <child> + <object class="GtkBox" id="method_box"> + <property name="spacing">6</property> + <property name="orientation">vertical</property> + <layout> + <property name="column">0</property> + <property name="column-span">2</property> + <property name="row">1</property> + </layout> + </object> + </child> + </template> +</interface> diff --git a/panels/network/wireless-security/ws-file-chooser-button.c b/panels/network/wireless-security/ws-file-chooser-button.c new file mode 100644 index 0000000..998df42 --- /dev/null +++ b/panels/network/wireless-security/ws-file-chooser-button.c @@ -0,0 +1,268 @@ +/* cc-file-chooser-button.c + * + * Copyright 2021 Georges Basile Stavracas Neto <georges.stavracas@gmail.com> + * + * 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 3 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/>. + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "ws-file-chooser-button.h" + +#include <glib/gi18n.h> + +struct _WsFileChooserButton +{ + GtkButton parent_instance; + + GtkFileChooser *filechooser; + GFile *file; + char *title; +}; + +G_DEFINE_FINAL_TYPE (WsFileChooserButton, ws_file_chooser_button, GTK_TYPE_BUTTON) + +enum +{ + PROP_0, + PROP_FILE, + PROP_TITLE, + N_PROPS +}; + +static GParamSpec *properties [N_PROPS] = { NULL, }; + +static const char * +get_title (WsFileChooserButton *self) +{ + return self->title ? self->title : _("Select a file"); +} + +static void +update_label (WsFileChooserButton *self) +{ + g_autofree gchar *label = NULL; + + if (self->file) + label = g_file_get_basename (self->file); + else + label = g_strdup (get_title (self)); + + gtk_button_set_label (GTK_BUTTON (self), label); +} + +static void +on_filechooser_dialog_response_cb (GtkFileChooser *filechooser, + gint response, + WsFileChooserButton *self) +{ + if (response == GTK_RESPONSE_ACCEPT) + { + g_autoptr(GFile) file = NULL; + + file = gtk_file_chooser_get_file (filechooser); + ws_file_chooser_button_set_file (self, file); + } + + gtk_widget_hide (GTK_WIDGET (filechooser)); +} +static void +ensure_filechooser (WsFileChooserButton *self) +{ + GtkNative *native; + GtkWidget *dialog; + + if (self->filechooser) + return; + + native = gtk_widget_get_native (GTK_WIDGET (self)); + + dialog = gtk_file_chooser_dialog_new (get_title (self), + GTK_WINDOW (native), + GTK_FILE_CHOOSER_ACTION_OPEN, + _("Cancel"), + GTK_RESPONSE_CANCEL, + _("Open"), + GTK_RESPONSE_ACCEPT, + NULL); + gtk_window_set_destroy_with_parent (GTK_WINDOW (dialog), TRUE); + gtk_window_set_hide_on_close (GTK_WINDOW (dialog), TRUE); + gtk_window_set_modal (GTK_WINDOW (dialog), TRUE); + + if (self->file) + gtk_file_chooser_set_file (GTK_FILE_CHOOSER (dialog), self->file, NULL); + + g_signal_connect (dialog, "response", G_CALLBACK (on_filechooser_dialog_response_cb), self); + + self->filechooser = GTK_FILE_CHOOSER (dialog); +} + + +static void +ws_file_chooser_button_clicked (GtkButton *button) +{ + WsFileChooserButton *self = WS_FILE_CHOOSER_BUTTON (button); + GtkNative *native = gtk_widget_get_native (GTK_WIDGET (self)); + + ensure_filechooser (self); + + gtk_window_set_transient_for (GTK_WINDOW (self->filechooser), GTK_WINDOW (native)); + gtk_window_present (GTK_WINDOW (self->filechooser)); +} + +static void +ws_file_chooser_button_finalize (GObject *object) +{ + WsFileChooserButton *self = (WsFileChooserButton *)object; + + g_clear_pointer (&self->title, g_free); + g_clear_object (&self->file); + + G_OBJECT_CLASS (ws_file_chooser_button_parent_class)->finalize (object); +} + +static void +ws_file_chooser_button_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + WsFileChooserButton *self = WS_FILE_CHOOSER_BUTTON (object); + + switch (prop_id) + { + case PROP_FILE: + g_value_set_object (value, self->file); + break; + + case PROP_TITLE: + g_value_set_string (value, self->title); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +ws_file_chooser_button_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + WsFileChooserButton *self = WS_FILE_CHOOSER_BUTTON (object); + + switch (prop_id) + { + case PROP_FILE: + ws_file_chooser_button_set_file (self, g_value_get_object (value)); + break; + + case PROP_TITLE: + ws_file_chooser_button_set_title (self, g_value_get_string (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +ws_file_chooser_button_class_init (WsFileChooserButtonClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkButtonClass *button_class = GTK_BUTTON_CLASS (klass); + + button_class->clicked = ws_file_chooser_button_clicked; + + object_class->finalize = ws_file_chooser_button_finalize; + object_class->get_property = ws_file_chooser_button_get_property; + object_class->set_property = ws_file_chooser_button_set_property; + + properties[PROP_FILE] = g_param_spec_object ("file", "", "", + G_TYPE_FILE, + G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); + properties[PROP_TITLE] = g_param_spec_string ("title", "", "", NULL, + G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); + g_object_class_install_properties (object_class, N_PROPS, properties); +} + +static void +ws_file_chooser_button_init (WsFileChooserButton *self) +{ + update_label (self); +} + +GtkWidget * +ws_file_chooser_button_new (void) +{ + return g_object_new (WS_TYPE_FILE_CHOOSER_BUTTON, NULL); +} + +void +ws_file_chooser_button_set_file (WsFileChooserButton *self, + GFile *file) +{ + g_return_if_fail (WS_IS_FILE_CHOOSER_BUTTON (self)); + + if (g_set_object (&self->file, file)) + { + gtk_file_chooser_set_file (self->filechooser, file, NULL); + update_label (self); + + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_FILE]); + } +} + +GFile * +ws_file_chooser_button_get_file (WsFileChooserButton *self) +{ + g_return_val_if_fail (WS_IS_FILE_CHOOSER_BUTTON (self), NULL); + + return self->file ? g_object_ref (self->file) : NULL; +} + +void +ws_file_chooser_button_set_title (WsFileChooserButton *self, + const char *title) +{ + g_autofree char *old_title = NULL; + + g_return_if_fail (WS_IS_FILE_CHOOSER_BUTTON (self)); + + old_title = g_steal_pointer (&self->title); + self->title = g_strdup (title); + + update_label (self); + + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_TITLE]); +} + +const char * +ws_file_chooser_button_get_title (WsFileChooserButton *self) +{ + g_return_val_if_fail (WS_IS_FILE_CHOOSER_BUTTON (self), NULL); + + return self->title; +} + +GtkFileChooser * +ws_file_chooser_button_get_filechooser (WsFileChooserButton *self) +{ + g_return_val_if_fail (WS_IS_FILE_CHOOSER_BUTTON (self), NULL); + + ensure_filechooser (self); + + return self->filechooser; +} diff --git a/panels/network/wireless-security/ws-file-chooser-button.h b/panels/network/wireless-security/ws-file-chooser-button.h new file mode 100644 index 0000000..db02020 --- /dev/null +++ b/panels/network/wireless-security/ws-file-chooser-button.h @@ -0,0 +1,44 @@ +/* ws-file-chooser-button.h + * + * Copyright 2021 Georges Basile Stavracas Neto <georges.stavracas@gmail.com> + * + * 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 3 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/>. + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include <gtk/gtk.h> + +G_BEGIN_DECLS + +#define WS_TYPE_FILE_CHOOSER_BUTTON (ws_file_chooser_button_get_type()) +G_DECLARE_FINAL_TYPE (WsFileChooserButton, ws_file_chooser_button, WS, FILE_CHOOSER_BUTTON, GtkButton) + +GtkWidget *ws_file_chooser_button_new (void); + +void ws_file_chooser_button_set_file (WsFileChooserButton *self, + GFile *file); + +GFile *ws_file_chooser_button_get_file (WsFileChooserButton *self); + +void ws_file_chooser_button_set_title (WsFileChooserButton *self, + const char *title); + +const char *ws_file_chooser_button_get_title (WsFileChooserButton *self); + +GtkFileChooser *ws_file_chooser_button_get_filechooser (WsFileChooserButton *self); + +G_END_DECLS diff --git a/panels/network/wireless-security/ws-leap.c b/panels/network/wireless-security/ws-leap.c new file mode 100644 index 0000000..a234015 --- /dev/null +++ b/panels/network/wireless-security/ws-leap.c @@ -0,0 +1,211 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* NetworkManager Applet -- allow user control over networking + * + * Dan Williams <dcbw@redhat.com> + * + * 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. + * + * Copyright 2007 - 2014 Red Hat, Inc. + */ + +#include <glib/gi18n.h> + +#include "helpers.h" +#include "nma-ui-utils.h" +#include "ui-helpers.h" +#include "wireless-security.h" +#include "ws-leap.h" + +struct _WirelessSecurityLEAP { + GtkGrid parent; + + GtkEntry *password_entry; + GtkLabel *password_label; + GtkCheckButton *show_password_check; + GtkEntry *username_entry; + GtkLabel *username_label; +}; + +static void wireless_security_iface_init (WirelessSecurityInterface *); + +G_DEFINE_TYPE_WITH_CODE (WirelessSecurityLEAP, ws_leap, GTK_TYPE_GRID, + G_IMPLEMENT_INTERFACE (wireless_security_get_type (), wireless_security_iface_init)); + +static void +show_toggled_cb (WirelessSecurityLEAP *self) +{ + gboolean visible; + + visible = gtk_check_button_get_active (GTK_CHECK_BUTTON (self->show_password_check)); + gtk_entry_set_visibility (self->password_entry, visible); +} + +static gboolean +validate (WirelessSecurity *security, GError **error) +{ + WirelessSecurityLEAP *self = WS_LEAP (security); + NMSettingSecretFlags secret_flags; + const char *text; + gboolean ret = TRUE; + + text = gtk_editable_get_text (GTK_EDITABLE (self->username_entry)); + if (!text || !strlen (text)) { + widget_set_error (GTK_WIDGET (self->username_entry)); + g_set_error_literal (error, NMA_ERROR, NMA_ERROR_GENERIC, _("missing leap-username")); + ret = FALSE; + } else + widget_unset_error (GTK_WIDGET (self->username_entry)); + + secret_flags = nma_utils_menu_to_secret_flags (GTK_WIDGET (self->password_entry)); + if (secret_flags & NM_SETTING_SECRET_FLAG_NOT_SAVED) { + widget_unset_error (GTK_WIDGET (self->password_entry)); + return TRUE; + } + + text = gtk_editable_get_text (GTK_EDITABLE (self->password_entry)); + if (!text || !strlen (text)) { + widget_set_error (GTK_WIDGET (self->password_entry)); + if (ret) { + g_set_error_literal (error, NMA_ERROR, NMA_ERROR_GENERIC, _("missing leap-password")); + ret = FALSE; + } + } else + widget_unset_error (GTK_WIDGET (self->password_entry)); + + return ret; +} + +static void +add_to_size_group (WirelessSecurity *security, GtkSizeGroup *group) +{ + WirelessSecurityLEAP *self = WS_LEAP (security); + gtk_size_group_add_widget (group, GTK_WIDGET (self->username_label)); + gtk_size_group_add_widget (group, GTK_WIDGET (self->password_label)); +} + +static void +fill_connection (WirelessSecurity *security, NMConnection *connection) +{ + WirelessSecurityLEAP *self = WS_LEAP (security); + NMSettingWirelessSecurity *s_wireless_sec; + NMSettingSecretFlags secret_flags; + const char *leap_password = NULL, *leap_username = NULL; + + /* Blow away the old security setting by adding a clear one */ + s_wireless_sec = (NMSettingWirelessSecurity *) nm_setting_wireless_security_new (); + nm_connection_add_setting (connection, (NMSetting *) s_wireless_sec); + + leap_username = gtk_editable_get_text (GTK_EDITABLE (self->username_entry)); + leap_password = gtk_editable_get_text (GTK_EDITABLE (self->password_entry)); + + g_object_set (s_wireless_sec, + NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, "ieee8021x", + NM_SETTING_WIRELESS_SECURITY_AUTH_ALG, "leap", + NM_SETTING_WIRELESS_SECURITY_LEAP_USERNAME, leap_username, + NM_SETTING_WIRELESS_SECURITY_LEAP_PASSWORD, leap_password, + NULL); + + /* Save LEAP_PASSWORD_FLAGS to the connection */ + secret_flags = nma_utils_menu_to_secret_flags (GTK_WIDGET (self->password_entry)); + nm_setting_set_secret_flags (NM_SETTING (s_wireless_sec), NM_SETTING_WIRELESS_SECURITY_LEAP_PASSWORD, + secret_flags, NULL); + + /* Update secret flags and popup when editing the connection */ + nma_utils_update_password_storage (GTK_WIDGET (self->password_entry), secret_flags, + NM_SETTING (s_wireless_sec), NM_SETTING_WIRELESS_SECURITY_LEAP_PASSWORD); +} + +static gboolean +adhoc_compatible (WirelessSecurity *security) +{ + return FALSE; +} + +static void +changed_cb (WirelessSecurityLEAP *self) +{ + wireless_security_notify_changed ((WirelessSecurity *) self); +} + +void +ws_leap_init (WirelessSecurityLEAP *self) +{ + gtk_widget_init_template (GTK_WIDGET (self)); +} + +void +ws_leap_class_init (WirelessSecurityLEAPClass *klass) +{ + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/Settings/network/ws-leap.ui"); + + gtk_widget_class_bind_template_child (widget_class, WirelessSecurityLEAP, password_entry); + gtk_widget_class_bind_template_child (widget_class, WirelessSecurityLEAP, password_label); + gtk_widget_class_bind_template_child (widget_class, WirelessSecurityLEAP, show_password_check); + gtk_widget_class_bind_template_child (widget_class, WirelessSecurityLEAP, username_entry); + gtk_widget_class_bind_template_child (widget_class, WirelessSecurityLEAP, username_label); +} + +static void +wireless_security_iface_init (WirelessSecurityInterface *iface) +{ + iface->validate = validate; + iface->add_to_size_group = add_to_size_group; + iface->fill_connection = fill_connection; + iface->adhoc_compatible = adhoc_compatible; +} + +WirelessSecurityLEAP * +ws_leap_new (NMConnection *connection) +{ + WirelessSecurityLEAP *self; + NMSettingWirelessSecurity *wsec = NULL; + + self = g_object_new (ws_leap_get_type (), NULL); + + if (connection) { + wsec = nm_connection_get_setting_wireless_security (connection); + if (wsec) { + const char *auth_alg; + + /* Ignore if wireless security doesn't specify LEAP */ + auth_alg = nm_setting_wireless_security_get_auth_alg (wsec); + if (!auth_alg || strcmp (auth_alg, "leap")) + wsec = NULL; + } + } + + g_signal_connect_swapped (self->password_entry, "changed", G_CALLBACK (changed_cb), self); + + /* Create password-storage popup menu for password entry under entry's secondary icon */ + nma_utils_setup_password_storage (GTK_WIDGET (self->password_entry), 0, (NMSetting *) wsec, NM_SETTING_WIRELESS_SECURITY_LEAP_PASSWORD, + FALSE, FALSE); + + if (wsec) + helper_fill_secret_entry (connection, + self->password_entry, + NM_TYPE_SETTING_WIRELESS_SECURITY, + (HelperSecretFunc) nm_setting_wireless_security_get_leap_password); + + g_signal_connect_swapped (self->username_entry, "changed", G_CALLBACK (changed_cb), self); + if (wsec) + gtk_editable_set_text (GTK_EDITABLE (self->username_entry), nm_setting_wireless_security_get_leap_username (wsec)); + + g_signal_connect_swapped (self->show_password_check, "toggled", G_CALLBACK (show_toggled_cb), self); + + return self; +} + diff --git a/panels/network/wireless-security/ws-leap.h b/panels/network/wireless-security/ws-leap.h new file mode 100644 index 0000000..ebe90bb --- /dev/null +++ b/panels/network/wireless-security/ws-leap.h @@ -0,0 +1,34 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* NetworkManager Applet -- allow user control over networking + * + * Dan Williams <dcbw@redhat.com> + * + * 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. + * + * Copyright 2007 - 2014 Red Hat, Inc. + */ + +#pragma once + +#include <gtk/gtk.h> +#include <NetworkManager.h> + +G_BEGIN_DECLS + +G_DECLARE_FINAL_TYPE (WirelessSecurityLEAP, ws_leap, WS, LEAP, GtkGrid) + +WirelessSecurityLEAP *ws_leap_new (NMConnection *connection); + +G_END_DECLS diff --git a/panels/network/wireless-security/ws-leap.ui b/panels/network/wireless-security/ws-leap.ui new file mode 100644 index 0000000..41e21d6 --- /dev/null +++ b/panels/network/wireless-security/ws-leap.ui @@ -0,0 +1,63 @@ +<?xml version="1.0" encoding="UTF-8"?> +<interface> + <requires lib="gtk+" version="3.4"/> + <template class="WirelessSecurityLEAP" parent="GtkGrid"> + <property name="valign">start</property> + <property name="column_spacing">6</property> + <property name="row_spacing">6</property> + <child> + <object class="GtkLabel" id="username_label"> + <property name="xalign">1</property> + <property name="label" translatable="yes">_Username</property> + <property name="use_underline">True</property> + <property name="mnemonic_widget">username_entry</property> + <layout> + <property name="column">0</property> + <property name="row">0</property> + </layout> + </object> + </child> + <child> + <object class="GtkLabel" id="password_label"> + <property name="xalign">1</property> + <property name="label" translatable="yes">_Password</property> + <property name="use_underline">True</property> + <property name="mnemonic_widget">password_entry</property> + <layout> + <property name="column">0</property> + <property name="row">1</property> + </layout> + </object> + </child> + <child> + <object class="GtkEntry" id="password_entry"> + <property name="hexpand">True</property> + <property name="visibility">False</property> + <layout> + <property name="column">1</property> + <property name="row">1</property> + </layout> + </object> + </child> + <child> + <object class="GtkCheckButton" id="show_password_check"> + <property name="label" translatable="yes">Sho_w password</property> + <property name="hexpand">True</property> + <property name="use_underline">True</property> + <layout> + <property name="column">1</property> + <property name="row">2</property> + </layout> + </object> + </child> + <child> + <object class="GtkEntry" id="username_entry"> + <property name="hexpand">True</property> + <layout> + <property name="column">1</property> + <property name="row">0</property> + </layout> + </object> + </child> + </template> +</interface> diff --git a/panels/network/wireless-security/ws-sae.c b/panels/network/wireless-security/ws-sae.c new file mode 100644 index 0000000..c9e4e50 --- /dev/null +++ b/panels/network/wireless-security/ws-sae.c @@ -0,0 +1,225 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- + * + * Jonathan Kang <songchuan.kang@suse.com> + * + * 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. + * + * Copyright (c) 2020 SUSE LINUX GmbH, Nuernberg, Germany. + */ + +#include <ctype.h> +#include <glib/gi18n.h> + +#include "helpers.h" +#include "nma-ui-utils.h" +#include "ui-helpers.h" +#include "ws-sae.h" +#include "wireless-security.h" + +#define WPA_PMK_LEN 32 + +struct _WirelessSecuritySAE +{ + GtkGrid parent; + + GtkEntry *password_entry; + GtkLabel *password_label; + GtkCheckButton *show_password_check; + GtkComboBox *type_combo; + GtkLabel *type_label; +}; + +static void wireless_security_iface_init (WirelessSecurityInterface *); + +G_DEFINE_TYPE_WITH_CODE (WirelessSecuritySAE, ws_sae, GTK_TYPE_GRID, + G_IMPLEMENT_INTERFACE (wireless_security_get_type (), wireless_security_iface_init)); + +static void +show_toggled_cb (WirelessSecuritySAE *self) +{ + gboolean visible; + + visible = gtk_check_button_get_active (GTK_CHECK_BUTTON (self->show_password_check)); + gtk_entry_set_visibility (self->password_entry, visible); +} + +static gboolean +validate (WirelessSecurity *security, GError **error) +{ + WirelessSecuritySAE *self = WS_SAE (security); + NMSettingSecretFlags secret_flags; + const char *key; + + secret_flags = nma_utils_menu_to_secret_flags (GTK_WIDGET (self->password_entry)); + if (secret_flags & NM_SETTING_SECRET_FLAG_NOT_SAVED) + { + widget_unset_error (GTK_WIDGET (self->password_entry)); + return TRUE; + } + + key = gtk_editable_get_text (GTK_EDITABLE (self->password_entry)); + + if (key == NULL || key[0] == '\0') + { + widget_set_error (GTK_WIDGET (self->password_entry)); + g_set_error_literal (error, NMA_ERROR, NMA_ERROR_GENERIC, _("Wi-Fi password is missing.")); + return FALSE; + } + widget_unset_error (GTK_WIDGET (self->password_entry)); + + return TRUE; +} + +static void +add_to_size_group (WirelessSecurity *security, GtkSizeGroup *group) +{ + WirelessSecuritySAE *self = WS_SAE (security); + + gtk_size_group_add_widget (group, GTK_WIDGET (self->type_label)); + gtk_size_group_add_widget (group, GTK_WIDGET (self->password_label)); +} + +static void +fill_connection (WirelessSecurity *security, NMConnection *connection) +{ + WirelessSecuritySAE *self = WS_SAE (security); + const char *key; + NMSettingWireless *s_wireless; + NMSettingWirelessSecurity *s_wireless_sec; + NMSettingSecretFlags secret_flags; + const char *mode; + gboolean is_adhoc = FALSE; + + s_wireless = nm_connection_get_setting_wireless (connection); + g_assert (s_wireless); + + mode = nm_setting_wireless_get_mode (s_wireless); + if (mode && !g_strcmp0 (mode, "adhoc")) + is_adhoc = TRUE; + + /* Blow away the old security setting by adding a clear one */ + s_wireless_sec = (NMSettingWirelessSecurity *) nm_setting_wireless_security_new (); + nm_connection_add_setting (connection, (NMSetting *) s_wireless_sec); + + key = gtk_editable_get_text (GTK_EDITABLE (self->password_entry)); + g_object_set (s_wireless_sec, NM_SETTING_WIRELESS_SECURITY_PSK, key, NULL); + + /* Save PSK_FLAGS to the connection */ + secret_flags = nma_utils_menu_to_secret_flags (GTK_WIDGET (self->password_entry)); + nm_setting_set_secret_flags (NM_SETTING (s_wireless_sec), NM_SETTING_WIRELESS_SECURITY_PSK, + secret_flags, NULL); + + /* Update secret flags and popup when editing the connection */ + nma_utils_update_password_storage (GTK_WIDGET (self->password_entry), secret_flags, + NM_SETTING (s_wireless_sec), NM_SETTING_WIRELESS_SECURITY_PSK); + + wireless_security_clear_ciphers (connection); + if (is_adhoc) + { + /* Ad-Hoc settings as specified by the supplicant */ + g_object_set (s_wireless_sec, NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, "sae", NULL); + nm_setting_wireless_security_add_proto (s_wireless_sec, "rsn"); + nm_setting_wireless_security_add_pairwise (s_wireless_sec, "ccmp"); + nm_setting_wireless_security_add_group (s_wireless_sec, "ccmp"); + } + else + { + g_object_set (s_wireless_sec, NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, "sae", NULL); + + /* Just leave ciphers and protocol empty, the supplicant will + * figure that out magically based on the AP IEs and card capabilities. + */ + } +} + +static gboolean +adhoc_compatible (WirelessSecurity *security) +{ + return FALSE; +} + +static void +changed_cb (WirelessSecuritySAE *self) +{ + wireless_security_notify_changed ((WirelessSecurity *) self); +} + +void +ws_sae_init (WirelessSecuritySAE *self) +{ + gtk_widget_init_template (GTK_WIDGET (self)); +} + +void +ws_sae_class_init (WirelessSecuritySAEClass *klass) +{ + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/Settings/network/ws-sae.ui"); + + gtk_widget_class_bind_template_child (widget_class, WirelessSecuritySAE, password_entry); + gtk_widget_class_bind_template_child (widget_class, WirelessSecuritySAE, password_label); + gtk_widget_class_bind_template_child (widget_class, WirelessSecuritySAE, show_password_check); + gtk_widget_class_bind_template_child (widget_class, WirelessSecuritySAE, type_combo); + gtk_widget_class_bind_template_child (widget_class, WirelessSecuritySAE, type_label); +} + +static void +wireless_security_iface_init (WirelessSecurityInterface *iface) +{ + iface->validate = validate; + iface->add_to_size_group = add_to_size_group; + iface->fill_connection = fill_connection; + iface->adhoc_compatible = adhoc_compatible; +} + +WirelessSecuritySAE * +ws_sae_new (NMConnection *connection) +{ + WirelessSecuritySAE *self; + NMSetting *setting = NULL; + + self = g_object_new (ws_sae_get_type (), NULL); + + g_signal_connect_swapped (self->password_entry, "changed", G_CALLBACK (changed_cb), self); + gtk_editable_set_width_chars (GTK_EDITABLE (self->password_entry), 28); + + /* Create password-storage popup menu for password entry under entry's secondary icon */ + if (connection) + setting = (NMSetting *) nm_connection_get_setting_wireless_security (connection); + nma_utils_setup_password_storage (GTK_WIDGET (self->password_entry), + 0, setting, NM_SETTING_WIRELESS_SECURITY_PSK, + FALSE, FALSE); + + /* Fill secrets, if any */ + if (connection) + { + helper_fill_secret_entry (connection, + self->password_entry, + NM_TYPE_SETTING_WIRELESS_SECURITY, + (HelperSecretFunc) nm_setting_wireless_security_get_psk); + } + + g_signal_connect_swapped (self->show_password_check, "toggled", + G_CALLBACK (show_toggled_cb), self); + + /* Hide WPA/RSN for now since this can be autodetected by NM and the + * supplicant when connecting to the AP. + */ + gtk_widget_hide (GTK_WIDGET (self->type_combo)); + gtk_widget_hide (GTK_WIDGET (self->type_label)); + + return self; +} diff --git a/panels/network/wireless-security/ws-sae.h b/panels/network/wireless-security/ws-sae.h new file mode 100644 index 0000000..770f658 --- /dev/null +++ b/panels/network/wireless-security/ws-sae.h @@ -0,0 +1,33 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- + * + * Jonathan Kang <songchuan.kang@suse.com> + * + * 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. + * + * Copyright (c) 2020 SUSE LINUX GmbH, Nuernberg, Germany. + */ + +#pragma once + +#include <gtk/gtk.h> +#include <NetworkManager.h> + +G_BEGIN_DECLS + +G_DECLARE_FINAL_TYPE (WirelessSecuritySAE, ws_sae, WS, SAE, GtkGrid) + +WirelessSecuritySAE* ws_sae_new (NMConnection *connection); + +G_END_DECLS diff --git a/panels/network/wireless-security/ws-sae.ui b/panels/network/wireless-security/ws-sae.ui new file mode 100644 index 0000000..028cd9f --- /dev/null +++ b/panels/network/wireless-security/ws-sae.ui @@ -0,0 +1,63 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- Generated with glade 3.20.0 --> +<interface domain="nm-applet"> + <requires lib="gtk+" version="3.10"/> + <template class="WirelessSecuritySAE" parent="GtkGrid"> + <property name="column_spacing">6</property> + <property name="row_spacing">6</property> + <child> + <object class="GtkLabel" id="password_label"> + <property name="label" translatable="yes">_Password</property> + <property name="use_underline">True</property> + <property name="mnemonic_widget">password_entry</property> + <property name="xalign">1</property> + <layout> + <property name="column">0</property> + <property name="row">0</property> + </layout> + </object> + </child> + <child> + <object class="GtkEntry" id="password_entry"> + <property name="hexpand">True</property> + <property name="max_length">64</property> + <property name="visibility">False</property> + <layout> + <property name="column">1</property> + <property name="row">0</property> + </layout> + </object> + </child> + <child> + <object class="GtkLabel" id="type_label"> + <property name="label" translatable="yes">_Type</property> + <property name="use_underline">True</property> + <property name="mnemonic_widget">type_combo</property> + <property name="xalign">1</property> + <layout> + <property name="column">0</property> + <property name="row">2</property> + </layout> + </object> + </child> + <child> + <object class="GtkCheckButton" id="show_password_check"> + <property name="label" translatable="yes">Sho_w password</property> + <property name="hexpand">True</property> + <property name="use_underline">True</property> + <layout> + <property name="column">1</property> + <property name="row">1</property> + </layout> + </object> + </child> + <child> + <object class="GtkComboBox" id="type_combo"> + <layout> + <property name="column">1</property> + <property name="row">2</property> + </layout> + </object> + </child> + </template> +</interface> diff --git a/panels/network/wireless-security/ws-wep-key.c b/panels/network/wireless-security/ws-wep-key.c new file mode 100644 index 0000000..8b354d5 --- /dev/null +++ b/panels/network/wireless-security/ws-wep-key.c @@ -0,0 +1,369 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* NetworkManager Applet -- allow user control over networking + * + * Dan Williams <dcbw@redhat.com> + * + * 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. + * + * Copyright 2007 - 2014 Red Hat, Inc. + */ + +#include <glib/gi18n.h> + +#include "helpers.h" +#include "nma-ui-utils.h" +#include "ui-helpers.h" +#include "ws-wep-key.h" +#include "wireless-security.h" + +struct _WirelessSecurityWEPKey { + GtkGrid parent; + + GtkComboBox *auth_method_combo; + GtkLabel *auth_method_label; + GtkEntry *key_entry; + GtkComboBox *key_index_combo; + GtkLabel *key_index_label; + GtkLabel *key_label; + GtkCheckButton *show_key_check; + + NMWepKeyType type; + char keys[4][65]; + guint8 cur_index; +}; + +static void wireless_security_iface_init (WirelessSecurityInterface *); + +G_DEFINE_TYPE_WITH_CODE (WirelessSecurityWEPKey, ws_wep_key, GTK_TYPE_GRID, + G_IMPLEMENT_INTERFACE (wireless_security_get_type (), wireless_security_iface_init)); + +static void +show_toggled_cb (WirelessSecurityWEPKey *self) +{ + gboolean visible; + + visible = gtk_check_button_get_active (GTK_CHECK_BUTTON (self->show_key_check)); + gtk_entry_set_visibility (self->key_entry, visible); +} + +static void +key_index_combo_changed_cb (WirelessSecurityWEPKey *self) +{ + const char *key; + int key_index; + + /* Save WEP key for old key index */ + key = gtk_editable_get_text (GTK_EDITABLE (self->key_entry)); + if (key) + g_strlcpy (self->keys[self->cur_index], key, sizeof (self->keys[self->cur_index])); + else + memset (self->keys[self->cur_index], 0, sizeof (self->keys[self->cur_index])); + + key_index = gtk_combo_box_get_active (self->key_index_combo); + g_return_if_fail (key_index <= 3); + g_return_if_fail (key_index >= 0); + + /* Populate entry with key from new index */ + gtk_editable_set_text (GTK_EDITABLE (self->key_entry), self->keys[key_index]); + self->cur_index = key_index; + + wireless_security_notify_changed ((WirelessSecurity *) self); +} + +static void +ws_wep_key_dispose (GObject *object) +{ + WirelessSecurityWEPKey *self = WS_WEP_KEY (object); + int i; + + for (i = 0; i < 4; i++) + memset (self->keys[i], 0, sizeof (self->keys[i])); + + G_OBJECT_CLASS (ws_wep_key_parent_class)->dispose (object); +} + +static gboolean +validate (WirelessSecurity *security, GError **error) +{ + WirelessSecurityWEPKey *self = WS_WEP_KEY (security); + NMSettingSecretFlags secret_flags; + const char *key; + int i; + + secret_flags = nma_utils_menu_to_secret_flags (GTK_WIDGET (self->key_entry)); + if (secret_flags & NM_SETTING_SECRET_FLAG_NOT_SAVED) { + widget_unset_error (GTK_WIDGET (self->key_entry)); + return TRUE; + } + + key = gtk_editable_get_text (GTK_EDITABLE (self->key_entry)); + if (!key) { + widget_set_error (GTK_WIDGET (self->key_entry)); + g_set_error_literal (error, NMA_ERROR, NMA_ERROR_GENERIC, _("missing wep-key")); + return FALSE; + } + + if (self->type == NM_WEP_KEY_TYPE_KEY) { + if ((strlen (key) == 10) || (strlen (key) == 26)) { + for (i = 0; i < strlen (key); i++) { + if (!g_ascii_isxdigit (key[i])) { + widget_set_error (GTK_WIDGET (self->key_entry)); + g_set_error (error, NMA_ERROR, NMA_ERROR_GENERIC, _("invalid wep-key: key with a length of %zu must contain only hex-digits"), strlen (key)); + return FALSE; + } + } + } else if ((strlen (key) == 5) || (strlen (key) == 13)) { + for (i = 0; i < strlen (key); i++) { + if (!g_ascii_isprint (key[i])) { + widget_set_error (GTK_WIDGET (self->key_entry)); + g_set_error (error, NMA_ERROR, NMA_ERROR_GENERIC, _("invalid wep-key: key with a length of %zu must contain only ascii characters"), strlen (key)); + return FALSE; + } + } + } else { + widget_set_error (GTK_WIDGET (self->key_entry)); + g_set_error (error, NMA_ERROR, NMA_ERROR_GENERIC, _("invalid wep-key: wrong key length %zu. A key must be either of length 5/13 (ascii) or 10/26 (hex)"), strlen (key)); + return FALSE; + } + } else if (self->type == NM_WEP_KEY_TYPE_PASSPHRASE) { + if (!*key || (strlen (key) > 64)) { + widget_set_error (GTK_WIDGET (self->key_entry)); + if (!*key) + g_set_error_literal (error, NMA_ERROR, NMA_ERROR_GENERIC, _("invalid wep-key: passphrase must be non-empty")); + else + g_set_error_literal (error, NMA_ERROR, NMA_ERROR_GENERIC, _("invalid wep-key: passphrase must be shorter than 64 characters")); + return FALSE; + } + } + widget_unset_error (GTK_WIDGET (self->key_entry)); + + return TRUE; +} + +static void +add_to_size_group (WirelessSecurity *security, GtkSizeGroup *group) +{ + WirelessSecurityWEPKey *self = WS_WEP_KEY (security); + gtk_size_group_add_widget (group, GTK_WIDGET (self->auth_method_label)); + gtk_size_group_add_widget (group, GTK_WIDGET (self->key_label)); + gtk_size_group_add_widget (group, GTK_WIDGET (self->key_index_label)); +} + +static void +fill_connection (WirelessSecurity *security, NMConnection *connection) +{ + WirelessSecurityWEPKey *self = WS_WEP_KEY (security); + NMSettingWirelessSecurity *s_wsec; + NMSettingSecretFlags secret_flags; + gint auth_alg; + const char *key; + int i; + + auth_alg = gtk_combo_box_get_active (self->auth_method_combo); + + key = gtk_editable_get_text (GTK_EDITABLE (self->key_entry)); + g_strlcpy (self->keys[self->cur_index], key, sizeof (self->keys[self->cur_index])); + + /* Blow away the old security setting by adding a clear one */ + s_wsec = (NMSettingWirelessSecurity *) nm_setting_wireless_security_new (); + nm_connection_add_setting (connection, (NMSetting *) s_wsec); + + g_object_set (s_wsec, + NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, "none", + NM_SETTING_WIRELESS_SECURITY_WEP_TX_KEYIDX, self->cur_index, + NM_SETTING_WIRELESS_SECURITY_AUTH_ALG, (auth_alg == 1) ? "shared" : "open", + NM_SETTING_WIRELESS_SECURITY_WEP_KEY_TYPE, self->type, + NULL); + + for (i = 0; i < 4; i++) { + if (strlen (self->keys[i])) + nm_setting_wireless_security_set_wep_key (s_wsec, i, self->keys[i]); + } + + /* Save WEP_KEY_FLAGS to the connection */ + secret_flags = nma_utils_menu_to_secret_flags (GTK_WIDGET (self->key_entry)); + g_object_set (s_wsec, NM_SETTING_WIRELESS_SECURITY_WEP_KEY_FLAGS, secret_flags, NULL); + + /* Update secret flags and popup when editing the connection */ + nma_utils_update_password_storage (GTK_WIDGET (self->key_entry), secret_flags, + NM_SETTING (s_wsec), NM_SETTING_WIRELESS_SECURITY_WEP_KEY0); +} + +static void +wep_entry_filter_cb (WirelessSecurityWEPKey *self, + gchar *text, + gint length, + gint *position) +{ + if (self->type == NM_WEP_KEY_TYPE_KEY) { + int i, count = 0; + g_autofree gchar *result = g_new (gchar, length+1); + + for (i = 0; i < length; i++) { + if (g_ascii_isprint (text[i])) + result[count++] = text[i]; + } + result[count] = 0; + + if (count > 0) { + g_signal_handlers_block_by_func (self->key_entry, G_CALLBACK (wep_entry_filter_cb), self); + gtk_editable_insert_text (GTK_EDITABLE (self->key_entry), result, count, position); + g_signal_handlers_unblock_by_func (self->key_entry, G_CALLBACK (wep_entry_filter_cb), self); + } + g_signal_stop_emission_by_name (self->key_entry, "insert-text"); + } +} + +static void +update_secrets (WirelessSecurityWEPKey *self, NMConnection *connection) +{ + NMSettingWirelessSecurity *s_wsec; + const char *tmp; + int i; + + s_wsec = nm_connection_get_setting_wireless_security (connection); + for (i = 0; s_wsec && i < 4; i++) { + tmp = nm_setting_wireless_security_get_wep_key (s_wsec, i); + if (tmp) + g_strlcpy (self->keys[i], tmp, sizeof (self->keys[i])); + } + + if (strlen (self->keys[self->cur_index])) + gtk_editable_set_text (GTK_EDITABLE (self->key_entry), self->keys[self->cur_index]); +} + +static void +changed_cb (WirelessSecurityWEPKey *self) +{ + wireless_security_notify_changed ((WirelessSecurity *) self); +} + +void +ws_wep_key_init (WirelessSecurityWEPKey *self) +{ + gtk_widget_init_template (GTK_WIDGET (self)); +} + +void +ws_wep_key_class_init (WirelessSecurityWEPKeyClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + object_class->dispose = ws_wep_key_dispose; + + gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/Settings/network/ws-wep-key.ui"); + + gtk_widget_class_bind_template_child (widget_class, WirelessSecurityWEPKey, auth_method_combo); + gtk_widget_class_bind_template_child (widget_class, WirelessSecurityWEPKey, auth_method_label); + gtk_widget_class_bind_template_child (widget_class, WirelessSecurityWEPKey, key_entry); + gtk_widget_class_bind_template_child (widget_class, WirelessSecurityWEPKey, key_index_combo); + gtk_widget_class_bind_template_child (widget_class, WirelessSecurityWEPKey, key_index_label); + gtk_widget_class_bind_template_child (widget_class, WirelessSecurityWEPKey, key_label); + gtk_widget_class_bind_template_child (widget_class, WirelessSecurityWEPKey, show_key_check); +} + +static void +wireless_security_iface_init (WirelessSecurityInterface *iface) +{ + iface->validate = validate; + iface->add_to_size_group = add_to_size_group; + iface->fill_connection = fill_connection; +} + +WirelessSecurityWEPKey * +ws_wep_key_new (NMConnection *connection, + NMWepKeyType type) +{ + WirelessSecurityWEPKey *self; + NMSettingWirelessSecurity *s_wsec = NULL; + NMSetting *setting = NULL; + guint8 default_key_idx = 0; + gboolean is_adhoc = FALSE; + gboolean is_shared_key = FALSE; + + self = g_object_new (ws_wep_key_get_type (), NULL); + + self->type = type; + + gtk_editable_set_width_chars (GTK_EDITABLE (self->key_entry), 28); + + /* Create password-storage popup menu for password entry under entry's secondary icon */ + if (connection) + setting = (NMSetting *) nm_connection_get_setting_wireless_security (connection); + nma_utils_setup_password_storage (GTK_WIDGET (self->key_entry), 0, setting, NM_SETTING_WIRELESS_SECURITY_WEP_KEY0, + FALSE, FALSE); + + if (connection) { + NMSettingWireless *s_wireless; + const char *mode, *auth_alg; + + s_wireless = nm_connection_get_setting_wireless (connection); + mode = s_wireless ? nm_setting_wireless_get_mode (s_wireless) : NULL; + if (mode && !strcmp (mode, "adhoc")) + is_adhoc = TRUE; + + s_wsec = nm_connection_get_setting_wireless_security (connection); + if (s_wsec) { + auth_alg = nm_setting_wireless_security_get_auth_alg (s_wsec); + if (auth_alg && !strcmp (auth_alg, "shared")) + is_shared_key = TRUE; + } + } + + g_signal_connect_swapped (self->key_entry, "changed", G_CALLBACK (changed_cb), self); + g_signal_connect_swapped (self->key_entry, "insert-text", G_CALLBACK (wep_entry_filter_cb), self); + if (self->type == NM_WEP_KEY_TYPE_KEY) + gtk_entry_set_max_length (self->key_entry, 26); + else if (self->type == NM_WEP_KEY_TYPE_PASSPHRASE) + gtk_entry_set_max_length (self->key_entry, 64); + + if (connection && s_wsec) + default_key_idx = nm_setting_wireless_security_get_wep_tx_keyidx (s_wsec); + + gtk_combo_box_set_active (self->key_index_combo, default_key_idx); + self->cur_index = default_key_idx; + g_signal_connect_swapped (self->key_index_combo, "changed", G_CALLBACK (key_index_combo_changed_cb), self); + + /* Key index is useless with adhoc networks */ + if (is_adhoc) { + gtk_widget_hide (GTK_WIDGET (self->key_index_combo)); + gtk_widget_hide (GTK_WIDGET (self->key_index_label)); + } + + /* Fill the key entry with the key for that index */ + if (connection) + update_secrets (self, connection); + + g_signal_connect_swapped (self->show_key_check, "toggled", G_CALLBACK (show_toggled_cb), self); + + gtk_combo_box_set_active (self->auth_method_combo, is_shared_key ? 1 : 0); + + g_signal_connect_swapped (self->auth_method_combo, "changed", G_CALLBACK (changed_cb), self); + + /* Don't show auth method for adhoc (which always uses open-system) or + * when in "simple" mode. + */ + if (is_adhoc) { + /* Ad-Hoc connections can't use Shared Key auth */ + if (is_adhoc) + gtk_combo_box_set_active (self->auth_method_combo, 0); + gtk_widget_hide (GTK_WIDGET (self->auth_method_combo)); + gtk_widget_hide (GTK_WIDGET (self->auth_method_label)); + } + + return self; +} + diff --git a/panels/network/wireless-security/ws-wep-key.h b/panels/network/wireless-security/ws-wep-key.h new file mode 100644 index 0000000..dc85e8b --- /dev/null +++ b/panels/network/wireless-security/ws-wep-key.h @@ -0,0 +1,35 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* NetworkManager Applet -- allow user control over networking + * + * Dan Williams <dcbw@redhat.com> + * + * 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. + * + * Copyright 2007 - 2014 Red Hat, Inc. + */ + +#pragma once + +#include <gtk/gtk.h> +#include <NetworkManager.h> + +G_BEGIN_DECLS + +G_DECLARE_FINAL_TYPE (WirelessSecurityWEPKey, ws_wep_key, WS, WEP_KEY, GtkGrid) + +WirelessSecurityWEPKey *ws_wep_key_new (NMConnection *connection, + NMWepKeyType type); + +G_END_DECLS diff --git a/panels/network/wireless-security/ws-wep-key.ui b/panels/network/wireless-security/ws-wep-key.ui new file mode 100644 index 0000000..49f525e --- /dev/null +++ b/panels/network/wireless-security/ws-wep-key.ui @@ -0,0 +1,141 @@ +<?xml version="1.0" encoding="UTF-8"?> +<interface> + <requires lib="gtk+" version="3.4"/> + <object class="GtkListStore" id="key_index_model"> + <columns> + <!-- column-name gchararray --> + <column type="gchararray"/> + </columns> + <data> + <row> + <col id="0" translatable="yes">1 (Default)</col> + </row> + <row> + <col id="0">2</col> + </row> + <row> + <col id="0">3</col> + </row> + <row> + <col id="0">4</col> + </row> + </data> + </object> + <object class="GtkListStore" id="auth_method_model"> + <columns> + <!-- column-name gchararray --> + <column type="gchararray"/> + </columns> + <data> + <row> + <col id="0" translatable="yes">Open System</col> + </row> + <row> + <col id="0" translatable="yes">Shared Key</col> + </row> + </data> + </object> + <template class="WirelessSecurityWEPKey" parent="GtkGrid"> + <property name="column_spacing">6</property> + <property name="row_spacing">6</property> + <child> + <object class="GtkLabel" id="key_label"> + <property name="xalign">1</property> + <property name="label" translatable="yes">_Key</property> + <property name="use_underline">True</property> + <property name="mnemonic_widget">key_entry</property> + <layout> + <property name="column">0</property> + <property name="row">0</property> + </layout> + </object> + </child> + <child> + <object class="GtkEntry" id="key_entry"> + <property name="hexpand">True</property> + <property name="max_length">64</property> + <property name="visibility">False</property> + <layout> + <property name="column">1</property> + <property name="row">0</property> + </layout> + </object> + </child> + <child> + <object class="GtkLabel"> + <property name="xalign">0</property> + <layout> + <property name="column">0</property> + <property name="row">1</property> + </layout> + </object> + </child> + <child> + <object class="GtkCheckButton" id="show_key_check"> + <property name="label" translatable="yes">Sho_w key</property> + <property name="hexpand">True</property> + <property name="use_underline">True</property> + <layout> + <property name="column">1</property> + <property name="row">1</property> + </layout> + </object> + </child> + <child> + <object class="GtkLabel" id="auth_method_label"> + <property name="xalign">1</property> + <property name="label" translatable="yes">Au_thentication</property> + <property name="use_underline">True</property> + <property name="mnemonic_widget">auth_method_combo</property> + <layout> + <property name="column">0</property> + <property name="row">3</property> + </layout> + </object> + </child> + <child> + <object class="GtkComboBox" id="auth_method_combo"> + <property name="hexpand">True</property> + <property name="model">auth_method_model</property> + <layout> + <property name="column">1</property> + <property name="row">3</property> + </layout> + <child> + <object class="GtkCellRendererText"/> + <attributes> + <attribute name="text">0</attribute> + </attributes> + </child> + </object> + </child> + <child> + <object class="GtkLabel" id="key_index_label"> + <property name="xalign">1</property> + <property name="label" translatable="yes">WEP inde_x</property> + <property name="use_underline">True</property> + <property name="mnemonic_widget">key_index_combo</property> + <layout> + <property name="column">0</property> + <property name="row">2</property> + </layout> + </object> + </child> + <child> + <object class="GtkComboBox" id="key_index_combo"> + <property name="hexpand">True</property> + <property name="model">key_index_model</property> + <layout> + <property name="column">1</property> + <property name="row">2</property> + </layout> + <child> + <object class="GtkCellRendererText"/> + <attributes> + <attribute name="text">0</attribute> + </attributes> + </child> + </object> + </child> + </template> +</interface> diff --git a/panels/network/wireless-security/ws-wpa-eap.c b/panels/network/wireless-security/ws-wpa-eap.c new file mode 100644 index 0000000..d2b01b6 --- /dev/null +++ b/panels/network/wireless-security/ws-wpa-eap.c @@ -0,0 +1,313 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* NetworkManager Applet -- allow user control over networking + * + * Dan Williams <dcbw@redhat.com> + * + * 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. + * + * Copyright 2007 - 2014 Red Hat, Inc. + */ + +#include <glib/gi18n.h> + +#include "ws-wpa-eap.h" +#include "wireless-security.h" +#include "eap-method.h" +#include "eap-method-fast.h" +#include "eap-method-leap.h" +#include "eap-method-peap.h" +#include "eap-method-simple.h" +#include "eap-method-tls.h" +#include "eap-method-ttls.h" + +struct _WirelessSecurityWPAEAP { + GtkGrid parent; + + GtkComboBox *auth_combo; + GtkLabel *auth_label; + GtkListStore *auth_model; + GtkBox *method_box; + + EAPMethodSimple *em_md5; + EAPMethodTLS *em_tls; + EAPMethodLEAP *em_leap; + EAPMethodSimple *em_pwd; + EAPMethodFAST *em_fast; + EAPMethodTTLS *em_ttls; + EAPMethodPEAP *em_peap; +}; + +static void wireless_security_iface_init (WirelessSecurityInterface *); + +G_DEFINE_TYPE_WITH_CODE (WirelessSecurityWPAEAP, ws_wpa_eap, GTK_TYPE_GRID, + G_IMPLEMENT_INTERFACE (wireless_security_get_type (), wireless_security_iface_init)); + +#define AUTH_NAME_COLUMN 0 +#define AUTH_ID_COLUMN 1 + +static EAPMethod * +get_eap (WirelessSecurityWPAEAP *self) +{ + GtkTreeIter iter; + g_autofree gchar *id = NULL; + + if (!gtk_combo_box_get_active_iter (self->auth_combo, &iter)) + return NULL; + gtk_tree_model_get (GTK_TREE_MODEL (self->auth_model), &iter, AUTH_ID_COLUMN, &id, -1); + + if (strcmp (id, "md5") == 0) + return EAP_METHOD (self->em_md5); + if (strcmp (id, "tls") == 0) + return EAP_METHOD (self->em_tls); + if (strcmp (id, "leap") == 0) + return EAP_METHOD (self->em_leap); + if (strcmp (id, "pwd") == 0) + return EAP_METHOD (self->em_pwd); + if (strcmp (id, "fast") == 0) + return EAP_METHOD (self->em_fast); + if (strcmp (id, "ttls") == 0) + return EAP_METHOD (self->em_ttls); + if (strcmp (id, "peap") == 0) + return EAP_METHOD (self->em_peap); + + return NULL; +} + +static gboolean +validate (WirelessSecurity *security, GError **error) +{ + WirelessSecurityWPAEAP *self = WS_WPA_EAP (security); + return eap_method_validate (get_eap (self), error); +} + +static void +add_to_size_group (WirelessSecurity *security, GtkSizeGroup *group) +{ + WirelessSecurityWPAEAP *self = WS_WPA_EAP (security); + + gtk_size_group_add_widget (group, GTK_WIDGET (self->auth_label)); + eap_method_add_to_size_group (EAP_METHOD (self->em_md5), group); + eap_method_add_to_size_group (EAP_METHOD (self->em_tls), group); + eap_method_add_to_size_group (EAP_METHOD (self->em_leap), group); + eap_method_add_to_size_group (EAP_METHOD (self->em_pwd), group); + eap_method_add_to_size_group (EAP_METHOD (self->em_fast), group); + eap_method_add_to_size_group (EAP_METHOD (self->em_ttls), group); + eap_method_add_to_size_group (EAP_METHOD (self->em_peap), group); +} + +static void +ws_802_1x_fill_connection (WirelessSecurityWPAEAP *self, NMConnection *connection) +{ + NMSettingWirelessSecurity *s_wireless_sec; + NMSetting8021x *s_8021x; + NMSettingSecretFlags secret_flags = NM_SETTING_SECRET_FLAG_NONE; + EAPMethod *eap; + + /* Get the EAPMethod object */ + eap = get_eap (self); + + /* Get previous pasword flags, if any. Otherwise default to agent-owned secrets */ + s_8021x = nm_connection_get_setting_802_1x (connection); + if (s_8021x) + nm_setting_get_secret_flags (NM_SETTING (s_8021x), eap_method_get_password_flags_name (eap), &secret_flags, NULL); + else + secret_flags = NM_SETTING_SECRET_FLAG_AGENT_OWNED; + + /* Blow away the old wireless security setting by adding a clear one */ + s_wireless_sec = (NMSettingWirelessSecurity *) nm_setting_wireless_security_new (); + nm_connection_add_setting (connection, (NMSetting *) s_wireless_sec); + + /* Blow away the old 802.1x setting by adding a clear one */ + s_8021x = (NMSetting8021x *) nm_setting_802_1x_new (); + nm_connection_add_setting (connection, (NMSetting *) s_8021x); + + eap_method_fill_connection (eap, connection, secret_flags); +} + +static void +fill_connection (WirelessSecurity *security, NMConnection *connection) +{ + WirelessSecurityWPAEAP *self = WS_WPA_EAP (security); + NMSettingWirelessSecurity *s_wireless_sec; + + ws_802_1x_fill_connection (self, connection); + + s_wireless_sec = nm_connection_get_setting_wireless_security (connection); + g_assert (s_wireless_sec); + + g_object_set (s_wireless_sec, NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, "wpa-eap", NULL); +} + +static gboolean +adhoc_compatible (WirelessSecurity *security) +{ + return FALSE; +} + +static void +auth_combo_changed_cb (WirelessSecurityWPAEAP *self) +{ + EAPMethod *eap; + GtkWidget *eap_default_field; + GtkWidget *child; + + eap = get_eap (self); + + /* Remove the previous method and migrate username/password across */ + child = gtk_widget_get_first_child (GTK_WIDGET (self->method_box)); + if (child != NULL) { + EAPMethod *old_eap = EAP_METHOD (child); + eap_method_set_username (eap, eap_method_get_username (old_eap)); + eap_method_set_password (eap, eap_method_get_password (old_eap)); + eap_method_set_show_password (eap, eap_method_get_show_password (old_eap)); + gtk_box_remove (self->method_box, child); + } + + gtk_box_append (self->method_box, g_object_ref (GTK_WIDGET (eap))); + eap_default_field = eap_method_get_default_field (eap); + if (eap_default_field) + gtk_widget_grab_focus (eap_default_field); + + wireless_security_notify_changed (WIRELESS_SECURITY (self)); +} + +void +ws_wpa_eap_init (WirelessSecurityWPAEAP *self) +{ + gtk_widget_init_template (GTK_WIDGET (self)); +} + +void +ws_wpa_eap_class_init (WirelessSecurityWPAEAPClass *klass) +{ + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/Settings/network/ws-wpa-eap.ui"); + + gtk_widget_class_bind_template_child (widget_class, WirelessSecurityWPAEAP, auth_combo); + gtk_widget_class_bind_template_child (widget_class, WirelessSecurityWPAEAP, auth_label); + gtk_widget_class_bind_template_child (widget_class, WirelessSecurityWPAEAP, auth_model); + gtk_widget_class_bind_template_child (widget_class, WirelessSecurityWPAEAP, method_box); +} + +static void +wireless_security_iface_init (WirelessSecurityInterface *iface) +{ + iface->validate = validate; + iface->add_to_size_group = add_to_size_group; + iface->fill_connection = fill_connection; + iface->adhoc_compatible = adhoc_compatible; +} + +WirelessSecurityWPAEAP * +ws_wpa_eap_new (NMConnection *connection) +{ + WirelessSecurityWPAEAP *self; + const gchar *remove_method, *default_method = NULL; + gboolean wired = FALSE; + GtkTreeIter iter; + + self = g_object_new (ws_wpa_eap_get_type (), NULL); + + /* Grab the default EAP method out of the security object */ + if (connection) { + NMSettingConnection *s_con; + NMSetting8021x *s_8021x; + const char *ctype = NULL; + + s_con = nm_connection_get_setting_connection (connection); + if (s_con) + ctype = nm_setting_connection_get_connection_type (s_con); + if ((g_strcmp0 (ctype, NM_SETTING_WIRED_SETTING_NAME) == 0) + || nm_connection_get_setting_wired (connection)) + wired = TRUE; + + s_8021x = nm_connection_get_setting_802_1x (connection); + if (s_8021x && nm_setting_802_1x_get_num_eap_methods (s_8021x)) + default_method = nm_setting_802_1x_get_eap_method (s_8021x, 0); + } + if (wired) + remove_method = "leap"; + else + remove_method = "md5"; + if (default_method == NULL) { + if (wired) + default_method = "md5"; + else + default_method = "tls"; + } + + self->em_md5 = eap_method_simple_new (connection, "md5", FALSE, FALSE); + gtk_widget_show (GTK_WIDGET (self->em_md5)); + g_signal_connect_object (self->em_md5, "changed", G_CALLBACK (wireless_security_notify_changed), self, G_CONNECT_SWAPPED); + self->em_tls = eap_method_tls_new (connection); + gtk_widget_show (GTK_WIDGET (self->em_tls)); + g_signal_connect_object (self->em_tls, "changed", G_CALLBACK (wireless_security_notify_changed), self, G_CONNECT_SWAPPED); + self->em_leap = eap_method_leap_new (connection); + gtk_widget_show (GTK_WIDGET (self->em_leap)); + g_signal_connect_object (self->em_leap, "changed", G_CALLBACK (wireless_security_notify_changed), self, G_CONNECT_SWAPPED); + self->em_pwd = eap_method_simple_new (connection, "pwd", FALSE, FALSE); + gtk_widget_show (GTK_WIDGET (self->em_pwd)); + g_signal_connect_object (self->em_pwd, "changed", G_CALLBACK (wireless_security_notify_changed), self, G_CONNECT_SWAPPED); + self->em_fast = eap_method_fast_new (connection); + gtk_widget_show (GTK_WIDGET (self->em_fast)); + g_signal_connect_object (self->em_fast, "changed", G_CALLBACK (wireless_security_notify_changed), self, G_CONNECT_SWAPPED); + self->em_ttls = eap_method_ttls_new (connection); + gtk_widget_show (GTK_WIDGET (self->em_ttls)); + g_signal_connect_object (self->em_ttls, "changed", G_CALLBACK (wireless_security_notify_changed), self, G_CONNECT_SWAPPED); + self->em_peap = eap_method_peap_new (connection); + gtk_widget_show (GTK_WIDGET (self->em_peap)); + g_signal_connect_object (self->em_peap, "changed", G_CALLBACK (wireless_security_notify_changed), self, G_CONNECT_SWAPPED); + + if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (self->auth_model), &iter)) { + do { + g_autofree gchar *id = NULL; + gtk_tree_model_get (GTK_TREE_MODEL (self->auth_model), &iter, AUTH_ID_COLUMN, &id, -1); + if (strcmp (id, remove_method) == 0) { + gtk_list_store_remove (self->auth_model, &iter); + break; + } + } while (gtk_tree_model_iter_next (GTK_TREE_MODEL (self->auth_model), &iter)); + } + if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (self->auth_model), &iter)) { + do { + g_autofree gchar *id = NULL; + gtk_tree_model_get (GTK_TREE_MODEL (self->auth_model), &iter, AUTH_ID_COLUMN, &id, -1); + if (strcmp (id, default_method) == 0) + gtk_combo_box_set_active_iter (self->auth_combo, &iter); + } while (gtk_tree_model_iter_next (GTK_TREE_MODEL (self->auth_model), &iter)); + } + + if (connection) { + NMSetting8021x *setting; + + setting = nm_connection_get_setting_802_1x (connection); + if (setting) { + eap_method_set_username (get_eap (self), nm_setting_802_1x_get_identity (setting)); + eap_method_set_password (get_eap (self), nm_setting_802_1x_get_password (setting)); + } + } + + g_signal_connect_object (G_OBJECT (self->auth_combo), "changed", G_CALLBACK (auth_combo_changed_cb), self, G_CONNECT_SWAPPED); + auth_combo_changed_cb (self); + + return self; +} + +void +ws_wpa_eap_fill_connection (WirelessSecurityWPAEAP *self, NMConnection *connection) +{ + ws_802_1x_fill_connection (self, connection); +} diff --git a/panels/network/wireless-security/ws-wpa-eap.h b/panels/network/wireless-security/ws-wpa-eap.h new file mode 100644 index 0000000..da5aa7f --- /dev/null +++ b/panels/network/wireless-security/ws-wpa-eap.h @@ -0,0 +1,37 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* NetworkManager Applet -- allow user control over networking + * + * Dan Williams <dcbw@redhat.com> + * + * 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. + * + * Copyright 2007 - 2014 Red Hat, Inc. + */ + +#pragma once + +#include <gtk/gtk.h> +#include <NetworkManager.h> + +G_BEGIN_DECLS + +G_DECLARE_FINAL_TYPE (WirelessSecurityWPAEAP, ws_wpa_eap, WS, WPA_EAP, GtkGrid) + +WirelessSecurityWPAEAP *ws_wpa_eap_new (NMConnection *connection); + +void ws_wpa_eap_fill_connection (WirelessSecurityWPAEAP *sec, + NMConnection *connection); + +G_END_DECLS diff --git a/panels/network/wireless-security/ws-wpa-eap.ui b/panels/network/wireless-security/ws-wpa-eap.ui new file mode 100644 index 0000000..222eba7 --- /dev/null +++ b/panels/network/wireless-security/ws-wpa-eap.ui @@ -0,0 +1,93 @@ +<?xml version="1.0" encoding="UTF-8"?> +<interface> + <requires lib="gtk+" version="3.4"/> + <object class="GtkListStore" id="auth_model"> + <columns> + <!-- column-name label --> + <column type="gchararray"/> + <!-- column-name id --> + <column type="gchararray"/> + <!-- column-name visible --> + <column type="gboolean"/> + </columns> + <data> + <row> + <col id="0" translatable="yes">MD5</col> + <col id="1">md5</col> + <col id="2">True</col> + </row> + <row> + <col id="0" translatable="yes">TLS</col> + <col id="1">tls</col> + <col id="2">True</col> + </row> + <row> + <col id="0" translatable="yes">LEAP</col> + <col id="1">leap</col> + <col id="2">True</col> + </row> + <row> + <col id="0" translatable="yes">PWD</col> + <col id="1">pwd</col> + <col id="2">True</col> + </row> + <row> + <col id="0" translatable="yes">FAST</col> + <col id="1">fast</col> + <col id="2">True</col> + </row> + <row> + <col id="0" translatable="yes">Tunneled TLS</col> + <col id="1">ttls</col> + <col id="2">True</col> + </row> + <row> + <col id="0" translatable="yes">Protected EAP (PEAP)</col> + <col id="1">peap</col> + <col id="2">True</col> + </row> + </data> + </object> + <template class="WirelessSecurityWPAEAP" parent="GtkGrid"> + <property name="column_spacing">6</property> + <property name="row_spacing">6</property> + <child> + <object class="GtkLabel" id="auth_label"> + <property name="xalign">1</property> + <property name="label" translatable="yes">Au_thentication</property> + <property name="use_underline">True</property> + <property name="mnemonic_widget">auth_combo</property> + <layout> + <property name="column">0</property> + <property name="row">0</property> + </layout> + </object> + </child> + <child> + <object class="GtkComboBox" id="auth_combo"> + <property name="hexpand">True</property> + <property name="model">auth_model</property> + <layout> + <property name="column">1</property> + <property name="row">0</property> + </layout> + <child> + <object class="GtkCellRendererText"/> + <attributes> + <attribute name="text">0</attribute> + </attributes> + </child> + </object> + </child> + <child> + <object class="GtkBox" id="method_box"> + <property name="orientation">vertical</property> + <layout> + <property name="column">0</property> + <property name="column-span">2</property> + <property name="row">1</property> + </layout> + </object> + </child> + </template> +</interface> diff --git a/panels/network/wireless-security/ws-wpa-psk.c b/panels/network/wireless-security/ws-wpa-psk.c new file mode 100644 index 0000000..b911b36 --- /dev/null +++ b/panels/network/wireless-security/ws-wpa-psk.c @@ -0,0 +1,237 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* NetworkManager Applet -- allow user control over networking + * + * Dan Williams <dcbw@redhat.com> + * + * 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. + * + * Copyright 2007 - 2014 Red Hat, Inc. + */ + +#include <ctype.h> +#include <glib/gi18n.h> + +#include "helpers.h" +#include "nma-ui-utils.h" +#include "ui-helpers.h" +#include "ws-wpa-psk.h" +#include "wireless-security.h" + +#define WPA_PMK_LEN 32 + +struct _WirelessSecurityWPAPSK { + GtkGrid parent; + + GtkEntry *password_entry; + GtkLabel *password_label; + GtkCheckButton *show_password_check; + GtkComboBox *type_combo; + GtkLabel *type_label; +}; + +static void wireless_security_iface_init (WirelessSecurityInterface *); + +G_DEFINE_TYPE_WITH_CODE (WirelessSecurityWPAPSK, ws_wpa_psk, GTK_TYPE_GRID, + G_IMPLEMENT_INTERFACE (wireless_security_get_type (), wireless_security_iface_init)); + +static void +show_toggled_cb (WirelessSecurityWPAPSK *self) +{ + gboolean visible; + + visible = gtk_check_button_get_active (GTK_CHECK_BUTTON (self->show_password_check)); + gtk_entry_set_visibility (self->password_entry, visible); +} + +static gboolean +validate (WirelessSecurity *security, GError **error) +{ + WirelessSecurityWPAPSK *self = WS_WPA_PSK (security); + NMSettingSecretFlags secret_flags; + const char *key; + gsize len; + int i; + + secret_flags = nma_utils_menu_to_secret_flags (GTK_WIDGET (self->password_entry)); + if (secret_flags & NM_SETTING_SECRET_FLAG_NOT_SAVED) { + widget_unset_error (GTK_WIDGET (self->password_entry)); + return TRUE; + } + + key = gtk_editable_get_text (GTK_EDITABLE (self->password_entry)); + len = key ? strlen (key) : 0; + if ((len < 8) || (len > 64)) { + widget_set_error (GTK_WIDGET (self->password_entry)); + g_set_error (error, NMA_ERROR, NMA_ERROR_GENERIC, _("invalid wpa-psk: invalid key-length %zu. Must be [8,63] bytes or 64 hex digits"), len); + return FALSE; + } + + if (len == 64) { + /* Hex PSK */ + for (i = 0; i < len; i++) { + if (!isxdigit (key[i])) { + widget_set_error (GTK_WIDGET (self->password_entry)); + g_set_error_literal (error, NMA_ERROR, NMA_ERROR_GENERIC, _("invalid wpa-psk: cannot interpret key with 64 bytes as hex")); + return FALSE; + } + } + } + widget_unset_error (GTK_WIDGET (self->password_entry)); + + /* passphrase can be between 8 and 63 characters inclusive */ + + return TRUE; +} + +static void +add_to_size_group (WirelessSecurity *security, GtkSizeGroup *group) +{ + WirelessSecurityWPAPSK *self = WS_WPA_PSK (security); + gtk_size_group_add_widget (group, GTK_WIDGET (self->type_label)); + gtk_size_group_add_widget (group, GTK_WIDGET (self->password_label)); +} + +static void +fill_connection (WirelessSecurity *security, NMConnection *connection) +{ + WirelessSecurityWPAPSK *self = WS_WPA_PSK (security); + const char *key; + NMSettingWireless *s_wireless; + NMSettingWirelessSecurity *s_wireless_sec; + NMSettingSecretFlags secret_flags; + const char *mode; + gboolean is_adhoc = FALSE; + + s_wireless = nm_connection_get_setting_wireless (connection); + g_assert (s_wireless); + + mode = nm_setting_wireless_get_mode (s_wireless); + if (mode && !strcmp (mode, "adhoc")) + is_adhoc = TRUE; + + /* Blow away the old security setting by adding a clear one */ + s_wireless_sec = (NMSettingWirelessSecurity *) nm_setting_wireless_security_new (); + nm_connection_add_setting (connection, (NMSetting *) s_wireless_sec); + + key = gtk_editable_get_text (GTK_EDITABLE (self->password_entry)); + g_object_set (s_wireless_sec, NM_SETTING_WIRELESS_SECURITY_PSK, key, NULL); + + /* Save PSK_FLAGS to the connection */ + secret_flags = nma_utils_menu_to_secret_flags (GTK_WIDGET (self->password_entry)); + nm_setting_set_secret_flags (NM_SETTING (s_wireless_sec), NM_SETTING_WIRELESS_SECURITY_PSK, + secret_flags, NULL); + + /* Update secret flags and popup when editing the connection */ + nma_utils_update_password_storage (GTK_WIDGET (self->password_entry), secret_flags, + NM_SETTING (s_wireless_sec), NM_SETTING_WIRELESS_SECURITY_PSK); + + wireless_security_clear_ciphers (connection); + if (is_adhoc) { + /* Ad-Hoc settings as specified by the supplicant */ + g_object_set (s_wireless_sec, NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, "wpa-none", NULL); + nm_setting_wireless_security_add_proto (s_wireless_sec, "wpa"); + nm_setting_wireless_security_add_pairwise (s_wireless_sec, "none"); + + /* Ad-hoc can only have _one_ group cipher... default to TKIP to be more + * compatible for now. Maybe we'll support selecting CCMP later. + */ + nm_setting_wireless_security_add_group (s_wireless_sec, "tkip"); + } else { + g_object_set (s_wireless_sec, NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, "wpa-psk", NULL); + + /* Just leave ciphers and protocol empty, the supplicant will + * figure that out magically based on the AP IEs and card capabilities. + */ + } +} + +static gboolean +adhoc_compatible (WirelessSecurity *security) +{ + return FALSE; +} + +static void +changed_cb (WirelessSecurityWPAPSK *self) +{ + wireless_security_notify_changed ((WirelessSecurity *) self); +} + +void +ws_wpa_psk_init (WirelessSecurityWPAPSK *self) +{ + gtk_widget_init_template (GTK_WIDGET (self)); +} + +void +ws_wpa_psk_class_init (WirelessSecurityWPAPSKClass *klass) +{ + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/Settings/network/ws-wpa-psk.ui"); + + gtk_widget_class_bind_template_child (widget_class, WirelessSecurityWPAPSK, password_entry); + gtk_widget_class_bind_template_child (widget_class, WirelessSecurityWPAPSK, password_label); + gtk_widget_class_bind_template_child (widget_class, WirelessSecurityWPAPSK, show_password_check); + gtk_widget_class_bind_template_child (widget_class, WirelessSecurityWPAPSK, type_combo); + gtk_widget_class_bind_template_child (widget_class, WirelessSecurityWPAPSK, type_label); +} + +static void +wireless_security_iface_init (WirelessSecurityInterface *iface) +{ + iface->validate = validate; + iface->add_to_size_group = add_to_size_group; + iface->fill_connection = fill_connection; + iface->adhoc_compatible = adhoc_compatible; +} + +WirelessSecurityWPAPSK * +ws_wpa_psk_new (NMConnection *connection) +{ + WirelessSecurityWPAPSK *self; + NMSetting *setting = NULL; + + self = g_object_new (ws_wpa_psk_get_type (), NULL); + + g_signal_connect_swapped (self->password_entry, "changed", G_CALLBACK (changed_cb), self); + gtk_editable_set_width_chars (GTK_EDITABLE (self->password_entry), 28); + + /* Create password-storage popup menu for password entry under entry's secondary icon */ + if (connection) + setting = (NMSetting *) nm_connection_get_setting_wireless_security (connection); + nma_utils_setup_password_storage (GTK_WIDGET (self->password_entry), 0, setting, NM_SETTING_WIRELESS_SECURITY_PSK, + FALSE, FALSE); + + /* Fill secrets, if any */ + if (connection) { + helper_fill_secret_entry (connection, + self->password_entry, + NM_TYPE_SETTING_WIRELESS_SECURITY, + (HelperSecretFunc) nm_setting_wireless_security_get_psk); + } + + g_signal_connect_swapped (self->show_password_check, "toggled", G_CALLBACK (show_toggled_cb), self); + + /* Hide WPA/RSN for now since this can be autodetected by NM and the + * supplicant when connecting to the AP. + */ + + gtk_widget_hide (GTK_WIDGET (self->type_combo)); + gtk_widget_hide (GTK_WIDGET (self->type_label)); + + return self; +} + diff --git a/panels/network/wireless-security/ws-wpa-psk.h b/panels/network/wireless-security/ws-wpa-psk.h new file mode 100644 index 0000000..7ca8676 --- /dev/null +++ b/panels/network/wireless-security/ws-wpa-psk.h @@ -0,0 +1,34 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* NetworkManager Applet -- allow user control over networking + * + * Dan Williams <dcbw@redhat.com> + * + * 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. + * + * Copyright 2007 - 2014 Red Hat, Inc. + */ + +#pragma once + +#include <gtk/gtk.h> +#include <NetworkManager.h> + +G_BEGIN_DECLS + +G_DECLARE_FINAL_TYPE (WirelessSecurityWPAPSK, ws_wpa_psk, WS, WPA_PSK, GtkGrid) + +WirelessSecurityWPAPSK *ws_wpa_psk_new (NMConnection *connection); + +G_END_DECLS diff --git a/panels/network/wireless-security/ws-wpa-psk.ui b/panels/network/wireless-security/ws-wpa-psk.ui new file mode 100644 index 0000000..26c9415 --- /dev/null +++ b/panels/network/wireless-security/ws-wpa-psk.ui @@ -0,0 +1,72 @@ +<?xml version="1.0" encoding="UTF-8"?> +<interface> + <requires lib="gtk+" version="3.4"/> + <template class="WirelessSecurityWPAPSK" parent="GtkGrid"> + <property name="column_spacing">6</property> + <property name="row_spacing">6</property> + <child> + <object class="GtkLabel" id="password_label"> + <property name="xalign">1</property> + <property name="label" translatable="yes">_Password</property> + <property name="use_underline">True</property> + <property name="mnemonic_widget">password_entry</property> + <layout> + <property name="column">0</property> + <property name="row">0</property> + </layout> + </object> + </child> + <child> + <object class="GtkEntry" id="password_entry"> + <property name="hexpand">True</property> + <property name="max_length">64</property> + <property name="visibility">False</property> + <layout> + <property name="column">1</property> + <property name="row">0</property> + </layout> + </object> + </child> + <child> + <object class="GtkLabel" id="type_label"> + <property name="xalign">1</property> + <property name="label" translatable="yes">_Type</property> + <property name="use_underline">True</property> + <property name="mnemonic_widget">type_combo</property> + <layout> + <property name="column">0</property> + <property name="row">2</property> + </layout> + </object> + </child> + <child> + <object class="GtkLabel"> + <property name="xalign">0</property> + <layout> + <property name="column">0</property> + <property name="row">1</property> + </layout> + </object> + </child> + <child> + <object class="GtkCheckButton" id="show_password_check"> + <property name="label" translatable="yes">Sho_w password</property> + <property name="hexpand">True</property> + <property name="use_underline">True</property> + <layout> + <property name="column">1</property> + <property name="row">1</property> + </layout> + </object> + </child> + <child> + <object class="GtkComboBox" id="type_combo"> + <property name="hexpand">True</property> + <layout> + <property name="column">1</property> + <property name="row">2</property> + </layout> + </object> + </child> + </template> +</interface> |