pmaports/temp/gnome-control-center/0005-Add-new-connection-editor.patch
Pablo Correa Gómez b5c970e13e
temp/gnome-control-center: enable printer panel (MR 2795)
The bug that made the cups client in the panel crash is now
fixed and the panel is finally usable
2021-12-28 19:54:58 +01:00

5287 lines
184 KiB
Diff
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

From f43b638fbbb1715f4901451aed3fd5b53ea326da Mon Sep 17 00:00:00 2001
From: Mohammed Sadiq <sadiq@sadiqpk.org>
Date: Fri, 18 Oct 2019 18:36:59 +0530
Subject: [PATCH 5/8] Add new connection editor
---
.../connection-editor/cc-connection-editor.c | 618 ++++++++++
.../connection-editor/cc-connection-editor.h | 43 +
.../connection-editor/cc-connection-editor.ui | 311 +++++
.../connection-editor/ce-details-page.c | 838 +++++++++++++
.../connection-editor/ce-details-page.h | 53 +
.../connection-editor/ce-details-page.ui | 179 +++
panels/network/connection-editor/ce-ip-page.c | 1055 +++++++++++++++++
panels/network/connection-editor/ce-ip-page.h | 47 +
.../network/connection-editor/ce-ip-page.ui | 421 +++++++
.../connection-editor/ce-password-row.c | 194 +++
.../connection-editor/ce-password-row.h | 43 +
.../connection-editor/ce-password-row.ui | 65 +
.../connection-editor/ce-security-page.c | 925 +++++++++++++++
.../connection-editor/ce-security-page.h | 45 +
.../connection-editor/ce-security-page.ui | 273 +++++
.../connection-editor.gresource.xml | 5 +
panels/network/connection-editor/meson.build | 10 +
17 files changed, 5125 insertions(+)
create mode 100644 panels/network/connection-editor/cc-connection-editor.c
create mode 100644 panels/network/connection-editor/cc-connection-editor.h
create mode 100644 panels/network/connection-editor/cc-connection-editor.ui
create mode 100644 panels/network/connection-editor/ce-details-page.c
create mode 100644 panels/network/connection-editor/ce-details-page.h
create mode 100644 panels/network/connection-editor/ce-details-page.ui
create mode 100644 panels/network/connection-editor/ce-ip-page.c
create mode 100644 panels/network/connection-editor/ce-ip-page.h
create mode 100644 panels/network/connection-editor/ce-ip-page.ui
create mode 100644 panels/network/connection-editor/ce-password-row.c
create mode 100644 panels/network/connection-editor/ce-password-row.h
create mode 100644 panels/network/connection-editor/ce-password-row.ui
create mode 100644 panels/network/connection-editor/ce-security-page.c
create mode 100644 panels/network/connection-editor/ce-security-page.h
create mode 100644 panels/network/connection-editor/ce-security-page.ui
diff --git a/panels/network/connection-editor/cc-connection-editor.c b/panels/network/connection-editor/cc-connection-editor.c
new file mode 100644
index 000000000..07daf706f
--- /dev/null
+++ b/panels/network/connection-editor/cc-connection-editor.c
@@ -0,0 +1,618 @@
+/* -*- Mode: C; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* cc-network-editor.c
+ *
+ * Copyright 2021 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-connection-editor"
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+#include <glib/gi18n.h>
+#include <NetworkManager.h>
+
+#include "../cc-network-panel.h"
+#include "list-box-helper.h"
+#include "cc-list-row.h"
+#include "ce-ip-page.h"
+#include "ce-details-page.h"
+#include "ce-security-page.h"
+#include "cc-connection-editor.h"
+
+/**
+ * @short_description: Connection Editor dialog
+ * @include: "cc-connection-editor.h"
+ *
+ * A Dialog to modify connection settings
+ */
+
+typedef enum
+{
+ NET_TYPE_NONE,
+ NET_TYPE_WIFI,
+ NET_TYPE_ETHERNET,
+ NET_TYPE_VPN
+} NetType;
+
+struct _CcConnectionEditor
+{
+ GtkDialog parent_instance;
+
+ /* GtkAdjustment *vadjustment; */
+ /* gdouble vadjustment_value; */
+
+ GtkButton *back_button;
+ GtkButton *cancel_button;
+ GtkButton *apply_button;
+
+ /* GtkScrolledWindow *main_view; */
+ GtkStack *main_stack;
+ GtkBox *main_page;
+
+ GtkListBox *details_box;
+ CcListRow *signal_row;
+ CcListRow *frequency_row;
+ CcListRow *route_row;
+ CcListRow *last_used_row;
+ CcListRow *details_row;
+
+ GtkListBox *switch_box;
+ CcListRow *auto_connect_row;
+ CcListRow *allow_others_row;
+ CcListRow *metered_row;
+
+ GtkListBox *advanced_box;
+ CcListRow *ip4_row;
+ CcListRow *ip6_row;
+ CcListRow *security_row;
+
+ CeDetailsPage *details_page;
+ CeIpPage *ip4_page;
+ CeIpPage *ip6_page;
+ CeSecurityPage *security_page;
+
+ NMClient *nm_client;
+ NMDevice *device;
+ NMConnection *orig_connection;
+ NMConnection *connection;
+ NMAccessPoint *ap;
+ NetType network_type;
+
+ /* Set after current view changed, reset after scroll set */
+ /* gboolean view_changed; */
+};
+
+G_DEFINE_TYPE (CcConnectionEditor, cc_connection_editor, GTK_TYPE_DIALOG)
+
+static void
+cc_connection_editor_update_ap (CcConnectionEditor *self)
+{
+ CeDetailsPage *details_page;
+ gchar *str;
+
+ g_assert (CC_IS_CONNECTION_EDITOR (self));
+
+ details_page = self->details_page;
+ ce_details_page_set_ap (details_page, self->ap);
+
+ gtk_widget_set_visible (GTK_WIDGET (self->signal_row), self->ap != NULL);
+ gtk_widget_set_visible (GTK_WIDGET (self->frequency_row), self->ap != NULL);
+
+ cc_list_row_set_secondary_label (self->signal_row,
+ ce_details_page_get_ap_strength (details_page));
+
+ str = ce_details_page_get_ap_frequency (details_page);
+ cc_list_row_set_secondary_label (self->frequency_row, str);
+ g_free (str);
+
+ str = ce_details_page_get_security (details_page);
+ cc_list_row_set_secondary_label (self->security_row, str);
+ g_free (str);
+}
+
+static void
+editor_visible_child_changed_cb (CcConnectionEditor *self)
+{
+ GtkWidget *visible_child;
+ const gchar *title = "";
+
+ g_assert (CC_IS_CONNECTION_EDITOR (self));
+
+ visible_child = gtk_stack_get_visible_child (self->main_stack);
+
+ if (visible_child == GTK_WIDGET (self->main_page))
+ title = _("Network Settings");
+ else if (visible_child == GTK_WIDGET (self->details_page))
+ title = _("Network Details");
+ else if (visible_child == GTK_WIDGET (self->ip4_page))
+ title = _("Network IPv4 Settings");
+ else if (visible_child == GTK_WIDGET (self->ip6_page))
+ title = _("Network IPv6 Settings");
+ else if (visible_child == GTK_WIDGET (self->security_page))
+ title = _("Network Security Settings");
+ else
+ g_return_if_reached ();
+
+ gtk_window_set_title (GTK_WINDOW (self), title);
+
+ /* Update Apply button */
+ gtk_widget_set_sensitive (GTK_WIDGET (self->apply_button), FALSE);
+
+ if (visible_child == GTK_WIDGET (self->main_page) ||
+ visible_child == GTK_WIDGET (self->details_page))
+ gtk_widget_hide (GTK_WIDGET (self->apply_button));
+ else
+ gtk_widget_show (GTK_WIDGET (self->apply_button));
+}
+
+static void
+editor_cancel_clicked_cb (CcConnectionEditor *self)
+{
+ GtkWidget *visible_child;
+
+ g_assert (CC_IS_CONNECTION_EDITOR (self));
+
+ visible_child = gtk_stack_get_visible_child (self->main_stack);
+
+ if (visible_child == GTK_WIDGET (self->main_page))
+ gtk_dialog_response (GTK_DIALOG (self), GTK_RESPONSE_CANCEL);
+ else
+ gtk_stack_set_visible_child (self->main_stack, GTK_WIDGET (self->main_page));
+
+ gtk_widget_show (GTK_WIDGET (self->back_button));
+}
+
+static void
+editor_apply_clicked_cb (CcConnectionEditor *self)
+{
+ GtkWidget *visible_child;
+ g_autoptr(GVariant) settings = NULL;
+ g_autoptr(GError) error = NULL;
+
+ g_assert (CC_IS_CONNECTION_EDITOR (self));
+
+ visible_child = gtk_stack_get_visible_child (self->main_stack);
+
+ if (visible_child == GTK_WIDGET (self->ip4_page))
+ ce_ip_page_save_connection (self->ip4_page);
+ else if (visible_child == GTK_WIDGET (self->ip6_page))
+ ce_ip_page_save_connection (self->ip6_page);
+ else if (visible_child == GTK_WIDGET (self->security_page))
+ ce_security_page_save_connection (self->security_page);
+ else
+ g_return_if_reached ();
+
+ settings = nm_connection_to_dbus (self->connection, NM_CONNECTION_SERIALIZE_ALL);
+ nm_connection_replace_settings (self->orig_connection, settings, &error);
+
+ if (error)
+ g_warning ("Error replacing settings: %s", error->message);
+ else
+ nm_remote_connection_commit_changes (NM_REMOTE_CONNECTION (self->orig_connection),
+ TRUE,
+ NULL, /* cancellable */
+ &error);
+ if (error)
+ g_warning ("Error saving settings: %s", error->message);
+
+ gtk_stack_set_visible_child (self->main_stack, GTK_WIDGET (self->main_page));
+}
+
+static void
+editor_row_activated_cb (CcConnectionEditor *self,
+ CcListRow *row)
+{
+ g_assert (CC_IS_CONNECTION_EDITOR (self));
+ g_assert (GTK_IS_LIST_BOX_ROW (row));
+
+ gtk_widget_show (GTK_WIDGET (self->cancel_button));
+ /* self->vadjustment_value = gtk_adjustment_get_value (self->vadjustment); */
+ /* self->view_changed = TRUE; */
+
+ if (row == self->details_row)
+ {
+ ce_details_page_refresh (self->details_page);
+ gtk_widget_show (GTK_WIDGET (self->back_button));
+ gtk_stack_set_visible_child (self->main_stack, GTK_WIDGET (self->details_page));
+ }
+ else if (row == self->ip4_row)
+ {
+ ce_ip_page_refresh (self->ip4_page);
+ gtk_stack_set_visible_child (self->main_stack, GTK_WIDGET (self->ip4_page));
+ }
+ else if (row == self->ip6_row)
+ {
+ ce_ip_page_refresh (self->ip6_page);
+ gtk_stack_set_visible_child (self->main_stack, GTK_WIDGET (self->ip6_page));
+ }
+ else if (row == self->security_row)
+ {
+ ce_security_page_refresh (self->security_page);
+ gtk_stack_set_visible_child (self->main_stack, GTK_WIDGET (self->security_page));
+ }
+ else
+ g_return_if_reached ();
+}
+
+/* static void */
+/* ce_main_view_scroll_changed_cb (CcConnectionEditor *self) */
+/* { */
+/* GtkWidget *current_view; */
+
+/* g_assert (CC_IS_CONNECTION_EDITOR (self)); */
+
+/* current_view = gtk_stack_get_visible_child (self->main_stack); */
+
+/* if (self->view_changed && current_view == GTK_WIDGET (self->main_page)) */
+/* { */
+/* gtk_adjustment_set_value (self->vadjustment, self->vadjustment_value); */
+/* self->view_changed = FALSE; */
+/* } */
+/* else if (self->view_changed) */
+/* gtk_adjustment_set_value (self->vadjustment, 0.0); */
+/* } */
+
+static void
+editor_settings_changed_cb (CcConnectionEditor *self)
+{
+ GtkWidget *visible_child;
+ gboolean has_error = TRUE;
+
+ g_assert (CC_IS_CONNECTION_EDITOR (self));
+
+ visible_child = gtk_stack_get_visible_child (self->main_stack);
+
+ if (visible_child == GTK_WIDGET (self->main_page))
+ return;
+
+ if (visible_child == GTK_WIDGET (self->ip4_page))
+ has_error = ce_ip_page_has_error (self->ip4_page);
+ else if (visible_child == GTK_WIDGET (self->ip6_page))
+ has_error = ce_ip_page_has_error (self->ip6_page);
+ else if (visible_child == GTK_WIDGET (self->security_page))
+ has_error = ce_security_page_has_error (self->security_page);
+ else
+ g_warn_if_reached ();
+
+ gtk_widget_set_sensitive (GTK_WIDGET (self->apply_button), !has_error);
+}
+
+static void
+editor_row_changed_cb (CcConnectionEditor *self,
+ GParamSpec *psec,
+ CcListRow *row)
+{
+ NMSettingConnection *con_setting;
+ g_autoptr(GError) error = NULL;
+ gboolean active;
+
+ g_assert (CC_IS_CONNECTION_EDITOR (self));
+ g_assert (CC_IS_LIST_ROW (row));
+
+ /* We are saving to the original connection, not clone */
+ con_setting = nm_connection_get_setting_connection (self->orig_connection);
+ g_object_get (row, "active", &active, NULL);
+
+ if (row == self->allow_others_row)
+ {
+ g_object_set (con_setting, "permissions", NULL, NULL);
+
+ /* Ie, Dont allow others */
+ if (!active)
+ nm_setting_connection_add_permission (con_setting, "user", g_get_user_name (), NULL);
+ }
+ else if (row == self->auto_connect_row)
+ g_object_set (con_setting, "autoconnect", active, NULL);
+ else if (row == self->metered_row)
+ g_object_set (con_setting, "metered", active, NULL);
+ else
+ g_return_if_reached ();
+
+ /* XXX: Currently all networks are assumed to be already saved */
+ if (!NM_IS_REMOTE_CONNECTION (self->orig_connection))
+ g_return_if_reached ();
+
+ nm_remote_connection_commit_changes (NM_REMOTE_CONNECTION (self->orig_connection),
+ TRUE, NULL, &error);
+ if (error)
+ g_warning ("Error saving settings: %s", error->message);
+}
+
+static void
+editor_delete_clicked_cb (CcConnectionEditor *self)
+{
+ g_assert (CC_IS_CONNECTION_EDITOR (self));
+
+ /* XXX: Don't close on fail? */
+ nm_remote_connection_delete (NM_REMOTE_CONNECTION (self->orig_connection),
+ NULL, NULL);
+ gtk_dialog_response (GTK_DIALOG (self), GTK_RESPONSE_CLOSE);
+}
+
+static void
+cc_connection_editor_response (GtkDialog *dialog,
+ gint response_id)
+{
+ CcConnectionEditor *self = (CcConnectionEditor *)dialog;
+ g_autoptr(GVariant) settings = NULL;
+
+ if (response_id != GTK_RESPONSE_APPLY)
+ return;
+
+ ce_ip_page_save_connection (self->ip4_page);
+ settings = nm_connection_to_dbus (self->connection, NM_CONNECTION_SERIALIZE_ALL);
+ nm_connection_replace_settings (self->orig_connection, settings, NULL);
+
+ ce_ip_page_save_connection (self->ip6_page);
+ settings = nm_connection_to_dbus (self->connection, NM_CONNECTION_SERIALIZE_ALL);
+ nm_connection_replace_settings (self->orig_connection, settings, NULL);
+
+ ce_security_page_save_connection (self->security_page);
+ settings = nm_connection_to_dbus (self->connection, NM_CONNECTION_SERIALIZE_ALL);
+ nm_connection_replace_settings (self->orig_connection, settings, NULL);
+
+ nm_remote_connection_commit_changes_async (NM_REMOTE_CONNECTION (self->orig_connection),
+ TRUE,
+ NULL, /* cancellable */
+ NULL,
+ NULL);
+}
+
+static void
+cc_connection_editor_show (GtkWidget *widget)
+{
+ CcConnectionEditor *self = (CcConnectionEditor *)widget;
+/* NMSettingConnection *con_setting; */
+/* const gchar *type; */
+/* gboolean others_allowed, auto_connect; */
+
+ /* Reset page scroll */
+ /* self->vadjustment_value = 0.0; */
+ /* gtk_adjustment_set_value (self->vadjustment, 0.0); */
+/* con_setting = nm_connection_get_setting_connection (self->connection); */
+
+/* auto_connect = nm_setting_connection_get_autoconnect (con_setting); */
+/* g_object_set (self->auto_connect_row, "active", auto_connect, NULL); */
+
+/* /\* If no users are added, all users are allowed *\/ */
+/* others_allowed = nm_setting_connection_get_num_permissions (con_setting) == 0; */
+/* g_object_set (self->allow_others_row, "active", others_allowed, NULL); */
+
+/* /\* 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 (con_setting); */
+/* if (type && !g_str_equal (type, NM_SETTING_VPN_SETTING_NAME)) */
+/* { */
+/* NMMetered metered; */
+
+/* metered = nm_setting_connection_get_metered (con_setting); */
+
+/* if (metered == NM_METERED_YES || metered == NM_METERED_GUESS_YES) */
+/* g_object_set (self->metered_row, "active", TRUE, NULL); */
+/* } */
+/* else */
+/* gtk_widget_hide (self->metered_row); */
+
+ GTK_WIDGET_CLASS (cc_connection_editor_parent_class)->show (widget);
+}
+
+static void
+cc_connection_editor_finalize (GObject *object)
+{
+ CcConnectionEditor *self = (CcConnectionEditor *)object;
+
+ g_clear_object (&self->device);
+ g_clear_object (&self->connection);
+ g_clear_object (&self->orig_connection);
+ g_object_unref (self->nm_client);
+
+ G_OBJECT_CLASS (cc_connection_editor_parent_class)->finalize (object);
+}
+
+static void
+cc_connection_editor_class_init (CcConnectionEditorClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+ object_class->finalize = cc_connection_editor_finalize;
+
+ widget_class->show = cc_connection_editor_show;
+
+ gtk_widget_class_set_template_from_resource (widget_class,
+ "/org/gnome/control-center/"
+ "network/cc-connection-editor.ui");
+
+ gtk_widget_class_bind_template_child (widget_class, CcConnectionEditor, back_button);
+ gtk_widget_class_bind_template_child (widget_class, CcConnectionEditor, cancel_button);
+ gtk_widget_class_bind_template_child (widget_class, CcConnectionEditor, apply_button);
+
+ /* gtk_widget_class_bind_template_child (widget_class, CcConnectionEditor, main_view); */
+ gtk_widget_class_bind_template_child (widget_class, CcConnectionEditor, main_stack);
+ gtk_widget_class_bind_template_child (widget_class, CcConnectionEditor, main_page);
+
+ gtk_widget_class_bind_template_child (widget_class, CcConnectionEditor, details_box);
+ gtk_widget_class_bind_template_child (widget_class, CcConnectionEditor, signal_row);
+ gtk_widget_class_bind_template_child (widget_class, CcConnectionEditor, frequency_row);
+ gtk_widget_class_bind_template_child (widget_class, CcConnectionEditor, route_row);
+ gtk_widget_class_bind_template_child (widget_class, CcConnectionEditor, last_used_row);
+ gtk_widget_class_bind_template_child (widget_class, CcConnectionEditor, details_row);
+
+ gtk_widget_class_bind_template_child (widget_class, CcConnectionEditor, switch_box);
+ gtk_widget_class_bind_template_child (widget_class, CcConnectionEditor, auto_connect_row);
+ gtk_widget_class_bind_template_child (widget_class, CcConnectionEditor, allow_others_row);
+ gtk_widget_class_bind_template_child (widget_class, CcConnectionEditor, metered_row);
+
+ gtk_widget_class_bind_template_child (widget_class, CcConnectionEditor, advanced_box);
+ gtk_widget_class_bind_template_child (widget_class, CcConnectionEditor, ip4_row);
+ gtk_widget_class_bind_template_child (widget_class, CcConnectionEditor, ip6_row);
+ gtk_widget_class_bind_template_child (widget_class, CcConnectionEditor, security_row);
+
+ gtk_widget_class_bind_template_child (widget_class, CcConnectionEditor, details_page);
+ gtk_widget_class_bind_template_child (widget_class, CcConnectionEditor, ip4_page);
+ gtk_widget_class_bind_template_child (widget_class, CcConnectionEditor, ip6_page);
+ gtk_widget_class_bind_template_child (widget_class, CcConnectionEditor, security_page);
+
+ gtk_widget_class_bind_template_callback (widget_class, editor_visible_child_changed_cb);
+ gtk_widget_class_bind_template_callback (widget_class, editor_cancel_clicked_cb);
+ gtk_widget_class_bind_template_callback (widget_class, editor_apply_clicked_cb);
+ gtk_widget_class_bind_template_callback (widget_class, editor_row_activated_cb);
+ gtk_widget_class_bind_template_callback (widget_class, editor_row_changed_cb);
+ /* gtk_widget_class_bind_template_callback (widget_class, ce_main_view_scroll_changed_cb); */
+ gtk_widget_class_bind_template_callback (widget_class, editor_settings_changed_cb);
+ gtk_widget_class_bind_template_callback (widget_class, editor_delete_clicked_cb);
+
+ g_type_ensure (CC_TYPE_LIST_ROW);
+ g_type_ensure (CE_TYPE_IP_PAGE);
+ g_type_ensure (CC_TYPE_NETWORK_PANEL);
+}
+
+static void
+cc_connection_editor_init (CcConnectionEditor *self)
+{
+ gtk_widget_init_template (GTK_WIDGET (self));
+ editor_visible_child_changed_cb (self);
+ ce_ip_page_set_version (self->ip4_page, AF_INET);
+ ce_ip_page_set_version (self->ip6_page, AF_INET6);
+
+ /* gtk_list_box_set_header_func (self->details_box, */
+ /* cc_list_box_update_header_func, */
+ /* NULL, NULL); */
+
+ /* gtk_list_box_set_header_func (self->switch_box, */
+ /* cc_list_box_update_header_func, */
+ /* NULL, NULL); */
+
+ /* gtk_list_box_set_header_func (self->advanced_box, */
+ /* cc_list_box_update_header_func, */
+ /* NULL, NULL); */
+
+ /* self->vadjustment = gtk_scrolled_window_get_vadjustment (self->main_view); */
+ /* g_signal_connect_swapped (self->vadjustment, "notify::upper", */
+ /* G_CALLBACK (ce_main_view_scroll_changed_cb), self); */
+}
+
+GtkWidget *
+cc_connection_editor_new (GtkWindow *parent_window,
+ NMClient *nm_client)
+{
+ CcConnectionEditor *self;
+
+ g_return_val_if_fail (GTK_IS_WINDOW (parent_window), NULL);
+ g_return_val_if_fail (NM_IS_CLIENT (nm_client), NULL);
+
+ self = g_object_new (CC_TYPE_CONNECTION_EDITOR,
+ "transient-for", parent_window,
+ "use-header-bar", TRUE,
+ NULL);
+ ce_ip_page_set_parent_window (self->ip4_page, GTK_WINDOW (self));
+ ce_ip_page_set_parent_window (self->ip6_page, GTK_WINDOW (self));
+ ce_details_page_set_parent_window (self->details_page, GTK_WINDOW (self));
+
+ self->nm_client = g_object_ref (nm_client);
+ ce_details_page_set_nm_client (self->details_page, nm_client);
+ ce_security_page_set_nm_client (self->security_page, nm_client);
+
+ return GTK_WIDGET (self);
+}
+
+void
+cc_connection_editor_set_connection (CcConnectionEditor *self,
+ NMConnection *connection,
+ NMDevice *device)
+{
+ NMSettingConnection *con_setting;
+ const gchar *type;
+ gchar *str;
+ gboolean others_allowed, is_active;
+
+ g_return_if_fail (CC_IS_CONNECTION_EDITOR (self));
+ g_return_if_fail (NM_IS_CONNECTION (connection));
+ g_return_if_fail (NM_IS_DEVICE (device));
+
+ g_set_object (&self->orig_connection, connection);
+ g_set_object (&self->device, device);
+
+ g_clear_object (&self->connection);
+ self->connection = nm_simple_connection_new_clone (self->orig_connection);
+ con_setting = nm_connection_get_setting_connection (self->connection);
+
+ g_object_bind_property (con_setting, "autoconnect",
+ self->auto_connect_row, "active",
+ G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL);
+
+ /* If no users are added, all users are allowed */
+ others_allowed = nm_setting_connection_get_num_permissions (con_setting) == 0;
+ g_object_set (self->allow_others_row, "active", others_allowed, NULL);
+
+ /* 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 (con_setting);
+ if (type && !g_str_equal (type, NM_SETTING_VPN_SETTING_NAME))
+ {
+ NMMetered metered;
+
+ metered = nm_setting_connection_get_metered (con_setting);
+
+ if (metered == NM_METERED_YES || metered == NM_METERED_GUESS_YES)
+ g_object_set (self->metered_row, "active", TRUE, NULL);
+ else
+ g_object_set (self->metered_row, "active", FALSE, NULL);
+ }
+ else
+ gtk_widget_hide (GTK_WIDGET (self->metered_row));
+
+ ce_security_page_set_connection (self->security_page, self->orig_connection, self->connection, device);
+ ce_details_page_set_connection (self->details_page, self->orig_connection, self->connection, device);
+ ce_ip_page_set_connection (self->ip4_page, self->connection, device);
+ ce_ip_page_set_connection (self->ip6_page, self->connection, device);
+
+ is_active = ce_deails_page_has_active_connection (self->details_page, TRUE);
+ gtk_widget_set_visible (GTK_WIDGET (self->route_row), is_active);
+ gtk_widget_set_visible (GTK_WIDGET (self->last_used_row), !is_active);
+
+ if (is_active)
+ {
+ const gchar *gateway;
+
+ gateway = ce_details_page_get_gateway (self->details_page);
+ cc_list_row_set_secondary_label (self->route_row, gateway);
+ }
+ else
+ {
+ str = ce_details_page_get_last_used (self->details_page);
+ cc_list_row_set_secondary_label (self->last_used_row, str);
+ g_free (str);
+ }
+}
+
+void
+cc_connection_editor_set_ap (CcConnectionEditor *self,
+ NMAccessPoint *ap)
+{
+ g_return_if_fail (CC_IS_CONNECTION_EDITOR (self));
+ g_return_if_fail (!ap || NM_IS_ACCESS_POINT (ap));
+
+ if (g_set_object (&self->ap, ap))
+ cc_connection_editor_update_ap (self);
+}
diff --git a/panels/network/connection-editor/cc-connection-editor.h b/panels/network/connection-editor/cc-connection-editor.h
new file mode 100644
index 000000000..4d710bee2
--- /dev/null
+++ b/panels/network/connection-editor/cc-connection-editor.h
@@ -0,0 +1,43 @@
+/* -*- Mode: C; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* cc-network-editor.h
+ *
+ * Copyright 2021 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 <handy.h>
+#include <shell/cc-panel.h>
+
+G_BEGIN_DECLS
+
+#define CC_TYPE_CONNECTION_EDITOR (cc_connection_editor_get_type())
+G_DECLARE_FINAL_TYPE (CcConnectionEditor, cc_connection_editor, CC, CONNECTION_EDITOR, GtkDialog)
+
+GtkWidget *cc_connection_editor_new (GtkWindow *parent_window,
+ NMClient *nm_client);
+void cc_connection_editor_set_connection (CcConnectionEditor *self,
+ NMConnection *connection,
+ NMDevice *device);
+void cc_connection_editor_set_ap (CcConnectionEditor *self,
+ NMAccessPoint *ap);
+
+G_END_DECLS
diff --git a/panels/network/connection-editor/cc-connection-editor.ui b/panels/network/connection-editor/cc-connection-editor.ui
new file mode 100644
index 000000000..065070937
--- /dev/null
+++ b/panels/network/connection-editor/cc-connection-editor.ui
@@ -0,0 +1,311 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <template class="CcConnectionEditor" parent="GtkDialog">
+ <signal name="delete-event" handler="gtk_widget_hide_on_delete"/>
+ <child type="titlebar">
+ <object class="GtkHeaderBar">
+ <property name="visible">1</property>
+ <property name="width-request">360</property>
+
+ <!-- Back button -->
+ <child>
+ <object class="GtkButton" id="back_button">
+ <property name="visible">1</property>
+ <signal name="clicked" handler="editor_cancel_clicked_cb" swapped="yes"/>
+ <child internal-child="accessible">
+ <object class="AtkObject">
+ <property name="accessible-name" translatable="yes">Back</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkImage">
+ <property name="visible">1</property>
+ <property name="icon-name">go-previous-symbolic</property>
+ </object>
+ </child>
+ </object>
+ </child>
+
+ <!-- Cancel button -->
+ <child>
+ <object class="GtkButton" id="cancel_button">
+ <property name="visible" bind-source="back_button" bind-property="visible" bind-flags="invert-boolean|bidirectional" />
+ <property name="use-underline">1</property>
+ <property name="label" translatable="yes">_Cancel</property>
+ <signal name="clicked" handler="editor_cancel_clicked_cb" swapped="yes"/>
+ </object>
+ </child>
+
+ <!-- Apply button -->
+ <child>
+ <object class="GtkButton" id="apply_button">
+ <property name="visible">0</property>
+ <property name="sensitive">0</property>
+ <property name="can-default">1</property>
+ <property name="use-underline">1</property>
+ <property name="label" translatable="yes">_Apply</property>
+ <signal name="clicked" handler="editor_apply_clicked_cb" swapped="yes"/>
+ <style>
+ <class name="default" />
+ </style>
+ </object>
+ <packing>
+ <property name="pack-type">end</property>
+ </packing>
+ </child>
+
+ </object>
+ </child>
+
+ <child internal-child="vbox">
+ <object class="GtkBox">
+ <property name="border-width">0</property>
+
+ <child>
+ <object class="GtkStack" id="main_stack">
+ <property name="visible">1</property>
+ <property name="transition-type">slide-left-right</property>
+ <property name="homogeneous">0</property>
+ <signal name="notify::visible-child" handler="editor_visible_child_changed_cb" swapped="yes" />
+
+ <child>
+ <object class="GtkScrolledWindow" id="main_page">
+ <property name="visible">1</property>
+ <property name="vexpand">1</property>
+ <property name="hscrollbar-policy">never</property>
+ <property name="min-content-height">400</property>
+
+ <child>
+ <object class="HdyClamp">
+ <property name="visible">1</property>
+ <property name="margin-top">32</property>
+ <property name="margin-bottom">32</property>
+ <child>
+ <object class="GtkBox">
+ <property name="margin-start">12</property>
+ <property name="margin-end">12</property>
+ <property name="visible">1</property>
+ <property name="orientation">vertical</property>
+
+ <!-- Details -->
+ <child>
+ <object class="GtkListBox" id="details_box">
+ <property name="visible">1</property>
+ <property name="valign">start</property>
+ <property name="margin-bottom">32</property>
+ <property name="selection-mode">none</property>
+ <signal name="row-activated" handler="editor_row_activated_cb" swapped="yes" />
+ <style>
+ <class name="content" />
+ </style>
+
+ <!-- Signal Strength row -->
+ <child>
+ <object class="CcListRow" id="signal_row">
+ <property name="visible">0</property>
+ <property name="activatable">0</property>
+ <property name="title" translatable="yes">Signal Strength</property>
+ </object>
+ </child>
+
+ <!-- Frequency row -->
+ <child>
+ <object class="CcListRow" id="frequency_row">
+ <property name="visible">0</property>
+ <property name="activatable">0</property>
+ <property name="title" translatable="yes">Frequency</property>
+ </object>
+ </child>
+
+ <!-- Default Route row -->
+ <child>
+ <object class="CcListRow" id="route_row">
+ <property name="visible">1</property>
+ <property name="activatable">0</property>
+ <property name="title" translatable="yes">Default Route</property>
+ </object>
+ </child>
+
+ <!-- Last Used row -->
+ <child>
+ <object class="CcListRow" id="last_used_row">
+ <property name="visible">0</property>
+ <property name="activatable">0</property>
+ <property name="title" translatable="yes">Last Used</property>
+ </object>
+ </child>
+
+ <!-- Details row -->
+ <child>
+ <object class="CcListRow" id="details_row">
+ <property name="visible">1</property>
+ <property name="title" translatable="yes">Details</property>
+ <property name="icon-name">go-next-symbolic</property>
+ </object>
+ </child>
+
+ </object>
+ </child>
+
+ <!-- Misc. Switches -->
+ <child>
+ <object class="GtkListBox" id="switch_box">
+ <property name="visible">1</property>
+ <property name="valign">start</property>
+ <property name="margin-bottom">32</property>
+ <property name="selection-mode">none</property>
+ <style>
+ <class name="content" />
+ </style>
+
+ <!-- Connect Automatically row -->
+ <child>
+ <object class="CcListRow" id="auto_connect_row">
+ <property name="visible">1</property>
+ <property name="show-switch">1</property>
+ <property name="title" translatable="yes">Connect Automatically</property>
+ <signal name="notify::active" handler="editor_row_changed_cb" swapped="yes" />
+ </object>
+ </child>
+
+ <!-- Allow Others row -->
+ <child>
+ <object class="CcListRow" id="allow_others_row">
+ <property name="visible">1</property>
+ <property name="show-switch">1</property>
+ <property name="title" translatable="yes">Available to other users</property>
+ <signal name="notify::active" handler="editor_row_changed_cb" swapped="yes" />
+ </object>
+ </child>
+
+ <!-- Metered connection row -->
+ <child>
+ <object class="CcListRow" id="metered_row">
+ <property name="visible">1</property>
+ <property name="show-switch">1</property>
+ <property name="title" translatable="yes">Metered connection</property>
+ <property name="subtitle" translatable="yes">Limit background data</property>
+ <signal name="notify::active" handler="editor_row_changed_cb" swapped="yes" />
+ </object>
+ </child>
+
+ </object>
+ </child>
+
+ <!-- Advanced settings -->
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">1</property>
+ <property name="margin-bottom">12</property>
+ <property name="halign">start</property>
+ <property name="label" translatable="yes">Advanced Settings</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ </child>
+
+ <child>
+ <object class="GtkListBox" id="advanced_box">
+ <property name="visible">1</property>
+ <property name="valign">start</property>
+ <property name="margin-bottom">32</property>
+ <property name="selection-mode">none</property>
+ <signal name="row-activated" handler="editor_row_activated_cb" swapped="yes" />
+ <style>
+ <class name="content" />
+ </style>
+
+ <!-- IPv4 row -->
+ <child>
+ <object class="CcListRow" id="ip4_row">
+ <property name="visible">1</property>
+ <property name="title" translatable="yes">IPv4</property>
+ <property name="icon-name">go-next-symbolic</property>
+ </object>
+ </child>
+
+ <!-- IPv6 row -->
+ <child>
+ <object class="CcListRow" id="ip6_row">
+ <property name="visible">1</property>
+ <property name="title" translatable="yes">IPv6</property>
+ <property name="icon-name">go-next-symbolic</property>
+ </object>
+ </child>
+
+ <!-- Security row -->
+ <child>
+ <object class="CcListRow" id="security_row">
+ <property name="visible">1</property>
+ <property name="title" translatable="yes">Security</property>
+ <property name="icon-name">go-next-symbolic</property>
+ </object>
+ </child>
+
+ </object>
+ </child>
+
+ <child>
+ <object class="GtkButton" id="delete_button">
+ <property name="visible">1</property>
+ <property name="can-default">1</property>
+ <property name="use-underline">1</property>
+ <property name="label" translatable="yes">_Forget this Network</property>
+ <signal name="clicked" handler="editor_delete_clicked_cb" swapped="yes" />
+ <style>
+ <class name="destructive-action" />
+ </style>
+ </object>
+ </child>
+
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="name">main-view</property>
+ </packing>
+ </child>
+
+ <!-- Details Page -->
+ <child>
+ <object class="CeDetailsPage" id="details_page">
+ <property name="visible">1</property>
+ </object>
+ </child>
+
+ <!-- IPv4 Page -->
+ <child>
+ <object class="CeIpPage" id="ip4_page">
+ <property name="visible">1</property>
+ <signal name="changed" handler="editor_settings_changed_cb" swapped="yes" />
+ </object>
+ </child>
+
+ <!-- IPv6 Page -->
+ <child>
+ <object class="CeIpPage" id="ip6_page">
+ <property name="visible">1</property>
+ <signal name="changed" handler="editor_settings_changed_cb" swapped="yes" />
+ </object>
+ </child>
+
+ <!-- Security Page -->
+ <child>
+ <object class="CeSecurityPage" id="security_page">
+ <property name="visible">1</property>
+ <property name="margin-start">12</property>
+ <property name="margin-end">12</property>
+ <signal name="changed" handler="editor_settings_changed_cb" swapped="yes" />
+ </object>
+ </child>
+
+ </object>
+ </child>
+
+ </object>
+ </child>
+ </template>
+</interface>
diff --git a/panels/network/connection-editor/ce-details-page.c b/panels/network/connection-editor/ce-details-page.c
new file mode 100644
index 000000000..4402af97c
--- /dev/null
+++ b/panels/network/connection-editor/ce-details-page.c
@@ -0,0 +1,838 @@
+/* -*- Mode: C; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* ce-wifi-page.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 "ce-details-page"
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+#include <glib/gi18n.h>
+#include <NetworkManager.h>
+
+#include "list-box-helper.h"
+#include "cc-list-row.h"
+#include "ce-details-page.h"
+
+/**
+ * @short_description: The Details page for a connection
+ * @include: "ce-details-page.h"
+ *
+ * Show details related to a network connection
+ */
+
+struct _CeDetailsPage
+{
+ GtkScrolledWindow parent_instance;
+ /* GtkBox parent_instance; */
+
+ GtkWidget *list_box;
+ GtkWidget *speed_row;
+ GtkWidget *dns_row;
+ GtkWidget *ipv4_row;
+ GtkWidget *ipv6_row;
+
+ GtkWidget *network_name_row;
+ GtkWidget *bssid_row;
+ GtkWidget *mac_row;
+ GtkWidget *cloned_mac_row;
+
+ GtkWidget *edit_dialog;
+ GtkWidget *apply_button;
+ GtkWidget *dialog_label;
+ GtkWidget *dialog_entry;
+ GtkWidget *dialog_combo_box;
+ /* The row that activated the dialog */
+ GtkWidget *dialog_row;
+
+ NMClient *nm_client;
+ NMDevice *device;
+ NMConnection *orig_connection;
+ NMConnection *connection;
+ NMAccessPoint *ap;
+ NMActiveConnection *active_connection;
+ /* NMSettingWireless *wireless_setting; */
+
+ gboolean device_is_active;
+};
+
+G_DEFINE_TYPE (CeDetailsPage, ce_details_page, GTK_TYPE_SCROLLED_WINDOW)
+
+/* Stolen from ce-page.c
+ * Written by Matthias Clasen and Bastien Nocera
+ */
+static gboolean
+ce_details_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 char *trimmed_addr = NULL;
+ gchar *end;
+ guint i;
+
+ /* MAC Address can be empty */
+ if (!addr || *addr == '\0')
+ return TRUE;
+
+ /* The address text may also have the device name at end, strip it */
+ trimmed_addr = g_strdup (addr);
+ end = strchr (trimmed_addr, ' ');
+ if (end != NULL)
+ *end = '\0';
+
+ 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;
+}
+
+static gchar *
+ce_details_get_human_time (guint64 unix_time)
+{
+ g_autoptr(GDateTime) now = NULL;
+ g_autoptr(GDateTime) utc_time = NULL;
+ gchar *last_used;
+ GTimeSpan diff;
+ gint days;
+
+ if (unix_time == 0)
+ return g_strdup (_("Never"));
+
+ now = g_date_time_new_now_utc ();
+ utc_time = g_date_time_new_from_unix_utc (unix_time);
+
+ diff = g_date_time_difference (now, utc_time);
+ 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);
+
+ return last_used;
+}
+
+static const gchar *
+details_get_ip_string (NMIPConfig *ip_config)
+{
+ NMIPAddress *address;
+ GPtrArray *ip_addresses;
+
+ g_assert (NM_IS_IP_CONFIG (ip_config));
+
+ ip_addresses = nm_ip_config_get_addresses (ip_config);
+
+ if (ip_addresses->len < 1)
+ return "";
+
+ address = ip_addresses->pdata[0];
+ return nm_ip_address_get_address (address);
+}
+
+static void
+ce_details_page_update_device_details (CeDetailsPage *self)
+{
+ NMIPConfig *ip_config;
+ const gchar * const *nameservers;
+ guint32 speed = 0;
+ gboolean is_active;
+
+ g_assert (CE_IS_DETAILS_PAGE (self));
+ g_assert (NM_IS_DEVICE (self->device));
+ g_assert (NM_IS_CONNECTION (self->connection));
+
+ is_active = ce_deails_page_has_active_connection (self, TRUE);
+ gtk_widget_set_visible (self->speed_row, is_active);
+ gtk_widget_set_visible (self->dns_row, is_active);
+ gtk_widget_set_visible (self->ipv4_row, is_active);
+ gtk_widget_set_visible (self->ipv6_row, is_active);
+
+ if (!is_active)
+ return;
+
+ /* Link Speed */
+ if (NM_IS_DEVICE_WIFI (self->device))
+ speed = nm_device_wifi_get_bitrate (NM_DEVICE_WIFI (self->device)) / 1000;
+ else if (NM_IS_DEVICE_ETHERNET (self->device))
+ speed = nm_device_ethernet_get_speed (NM_DEVICE_ETHERNET (self->device));
+ else
+ g_warn_if_reached ();
+
+ if (speed > 0)
+ {
+ g_autofree gchar *speed_label = NULL;
+
+ speed_label = g_strdup_printf (_("%u Mb/s"), speed);
+ cc_list_row_set_secondary_label (CC_LIST_ROW (self->speed_row), speed_label);
+ }
+
+ /* DNS servers list */
+ ip_config = nm_active_connection_get_ip4_config (self->active_connection);
+
+ /* XXX: check IPv6? */
+ if (!ip_config)
+ return;
+
+ nameservers = nm_ip_config_get_nameservers (ip_config);
+
+ if (nameservers)
+ {
+ g_autofree gchar *dns_list = NULL;
+
+ dns_list = g_strjoinv (", ", (gchar **)nameservers);
+ cc_list_row_set_secondary_label (CC_LIST_ROW (self->dns_row), dns_list);
+ }
+
+ /* IPv4 DHCP/Static address list */
+ ip_config = nm_active_connection_get_ip4_config (self->active_connection);
+ cc_list_row_set_secondary_label (CC_LIST_ROW (self->ipv4_row),
+ details_get_ip_string (ip_config));
+
+ /* IPv6 DHCP/Static address list */
+ ip_config = nm_active_connection_get_ip6_config (self->active_connection);
+ cc_list_row_set_secondary_label (CC_LIST_ROW (self->ipv6_row),
+ details_get_ip_string (ip_config));
+}
+
+static gchar *
+ce_details_get_ssid (CeDetailsPage *self)
+{
+ NMSettingWireless *setting;
+ GBytes *ssid;
+ gchar *ssid_text;
+
+ g_assert (CE_IS_DETAILS_PAGE (self));
+
+ setting = nm_connection_get_setting_wireless (self->orig_connection);
+ g_return_val_if_fail (setting, g_strdup (""));
+
+ ssid = nm_setting_wireless_get_ssid (setting);
+ if (ssid)
+ ssid_text = nm_utils_ssid_to_utf8 (g_bytes_get_data (ssid, NULL),
+ g_bytes_get_size (ssid));
+ else
+ ssid_text = g_strdup ("");
+
+ return ssid_text;
+}
+
+static void
+ce_details_page_update_wireless (CeDetailsPage *self)
+{
+ NMSettingWireless *setting;
+ g_autofree gchar *ssid_text = NULL;
+
+ g_assert (CE_IS_DETAILS_PAGE (self));
+
+ g_object_set (self->network_name_row, "subtitle", "", NULL);
+
+ setting = nm_connection_get_setting_wireless (self->orig_connection);
+ if (!setting)
+ return;
+
+ g_object_set (self->network_name_row, "subtitle", _("SSID"), NULL);
+
+ ssid_text = ce_details_get_ssid (self);
+ cc_list_row_set_secondary_label (CC_LIST_ROW (self->network_name_row), ssid_text);
+
+ cc_list_row_set_secondary_label (CC_LIST_ROW (self->bssid_row),
+ nm_setting_wireless_get_bssid (setting));
+}
+
+static void
+ce_details_page_populate_bssid (CeDetailsPage *self)
+{
+ NMSettingWireless *setting;
+ const gchar *bssid;
+
+ g_assert (CE_IS_DETAILS_PAGE (self));
+
+ setting = nm_connection_get_setting_wireless (self->orig_connection);
+ g_return_if_fail (setting);
+
+ for (guint32 i = 0; i < nm_setting_wireless_get_num_seen_bssids (setting); i++)
+ {
+ bssid = nm_setting_wireless_get_seen_bssid (setting, i);
+ gtk_combo_box_text_append (GTK_COMBO_BOX_TEXT (self->dialog_combo_box), NULL, bssid);
+ }
+}
+
+static void
+ce_details_page_populate_device_mac (CeDetailsPage *self)
+{
+ const GPtrArray *devices;
+ NMDeviceType device_type;
+
+ devices = nm_client_get_devices (self->nm_client);
+
+ if (!devices)
+ return;
+
+ if (NM_IS_DEVICE_WIFI (self->device))
+ device_type = NM_DEVICE_TYPE_WIFI;
+ else /* TODO */
+ g_return_if_reached ();
+
+ for (int i = 0; i < devices->len; i++)
+ {
+ NMDevice *device;
+ const char *iface;
+ g_autofree gchar *mac_address = NULL;
+ g_autofree gchar *item = NULL;
+
+ device = devices->pdata[i];
+
+ if (nm_device_get_device_type (device) != device_type)
+ continue;
+
+ g_object_get (G_OBJECT (device), "perm-hw-address", &mac_address, NULL);
+ iface = nm_device_get_iface (device);
+ item = g_strdup_printf ("%s (%s)", mac_address, iface);
+
+ gtk_combo_box_text_append (GTK_COMBO_BOX_TEXT (self->dialog_combo_box), NULL, item);
+ }
+}
+
+static void
+details_dialog_save (CeDetailsPage *self)
+{
+ NMSettingConnection *connection_setting;
+ NMSettingWireless *wireless_setting;
+ GtkEntry *entry;
+ g_autoptr(GError) error = NULL;
+ const gchar *value;
+
+ g_assert (CE_IS_DETAILS_PAGE (self));
+
+ if (self->dialog_row == self->network_name_row)
+ entry = GTK_ENTRY (self->dialog_entry);
+ else
+ entry = GTK_ENTRY (gtk_bin_get_child (GTK_BIN (self->dialog_combo_box)));
+
+ connection_setting = nm_connection_get_setting_connection (self->orig_connection);
+ wireless_setting = nm_connection_get_setting_wireless (self->orig_connection);
+ value = gtk_entry_get_text (entry);
+
+ if (!value || !*value)
+ value = NULL;
+
+ if (self->dialog_row == self->network_name_row)
+ {
+ g_object_set (G_OBJECT (connection_setting), "id", value, NULL);
+ if (wireless_setting)
+ {
+ g_autoptr(GBytes) ssid = NULL;
+
+ if (value)
+ ssid = g_bytes_new_static (value, strlen (value));
+ g_object_set (G_OBJECT (wireless_setting), "ssid", ssid, NULL);
+ }
+ }
+ else if (self->dialog_row == self->bssid_row)
+ {
+ if (wireless_setting)
+ g_object_set (G_OBJECT (wireless_setting), "bssid", value, NULL);
+ }
+ else if (self->dialog_row == self->mac_row)
+ {
+ g_autofree gchar *addr = NULL;
+ gchar *end = NULL;
+
+ /* The address text may also have the device name at end, strip it */
+ addr = g_strdup (value);
+ if (addr)
+ end = strchr (addr, ' ');
+ if (end != NULL)
+ *end = '\0';
+
+ if (wireless_setting)
+ g_object_set (G_OBJECT (wireless_setting), "mac-address", addr, NULL);
+ }
+ else if (self->dialog_row == self->cloned_mac_row)
+ {
+ const gchar *id;
+
+ id = gtk_combo_box_get_active_id (GTK_COMBO_BOX (self->dialog_combo_box));
+
+ if (id)
+ value = id;
+
+ if (wireless_setting)
+ g_object_set (G_OBJECT (wireless_setting), "cloned-mac-address", value, NULL);
+ }
+
+ nm_remote_connection_commit_changes (NM_REMOTE_CONNECTION (self->orig_connection),
+ TRUE, NULL, &error);
+ if (error)
+ g_warning ("Error saving settings: %s", error->message);
+ else
+ ce_details_page_update_wireless (self);
+}
+
+static void
+ce_details_page_populate_spoof_box (CeDetailsPage *self,
+ const gchar *mac)
+{
+ GtkComboBoxText *combo;
+ static const char *items[][2] = {
+ { "preserve", N_("Preserve") },
+ { "permanent", N_("Permanent") },
+ { "random", N_("Random") },
+ { "stable", N_("Stable") } };
+ gchar *match = NULL;
+
+ g_assert (CE_IS_DETAILS_PAGE (self));
+
+ if (mac)
+ match = strchr (mac, ':');
+
+ combo = GTK_COMBO_BOX_TEXT (self->dialog_combo_box);
+ for (int i = 0; i < G_N_ELEMENTS (items); i++)
+ gtk_combo_box_text_append (combo, items[i][0], _(items[i][1]));
+
+ if (!match && mac)
+ gtk_combo_box_set_active_id (GTK_COMBO_BOX (combo), mac);
+}
+
+static void
+details_row_activated_cb (CeDetailsPage *self,
+ GtkWidget *row)
+{
+ NMSettingWireless *setting;
+ GtkEntry *entry;
+ const gchar *label, *value = NULL;
+ gint response;
+ gboolean is_wifi;
+
+ g_assert (CE_IS_DETAILS_PAGE (self));
+ g_assert (GTK_IS_LIST_BOX_ROW (row));
+
+ self->dialog_row = NULL;
+ setting = nm_connection_get_setting_wireless (self->orig_connection);
+ entry = GTK_ENTRY (gtk_bin_get_child (GTK_BIN (self->dialog_combo_box)));
+ is_wifi = NM_IS_DEVICE_WIFI (self->device);
+ gtk_widget_hide (self->dialog_entry);
+
+ gtk_entry_set_text (entry, "");
+ gtk_entry_set_placeholder_text (entry, "");
+ gtk_entry_set_text (GTK_ENTRY (self->dialog_entry), "");
+ gtk_widget_set_sensitive (GTK_WIDGET (self->apply_button), FALSE);
+
+ gtk_combo_box_text_remove_all (GTK_COMBO_BOX_TEXT (self->dialog_combo_box));
+ if (row == self->network_name_row)
+ {
+ gtk_widget_show (self->dialog_entry);
+
+ if (is_wifi)
+ {
+ g_autofree gchar *ssid = NULL;
+
+ ssid = ce_details_get_ssid (self);
+ gtk_entry_set_text (GTK_ENTRY (self->dialog_entry), ssid);
+
+ label = _("Choose a new network SSID. This is how it will appear to others.");
+ }
+ else
+ label = _("Choose a new network Name. This is how it will appear to others.");
+ }
+ else if (row == self->bssid_row)
+ {
+ label = _("Choose a new network BSSID. This is how it will appear to others.");
+ value = nm_setting_wireless_get_bssid (setting);
+ ce_details_page_populate_bssid (self);
+
+ if (value && *value)
+ gtk_entry_set_text (entry, value);
+ else
+ gtk_entry_set_placeholder_text (entry, _("Allow connecting to any BSSID"));
+ }
+ else if (row == self->mac_row)
+ {
+ label = _("Choose a new network MAC Address. This is how it will appear to others.");
+ ce_details_page_populate_device_mac (self);
+
+ if (setting)
+ value = nm_setting_wireless_get_mac_address (setting);
+
+ if (value)
+ gtk_entry_set_text (entry, value);
+ }
+ else if (row == self->cloned_mac_row)
+ {
+ label = _("Choose a new Spoofed MAC Address. This is how it will appear to others.");
+
+ if (setting)
+ value = nm_setting_wireless_get_cloned_mac_address (setting);
+
+ ce_details_page_populate_spoof_box (self, value);
+ }
+ else
+ g_return_if_reached ();
+
+ self->dialog_row = row;
+ gtk_label_set_label (GTK_LABEL (self->dialog_label), label);
+
+ response = gtk_dialog_run (GTK_DIALOG (self->edit_dialog));
+ gtk_widget_hide (self->edit_dialog);
+
+ if (response == GTK_RESPONSE_APPLY)
+ details_dialog_save (self);
+}
+
+static void
+details_value_changed_cb (CeDetailsPage *self,
+ GtkWidget *widget)
+{
+ const gchar *value;
+ gboolean is_valid;
+
+ g_assert (CE_IS_DETAILS_PAGE (self));
+ g_assert (GTK_IS_WIDGET (self));
+
+ if (self->dialog_row == NULL)
+ return;
+
+ if (widget == self->dialog_entry)
+ {
+ if (!gtk_widget_get_visible (widget))
+ return;
+
+ value = gtk_entry_get_text (GTK_ENTRY (widget));
+ is_valid = value && *value;
+ }
+ else if (widget == self->dialog_combo_box)
+ {
+ g_autofree gchar *text = NULL;
+ text = gtk_combo_box_text_get_active_text (GTK_COMBO_BOX_TEXT (widget));
+
+ if (self->dialog_row == self->cloned_mac_row)
+ {
+ is_valid = gtk_combo_box_get_active (GTK_COMBO_BOX (self->dialog_combo_box)) != -1;
+ is_valid = is_valid || ce_details_address_is_valid (text);
+ }
+ else
+ is_valid = ce_details_address_is_valid (text);
+ }
+ else
+ g_return_if_reached ();
+
+ gtk_widget_set_sensitive (self->apply_button, is_valid);
+}
+
+static void
+ce_details_page_finalize (GObject *object)
+{
+ CeDetailsPage *self = (CeDetailsPage *)object;
+
+ g_clear_object (&self->device);
+ g_clear_object (&self->connection);
+ g_clear_object (&self->nm_client);
+
+ G_OBJECT_CLASS (ce_details_page_parent_class)->finalize (object);
+}
+
+static void
+ce_details_page_class_init (CeDetailsPageClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+ object_class->finalize = ce_details_page_finalize;
+
+ gtk_widget_class_set_template_from_resource (widget_class,
+ "/org/gnome/control-center/"
+ "network/ce-details-page.ui");
+
+ gtk_widget_class_bind_template_child (widget_class, CeDetailsPage, list_box);
+ gtk_widget_class_bind_template_child (widget_class, CeDetailsPage, speed_row);
+ gtk_widget_class_bind_template_child (widget_class, CeDetailsPage, dns_row);
+ gtk_widget_class_bind_template_child (widget_class, CeDetailsPage, ipv4_row);
+ gtk_widget_class_bind_template_child (widget_class, CeDetailsPage, ipv6_row);
+
+ gtk_widget_class_bind_template_child (widget_class, CeDetailsPage, network_name_row);
+ gtk_widget_class_bind_template_child (widget_class, CeDetailsPage, bssid_row);
+ gtk_widget_class_bind_template_child (widget_class, CeDetailsPage, mac_row);
+ gtk_widget_class_bind_template_child (widget_class, CeDetailsPage, cloned_mac_row);
+
+ gtk_widget_class_bind_template_child (widget_class, CeDetailsPage, edit_dialog);
+ gtk_widget_class_bind_template_child (widget_class, CeDetailsPage, apply_button);
+ gtk_widget_class_bind_template_child (widget_class, CeDetailsPage, dialog_label);
+ gtk_widget_class_bind_template_child (widget_class, CeDetailsPage, dialog_entry);
+ gtk_widget_class_bind_template_child (widget_class, CeDetailsPage, dialog_combo_box);
+
+ gtk_widget_class_bind_template_callback (widget_class, details_row_activated_cb);
+ gtk_widget_class_bind_template_callback (widget_class, details_value_changed_cb);
+}
+
+static void
+ce_details_page_init (CeDetailsPage *self)
+{
+ gtk_widget_init_template (GTK_WIDGET (self));
+
+ /* gtk_list_box_set_header_func (GTK_LIST_BOX (self->list_box), */
+ /* cc_list_box_update_header_func, */
+ /* NULL, NULL); */
+}
+
+void
+ce_details_page_set_parent_window (CeDetailsPage *self,
+ GtkWindow *parent_window)
+{
+ g_return_if_fail (CE_IS_DETAILS_PAGE (self));
+ g_return_if_fail (GTK_IS_WINDOW (parent_window));
+
+ gtk_window_set_transient_for (GTK_WINDOW (self->edit_dialog),
+ parent_window);
+}
+
+void
+ce_details_page_set_nm_client (CeDetailsPage *self,
+ NMClient *nm_client)
+{
+ g_return_if_fail (CE_IS_DETAILS_PAGE (self));
+ g_return_if_fail (NM_IS_CLIENT (nm_client));
+
+ g_set_object (&self->nm_client, nm_client);
+}
+
+void
+ce_details_page_set_connection (CeDetailsPage *self,
+ NMConnection *orig_connection,
+ NMConnection *connection,
+ NMDevice *device)
+{
+ g_return_if_fail (CE_IS_DETAILS_PAGE (self));
+ g_return_if_fail (NM_IS_REMOTE_CONNECTION (orig_connection));
+ g_return_if_fail (NM_IS_CONNECTION (connection));
+ g_return_if_fail (NM_IS_DEVICE (device));
+
+ g_set_object (&self->connection, connection);
+ g_set_object (&self->orig_connection, orig_connection);
+ g_set_object (&self->device, device);
+}
+
+void
+ce_details_page_set_ap (CeDetailsPage *self,
+ NMAccessPoint *ap)
+{
+ g_return_if_fail (CE_IS_DETAILS_PAGE (self));
+ g_return_if_fail (!ap || NM_IS_ACCESS_POINT (ap));
+
+ g_set_object (&self->ap, ap);
+}
+
+const gchar *
+ce_details_page_get_ap_strength (CeDetailsPage *self)
+{
+ const gchar *strength_label;
+ guint8 strength;
+
+ g_return_val_if_fail (CE_IS_DETAILS_PAGE (self), NULL);
+
+ if (!self->ap)
+ return "";
+
+ 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");
+
+ return strength_label;
+}
+
+gchar *
+ce_details_page_get_ap_frequency (CeDetailsPage *self)
+{
+ guint32 frequency;
+
+ g_return_val_if_fail (CE_IS_DETAILS_PAGE (self), NULL);
+
+ if (!self->ap)
+ return g_strdup ("");
+
+ frequency = nm_access_point_get_frequency (self->ap);
+
+ if (!frequency)
+ return g_strdup ("");
+
+ return g_strdup_printf ("%.2g GHz", frequency / (1000 * 1.0));
+}
+
+void
+ce_details_page_refresh (CeDetailsPage *self)
+{
+ g_return_if_fail (CE_IS_DETAILS_PAGE (self));
+
+ self->active_connection = NULL;
+ if (self->device)
+ self->active_connection = nm_device_get_active_connection (self->device);
+
+ if (self->device && self->connection && self->active_connection)
+ ce_details_page_update_device_details (self);
+
+ ce_details_page_update_wireless (self);
+}
+
+gboolean
+ce_deails_page_has_active_connection (CeDetailsPage *self,
+ gboolean force_refresh)
+{
+ g_return_val_if_fail (CE_IS_DETAILS_PAGE (self), FALSE);
+
+ if (force_refresh)
+ {
+ const gchar *uuid_active, *uuid;
+
+ if (self->device)
+ self->active_connection = nm_device_get_active_connection (self->device);
+
+ if (self->connection && self->active_connection)
+ {
+ uuid_active = nm_active_connection_get_uuid (self->active_connection);
+ uuid = nm_connection_get_uuid (self->connection);
+
+ self->device_is_active = g_str_equal (uuid, uuid_active);
+ }
+ }
+
+ return self->device_is_active;
+}
+
+gchar *
+ce_details_page_get_last_used (CeDetailsPage *self)
+{
+ NMSettingConnection *setting;
+ guint64 last_used;
+
+ g_return_val_if_fail (CE_IS_DETAILS_PAGE (self), ("Unknown"));
+ g_return_val_if_fail (self->connection, ("Unknown"));
+
+ if (ce_deails_page_has_active_connection (self, FALSE))
+ return g_strdup (_("Now"));
+
+ setting = nm_connection_get_setting_connection (self->connection);
+ last_used = nm_setting_connection_get_timestamp (setting);
+
+ return ce_details_get_human_time (last_used);
+}
+
+const gchar *
+ce_details_page_get_gateway (CeDetailsPage *self)
+{
+ NMIPConfig *ip_config;
+ const gchar *gateway = NULL;
+
+ g_return_val_if_fail (CE_IS_DETAILS_PAGE (self), "");
+
+ if (!ce_deails_page_has_active_connection (self, TRUE))
+ return "";
+
+ /* Check IPv4 Gateway */
+ ip_config = nm_active_connection_get_ip4_config (self->active_connection);
+ if (ip_config)
+ gateway = nm_ip_config_get_gateway (ip_config);
+
+ if (gateway && *gateway)
+ return gateway;
+
+ /* Check IPv6 Gateway */
+ ip_config = nm_active_connection_get_ip6_config (self->active_connection);
+ if (ip_config)
+ gateway = nm_ip_config_get_gateway (ip_config);
+
+ if (gateway)
+ return gateway;
+
+ return "";
+}
+
+gchar *
+ce_details_page_get_security (CeDetailsPage *self)
+{
+ GString *str;
+ NM80211ApSecurityFlags wpa_flags, rsn_flags;
+ NM80211ApFlags flags;
+
+ g_return_val_if_fail (CE_IS_DETAILS_PAGE (self), g_strdup (""));
+
+ if (!self->ap)
+ return g_strdup ("");
+
+ flags = nm_access_point_get_flags (self->ap);
+ wpa_flags = nm_access_point_get_wpa_flags (self->ap);
+ rsn_flags = nm_access_point_get_rsn_flags (self->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)
+ /* 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"));
+
+ /* Strip the comma suffix */
+ 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);
+}
diff --git a/panels/network/connection-editor/ce-details-page.h b/panels/network/connection-editor/ce-details-page.h
new file mode 100644
index 000000000..c1fffe216
--- /dev/null
+++ b/panels/network/connection-editor/ce-details-page.h
@@ -0,0 +1,53 @@
+/* -*- Mode: C; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* ce-wifi-page.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>
+
+G_BEGIN_DECLS
+
+#define CE_TYPE_DETAILS_PAGE (ce_details_page_get_type())
+G_DECLARE_FINAL_TYPE (CeDetailsPage, ce_details_page, CE, DETAILS_PAGE, GtkScrolledWindow)
+
+void ce_details_page_set_parent_window (CeDetailsPage *self,
+ GtkWindow *parent_window);
+void ce_details_page_set_nm_client (CeDetailsPage *self,
+ NMClient *nm_client);
+void ce_details_page_set_connection (CeDetailsPage *self,
+ NMConnection *orig_connection,
+ NMConnection *connection,
+ NMDevice *device);
+void ce_details_page_set_ap (CeDetailsPage *self,
+ NMAccessPoint *ap);
+void ce_details_page_refresh (CeDetailsPage *self);
+const gchar *ce_details_page_get_ap_strength (CeDetailsPage *self);
+gchar *ce_details_page_get_ap_frequency (CeDetailsPage *self);
+gboolean ce_deails_page_has_active_connection (CeDetailsPage *self,
+ gboolean force_refresh);
+gchar *ce_details_page_get_last_used (CeDetailsPage *self);
+const gchar *ce_details_page_get_gateway (CeDetailsPage *self);
+gchar *ce_details_page_get_security (CeDetailsPage *self);
+
+G_END_DECLS
diff --git a/panels/network/connection-editor/ce-details-page.ui b/panels/network/connection-editor/ce-details-page.ui
new file mode 100644
index 000000000..8049e32dd
--- /dev/null
+++ b/panels/network/connection-editor/ce-details-page.ui
@@ -0,0 +1,179 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <template class="CeDetailsPage" parent="GtkScrolledWindow">
+ <property name="vexpand">1</property>
+ <property name="hscrollbar-policy">never</property>
+ <property name="min-content-height">400</property>
+
+ <child>
+ <object class="HdyClamp">
+ <property name="visible">1</property>
+ <property name="margin-top">32</property>
+ <property name="margin-bottom">32</property>
+ <child>
+ <object class="GtkBox">
+ <property name="visible">1</property>
+ <property name="orientation">vertical</property>
+ <property name="margin-start">12</property>
+ <property name="margin-end">12</property>
+ <child>
+ <object class="GtkListBox" id="list_box">
+ <property name="visible">1</property>
+ <property name="selection-mode">none</property>
+ <signal name="row-activated" handler="details_row_activated_cb" swapped="yes" />
+ <style>
+ <class name="content" />
+ </style>
+
+ <!-- Link Speed Row -->
+ <child>
+ <object class="CcListRow" id="speed_row">
+ <property name="visible">1</property>
+ <property name="can-focus">0</property>
+ <property name="activatable">0</property>
+ <property name="title" translatable="yes">Link Speed</property>
+ </object>
+ </child>
+
+ <!-- DNS Servers row -->
+ <child>
+ <object class="CcListRow" id="dns_row">
+ <property name="visible">1</property>
+ <property name="can-focus">0</property>
+ <property name="activatable">0</property>
+ <property name="title" translatable="yes">DNS Servers</property>
+ </object>
+ </child>
+
+ <!-- IPv4 row -->
+ <child>
+ <object class="CcListRow" id="ipv4_row">
+ <property name="visible">1</property>
+ <property name="can-focus">0</property>
+ <property name="activatable">0</property>
+ <property name="title" translatable="yes">IPv4 Address</property>
+ </object>
+ </child>
+
+ <!-- IPv6 row -->
+ <child>
+ <object class="CcListRow" id="ipv6_row">
+ <property name="visible">1</property>
+ <property name="can-focus">0</property>
+ <property name="activatable">0</property>
+ <property name="title" translatable="yes">IPv6 Address</property>
+ </object>
+ </child>
+
+ <!-- Network Name row -->
+ <child>
+ <object class="CcListRow" id="network_name_row">
+ <property name="visible">1</property>
+ <property name="title" translatable="yes">Network Name</property>
+ <property name="icon-name">go-next-symbolic</property>
+ </object>
+ </child>
+
+ <!-- BSSID row -->
+ <child>
+ <object class="CcListRow" id="bssid_row">
+ <property name="visible">1</property>
+ <property name="title" translatable="yes">Access Point</property>
+ <property name="subtitle" translatable="yes">BSSID</property>
+ <property name="icon-name">go-next-symbolic</property>
+ </object>
+ </child>
+
+ <!-- MAC row -->
+ <child>
+ <object class="CcListRow" id="mac_row">
+ <property name="visible">1</property>
+ <property name="title" translatable="yes">Hardware Address</property>
+ <property name="subtitle" translatable="yes">MAC Address</property>
+ <property name="icon-name">go-next-symbolic</property>
+ </object>
+ </child>
+
+ <!-- Cloned MAC row -->
+ <child>
+ <object class="CcListRow" id="cloned_mac_row">
+ <property name="visible">1</property>
+ <property name="title" translatable="yes">Cloned Address</property>
+ <property name="subtitle" translatable="yes">MAC Address</property>
+ <property name="icon-name">go-next-symbolic</property>
+ </object>
+ </child>
+
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+
+ </template>
+ <object class="GtkDialog" id="edit_dialog">
+ <property name="visible">0</property>
+ <property name="modal">1</property>
+ <property name="use-header-bar">1</property>
+ <property name="default-height">24</property>
+ <signal name="delete-event" handler="gtk_widget_hide_on_delete"/>
+
+ <child internal-child="vbox">
+ <object class="GtkBox">
+ <property name="border-width">0</property>
+ <property name="margin-top">18</property>
+ <property name="margin-start">12</property>
+ <property name="margin-end">12</property>
+ <property name="margin-bottom">18</property>
+ <child>
+ <object class="GtkLabel" id="dialog_label">
+ <property name="visible">1</property>
+ <property name="margin-bottom">18</property>
+ <property name="wrap">1</property>
+ <property name="max-width-chars">35</property>
+ <property name="xalign">0.0</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkEntry" id="dialog_entry">
+ <property name="visible">1</property>
+ <signal name="changed" handler="details_value_changed_cb" swapped="yes" />
+ </object>
+ </child>
+ <child>
+ <object class="GtkComboBoxText" id="dialog_combo_box">
+ <property name="visible" bind-source="dialog_entry" bind-property="visible" bind-flags="invert-boolean" />
+ <property name="has-entry">1</property>
+ <signal name="changed" handler="details_value_changed_cb" swapped="yes" />
+ </object>
+ </child>
+ </object>
+ </child>
+
+ <!-- Apply button -->
+ <child type="action">
+ <object class="GtkButton" id="apply_button">
+ <property name="visible">1</property>
+ <property name="sensitive">0</property>
+ <property name="can-default">1</property>
+ <property name="use-underline">1</property>
+ <property name="label" translatable="yes">_Apply</property>
+ </object>
+ </child>
+
+ <!-- Cancel button -->
+ <child type="action">
+ <object class="GtkButton" id="cancel_button">
+ <property name="visible">1</property>
+ <property name="use-underline">1</property>
+ <property name="label" translatable="yes">_Cancel</property>
+ </object>
+ </child>
+
+ <action-widgets>
+ <action-widget response="apply" default="true">apply_button</action-widget>
+ <action-widget response="cancel">cancel_button</action-widget>
+ </action-widgets>
+ </object>
+</interface>
diff --git a/panels/network/connection-editor/ce-ip-page.c b/panels/network/connection-editor/ce-ip-page.c
new file mode 100644
index 000000000..48c7bf658
--- /dev/null
+++ b/panels/network/connection-editor/ce-ip-page.c
@@ -0,0 +1,1055 @@
+/* -*- Mode: C; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* ce-wifi-page.c
+ *
+ * Copyright 2021 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 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/>.
+ *
+ * Author(s):
+ * Mohammed Sadiq <sadiq@sadiqpk.org>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "ce-ip-page"
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+#include <errno.h>
+#include <arpa/inet.h>
+#include <glib/gi18n.h>
+#include <NetworkManager.h>
+
+#include <handy.h>
+
+#include "ui-helpers.h"
+#include "list-box-helper.h"
+#include "cc-list-row.h"
+#include "ce-ip-page.h"
+
+/**
+ * @short_description: The Ip page for a connection
+ * @include: "ce-ip-page.h"
+ *
+ * Show ip related to a network connection
+ */
+
+#define MANUAL_IP_NOT_CHANGED 0
+#define MANUAL_IP_ADDED 1 /* New manual IP added */
+#define MANUAL_IP_LOADED 2 /* Manual IP loaded from NMConnection */
+#define MANUAL_IP_MODIFIED 3
+#define MANUAL_IP_DELETED 4
+
+#define ROUTE_NOT_CHANGED 0
+#define ROUTE_ADDED 1 /* New route added */
+#define ROUTE_LOADED 2 /* Route loaded from NMConnection */
+#define ROUTE_MODIFIED 3
+#define ROUTE_DELETED 4
+
+struct _CeIpPage
+{
+ GtkScrolledWindow parent_instance;
+ /* GtkBox parent_instance; */
+
+ GtkWidget *auto_list_box;
+ GtkWidget *ip_method_row;
+ GtkWidget *auto_dns_row;
+ GtkWidget *auto_route_row;
+
+ GtkWidget *dns_list_box;
+ GtkWidget *dns_title_label;
+ GtkWidget *add_dns_row;
+
+ GtkWidget *manual_list_box;
+ GtkWidget *manual_address_entry;
+ GtkWidget *manual_netmask_entry;
+ GtkWidget *manual_gateway_entry;
+
+ GtkWidget *add_dns_dialog;
+ GtkWidget *add_button;
+ GtkWidget *dns_entry;
+
+ GtkWidget *route_list_box;
+ GtkWidget *route_title_label;
+ GtkWidget *route_address_entry;
+ GtkWidget *route_netmask_entry;
+ GtkWidget *route_gateway_entry;
+ GtkWidget *route_metric_entry;
+
+ NMClient *nm_client;
+ NMDevice *device;
+ NMConnection *connection;
+ NMSetting *ip_setting;
+
+ GListStore *ip_methods;
+ gchar *current_method;
+
+ gint version;
+ gint route_status;
+ gint manual_status;
+ gboolean ip_has_error; /* Manual IP */
+ gboolean route_has_error;
+};
+
+G_DEFINE_TYPE (CeIpPage, ce_ip_page, GTK_TYPE_SCROLLED_WINDOW)
+
+enum {
+ CHANGED,
+ N_SIGNALS
+};
+
+static guint signals[N_SIGNALS];
+
+static gboolean
+parse_netmask (const char *str,
+ guint *prefix,
+ gint version)
+{
+ struct in_addr tmp_addr;
+ glong tmp_prefix;
+ gint max_limit;
+
+ if (version == AF_INET)
+ max_limit = 32;
+ else
+ max_limit = 128;
+
+ errno = 0;
+
+ /* Is it a prefix? */
+ if (!strchr (str, '.')) {
+ tmp_prefix = strtol (str, NULL, 10);
+ if (!errno && tmp_prefix >= 0 && tmp_prefix <= max_limit) {
+ if (prefix)
+ *prefix = tmp_prefix;
+ return TRUE;
+ }
+ }
+
+ if (version != AF_INET)
+ return FALSE;
+
+ /* Is it a netmask? */
+ if (inet_pton (AF_INET, str, &tmp_addr) > 0) {
+ if (prefix)
+ *prefix = nm_utils_ip4_netmask_to_prefix (tmp_addr.s_addr);
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static void
+ce_ip_page_populate_methods (CeIpPage *self)
+{
+ HdyValueObject *object;
+
+ g_assert (CE_IS_IP_PAGE (self));
+
+ g_list_store_remove_all (self->ip_methods);
+
+ /* XXX: Do we need different titles here? */
+ if (self->version == AF_INET6)
+ object = hdy_value_object_new_string (_("Automatic"));
+ else
+ object = hdy_value_object_new_string (_("Automatic (DHCP)"));
+
+ g_object_set_data (G_OBJECT (object), "value", "auto");
+ g_list_store_append (self->ip_methods, object);
+ g_object_unref (object);
+
+ if (self->version == AF_INET6)
+ {
+ object = hdy_value_object_new_string (_("Automatic, DHCP only"));
+ g_object_set_data (G_OBJECT (object), "value", "dhcp");
+ g_list_store_append (self->ip_methods, object);
+ g_object_unref (object);
+ }
+
+ object = hdy_value_object_new_string (_("Link-Local Only"));
+ g_object_set_data (G_OBJECT (object), "value", "link-local");
+ g_list_store_append (self->ip_methods, object);
+ g_object_unref (object);
+
+ object = hdy_value_object_new_string (_("Manual"));
+ g_object_set_data (G_OBJECT (object), "value", "manual");
+ g_list_store_append (self->ip_methods, object);
+ g_object_unref (object);
+
+ object = hdy_value_object_new_string (_("Disable"));
+ g_object_set_data (G_OBJECT (object), "value", "disabled");
+ g_list_store_append (self->ip_methods, object);
+ g_object_unref (object);
+}
+
+static void
+ce_ip_page_remove_dns_row (GtkWidget *widget,
+ CeIpPage *self)
+{
+ g_assert (CE_IS_IP_PAGE (self));
+ g_assert (GTK_IS_WIDGET (widget));
+
+ if (widget != self->add_dns_row)
+ gtk_container_remove (GTK_CONTAINER (self->dns_list_box), widget);
+}
+
+static void
+dns_delete_clicked_cb (CeIpPage *self,
+ GtkButton *button)
+{
+ GtkWidget *row, *label;
+ const gchar *dns;
+
+ g_assert (CE_IS_IP_PAGE (self));
+ g_assert (GTK_IS_BUTTON (button));
+
+ row = g_object_get_data (G_OBJECT (button), "row");
+ label = g_object_get_data (G_OBJECT (button), "label");
+ dns = gtk_label_get_label (GTK_LABEL (label));
+
+ if (nm_setting_ip_config_remove_dns_by_value (NM_SETTING_IP_CONFIG (self->ip_setting), dns))
+ gtk_container_remove (GTK_CONTAINER (self->dns_list_box), row);
+ else /* Debug */
+ g_return_if_reached ();
+
+ g_signal_emit (self, signals[CHANGED], 0);
+}
+
+static GtkWidget *
+ce_ip_list_row_new (CeIpPage *self,
+ const gchar *dns)
+{
+ GtkWidget *row, *box, *label, *button;
+ GtkStyleContext *style_context;
+
+ g_assert (CE_IS_IP_PAGE (self));
+ g_assert (nm_utils_ipaddr_valid (self->version, dns));
+
+ row = gtk_list_box_row_new ();
+ gtk_widget_set_can_focus (row, FALSE);
+
+ box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 12);
+ g_object_set (box, "margin", 6, NULL);
+ gtk_widget_set_hexpand (box, TRUE);
+ gtk_container_add (GTK_CONTAINER (row), box);
+
+ label = gtk_label_new (dns);
+ gtk_widget_set_hexpand (label, TRUE);
+ gtk_widget_set_halign (label, GTK_ALIGN_START);
+ style_context = gtk_widget_get_style_context (label);
+ gtk_style_context_add_class (style_context, "dim-label");
+ gtk_container_add (GTK_CONTAINER (box), label);
+
+ button = gtk_button_new_from_icon_name ("edit-delete-symbolic", GTK_ICON_SIZE_BUTTON);
+ g_object_set_data (G_OBJECT (button), "row", row);
+ g_object_set_data (G_OBJECT (button), "label", label);
+ g_signal_connect_object (button, "clicked",
+ G_CALLBACK (dns_delete_clicked_cb),
+ self,
+ G_CONNECT_SWAPPED);
+ gtk_container_add (GTK_CONTAINER (box), button);
+
+ gtk_widget_show_all (row);
+
+ return row;
+}
+
+static void
+ce_ip_page_add_dns_row (CeIpPage *self,
+ const gchar *dns)
+{
+ GtkWidget *row;
+ gint last_index;
+
+ g_assert (CE_IS_IP_PAGE (self));
+ g_assert (nm_utils_ipaddr_valid (self->version, dns));
+
+ row = ce_ip_list_row_new (self, dns);
+ last_index = gtk_list_box_row_get_index (GTK_LIST_BOX_ROW (self->add_dns_row));
+ gtk_list_box_insert (GTK_LIST_BOX (self->dns_list_box), row, last_index);
+}
+
+static void
+ce_ip_page_update_device_ip (CeIpPage *self)
+{
+ g_assert (CE_IS_IP_PAGE (self));
+ g_assert (NM_IS_DEVICE (self->device));
+ g_assert (NM_IS_CONNECTION (self->connection));
+}
+
+static void
+ip_method_changed_cb (CeIpPage *self)
+{
+ g_autoptr(HdyValueObject) object = NULL;
+ gchar *method;
+ gint index;
+
+ g_assert (CE_IS_IP_PAGE (self));
+
+
+ index = hdy_combo_row_get_selected_index (HDY_COMBO_ROW (self->ip_method_row));
+ if (index == -1)
+ return;
+
+ object = g_list_model_get_item (G_LIST_MODEL (self->ip_methods), index);
+ g_assert (G_IS_OBJECT (object));
+ method = g_object_get_data (G_OBJECT (object), "value");
+
+ if (g_strcmp0 (self->current_method, method) == 0)
+ return;
+
+ self->current_method = method;
+ gtk_widget_set_sensitive (self->auto_dns_row, TRUE);
+ gtk_widget_set_sensitive (self->dns_list_box, TRUE);
+ gtk_widget_hide (self->manual_list_box);
+
+ if (g_str_equal (self->current_method, "manual"))
+ {
+ gtk_widget_show (self->manual_list_box);
+ }
+ else if (g_str_equal (self->current_method, "disabled"))
+ {
+ gtk_widget_set_sensitive (self->auto_dns_row, FALSE);
+ gtk_widget_set_sensitive (self->dns_list_box, FALSE);
+ }
+ else if (g_str_equal (self->current_method, "link-local"))
+ {
+ gtk_widget_set_sensitive (self->dns_list_box, FALSE);
+ }
+
+ g_signal_emit (self, signals[CHANGED], 0);
+}
+
+static void
+ip_auto_row_changed_cb (CeIpPage *self,
+ GParamSpec *pspec,
+ GtkWidget *row)
+{
+ const char *label;
+ gboolean is_auto;
+
+ g_assert (CE_IS_IP_PAGE (self));
+ g_assert (GTK_IS_LIST_BOX_ROW (row));
+
+ g_object_get (row, "active", &is_auto, NULL);
+
+ if (row == self->auto_dns_row)
+ {
+ if (is_auto)
+ label = _("Additional DNS Servers");
+ else
+ label = _("DNS Servers");
+
+ gtk_label_set_text (GTK_LABEL (self->dns_title_label), label);
+ }
+ else if (row == self->auto_route_row)
+ {
+ if (is_auto)
+ label = _("Additional Route");
+ else
+ label = _("Route");
+
+ gtk_label_set_text (GTK_LABEL (self->route_title_label), label);
+ }
+
+ g_signal_emit (self, signals[CHANGED], 0);
+}
+
+static void
+dns_row_activated_cb (CeIpPage *self,
+ GtkWidget *row)
+{
+ int result;
+
+ g_assert (CE_IS_IP_PAGE (self));
+ g_assert (GTK_IS_LIST_BOX_ROW (row));
+ g_assert (self->ip_setting);
+
+ if (row != self->add_dns_row)
+ return;
+
+ gtk_widget_grab_focus (self->dns_entry);
+ gtk_entry_set_text (GTK_ENTRY (self->dns_entry), "");
+ result = gtk_dialog_run (GTK_DIALOG (self->add_dns_dialog));
+ gtk_widget_hide (self->add_dns_dialog);
+
+ if (result == GTK_RESPONSE_APPLY)
+ {
+ const gchar *dns;
+
+ dns = gtk_entry_get_text (GTK_ENTRY (self->dns_entry));
+ if (nm_setting_ip_config_add_dns (NM_SETTING_IP_CONFIG (self->ip_setting), dns))
+ {
+ ce_ip_page_add_dns_row (self, dns);
+ g_signal_emit (self, signals[CHANGED], 0);
+ }
+ }
+}
+
+static void
+ce_entry_set_error (GtkWidget *widget,
+ gboolean has_error)
+{
+ if (has_error)
+ widget_set_error (widget);
+ else
+ widget_unset_error (widget);
+}
+
+static gboolean
+ce_ip_entry_validate (GtkWidget *widget,
+ gint version)
+{
+ GtkEntry *entry;
+ const gchar *ip;
+ gboolean valid;
+
+ g_assert (GTK_IS_ENTRY (widget));
+ g_assert (version == AF_INET || version == AF_INET6);
+
+ entry = GTK_ENTRY (widget);
+ ip = gtk_entry_get_text (entry);
+
+ valid = nm_utils_ipaddr_valid (version, ip);
+ ce_entry_set_error (widget, !valid);
+
+ return valid;
+}
+
+static gboolean
+ce_netmask_entry_validate (GtkWidget *widget,
+ gint version)
+{
+ GtkEntry *entry;
+ const gchar *text;
+ gchar *end;
+ glong prefix;
+ gboolean valid;
+
+ g_assert (GTK_IS_ENTRY (widget));
+ g_assert (version == AF_INET || version == AF_INET6);
+
+ valid = FALSE;
+ entry = GTK_ENTRY (widget);
+ text = gtk_entry_get_text (entry);
+
+ if (!*text)
+ {
+ widget_set_error (widget);
+ return FALSE;
+ }
+
+ if (version == AF_INET)
+ valid = ce_ip_entry_validate (widget, version);
+
+ if (gtk_entry_get_text_length (entry) > 4)
+ return valid;
+
+ prefix = strtol (text, &end, 10);
+
+ if (*end == '\0')
+ {
+ if (version == AF_INET)
+ valid = prefix >= 0 && prefix <= 32;
+ else
+ valid = prefix >= 0 && prefix <= 128;
+ }
+
+ ce_entry_set_error (widget, !valid);
+
+ return valid;
+}
+
+static gboolean
+ce_metric_entry_validate (GtkWidget *widget)
+{
+ GtkEntry *entry;
+ const gchar *text;
+ gchar *end;
+ glong metric;
+ gboolean valid;
+
+ g_assert (GTK_IS_ENTRY (widget));
+
+ valid = FALSE;
+ entry = GTK_ENTRY (widget);
+ text = gtk_entry_get_text (entry);
+
+ if (!*text)
+ {
+ widget_unset_error (widget);
+ return TRUE;
+ }
+
+ metric = strtol (text, &end, 10);
+
+ if (*end == '\0' &&
+ metric >= 0 && metric <= G_MAXUINT32)
+ valid = TRUE;
+
+ ce_entry_set_error (widget, !valid);
+
+ return valid;
+}
+
+static void
+ce_manual_entry_changed_cb (CeIpPage *self)
+{
+ const gchar *address, *gateway, *netmask;
+ gboolean valid, has_error;
+
+ g_assert (CE_IS_IP_PAGE (self));
+
+ if (!gtk_widget_get_visible (self->manual_list_box))
+ return;
+
+ address = gtk_entry_get_text (GTK_ENTRY (self->manual_address_entry));
+ gateway = gtk_entry_get_text (GTK_ENTRY (self->manual_gateway_entry));
+ netmask = gtk_entry_get_text (GTK_ENTRY (self->manual_netmask_entry));
+
+ if (!*address && !*gateway && !*netmask)
+ {
+ widget_set_error (self->manual_address_entry);
+ widget_set_error (self->manual_gateway_entry);
+ widget_set_error (self->manual_netmask_entry);
+
+ if (self->manual_status == MANUAL_IP_LOADED ||
+ self->manual_status == MANUAL_IP_MODIFIED)
+ self->manual_status = MANUAL_IP_DELETED;
+ else
+ self->manual_status = MANUAL_IP_NOT_CHANGED;
+
+ self->ip_has_error = TRUE;
+ goto end;
+ }
+
+ valid = ce_ip_entry_validate (self->manual_address_entry, self->version);
+ has_error = !valid;
+
+ valid = ce_ip_entry_validate (self->manual_gateway_entry, self->version);
+ has_error = has_error || !valid;
+
+ valid = ce_netmask_entry_validate (self->manual_netmask_entry, self->version);
+ has_error = has_error || !valid;
+
+ self->ip_has_error = has_error;
+ if (has_error)
+ goto end;
+
+ if (self->manual_status == MANUAL_IP_LOADED ||
+ self->manual_status == MANUAL_IP_DELETED)
+ self->manual_status = MANUAL_IP_MODIFIED;
+ else if (self->manual_status == MANUAL_IP_NOT_CHANGED)
+ self->manual_status = MANUAL_IP_ADDED;
+
+ end:
+ g_signal_emit (self, signals[CHANGED], 0);
+}
+
+static void
+ce_route_entry_changed_cb (CeIpPage *self)
+{
+ const gchar *address, *gateway, *netmask, *metric;
+ gboolean valid, has_error;
+
+ g_assert (CE_IS_IP_PAGE (self));
+
+ address = gtk_entry_get_text (GTK_ENTRY (self->route_address_entry));
+ gateway = gtk_entry_get_text (GTK_ENTRY (self->route_gateway_entry));
+ netmask = gtk_entry_get_text (GTK_ENTRY (self->route_netmask_entry));
+ metric = gtk_entry_get_text (GTK_ENTRY (self->route_metric_entry));
+
+ if (!*address && !*gateway && !*netmask && !*metric)
+ {
+ widget_unset_error (self->route_address_entry);
+ widget_unset_error (self->route_gateway_entry);
+ widget_unset_error (self->route_netmask_entry);
+ widget_unset_error (self->route_metric_entry);
+
+ if (self->route_status == ROUTE_LOADED ||
+ self->route_status == ROUTE_MODIFIED)
+ self->route_status = ROUTE_DELETED;
+ else
+ self->route_status = ROUTE_NOT_CHANGED;
+
+ self->route_has_error = FALSE;
+ goto end;
+ }
+
+ valid = ce_ip_entry_validate (self->route_address_entry, self->version);
+ has_error = !valid;
+
+ valid = ce_ip_entry_validate (self->route_gateway_entry, self->version);
+ has_error = has_error || !valid;
+
+ valid = ce_netmask_entry_validate (self->route_netmask_entry, self->version);
+ has_error = has_error || !valid;
+
+ valid = ce_metric_entry_validate (self->route_metric_entry);
+ has_error = has_error || !valid;
+
+ self->route_has_error = has_error;
+ if (has_error)
+ goto end;
+
+ if (self->route_status == ROUTE_LOADED ||
+ self->route_status == ROUTE_DELETED)
+ self->route_status = ROUTE_MODIFIED;
+ else if (self->route_status == ROUTE_NOT_CHANGED)
+ self->route_status = ROUTE_ADDED;
+
+ end:
+ g_signal_emit (self, signals[CHANGED], 0);
+}
+
+static void
+ce_dns_entry_changed_cb (CeIpPage *self)
+{
+ GtkStyleContext *style_context;
+ const gchar *ip;
+ gboolean valid;
+
+ g_assert (CE_IS_IP_PAGE (self));
+
+ ip = gtk_entry_get_text (GTK_ENTRY (self->dns_entry));
+ style_context = gtk_widget_get_style_context (self->dns_entry);
+
+ valid = nm_utils_ipaddr_valid (self->version, ip);
+ gtk_widget_set_sensitive (self->add_button, valid);
+
+ if (valid || *ip == '\0')
+ gtk_style_context_remove_class (style_context, "error");
+ else
+ gtk_style_context_add_class (style_context, "error");
+
+}
+
+static void
+ce_ip_page_finalize (GObject *object)
+{
+ CeIpPage *self = (CeIpPage *)object;
+
+ g_clear_object (&self->ip_methods);
+ g_clear_object (&self->device);
+ g_clear_object (&self->connection);
+ g_clear_object (&self->nm_client);
+
+ G_OBJECT_CLASS (ce_ip_page_parent_class)->finalize (object);
+}
+
+static void
+ce_ip_page_class_init (CeIpPageClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+ object_class->finalize = ce_ip_page_finalize;
+
+ signals[CHANGED] =
+ g_signal_new ("changed",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_FIRST,
+ 0, NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ gtk_widget_class_set_template_from_resource (widget_class,
+ "/org/gnome/control-center/"
+ "network/ce-ip-page.ui");
+
+ gtk_widget_class_bind_template_child (widget_class, CeIpPage, auto_list_box);
+ gtk_widget_class_bind_template_child (widget_class, CeIpPage, ip_method_row);
+ gtk_widget_class_bind_template_child (widget_class, CeIpPage, auto_dns_row);
+ gtk_widget_class_bind_template_child (widget_class, CeIpPage, auto_route_row);
+
+ gtk_widget_class_bind_template_child (widget_class, CeIpPage, manual_list_box);
+ gtk_widget_class_bind_template_child (widget_class, CeIpPage, manual_address_entry);
+ gtk_widget_class_bind_template_child (widget_class, CeIpPage, manual_netmask_entry);
+ gtk_widget_class_bind_template_child (widget_class, CeIpPage, manual_gateway_entry);
+
+ gtk_widget_class_bind_template_child (widget_class, CeIpPage, dns_list_box);
+ gtk_widget_class_bind_template_child (widget_class, CeIpPage, dns_title_label);
+ gtk_widget_class_bind_template_child (widget_class, CeIpPage, add_dns_row);
+
+ gtk_widget_class_bind_template_child (widget_class, CeIpPage, add_dns_dialog);
+ gtk_widget_class_bind_template_child (widget_class, CeIpPage, add_button);
+ gtk_widget_class_bind_template_child (widget_class, CeIpPage, dns_entry);
+
+ gtk_widget_class_bind_template_child (widget_class, CeIpPage, route_list_box);
+ gtk_widget_class_bind_template_child (widget_class, CeIpPage, route_title_label);
+ gtk_widget_class_bind_template_child (widget_class, CeIpPage, route_address_entry);
+ gtk_widget_class_bind_template_child (widget_class, CeIpPage, route_netmask_entry);
+ gtk_widget_class_bind_template_child (widget_class, CeIpPage, route_gateway_entry);
+ gtk_widget_class_bind_template_child (widget_class, CeIpPage, route_metric_entry);
+
+ gtk_widget_class_bind_template_callback (widget_class, ip_method_changed_cb);
+ gtk_widget_class_bind_template_callback (widget_class, ip_auto_row_changed_cb);
+ gtk_widget_class_bind_template_callback (widget_class, dns_row_activated_cb);
+ gtk_widget_class_bind_template_callback (widget_class, ce_manual_entry_changed_cb);
+ gtk_widget_class_bind_template_callback (widget_class, ce_route_entry_changed_cb);
+ gtk_widget_class_bind_template_callback (widget_class, ce_dns_entry_changed_cb);
+}
+
+static void
+ce_ip_page_init (CeIpPage *self)
+{
+ gtk_widget_init_template (GTK_WIDGET (self));
+ self->ip_methods = g_list_store_new (HDY_TYPE_VALUE_OBJECT);
+
+ hdy_combo_row_bind_name_model (HDY_COMBO_ROW (self->ip_method_row),
+ G_LIST_MODEL (self->ip_methods),
+ (HdyComboRowGetNameFunc) hdy_value_object_dup_string,
+ NULL, NULL);
+
+ gtk_list_box_set_header_func (GTK_LIST_BOX (self->auto_list_box),
+ cc_list_box_update_header_func,
+ NULL, NULL);
+
+ gtk_list_box_set_header_func (GTK_LIST_BOX (self->dns_list_box),
+ cc_list_box_update_header_func,
+ NULL, NULL);
+
+ gtk_list_box_set_header_func (GTK_LIST_BOX (self->route_list_box),
+ cc_list_box_update_header_func,
+ NULL, NULL);
+}
+
+void
+ce_ip_page_set_parent_window (CeIpPage *self,
+ GtkWindow *parent_window)
+{
+ g_return_if_fail (CE_IS_IP_PAGE (self));
+ g_return_if_fail (GTK_IS_WINDOW (parent_window));
+
+ gtk_window_set_transient_for (GTK_WINDOW (self->add_dns_dialog),
+ parent_window);
+}
+
+void
+ce_ip_page_set_version (CeIpPage *self,
+ gint version)
+{
+ g_return_if_fail (CE_IS_IP_PAGE (self));
+ g_return_if_fail (version != AF_INET || version != AF_INET6);
+
+ self->version = version;
+
+ ce_ip_page_populate_methods (self);
+
+ if (version == AF_INET6)
+ g_object_set (self->ip_method_row, "title", _("IPv6 Method"), NULL);
+ else
+ g_object_set (self->ip_method_row, "title", _("IPv4 Method"), NULL);
+}
+
+void
+ce_ip_page_set_connection (CeIpPage *self,
+ NMConnection *connection,
+ NMDevice *device)
+{
+ g_return_if_fail (CE_IS_IP_PAGE (self));
+ g_return_if_fail (self->version != 0);
+ g_return_if_fail (NM_IS_CONNECTION (connection));
+ g_return_if_fail (NM_IS_DEVICE (device));
+
+ g_set_object (&self->connection, connection);
+ g_set_object (&self->device, device);
+
+ if (self->version == AF_INET)
+ self->ip_setting = NM_SETTING (nm_connection_get_setting_ip4_config (connection));
+ if (self->version == AF_INET6)
+ self->ip_setting = NM_SETTING (nm_connection_get_setting_ip6_config (connection));
+
+ if (!self->ip_setting)
+ {
+ if (self->version == AF_INET)
+ self->ip_setting = nm_setting_ip4_config_new ();
+ else if (self->version == AF_INET6)
+ self->ip_setting = nm_setting_ip6_config_new ();
+ else /* Debug */
+ g_return_if_reached ();
+ nm_connection_add_setting (connection, self->ip_setting);
+
+ g_signal_emit (self, signals[CHANGED], 0);
+ }
+
+ g_object_bind_property (self->ip_setting, "ignore-auto-dns",
+ self->auto_dns_row, "active",
+ G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL | G_BINDING_INVERT_BOOLEAN);
+
+ g_object_bind_property (self->ip_setting, "ignore-auto-routes",
+ self->auto_route_row, "active",
+ G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL | G_BINDING_INVERT_BOOLEAN);
+}
+
+void
+ce_ip_page_refresh (CeIpPage *self)
+{
+ NMSettingIPConfig *setting;
+
+ g_return_if_fail (CE_IS_IP_PAGE (self));
+ g_return_if_fail (self->version != 0);
+
+ if (self->device && self->connection)
+ ce_ip_page_update_device_ip (self);
+
+ ip_auto_row_changed_cb (self, NULL, self->auto_dns_row);
+ ip_auto_row_changed_cb (self, NULL, self->auto_route_row);
+
+ setting = NM_SETTING_IP_CONFIG (self->ip_setting);
+ gtk_container_foreach (GTK_CONTAINER (self->dns_list_box),
+ (GtkCallback)ce_ip_page_remove_dns_row, self);
+
+ for (int i = 0; i < nm_setting_ip_config_get_num_dns (setting); i++)
+ {
+ const char *dns;
+
+ dns = nm_setting_ip_config_get_dns (setting, i);
+ ce_ip_page_add_dns_row (self, dns);
+ }
+
+ /* Reset entries */
+ gtk_entry_set_text (GTK_ENTRY (self->route_address_entry), "");
+ gtk_entry_set_text (GTK_ENTRY (self->route_netmask_entry), "");
+ gtk_entry_set_text (GTK_ENTRY (self->route_gateway_entry), "");
+ gtk_entry_set_text (GTK_ENTRY (self->route_metric_entry), "");
+
+ gtk_entry_set_text (GTK_ENTRY (self->manual_address_entry), "");
+ gtk_entry_set_text (GTK_ENTRY (self->manual_netmask_entry), "");
+ gtk_entry_set_text (GTK_ENTRY (self->manual_gateway_entry), "");
+
+ /* We set only one route, the rest is ignored */
+ if (nm_setting_ip_config_get_num_routes (setting) > 0)
+ {
+ NMIPRoute *route;
+ gchar *str;
+ gint64 metric;
+ guint32 prefix;
+
+ route = nm_setting_ip_config_get_route (setting, 0);
+ gtk_entry_set_text (GTK_ENTRY (self->route_address_entry),
+ nm_ip_route_get_dest (route));
+ gtk_entry_set_text (GTK_ENTRY (self->route_gateway_entry),
+ nm_ip_route_get_next_hop (route));
+ self->route_status = ROUTE_LOADED;
+
+ prefix = nm_ip_route_get_prefix (route);
+ if (self->version == AF_INET)
+ {
+ gchar text_netmask[INET_ADDRSTRLEN + 1];
+ gulong netmask;
+
+ netmask = nm_utils_ip4_prefix_to_netmask (prefix);
+ inet_ntop (AF_INET, &netmask, text_netmask, sizeof (text_netmask));
+ gtk_entry_set_text (GTK_ENTRY (self->route_netmask_entry), text_netmask);
+ }
+ else
+ {
+ str = g_strdup_printf ("%ud", prefix);
+ gtk_entry_set_text (GTK_ENTRY (self->route_netmask_entry), str);
+ g_free (str);
+ }
+
+ metric = nm_ip_route_get_metric (route);
+ if (metric >= 0)
+ {
+ str = g_strdup_printf ("%ld", metric);
+ gtk_entry_set_text (GTK_ENTRY (self->route_metric_entry), str);
+ g_free (str);
+ }
+ }
+
+ /* We set only one address, the rest is ignored */
+ if (nm_setting_ip_config_get_num_addresses (setting) > 0)
+ {
+ NMIPAddress *address;
+ gchar *str;
+ guint32 prefix;
+
+ address = nm_setting_ip_config_get_address (setting, 0);
+ gtk_entry_set_text (GTK_ENTRY (self->manual_address_entry),
+ nm_ip_address_get_address (address));
+ gtk_entry_set_text (GTK_ENTRY (self->manual_gateway_entry),
+ nm_setting_ip_config_get_gateway (setting));
+ self->manual_status = MANUAL_IP_LOADED;
+
+ prefix = nm_ip_address_get_prefix (address);
+ if (self->version == AF_INET)
+ {
+ gchar text_netmask[INET_ADDRSTRLEN + 1];
+ gulong netmask;
+
+ netmask = nm_utils_ip4_prefix_to_netmask (prefix);
+ inet_ntop (AF_INET, &netmask, text_netmask, sizeof (text_netmask));
+ gtk_entry_set_text (GTK_ENTRY (self->manual_netmask_entry), text_netmask);
+ }
+ else
+ {
+ str = g_strdup_printf ("%ud", prefix);
+ gtk_entry_set_text (GTK_ENTRY (self->manual_netmask_entry), str);
+ g_free (str);
+ }
+ }
+}
+
+static void
+ce_ip_page_save_routes (CeIpPage *self)
+{
+ g_autoptr(GPtrArray) routes = NULL;
+ NMSettingIPConfig *setting;
+ NMIPRoute *route, *new_route;
+ const gchar *address, *text_metric, *gateway;
+ gint64 metric;
+ guint prefix;
+
+ g_assert (CE_IS_IP_PAGE (self));
+
+ if (self->route_status == ROUTE_NOT_CHANGED)
+ return;
+
+ setting = NM_SETTING_IP_CONFIG (self->ip_setting);
+
+ /* If we modified the route, we delete it, and re-add */
+ if (self->route_status == ROUTE_DELETED ||
+ self->route_status == ROUTE_MODIFIED)
+ nm_setting_ip_config_remove_route (setting, 0);
+
+ if (self->route_status == ROUTE_DELETED)
+ return;
+
+ if (!parse_netmask (gtk_entry_get_text (GTK_ENTRY (self->route_netmask_entry)),
+ &prefix, self->version))
+ return;
+
+ address = gtk_entry_get_text (GTK_ENTRY (self->route_address_entry));
+ gateway = gtk_entry_get_text (GTK_ENTRY (self->route_gateway_entry));
+ text_metric = gtk_entry_get_text (GTK_ENTRY (self->route_metric_entry));
+
+ if (!text_metric || !*text_metric)
+ metric = -1;
+ else
+ metric = strtol (text_metric, NULL, 10);
+
+ /*
+ * We create a copy of all routes, and put the one we added/changed
+ * as the first item, so that it'll always be the first item.
+ */
+ routes = g_ptr_array_new_full (1, (GDestroyNotify)nm_ip_route_unref);
+ new_route = nm_ip_route_new (self->version, address, prefix, gateway, metric, NULL);
+ g_ptr_array_add (routes, new_route);
+
+ for (guint i = 0; i < nm_setting_ip_config_get_num_routes (setting); i++)
+ {
+ route = nm_setting_ip_config_get_route (setting, i);
+ nm_ip_route_ref (route);
+ g_ptr_array_add (routes, route); /* XXX: Safe? */
+ }
+
+ g_object_set (self->ip_setting,
+ NM_SETTING_IP_CONFIG_ROUTES, routes,
+ NULL);
+}
+
+static void
+ce_ip_page_save_manual_ips (CeIpPage *self)
+{
+ g_autoptr(GPtrArray) addresses = NULL;
+ NMSettingIPConfig *setting;
+ NMIPAddress *ip_address;
+ const gchar *address, *gateway;
+ guint prefix;
+
+ g_assert (CE_IS_IP_PAGE (self));
+
+ if (self->manual_status == ROUTE_NOT_CHANGED)
+ return;
+
+ setting = NM_SETTING_IP_CONFIG (self->ip_setting);
+
+ /* If we modified the route, we delete it, and re-add */
+ if (self->manual_status == ROUTE_DELETED ||
+ self->manual_status == ROUTE_MODIFIED)
+ nm_setting_ip_config_remove_address (setting, 0);
+
+ if (self->manual_status == ROUTE_DELETED)
+ return;
+
+ if (!parse_netmask (gtk_entry_get_text (GTK_ENTRY (self->manual_netmask_entry)),
+ &prefix, self->version))
+ return;
+
+ address = gtk_entry_get_text (GTK_ENTRY (self->manual_address_entry));
+ gateway = gtk_entry_get_text (GTK_ENTRY (self->manual_gateway_entry));
+
+ /*
+ * We create a copy of all address, and put the one we added/changed
+ * as the first item, so that it'll always be the first item.
+ */
+ addresses = g_ptr_array_new_with_free_func ((GDestroyNotify) nm_ip_address_unref);
+
+ ip_address = nm_ip_address_new (self->version, address, prefix, NULL);
+ g_ptr_array_add (addresses, ip_address);
+
+ for (guint i = 0; i < nm_setting_ip_config_get_num_addresses (setting); i++)
+ {
+ ip_address = nm_setting_ip_config_get_address (setting, i);
+ nm_ip_address_ref (ip_address);
+ g_ptr_array_add (addresses, ip_address); /* XXX: Safe? */
+ }
+
+ g_object_set (setting,
+ NM_SETTING_IP_CONFIG_ADDRESSES, addresses,
+ NM_SETTING_IP_CONFIG_GATEWAY, gateway,
+ NULL);
+}
+
+gboolean
+ce_ip_page_has_error (CeIpPage *self)
+{
+ g_return_val_if_fail (CE_IS_IP_PAGE (self), TRUE);
+
+ if (self->route_has_error ||
+ (self->ip_has_error && g_str_equal (self->current_method, "manual")))
+ return TRUE;
+
+ return FALSE;
+}
+
+void
+ce_ip_page_save_connection (CeIpPage *self)
+{
+ g_autoptr(HdyValueObject) object = NULL;
+ const gchar *method;
+ gint index;
+
+ g_return_if_fail (CE_IS_IP_PAGE (self));
+ g_return_if_fail (self->version != 0);
+
+ if (ce_ip_page_has_error (self))
+ return;
+
+ index = hdy_combo_row_get_selected_index (HDY_COMBO_ROW (self->ip_method_row));
+ object = g_list_model_get_item (G_LIST_MODEL (self->ip_methods), index);
+ method = g_object_get_data (G_OBJECT (object), "value");
+
+ if (method)
+ g_object_set (self->ip_setting, "method", method, NULL);
+ else
+ g_warn_if_reached ();
+
+ ce_ip_page_save_routes (self);
+ ce_ip_page_save_manual_ips (self);
+}
diff --git a/panels/network/connection-editor/ce-ip-page.h b/panels/network/connection-editor/ce-ip-page.h
new file mode 100644
index 000000000..9877a80d8
--- /dev/null
+++ b/panels/network/connection-editor/ce-ip-page.h
@@ -0,0 +1,47 @@
+/* -*- Mode: C; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* ce-ip-page.h
+ *
+ * Copyright 2021 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 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/>.
+ *
+ * Author(s):
+ * Mohammed Sadiq <sadiq@sadiqpk.org>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#pragma once
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define CE_TYPE_IP_PAGE (ce_ip_page_get_type())
+G_DECLARE_FINAL_TYPE (CeIpPage, ce_ip_page, CE, IP_PAGE, GtkScrolledWindow)
+
+void ce_ip_page_set_parent_window (CeIpPage *self,
+ GtkWindow *parent_window);
+void ce_ip_page_set_nm_client (CeIpPage *self,
+ NMClient *nm_client);
+void ce_ip_page_set_version (CeIpPage *self,
+ gint version);
+void ce_ip_page_set_connection (CeIpPage *self,
+ NMConnection *connection,
+ NMDevice *device);
+gboolean ce_ip_page_has_error (CeIpPage *self);
+void ce_ip_page_save_connection (CeIpPage *self);
+void ce_ip_page_refresh (CeIpPage *self);
+
+G_END_DECLS
diff --git a/panels/network/connection-editor/ce-ip-page.ui b/panels/network/connection-editor/ce-ip-page.ui
new file mode 100644
index 000000000..115e5fe1c
--- /dev/null
+++ b/panels/network/connection-editor/ce-ip-page.ui
@@ -0,0 +1,421 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <template class="CeIpPage" parent="GtkScrolledWindow">
+
+ <child>
+ <object class="HdyClamp">
+ <property name="visible">1</property>
+ <property name="margin-top">32</property>
+ <property name="margin-bottom">32</property>
+ <child>
+ <object class="GtkBox">
+ <property name="visible">1</property>
+ <property name="orientation">vertical</property>
+ <property name="margin-start">12</property>
+ <property name="margin-end">12</property>
+ <child>
+ <object class="GtkListBox" id="auto_list_box">
+ <property name="visible">1</property>
+ <property name="margin-bottom">32</property>
+ <property name="selection-mode">none</property>
+ <style>
+ <class name="frame" />
+ </style>
+
+ <!-- IP Method -->
+ <child>
+ <object class="HdyComboRow" id="ip_method_row">
+ <property name="visible">1</property>
+ <signal name="notify::selected-index" handler="ip_method_changed_cb" swapped="yes" />
+ </object>
+ </child>
+
+ <!-- Automatic DNS Row -->
+ <child>
+ <object class="CcListRow" id="auto_dns_row">
+ <property name="visible">1</property>
+ <property name="show-switch">1</property>
+ <property name="title" translatable="yes">Automatic DNS</property>
+ <signal name="notify::active" handler="ip_auto_row_changed_cb" object="CeIpPage" after="yes" swapped="yes" />
+ </object>
+ </child>
+
+ <!-- Automatic Route Row -->
+ <child>
+ <object class="CcListRow" id="auto_route_row">
+ <property name="visible">1</property>
+ <property name="sensitive" bind-source="auto_dns_row" bind-property="sensitive" />
+ <property name="show-switch">1</property>
+ <property name="title" translatable="yes">Automatic Routing</property>
+ <signal name="notify::active" handler="ip_auto_row_changed_cb" object="CeIpPage" after="yes" swapped="yes" />
+ </object>
+ </child>
+ </object>
+ </child>
+
+ <!-- Manual IP title -->
+ <child>
+ <object class="GtkLabel" id="manual_title_label">
+ <property name="visible" bind-source="manual_list_box" bind-property="visible" />
+ <property name="margin-bottom">12</property>
+ <property name="halign">start</property>
+ <property name="label" translatable="yes" context="network connection editor">Address</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ </child>
+
+ <!-- Route Settings -->
+ <child>
+ <object class="GtkListBox" id="manual_list_box">
+ <property name="visible">0</property>
+ <property name="margin-bottom">32</property>
+ <property name="selection-mode">none</property>
+ <signal name="notify::visible" handler="ce_manual_entry_changed_cb" object="CeIpPage" swapped="yes" />
+ <style>
+ <class name="frame" />
+ </style>
+
+ <!-- Address row -->
+ <child>
+ <object class="GtkListBoxRow">
+ <property name="visible">1</property>
+ <child>
+ <object class="GtkBox">
+ <property name="visible">1</property>
+ <property name="hexpand">1</property>
+ <property name="margin">12</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">1</property>
+ <property name="hexpand">1</property>
+ <property name="halign">start</property>
+ <property name="label" translatable="yes" context="network connection editor">Address</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkEntry" id="manual_address_entry">
+ <property name="visible">1</property>
+ <property name="halign">end</property>
+ <signal name="changed" handler="ce_manual_entry_changed_cb" object="CeIpPage" swapped="yes" />
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+
+ <!-- Netmask row -->
+ <child>
+ <object class="GtkListBoxRow">
+ <property name="visible">1</property>
+ <child>
+ <object class="GtkBox">
+ <property name="visible">1</property>
+ <property name="hexpand">1</property>
+ <property name="margin">12</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">1</property>
+ <property name="hexpand">1</property>
+ <property name="halign">start</property>
+ <property name="label" translatable="yes">Netmask</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkEntry" id="manual_netmask_entry">
+ <property name="visible">1</property>
+ <property name="halign">end</property>
+ <signal name="changed" handler="ce_manual_entry_changed_cb" object="CeIpPage" swapped="yes" />
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+
+ <!-- Gateway row -->
+ <child>
+ <object class="GtkListBoxRow">
+ <property name="visible">1</property>
+ <child>
+ <object class="GtkBox">
+ <property name="visible">1</property>
+ <property name="hexpand">1</property>
+ <property name="margin">12</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">1</property>
+ <property name="hexpand">1</property>
+ <property name="halign">start</property>
+ <property name="label" translatable="yes">Gateway</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkEntry" id="manual_gateway_entry">
+ <property name="visible">1</property>
+ <property name="halign">end</property>
+ <signal name="changed" handler="ce_manual_entry_changed_cb" object="CeIpPage" swapped="yes" />
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+
+ </object>
+ </child>
+
+ <!-- DNS settings title -->
+ <child>
+ <object class="GtkLabel" id="dns_title_label">
+ <property name="visible">1</property>
+ <property name="sensitive" bind-source="dns_list_box" bind-property="sensitive" />
+ <property name="margin-bottom">12</property>
+ <property name="halign">start</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ </child>
+
+ <!-- DNS Settings -->
+ <child>
+ <object class="GtkListBox" id="dns_list_box">
+ <property name="visible">1</property>
+ <property name="margin-bottom">32</property>
+ <property name="selection-mode">none</property>
+ <signal name="row-activated" handler="dns_row_activated_cb" object="CeIpPage" swapped="yes" />
+ <style>
+ <class name="frame" />
+ </style>
+ <child>
+ <object class="GtkListBoxRow" id="add_dns_row">
+ <property name="visible">1</property>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">1</property>
+ <property name="margin">12</property>
+ <property name="halign">center</property>
+ <property name="label" translatable="yes">Add DNS Server...</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+
+ <!-- Route title -->
+ <child>
+ <object class="GtkLabel" id="route_title_label">
+ <property name="visible">1</property>
+ <property name="sensitive" bind-source="dns_list_box" bind-property="sensitive" />
+ <property name="margin-bottom">12</property>
+ <property name="halign">start</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ </child>
+
+ <!-- Route Settings -->
+ <child>
+ <object class="GtkListBox" id="route_list_box">
+ <property name="visible">1</property>
+ <property name="sensitive" bind-source="dns_list_box" bind-property="sensitive" />
+ <property name="selection-mode">none</property>
+ <style>
+ <class name="frame" />
+ </style>
+
+ <!-- Address row -->
+ <child>
+ <object class="GtkListBoxRow">
+ <property name="visible">1</property>
+ <child>
+ <object class="GtkBox">
+ <property name="visible">1</property>
+ <property name="hexpand">1</property>
+ <property name="margin">12</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">1</property>
+ <property name="hexpand">1</property>
+ <property name="halign">start</property>
+ <property name="label" translatable="yes" context="network connection editor">Address</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkEntry" id="route_address_entry">
+ <property name="visible">1</property>
+ <property name="halign">end</property>
+ <signal name="changed" handler="ce_route_entry_changed_cb" object="CeIpPage" swapped="yes" />
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+
+ <!-- Netmask row -->
+ <child>
+ <object class="GtkListBoxRow">
+ <property name="visible">1</property>
+ <child>
+ <object class="GtkBox">
+ <property name="visible">1</property>
+ <property name="hexpand">1</property>
+ <property name="margin">12</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">1</property>
+ <property name="hexpand">1</property>
+ <property name="halign">start</property>
+ <property name="label" translatable="yes">Netmask</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkEntry" id="route_netmask_entry">
+ <property name="visible">1</property>
+ <property name="halign">end</property>
+ <signal name="changed" handler="ce_route_entry_changed_cb" object="CeIpPage" swapped="yes" />
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+
+ <!-- Gateway row -->
+ <child>
+ <object class="GtkListBoxRow">
+ <property name="visible">1</property>
+ <child>
+ <object class="GtkBox">
+ <property name="visible">1</property>
+ <property name="hexpand">1</property>
+ <property name="margin">12</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">1</property>
+ <property name="hexpand">1</property>
+ <property name="halign">start</property>
+ <property name="label" translatable="yes">Gateway</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkEntry" id="route_gateway_entry">
+ <property name="visible">1</property>
+ <property name="halign">end</property>
+ <signal name="changed" handler="ce_route_entry_changed_cb" object="CeIpPage" swapped="yes" />
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+
+ <!-- Metric row -->
+ <child>
+ <object class="GtkListBoxRow">
+ <property name="visible">1</property>
+ <child>
+ <object class="GtkBox">
+ <property name="visible">1</property>
+ <property name="hexpand">1</property>
+ <property name="margin">12</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">1</property>
+ <property name="hexpand">1</property>
+ <property name="halign">start</property>
+ <property name="label" translatable="yes">Metric</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkEntry" id="route_metric_entry">
+ <property name="visible">1</property>
+ <property name="halign">end</property>
+ <signal name="changed" handler="ce_route_entry_changed_cb" object="CeIpPage" swapped="yes" />
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+
+ </object>
+ </child>
+
+ </object>
+ </child>
+ </object>
+ </child>
+ </template>
+
+ <object class="GtkDialog" id="add_dns_dialog">
+ <property name="visible">0</property>
+ <property name="modal">1</property>
+ <property name="use-header-bar">1</property>
+ <property name="default-height">24</property>
+ <signal name="delete-event" handler="gtk_widget_hide_on_delete"/>
+
+ <child internal-child="vbox">
+ <object class="GtkBox">
+ <property name="border-width">0</property>
+ <property name="margin-top">18</property>
+ <property name="margin-start">12</property>
+ <property name="margin-end">12</property>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">1</property>
+ <property name="margin-bottom">18</property>
+ <property name="wrap">1</property>
+ <property name="max-width-chars">35</property>
+ <property name="xalign">0.0</property>
+ <property name="label" translatable="yes">Add a custom Domain Name System (DNS) server for resolving IP Addresses.</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkEntry" id="dns_entry">
+ <property name="visible">1</property>
+ <signal name="changed" handler="ce_dns_entry_changed_cb" object="CeIpPage" swapped="yes" />
+ </object>
+ </child>
+ </object>
+ </child>
+
+ <!-- Add button -->
+ <child type="action">
+ <object class="GtkButton" id="add_button">
+ <property name="visible">1</property>
+ <property name="sensitive">0</property>
+ <property name="can-default">1</property>
+ <property name="use-underline">1</property>
+ <property name="label" translatable="yes">_Add</property>
+ </object>
+ </child>
+
+ <!-- Cancel button -->
+ <child type="action">
+ <object class="GtkButton" id="cancel_button">
+ <property name="visible">1</property>
+ <property name="use-underline">1</property>
+ <property name="label" translatable="yes">_Cancel</property>
+ </object>
+ </child>
+
+ <action-widgets>
+ <action-widget response="apply" default="true">add_button</action-widget>
+ <action-widget response="cancel">cancel_button</action-widget>
+ </action-widgets>
+ </object>
+</interface>
diff --git a/panels/network/connection-editor/ce-password-row.c b/panels/network/connection-editor/ce-password-row.c
new file mode 100644
index 000000000..6802bc4b5
--- /dev/null
+++ b/panels/network/connection-editor/ce-password-row.c
@@ -0,0 +1,194 @@
+/* -*- Mode: C; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* ce-password-row.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 "ce-password-row"
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+#include <glib/gi18n.h>
+#include <NetworkManager.h>
+
+#define HANDY_USE_UNSTABLE_API
+#include <handy.h>
+
+#include "ui-helpers.h"
+#include "list-box-helper.h"
+#include "ce-password-row.h"
+
+/**
+ * @short_description: A widget to show passwords
+ * @include: "ce-password-row.h"
+ *
+ * A #GtkListBoxRow to show/modify passwords
+ */
+
+struct _CePasswordRow
+{
+ GtkListBoxRow parent_instance;
+
+ GtkLabel *password_mask_label;
+ GtkToggleButton *password_button;
+ GtkImage *button_image;
+ GtkEntry *password_entry;
+ gchar *password;
+};
+
+G_DEFINE_TYPE (CePasswordRow, ce_password_row, GTK_TYPE_LIST_BOX_ROW)
+
+enum {
+ CHANGED,
+ N_SIGNALS
+};
+
+static guint signals[N_SIGNALS];
+
+static void
+password_entry_changed_cb (CePasswordRow *self)
+{
+ g_autofree gchar *text = NULL;
+ guint16 length;
+
+ g_assert (CE_IS_PASSWORD_ROW (self));
+
+ length = gtk_entry_get_text_length (self->password_entry);
+
+ if (length > 0)
+ {
+ GString *str = g_string_sized_new (length * 2 + 1);
+
+ /* Limit to 10 char */
+ if (length > 10)
+ length = 10;
+
+ while (length--)
+ g_string_append (str, "●");
+
+ text = g_string_free (str, FALSE);
+ }
+ else
+ text = g_strdup ("");
+
+ gtk_label_set_text (self->password_mask_label, text);
+
+ g_signal_emit (self, signals[CHANGED], 0);
+}
+
+static void
+ce_password_row_finalize (GObject *object)
+{
+ CePasswordRow *self = (CePasswordRow *)object;
+
+ ce_password_row_clear_password (self);
+
+ G_OBJECT_CLASS (ce_password_row_parent_class)->finalize (object);
+}
+
+static void
+ce_password_row_class_init (CePasswordRowClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+ object_class->finalize = ce_password_row_finalize;
+
+ signals[CHANGED] =
+ g_signal_new ("changed",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_FIRST,
+ 0, NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ gtk_widget_class_set_template_from_resource (widget_class,
+ "/org/gnome/control-center/"
+ "network/ce-password-row.ui");
+
+ gtk_widget_class_bind_template_child (widget_class, CePasswordRow, password_mask_label);
+ gtk_widget_class_bind_template_child (widget_class, CePasswordRow, password_button);
+ gtk_widget_class_bind_template_child (widget_class, CePasswordRow, button_image);
+ gtk_widget_class_bind_template_child (widget_class, CePasswordRow, password_entry);
+
+ gtk_widget_class_bind_template_callback (widget_class, password_entry_changed_cb);
+}
+
+static void
+ce_password_row_init (CePasswordRow *self)
+{
+ gtk_widget_init_template (GTK_WIDGET (self));
+}
+
+void
+ce_password_row_set_password (CePasswordRow *self,
+ const gchar *password)
+{
+ g_return_if_fail (CE_IS_PASSWORD_ROW (self));
+
+ if (!password)
+ password = "";
+
+ gtk_entry_set_text (self->password_entry, password);
+}
+
+const gchar *
+ce_password_row_get_password (CePasswordRow *self)
+{
+ g_return_val_if_fail (CE_IS_PASSWORD_ROW (self), "");
+
+ return gtk_entry_get_text (self->password_entry);
+}
+
+void
+ce_password_row_clear_password (CePasswordRow *self)
+{
+ g_return_if_fail (CE_IS_PASSWORD_ROW (self));
+
+ if (!self->password)
+ return;
+
+ memset (self->password, 0, strlen (self->password));
+ g_free (self->password);
+}
+
+void
+ce_password_row_set_visible (CePasswordRow *self,
+ gboolean visible)
+{
+ g_return_if_fail (CE_IS_PASSWORD_ROW (self));
+
+ gtk_toggle_button_set_active (self->password_button, visible);
+}
+
+void
+ce_password_row_set_error (CePasswordRow *self,
+ gboolean error)
+{
+ g_return_if_fail (CE_IS_PASSWORD_ROW (self));
+
+ if (error)
+ gtk_style_context_add_class (gtk_widget_get_style_context (self->password_entry), "error");
+ else
+ gtk_style_context_remove_class (gtk_widget_get_style_context (self->password_entry), "error");
+}
diff --git a/panels/network/connection-editor/ce-password-row.h b/panels/network/connection-editor/ce-password-row.h
new file mode 100644
index 000000000..3bcc46baf
--- /dev/null
+++ b/panels/network/connection-editor/ce-password-row.h
@@ -0,0 +1,43 @@
+/* -*- Mode: C; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* ce-password-row.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>
+
+G_BEGIN_DECLS
+
+#define CE_TYPE_PASSWORD_ROW (ce_password_row_get_type())
+G_DECLARE_FINAL_TYPE (CePasswordRow, ce_password_row, CE, PASSWORD_ROW, GtkListBoxRow)
+
+void ce_password_row_set_password (CePasswordRow *self,
+ const gchar *password);
+const gchar *ce_password_row_get_password (CePasswordRow *self);
+void ce_password_row_clear_password (CePasswordRow *self);
+void ce_password_row_set_visible (CePasswordRow *self,
+ gboolean visible);
+void ce_password_row_set_error (CePasswordRow *self,
+ gboolean error);
+
+G_END_DECLS
diff --git a/panels/network/connection-editor/ce-password-row.ui b/panels/network/connection-editor/ce-password-row.ui
new file mode 100644
index 000000000..cf3f19083
--- /dev/null
+++ b/panels/network/connection-editor/ce-password-row.ui
@@ -0,0 +1,65 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <template class="CePasswordRow" parent="GtkListBoxRow">
+ <child>
+ <object class="GtkGrid">
+ <property name="visible">1</property>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">1</property>
+ <property name="hexpand">1</property>
+ <property name="halign">start</property>
+ <property name="margin-start">12</property>
+ <property name="label" translatable="yes">Password</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel" id="password_mask_label">
+ <property name="visible">1</property>
+ <property name="halign">end</property>
+ <style>
+ <class name="dim-label" />
+ </style>
+ </object>
+ </child>
+ <child>
+ <object class="GtkToggleButton" id="password_button">
+ <property name="visible">1</property>
+ <property name="margin">6</property>
+ <child>
+ <object class="GtkImage" id="button_image">
+ <property name="visible">1</property>
+ <property name="icon-name">view-layout-symbolic</property>
+ </object>
+ </child>
+ </object>
+ </child>
+
+ <child>
+ <object class="GtkSeparator">
+ <property name="visible" bind-source="password_button" bind-property="active" />
+ </object>
+ <packing>
+ <property name="left-attach">0</property>
+ <property name="top-attach">1</property>
+ <property name="width">3</property>
+ </packing>
+ </child>
+
+ <child>
+ <object class="GtkEntry" id="password_entry">
+ <property name="visible" bind-source="password_button" bind-property="active" />
+ <property name="margin">6</property>
+ <signal name="changed" handler="password_entry_changed_cb" swapped="yes" />
+ </object>
+ <packing>
+ <property name="left-attach">0</property>
+ <property name="top-attach">2</property>
+ <property name="width">3</property>
+ </packing>
+ </child>
+
+ </object>
+ </child>
+ </template>
+</interface>
diff --git a/panels/network/connection-editor/ce-security-page.c b/panels/network/connection-editor/ce-security-page.c
new file mode 100644
index 000000000..c73bdcbeb
--- /dev/null
+++ b/panels/network/connection-editor/ce-security-page.c
@@ -0,0 +1,925 @@
+/* -*- Mode: C; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* ce-security-page.c
+ *
+ * Copyright 2020 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 "ce-security-page"
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+#include <glib/gi18n.h>
+#include <NetworkManager.h>
+
+#define HANDY_USE_UNSTABLE_API
+#include <handy.h>
+
+#include "ui-helpers.h"
+#include "list-box-helper.h"
+#include "ce-security-page.h"
+#include "ce-password-row.h"
+
+/**
+ * @short_description: The Security page for a connection
+ * @include: "ce-security-page.h"
+ *
+ * Show Security settings related to a network connection
+ */
+
+struct _CeSecurityPage
+{
+ GtkBox parent_instance;
+
+ GtkWidget *main_list_box;
+ HdyComboRow *connection_list_row;
+ GListStore *connection_list;
+ GtkBox *name_box;
+ GtkEntry *name_entry;
+
+ HdyComboRow *security_method_row;
+ GListStore *security_methods;
+ gchar *current_method;
+
+ CePasswordRow *wpa_password_row;
+ CePasswordRow *wep_password_row;
+ GtkEntry *leap_username_entry;
+ CePasswordRow *leap_password_row;
+ GtkWidget *wep_index_row;
+ GtkWidget *wep_authentication_row;
+ GListStore *wep_indexes;
+ GListStore *wep_authentications;
+
+ GListStore *wpa2_enterprise_auth_list;
+
+ NMClient *nm_client;
+ NMDevice *device;
+ NMConnection *connection;
+ NMConnection *orig_connection;
+ NMSetting *setting;
+ NMUtilsSecurityType security_type;
+
+ gboolean has_error;
+ gboolean allow_create;
+};
+
+G_DEFINE_TYPE (CeSecurityPage, ce_security_page, GTK_TYPE_BOX)
+
+enum {
+ CHANGED,
+ N_SIGNALS
+};
+
+static guint signals[N_SIGNALS];
+
+enum {
+ PROP_0,
+ PROP_ALLOW_CREATE,
+ N_PROPS
+};
+
+static GParamSpec *properties[N_PROPS];
+
+static gboolean
+security_has_proto (NMSettingWirelessSecurity *sec, const char *item)
+{
+ g_assert (sec);
+ g_assert (item);
+
+ for (guint32 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 void
+wep_password_changed_cb (CeSecurityPage *self)
+{
+ g_autoptr(HdyValueObject) object = NULL;
+ const gchar *method, *password;
+ gint index;
+ gboolean valid = FALSE;
+
+ g_assert (CE_IS_SECURITY_PAGE (self));
+
+ index = hdy_combo_row_get_selected_index (HDY_COMBO_ROW (self->security_method_row));
+ object = g_list_model_get_item (G_LIST_MODEL (self->security_methods), index);
+ method = g_object_get_data (G_OBJECT (object), "value");
+ password = ce_password_row_get_password (self->wep_password_row);
+
+ if (password && *password)
+ {
+ if (g_str_equal (method, "wep"))
+ valid = nm_utils_wep_key_valid (password, NM_WEP_KEY_TYPE_KEY);
+ else
+ valid = nm_utils_wep_key_valid (password, NM_WEP_KEY_TYPE_PASSPHRASE);
+ }
+
+ ce_password_row_set_error (self->wep_password_row, !valid);
+ self->has_error = !valid;
+}
+
+static void
+wpa_password_changed_cb (CeSecurityPage *self)
+{
+ const gchar *password;
+ gboolean valid = FALSE;
+
+ g_assert (CE_IS_SECURITY_PAGE (self));
+
+ password = ce_password_row_get_password (self->wpa_password_row);
+ if (password && *password)
+ valid = nm_utils_wpa_psk_valid (password);
+
+ ce_password_row_set_error (self->wpa_password_row, !valid);
+ self->has_error = !valid;
+}
+
+static void
+leap_password_changed_cb (CeSecurityPage *self)
+{
+ const gchar *password;
+ gboolean valid = TRUE;
+ gsize len;
+
+ g_assert (CE_IS_SECURITY_PAGE (self));
+
+ password = ce_password_row_get_password (self->leap_password_row);
+ len = password ? strlen (password) : 0;
+
+ if ((len < 8) || (len > 64))
+ valid = FALSE;
+ else if (len == 64)
+ for (gsize i = 0; i < len; i++)
+ if (!g_ascii_isxdigit (password[i]))
+ {
+ valid = FALSE;
+ break;
+ }
+
+ ce_password_row_set_error (self->wpa_password_row, !valid);
+ self->has_error = !valid;
+}
+
+/* Modified from ce-page-security.c */
+static NMUtilsSecurityType
+security_page_get_type (CeSecurityPage *self)
+{
+ NMSettingWirelessSecurity *setting;
+ const char *key_mgmt, *auth_alg;
+
+ g_assert (CE_IS_SECURITY_PAGE (self));
+ g_return_val_if_fail (self->connection, NMU_SEC_NONE);
+
+ setting = nm_connection_get_setting_wireless_security (self->connection);
+
+ if (!setting)
+ return NMU_SEC_NONE;
+
+ key_mgmt = nm_setting_wireless_security_get_key_mgmt (setting);
+ auth_alg = nm_setting_wireless_security_get_auth_alg (setting);
+
+ /* 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 (!strcmp (key_mgmt, "wpa-none") ||
+ !strcmp (key_mgmt, "wpa-psk"))
+ {
+ if (security_has_proto (setting, "rsn"))
+ return NMU_SEC_WPA2_PSK;
+ else
+ return NMU_SEC_WPA_PSK;
+ }
+
+ if (!strcmp (key_mgmt, "wpa-eap"))
+ {
+ if (security_has_proto (setting, "rsn"))
+ return NMU_SEC_WPA2_ENTERPRISE;
+ else
+ return NMU_SEC_WPA_ENTERPRISE;
+ }
+
+ return NMU_SEC_INVALID;
+}
+
+static void
+connection_list_item_changed_cb (CeSecurityPage *self)
+{
+ g_autoptr(HdyValueObject) object = NULL;
+ NMConnection *connection;
+ gint index;
+
+ g_assert (CE_IS_SECURITY_PAGE (self));
+
+ index = hdy_combo_row_get_selected_index (HDY_COMBO_ROW (self->connection_list_row));
+ if (index == -1)
+ return;
+
+ /* New connection */
+ if (index == 0)
+ {
+ gtk_widget_set_sensitive (GTK_WIDGET (self->name_box), TRUE);
+ gtk_entry_set_text (self->name_entry, "");
+ ce_password_row_set_password (self->wpa_password_row, "");
+ ce_password_row_set_password (self->wep_password_row, "");
+ ce_password_row_set_password (self->leap_password_row, "");
+ gtk_entry_set_text (self->leap_username_entry, "");
+ hdy_combo_row_set_selected_index (self->security_method_row, 0);
+
+ return;
+ }
+
+ object = g_list_model_get_item (G_LIST_MODEL (self->connection_list), index);
+ connection = g_object_get_data (G_OBJECT (object), "value");
+ g_assert (connection);
+ g_set_object (&self->connection, connection);
+ g_set_object (&self->orig_connection, connection);
+
+ gtk_entry_set_text (self->name_entry, nm_connection_get_id (connection));
+ ce_security_page_refresh (self);
+
+ gtk_widget_set_sensitive (GTK_WIDGET (self->name_box), FALSE);
+ gtk_widget_set_sensitive (GTK_WIDGET (self->security_method_row), FALSE);
+}
+
+static void
+security_page_modified_cb (CeSecurityPage *self)
+{
+ g_autoptr(HdyValueObject) object = NULL;
+ gchar *method;
+ gint index;
+
+ g_assert (CE_IS_SECURITY_PAGE (self));
+
+ index = hdy_combo_row_get_selected_index (HDY_COMBO_ROW (self->security_method_row));
+ if (index == -1)
+ return;
+
+ object = g_list_model_get_item (G_LIST_MODEL (self->security_methods), index);
+ g_assert (G_IS_OBJECT (object));
+ method = g_object_get_data (G_OBJECT (object), "value");
+
+ self->has_error = FALSE;
+
+ /* Widgets are hidden separately (instead of hiding them all and showing)
+ * so that currently focused widget state wont change
+ */
+ if (g_str_equal (method, "wpa") ||
+ g_str_equal (method, "wpa3-personal"))
+ {
+ gtk_widget_hide (GTK_WIDGET (self->wep_password_row));
+ gtk_widget_hide (GTK_WIDGET (self->leap_password_row));
+
+ gtk_widget_show (GTK_WIDGET (self->wpa_password_row));
+ wpa_password_changed_cb (self);
+ }
+ else if (g_str_equal (method, "leap"))
+ {
+ gtk_widget_hide (GTK_WIDGET (self->wpa_password_row));
+ gtk_widget_hide (GTK_WIDGET (self->wep_password_row));
+
+ gtk_widget_show (GTK_WIDGET (self->leap_password_row));
+ leap_password_changed_cb (self);
+ }
+ else if (g_str_has_prefix (method, "wep"))
+ {
+ gtk_widget_hide (GTK_WIDGET (self->wpa_password_row));
+ gtk_widget_hide (GTK_WIDGET (self->leap_password_row));
+
+ gtk_widget_show (GTK_WIDGET (self->wep_password_row));
+ wep_password_changed_cb (self);
+ }
+ else if (g_str_has_prefix (method, "wpa-enterprise"))
+ {
+ gtk_widget_hide (GTK_WIDGET (self->wpa_password_row));
+ gtk_widget_hide (GTK_WIDGET (self->leap_password_row));
+
+ gtk_widget_show (GTK_WIDGET (self->wep_password_row));
+ wep_password_changed_cb (self);
+ }
+ else
+ {
+ gtk_widget_hide (GTK_WIDGET (self->wpa_password_row));
+ gtk_widget_hide (GTK_WIDGET (self->wep_password_row));
+ gtk_widget_hide (GTK_WIDGET (self->leap_password_row));
+ }
+
+ if (self->allow_create && !self->has_error)
+ {
+ const gchar *name;
+
+ name = gtk_entry_get_text (self->name_entry);
+
+ if (!name || !*name)
+ self->has_error = TRUE;
+ }
+
+ g_signal_emit (self, signals[CHANGED], 0);
+}
+
+static void
+ce_security_page_load_wpa (CeSecurityPage *self,
+ NMSettingWirelessSecurity *setting)
+{
+ const gchar *password;
+
+ g_assert (CE_IS_SECURITY_PAGE (self));
+ g_assert (self->security_type == NMU_SEC_WPA_PSK ||
+ self->security_type == NMU_SEC_WPA2_PSK);
+
+ password = nm_setting_wireless_security_get_psk (setting);
+
+ ce_password_row_set_password (self->wpa_password_row, password);
+ hdy_combo_row_set_selected_index (HDY_COMBO_ROW (self->security_method_row), 4);
+}
+
+static void
+ce_security_page_load_wep (CeSecurityPage *self,
+ NMSettingWirelessSecurity *setting)
+{
+ const gchar *auth, *password;
+ NMWepKeyType type;
+ gint index;
+
+ g_assert (CE_IS_SECURITY_PAGE (self));
+ g_assert (self->security_type == NMU_SEC_STATIC_WEP);
+
+ type = nm_setting_wireless_security_get_wep_key_type (setting);
+ auth = nm_setting_wireless_security_get_auth_alg (setting);
+
+ if (type == NM_WEP_KEY_TYPE_PASSPHRASE)
+ hdy_combo_row_set_selected_index (HDY_COMBO_ROW (self->security_method_row), 3);
+ else
+ hdy_combo_row_set_selected_index (HDY_COMBO_ROW (self->security_method_row), 2);
+
+ if (auth && g_str_equal (auth, "shared"))
+ hdy_combo_row_set_selected_index (HDY_COMBO_ROW (self->wep_authentication_row), 1);
+ else
+ hdy_combo_row_set_selected_index (HDY_COMBO_ROW (self->wep_authentication_row), 0);
+
+ index = nm_setting_wireless_security_get_wep_tx_keyidx (setting);
+ hdy_combo_row_set_selected_index (HDY_COMBO_ROW (self->wep_index_row), index);
+
+ password = nm_setting_wireless_security_get_wep_key (setting, index);
+ ce_password_row_set_password (self->wep_password_row, password);
+}
+
+static void
+ce_security_page_load_leap (CeSecurityPage *self,
+ NMSettingWirelessSecurity *setting)
+{
+ const char *password, *username;
+
+ g_assert (CE_IS_SECURITY_PAGE (self));
+ g_assert (self->security_type == NMU_SEC_LEAP);
+
+ username = nm_setting_wireless_security_get_leap_username (setting);
+ password = nm_setting_wireless_security_get_leap_password (setting);
+
+ ce_password_row_set_password (self->leap_password_row, password);
+ gtk_entry_set_text (self->leap_username_entry, username);
+ hdy_combo_row_set_selected_index (HDY_COMBO_ROW (self->security_method_row), 3);
+}
+
+static void
+ce_security_page_update (CeSecurityPage *self)
+{
+ NMSettingWirelessSecurity *setting;
+ NMUtilsSecurityType security_type;
+
+ g_assert (CE_IS_SECURITY_PAGE (self));
+
+ setting = nm_connection_get_setting_wireless_security (self->connection);
+ security_type = security_page_get_type (self);
+ self->security_type = security_type;
+
+ if (security_type != NMU_SEC_NONE &&
+ NM_IS_REMOTE_CONNECTION (self->orig_connection))
+ {
+ g_autoptr(GVariant) secrets = NULL;
+ g_autoptr(GError) error = NULL;
+
+ secrets = nm_remote_connection_get_secrets (NM_REMOTE_CONNECTION (self->orig_connection),
+ NM_SETTING_WIRELESS_SECURITY_SETTING_NAME,
+ NULL, &error);
+ if (!error)
+ nm_connection_update_secrets (self->connection,
+ NM_SETTING_WIRELESS_SECURITY_SETTING_NAME,
+ secrets, &error);
+ if (error)
+ g_warning ("Error: %s", error->message);
+ }
+
+ if (security_type == NMU_SEC_NONE)
+ hdy_combo_row_set_selected_index (HDY_COMBO_ROW (self->security_method_row), 0);
+ else if (security_type == NMU_SEC_WPA_PSK ||
+ security_type == NMU_SEC_WPA2_PSK)
+ ce_security_page_load_wpa (self, setting);
+ else if (security_type == NMU_SEC_STATIC_WEP)
+ ce_security_page_load_wep (self, setting);
+ else if (security_type == NMU_SEC_LEAP)
+ ce_security_page_load_leap (self, setting);
+ else
+ g_return_if_reached ();
+
+ nm_connection_clear_secrets (self->connection);
+ security_page_modified_cb (self);
+}
+
+static void
+ce_security_page_populate_connection_list (CeSecurityPage *self)
+{
+ HdyValueObject *object;
+ const GPtrArray *connections;
+ g_autoptr(GPtrArray) valid_connections = NULL;
+
+ g_assert (CE_IS_SECURITY_PAGE (self));
+
+ if (!self->nm_client || !self->device)
+ return;
+
+ if (!gtk_widget_get_visible (GTK_WIDGET (self->connection_list_row)))
+ return;
+
+ object = hdy_value_object_new_string (_("New"));
+ g_object_set_data (G_OBJECT (object), "value", NULL);
+ g_list_store_append (self->connection_list, object);
+ g_object_unref (object);
+
+ connections = nm_client_get_connections (self->nm_client);
+ valid_connections = nm_device_filter_connections (self->device, connections);
+
+ for (int i = 0; i < valid_connections->len; i++)
+ {
+ NMConnection *connection;
+ g_autofree gchar *mac_address = NULL;
+ g_autofree gchar *item = NULL;
+
+ connection = valid_connections->pdata[i];
+
+ object = hdy_value_object_new_string (nm_connection_get_id (connection));
+ g_object_set_data_full (G_OBJECT (object), "value",
+ g_object_ref (connection), g_object_unref);
+ g_list_store_append (self->connection_list, object);
+ g_object_unref (object);
+ }
+}
+
+static void
+ce_security_page_populate_stores (CeSecurityPage *self)
+{
+ HdyValueObject *object;
+
+ g_assert (CE_IS_SECURITY_PAGE (self));
+
+ /* NOTE: Dont change the order items are added. Itll break the code */
+
+ /* Security methods */
+ object = hdy_value_object_new_string (_("None"));
+ g_object_set_data (G_OBJECT (object), "value", "off");
+ g_list_store_append (self->security_methods, object);
+ g_object_unref (object);
+
+ object = hdy_value_object_new_string (_("WEP 40/128-bit Key (Hex or ASCII)"));
+ g_object_set_data (G_OBJECT (object), "value", "wep");
+ g_list_store_append (self->security_methods, object);
+ g_object_unref (object);
+
+ object = hdy_value_object_new_string (_("WEP 128-bit Passphrase"));
+ g_object_set_data (G_OBJECT (object), "value", "wep128");
+ g_list_store_append (self->security_methods, object);
+ g_object_unref (object);
+
+ object = hdy_value_object_new_string (_("LEAP"));
+ g_object_set_data (G_OBJECT (object), "value", "leap");
+ g_list_store_append (self->security_methods, object);
+ g_object_unref (object);
+
+ object = hdy_value_object_new_string (_("WPA & WPA2 Personal"));
+ g_object_set_data (G_OBJECT (object), "value", "wpa");
+ g_list_store_append (self->security_methods, object);
+ g_object_unref (object);
+
+ /* object = hdy_value_object_new_string (_("WPA3 Personal")); */
+ /* g_object_set_data (G_OBJECT (object), "value", "wpa3-personal"); */
+ /* g_list_store_append (self->security_methods, object); */
+ /* g_object_unref (object); */
+
+ /* object = hdy_value_object_new_string (_("WPA & WPA2 Enterprise")); */
+ /* g_object_set_data (G_OBJECT (object), "value", "wpa-enterprise"); */
+ /* g_list_store_append (self->security_methods, object); */
+ /* g_object_unref (object); */
+
+
+ /* WEP Indexes */
+ object = hdy_value_object_new_string (_("1 (Default)"));
+ g_list_store_append (self->wep_indexes, object);
+ g_object_unref (object);
+
+ object = hdy_value_object_new_string ("2");
+ g_list_store_append (self->wep_indexes, object);
+ g_object_unref (object);
+
+ object = hdy_value_object_new_string ("3");
+ g_list_store_append (self->wep_indexes, object);
+ g_object_unref (object);
+
+ object = hdy_value_object_new_string ("4");
+ g_list_store_append (self->wep_indexes, object);
+ g_object_unref (object);
+
+
+ /* WEP Authentications */
+ object = hdy_value_object_new_string (_("Open System"));
+ g_list_store_append (self->wep_authentications, object);
+ g_object_unref (object);
+
+ object = hdy_value_object_new_string (_("Shared Key"));
+ g_list_store_append (self->wep_authentications, object);
+ g_object_unref (object);
+
+
+ /* WPA & WPA2 Enterprise */
+ object = hdy_value_object_new_string (_("TLS"));
+ g_object_set_data (G_OBJECT (object), "value", "tls");
+ g_list_store_append (self->wpa2_enterprise_auth_list, object);
+ g_object_unref (object);
+
+ object = hdy_value_object_new_string (_("LEAP"));
+ g_object_set_data (G_OBJECT (object), "value", "leap");
+ g_list_store_append (self->wpa2_enterprise_auth_list, object);
+ g_object_unref (object);
+
+ object = hdy_value_object_new_string (_("PWD"));
+ g_object_set_data (G_OBJECT (object), "value", "pwd");
+ g_list_store_append (self->wpa2_enterprise_auth_list, object);
+ g_object_unref (object);
+
+ object = hdy_value_object_new_string (_("FAST"));
+ g_object_set_data (G_OBJECT (object), "value", "fast");
+ g_list_store_append (self->wpa2_enterprise_auth_list, object);
+ g_object_unref (object);
+
+ object = hdy_value_object_new_string (_("Tunneled TLS"));
+ g_object_set_data (G_OBJECT (object), "value", "tunneled-tls");
+ g_list_store_append (self->wpa2_enterprise_auth_list, object);
+ g_object_unref (object);
+
+ object = hdy_value_object_new_string (_("Protected EAP (PEAP)"));
+ g_object_set_data (G_OBJECT (object), "value", "peap");
+ g_list_store_append (self->wpa2_enterprise_auth_list, object);
+ g_object_unref (object);
+}
+
+static void
+wep_index_changed_cb (CeSecurityPage *self)
+{
+ g_assert (CE_IS_SECURITY_PAGE (self));
+
+ g_signal_emit (self, signals[CHANGED], 0);
+}
+
+static void
+wep_authentication_changed_cb (CeSecurityPage *self)
+{
+ g_assert (CE_IS_SECURITY_PAGE (self));
+
+ g_signal_emit (self, signals[CHANGED], 0);
+}
+
+static void
+ce_security_page_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ CeSecurityPage *self = CE_SECURITY_PAGE (object);
+
+ switch (prop_id)
+ {
+ case PROP_ALLOW_CREATE:
+ self->allow_create = g_value_get_boolean (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+ce_security_page_constructed (GObject *object)
+{
+ CeSecurityPage *self = CE_SECURITY_PAGE (object);
+
+ if (self->allow_create)
+ {
+ /* When allow_create set, the name will be empty, and thus set error */
+ self->has_error = TRUE;
+ gtk_entry_set_text (self->name_entry, "");
+ hdy_combo_row_set_selected_index (self->security_method_row, 0);
+ gtk_widget_show (GTK_WIDGET (self->connection_list_row));
+ }
+
+ G_OBJECT_CLASS (ce_security_page_parent_class)->constructed (object);
+}
+
+static void
+ce_security_page_finalize (GObject *object)
+{
+ CeSecurityPage *self = CE_SECURITY_PAGE (object);
+
+ g_clear_object (&self->security_methods);
+ g_clear_object (&self->wep_indexes);
+ g_clear_object (&self->wep_authentications);
+
+ g_clear_object (&self->device);
+ g_clear_object (&self->connection);
+ g_clear_object (&self->orig_connection);
+ g_clear_object (&self->nm_client);
+
+ G_OBJECT_CLASS (ce_security_page_parent_class)->finalize (object);
+}
+
+static void
+ce_security_page_class_init (CeSecurityPageClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+ object_class->set_property = ce_security_page_set_property;
+ object_class->constructed = ce_security_page_constructed;
+ object_class->finalize = ce_security_page_finalize;
+
+ properties[PROP_ALLOW_CREATE] =
+ g_param_spec_boolean ("allow-create",
+ "Allow Create",
+ "Allow Creating new connection",
+ FALSE,
+ G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY |G_PARAM_STATIC_STRINGS);
+
+ g_object_class_install_properties (object_class, N_PROPS, properties);
+
+ signals[CHANGED] =
+ g_signal_new ("changed",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_FIRST,
+ 0, NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ gtk_widget_class_set_template_from_resource (widget_class,
+ "/org/gnome/control-center/"
+ "network/ce-security-page.ui");
+
+ gtk_widget_class_bind_template_child (widget_class, CeSecurityPage, main_list_box);
+ gtk_widget_class_bind_template_child (widget_class, CeSecurityPage, connection_list_row);
+ gtk_widget_class_bind_template_child (widget_class, CeSecurityPage, name_box);
+ gtk_widget_class_bind_template_child (widget_class, CeSecurityPage, name_entry);
+
+ gtk_widget_class_bind_template_child (widget_class, CeSecurityPage, security_method_row);
+ gtk_widget_class_bind_template_child (widget_class, CeSecurityPage, wpa_password_row);
+ gtk_widget_class_bind_template_child (widget_class, CeSecurityPage, leap_username_entry);
+ gtk_widget_class_bind_template_child (widget_class, CeSecurityPage, leap_password_row);
+ gtk_widget_class_bind_template_child (widget_class, CeSecurityPage, wep_password_row);
+ gtk_widget_class_bind_template_child (widget_class, CeSecurityPage, wep_index_row);
+ gtk_widget_class_bind_template_child (widget_class, CeSecurityPage, wep_authentication_row);
+
+ gtk_widget_class_bind_template_callback (widget_class, connection_list_item_changed_cb);
+ gtk_widget_class_bind_template_callback (widget_class, security_page_modified_cb);
+
+ gtk_widget_class_bind_template_callback (widget_class, wep_index_changed_cb);
+ gtk_widget_class_bind_template_callback (widget_class, wep_authentication_changed_cb);
+
+ g_type_ensure (CE_TYPE_PASSWORD_ROW);
+}
+
+static void
+ce_security_page_init (CeSecurityPage *self)
+{
+ gtk_widget_init_template (GTK_WIDGET (self));
+
+ self->connection_list = g_list_store_new (HDY_TYPE_VALUE_OBJECT);
+ hdy_combo_row_bind_name_model (HDY_COMBO_ROW (self->connection_list_row),
+ G_LIST_MODEL (self->connection_list),
+ (HdyComboRowGetNameFunc) hdy_value_object_dup_string,
+ NULL, NULL);
+
+ self->wpa2_enterprise_auth_list = g_list_store_new (HDY_TYPE_VALUE_OBJECT);
+ self->security_methods = g_list_store_new (HDY_TYPE_VALUE_OBJECT);
+ self->wep_indexes = g_list_store_new (HDY_TYPE_VALUE_OBJECT);
+ self->wep_authentications = g_list_store_new (HDY_TYPE_VALUE_OBJECT);
+ ce_security_page_populate_stores (self);
+
+ hdy_combo_row_bind_name_model (HDY_COMBO_ROW (self->security_method_row),
+ G_LIST_MODEL (self->security_methods),
+ (HdyComboRowGetNameFunc) hdy_value_object_dup_string,
+ NULL, NULL);
+
+ hdy_combo_row_bind_name_model (HDY_COMBO_ROW (self->wep_index_row),
+ G_LIST_MODEL (self->wep_indexes),
+ (HdyComboRowGetNameFunc) hdy_value_object_dup_string,
+ NULL, NULL);
+
+ hdy_combo_row_bind_name_model (HDY_COMBO_ROW (self->wep_authentication_row),
+ G_LIST_MODEL (self->wep_authentications),
+ (HdyComboRowGetNameFunc) hdy_value_object_dup_string,
+ NULL, NULL);
+
+ /* gtk_list_box_set_header_func (GTK_LIST_BOX (self->main_list_box), */
+ /* cc_list_box_update_header_func, */
+ /* NULL, NULL); */
+}
+
+void
+ce_security_page_set_nm_client (CeSecurityPage *self,
+ NMClient *nm_client)
+{
+ g_return_if_fail (CE_IS_SECURITY_PAGE (self));
+ g_return_if_fail (NM_IS_CLIENT (nm_client));
+
+ g_set_object (&self->nm_client, nm_client);
+}
+
+void
+ce_security_page_set_connection (CeSecurityPage *self,
+ NMConnection *orig_connection,
+ NMConnection *connection,
+ NMDevice *device)
+{
+ g_return_if_fail (CE_IS_SECURITY_PAGE (self));
+ g_return_if_fail (!connection || NM_IS_CONNECTION (connection));
+ g_return_if_fail (!orig_connection || NM_IS_CONNECTION (orig_connection));
+ g_return_if_fail (NM_IS_DEVICE (device));
+
+ g_set_object (&self->connection, connection);
+ g_set_object (&self->orig_connection, orig_connection);
+ g_set_object (&self->device, device);
+
+ ce_security_page_populate_connection_list (self);
+}
+
+void
+ce_security_page_refresh (CeSecurityPage *self)
+{
+ g_return_if_fail (CE_IS_SECURITY_PAGE (self));
+
+ ce_password_row_set_visible (self->wpa_password_row, FALSE);
+ ce_security_page_update (self);
+}
+
+void
+ce_security_page_save_connection (CeSecurityPage *self)
+{
+ NMSetting *setting;
+ g_autoptr(HdyValueObject) object = NULL;
+ const gchar *method, *password;
+ gint index;
+
+ g_return_if_fail (CE_IS_SECURITY_PAGE (self));
+
+ if (!self->connection)
+ {
+ NMRemoteConnection *connection;
+ g_autoptr(GBytes) ssid = NULL;
+ g_autofree char *id = NULL;
+ const gchar *str;
+ int i = 0;
+
+ g_return_if_fail (self->allow_create);
+
+ self->connection = nm_simple_connection_new ();
+ setting = nm_setting_wireless_new ();
+ str = gtk_entry_get_text (self->name_entry);
+ ssid = g_bytes_new_static (str, strlen (str));
+ g_object_set (setting, "ssid", ssid, NULL);
+ nm_connection_add_setting (self->connection, setting);
+
+ setting = nm_setting_connection_new ();
+
+ id = g_strdup (gtk_entry_get_text (self->name_entry));
+ connection = nm_client_get_connection_by_id (self->nm_client, id);
+
+ /* Add a number suffix until we get a unique id */
+ while (connection)
+ {
+ i++;
+
+ g_free (id);
+ id = g_strdup_printf ("%s %d", gtk_entry_get_text (self->name_entry), i);
+ connection = nm_client_get_connection_by_id (self->nm_client, id);
+ }
+
+ g_object_set (setting, "id", id, NULL);
+ nm_connection_add_setting (self->connection, setting);
+ }
+
+ index = hdy_combo_row_get_selected_index (HDY_COMBO_ROW (self->security_method_row));
+ if (index == -1 || self->connection == NULL)
+ g_return_if_reached ();
+
+ nm_connection_remove_setting (self->connection, NM_TYPE_SETTING_WIRELESS_SECURITY);
+ nm_connection_remove_setting (self->connection, NM_TYPE_SETTING_802_1X);
+
+ if (index == 0)
+ return;
+
+ object = g_list_model_get_item (G_LIST_MODEL (self->security_methods), index);
+ g_assert (G_IS_OBJECT (object));
+ method = g_object_get_data (G_OBJECT (object), "value");
+
+ setting = nm_setting_wireless_security_new ();
+ nm_connection_add_setting (self->connection, setting);
+ nm_setting_set_secret_flags (setting, NM_SETTING_WIRELESS_SECURITY_PSK,
+ NM_SETTING_SECRET_FLAG_NONE, NULL);
+ if (g_str_equal (method, "leap"))
+ {
+ const char *username;
+
+ username = gtk_entry_get_text (self->leap_username_entry);
+ password = ce_password_row_get_password (self->leap_password_row);
+
+ g_object_set (setting,
+ NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, "ieee8021x",
+ NM_SETTING_WIRELESS_SECURITY_AUTH_ALG, "leap",
+ NM_SETTING_WIRELESS_SECURITY_LEAP_PASSWORD, password,
+ NM_SETTING_WIRELESS_SECURITY_LEAP_USERNAME, username,
+ NULL);
+ }
+ else if (g_str_equal (method, "wpa"))
+ {
+ password = ce_password_row_get_password (self->wpa_password_row);
+
+ g_object_set (setting, NM_SETTING_WIRELESS_SECURITY_PSK, password, NULL);
+ g_object_set (setting, NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, "wpa-psk", NULL);
+ }
+ else if (g_str_equal (method, "wpa3-personal"))
+ {
+ password = ce_password_row_get_password (self->wpa_password_row);
+
+ g_object_set (setting, NM_SETTING_WIRELESS_SECURITY_PSK, password, NULL);
+ g_object_set (setting, NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, "sae", NULL);
+ }
+ else if (g_str_has_prefix (method, "wep"))
+ {
+ if (g_str_equal (method, "wep"))
+ g_object_set (setting, NM_SETTING_WIRELESS_SECURITY_WEP_KEY_TYPE, NM_WEP_KEY_TYPE_KEY, NULL);
+ else
+ g_object_set (setting, NM_SETTING_WIRELESS_SECURITY_WEP_KEY_TYPE, NM_WEP_KEY_TYPE_PASSPHRASE, NULL);
+
+ index = hdy_combo_row_get_selected_index (HDY_COMBO_ROW (self->wep_authentication_row));
+ g_object_set (setting, "auth-alg", (index == 0) ? "open" : "shared", NULL);
+
+ password = ce_password_row_get_password (self->wep_password_row);
+ index = hdy_combo_row_get_selected_index (HDY_COMBO_ROW (self->wep_index_row));
+ nm_setting_wireless_security_set_wep_key (NM_SETTING_WIRELESS_SECURITY (setting), index, password);
+
+ g_object_set (setting,
+ NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, "none",
+ NM_SETTING_WIRELESS_SECURITY_WEP_TX_KEYIDX, index,
+ NULL);
+ }
+}
+
+NMConnection *
+ce_security_page_get_connection (CeSecurityPage *self)
+{
+ g_return_val_if_fail (CE_IS_SECURITY_PAGE (self), NULL);
+
+ return self->connection;
+}
+
+gboolean
+ce_security_page_has_error (CeSecurityPage *self)
+{
+ g_return_val_if_fail (CE_IS_SECURITY_PAGE (self), TRUE);
+
+ return self->has_error;
+}
diff --git a/panels/network/connection-editor/ce-security-page.h b/panels/network/connection-editor/ce-security-page.h
new file mode 100644
index 000000000..d64990fca
--- /dev/null
+++ b/panels/network/connection-editor/ce-security-page.h
@@ -0,0 +1,45 @@
+/* -*- Mode: C; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* ce-security-page.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>
+
+G_BEGIN_DECLS
+
+#define CE_TYPE_SECURITY_PAGE (ce_security_page_get_type())
+G_DECLARE_FINAL_TYPE (CeSecurityPage, ce_security_page, CE, SECURITY_PAGE, GtkBox)
+
+void ce_security_page_set_nm_client (CeSecurityPage *self,
+ NMClient *nm_client);
+void ce_security_page_set_connection (CeSecurityPage *self,
+ NMConnection *orig_connection,
+ NMConnection *connection,
+ NMDevice *device);
+void ce_security_page_save_connection (CeSecurityPage *self);
+NMConnection *ce_security_page_get_connection (CeSecurityPage *self);
+void ce_security_page_refresh (CeSecurityPage *self);
+gboolean ce_security_page_has_error (CeSecurityPage *self);
+
+G_END_DECLS
diff --git a/panels/network/connection-editor/ce-security-page.ui b/panels/network/connection-editor/ce-security-page.ui
new file mode 100644
index 000000000..200b873af
--- /dev/null
+++ b/panels/network/connection-editor/ce-security-page.ui
@@ -0,0 +1,273 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <template class="CeSecurityPage" parent="GtkBox">
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkListBox" id="main_list_box">
+ <property name="visible">1</property>
+ <property name="margin-top">32</property>
+ <property name="margin-bottom">32</property>
+ <property name="selection-mode">none</property>
+ <style>
+ <class name="content" />
+ </style>
+
+ <!-- Connection List -->
+ <child>
+ <object class="HdyComboRow" id="connection_list_row">
+ <property name="visible">0</property>
+ <property name="title" translatable="yes">Connection</property>
+ <signal name="notify::selected-index" handler="connection_list_item_changed_cb" swapped="yes" />
+ </object>
+ </child>
+
+ <!-- Network Name -->
+ <child>
+ <object class="GtkListBoxRow">
+ <property name="visible" bind-source="connection_list_row" bind-property="visible" bind-flags="sync-create" />
+ <child>
+ <object class="GtkBox" id="name_box">
+ <property name="visible">1</property>
+ <property name="margin">12</property>
+ <property name="spacing">12</property>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">1</property>
+ <property name="hexpand">1</property>
+ <property name="halign">start</property>
+ <property name="label" translatable="yes">Network Name</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkEntry" id="name_entry">
+ <property name="visible">1</property>
+ <signal name="changed" handler="security_page_modified_cb" swapped="yes" />
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+
+ <!-- Security Method -->
+ <child>
+ <object class="HdyComboRow" id="security_method_row">
+ <property name="visible">1</property>
+ <property name="sensitive" bind-source="name_box" bind-property="sensitive"/>
+ <property name="title" translatable="yes">Security</property>
+ <signal name="notify::selected-index" handler="security_page_modified_cb" swapped="yes" />
+ </object>
+ </child>
+
+ <!-- WPA/WPA2 Personal -->
+ <child>
+ <object class="CePasswordRow" id="wpa_password_row">
+ <property name="visible">0</property>
+ <property name="sensitive" bind-source="name_box" bind-property="sensitive" />
+ <signal name="changed" handler="security_page_modified_cb" swapped="yes" />
+ </object>
+ </child>
+
+ <!-- Leap Username -->
+ <child>
+ <object class="GtkListBoxRow">
+ <property name="visible" bind-source="leap_password_row" bind-property="visible" bind-flags="sync-create" />
+ <property name="sensitive" bind-source="name_box" bind-property="sensitive" />
+ <child>
+ <object class="GtkBox">
+ <property name="visible">1</property>
+ <property name="margin">12</property>
+ <property name="spacing">12</property>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">1</property>
+ <property name="hexpand">1</property>
+ <property name="halign">start</property>
+ <property name="label" translatable="yes">Username</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkEntry" id="leap_username_entry">
+ <property name="visible">1</property>
+ <signal name="changed" handler="security_page_modified_cb" swapped="yes" />
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+
+ <!-- Leap password -->
+ <child>
+ <object class="CePasswordRow" id="leap_password_row">
+ <property name="visible">0</property>
+ <property name="sensitive" bind-source="name_box" bind-property="sensitive" />
+ <signal name="changed" handler="security_page_modified_cb" swapped="yes" />
+ </object>
+ </child>
+
+ <!-- WEP -->
+ <child>
+ <object class="CePasswordRow" id="wep_password_row">
+ <property name="visible">0</property>
+ <property name="sensitive" bind-source="name_box" bind-property="sensitive" />
+ <signal name="changed" handler="security_page_modified_cb" swapped="yes" />
+ </object>
+ </child>
+
+ <!-- WEP index -->
+ <child>
+ <object class="HdyComboRow" id="wep_index_row">
+ <property name="visible" bind-source="wep_password_row" bind-property="visible" />
+ <property name="sensitive" bind-source="name_box" bind-property="sensitive" />
+ <property name="title" translatable="yes">WEP index</property>
+ <signal name="notify::selected-index" handler="wep_index_changed_cb" swapped="yes" />
+ </object>
+ </child>
+
+ <!-- WEP Authentication -->
+ <child>
+ <object class="HdyComboRow" id="wep_authentication_row">
+ <property name="visible" bind-source="wep_password_row" bind-property="visible" />
+ <property name="sensitive" bind-source="name_box" bind-property="sensitive" />
+ <property name="title" translatable="yes">WEP Authentication</property>
+ <signal name="notify::selected-index" handler="wep_authentication_changed_cb" swapped="yes" />
+ </object>
+ </child>
+
+ <!-- WPA/WPA2 Enterprise -->
+ <child>
+ <object class="HdyComboRow" id="wpa_enterprise_auth_row">
+ <property name="visible">False</property>
+ <property name="sensitive" bind-source="name_box" bind-property="sensitive" />
+ <property name="title" translatable="yes">Authentication</property>
+ <signal name="notify::selected-index" handler="wpa_enterprise_auth_changed_cb" swapped="yes" />
+ </object>
+ </child>
+
+ <!-- WPA/WPA2 Enterprise Identity -->
+ <child>
+ <object class="GtkListBoxRow">
+ <property name="visible" bind-source="wpa_enterprise_auth_row" bind-property="visible" />
+ <property name="sensitive" bind-source="name_box" bind-property="sensitive" />
+ <child>
+ <object class="GtkBox">
+ <property name="visible">1</property>
+ <property name="margin">12</property>
+ <property name="spacing">12</property>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">1</property>
+ <property name="hexpand">1</property>
+ <property name="halign">start</property>
+ <property name="label" translatable="yes">Identity</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkEntry" id="wpa_enterprise_identity_entry">
+ <property name="visible">1</property>
+ <signal name="changed" handler="security_page_modified_cb" swapped="yes" />
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+
+ <!-- WPA/WPA2 Enterprise Domain -->
+ <child>
+ <object class="GtkListBoxRow">
+ <property name="visible" bind-source="wpa_enterprise_auth_row" bind-property="visible" />
+ <property name="sensitive" bind-source="name_box" bind-property="sensitive" />
+ <child>
+ <object class="GtkBox">
+ <property name="visible">1</property>
+ <property name="margin">12</property>
+ <property name="spacing">12</property>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">1</property>
+ <property name="hexpand">1</property>
+ <property name="halign">start</property>
+ <property name="label" translatable="yes">Domain</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkEntry" id="wpa_enterprise_domain_entry">
+ <property name="visible">1</property>
+ <signal name="changed" handler="security_page_modified_cb" swapped="yes" />
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+
+ <!-- WPA/WPA2 Enterprise CA certificate -->
+ <child>
+ <object class="CcListRow" id="wpa_enterprise_cert_row">
+ <property name="visible" bind-source="wpa_enterprise_auth_row" bind-property="visible" bind-flags="sync-create" />
+ <property name="sensitive" bind-source="name_box" bind-property="sensitive" />
+ <property name="title" translatable="yes">CA certificate</property>
+ </object>
+ </child>
+
+ <!-- WPA/WPA2 Enterprise CA password -->
+ <child>
+ <object class="CePasswordRow" id="ca_enterprise_cert_password_row">
+ <property name="visible" bind-source="wpa_enterprise_auth_row" bind-property="visible" bind-flags="sync-create" />
+ <property name="sensitive" bind-source="name_box" bind-property="sensitive" />
+ <signal name="changed" handler="security_page_modified_cb" swapped="yes" />
+ </object>
+ </child>
+
+ <!-- WPA/WPA2 Enterprise No CA certificate required -->
+ <child>
+ <object class="CcListRow" id="wpa_enterprise_show_pass_row">
+ <property name="visible" bind-source="wpa_enterprise_auth_row" bind-property="visible" bind-flags="sync-create" />
+ <property name="sensitive" bind-source="name_box" bind-property="sensitive" />
+ <property name="show-switch">True</property>
+ <property name="title" translatable="yes">No CA Certificate required</property>
+ </object>
+ </child>
+
+ <!-- WPA/WPA2 Enterprise User certificate -->
+ <child>
+ <object class="CcListRow" id="wpa_enterprise_user_cert_row">
+ <property name="visible" bind-source="wpa_enterprise_auth_row" bind-property="visible" bind-flags="sync-create" />
+ <property name="sensitive" bind-source="name_box" bind-property="sensitive" />
+ <property name="title" translatable="yes">User Certificate</property>
+ </object>
+ </child>
+
+ <!-- WPA/WPA2 Enterprise User Cert pass -->
+ <child>
+ <object class="CePasswordRow" id="ca_enterprise_user_cert_pass_row">
+ <property name="visible" bind-source="wpa_enterprise_auth_row" bind-property="visible" bind-flags="sync-create" />
+ <property name="sensitive" bind-source="name_box" bind-property="sensitive" />
+ <signal name="changed" handler="security_page_modified_cb" swapped="yes" />
+ </object>
+ </child>
+
+ <!-- WPA/WPA2 Enterprise User private key-->
+ <child>
+ <object class="CcListRow" id="wpa_enterprise_user_priv_key_row">
+ <property name="visible" bind-source="wpa_enterprise_auth_row" bind-property="visible" bind-flags="sync-create" />
+ <property name="sensitive" bind-source="name_box" bind-property="sensitive" />
+ <property name="title" translatable="yes">User Private key</property>
+ </object>
+ </child>
+
+ <!-- WPA/WPA2 Enterprise User key pass -->
+ <child>
+ <object class="CePasswordRow" id="ca_enterprise_user_key_pass_row">
+ <property name="visible" bind-source="wpa_enterprise_auth_row" bind-property="visible" bind-flags="sync-create" />
+ <property name="sensitive" bind-source="name_box" bind-property="sensitive" />
+ <signal name="changed" handler="security_page_modified_cb" swapped="yes" />
+ </object>
+ </child>
+
+ </object>
+ </child>
+ </template>
+</interface>
diff --git a/panels/network/connection-editor/connection-editor.gresource.xml b/panels/network/connection-editor/connection-editor.gresource.xml
index 3d06f5a77..bec2ccdd3 100644
--- a/panels/network/connection-editor/connection-editor.gresource.xml
+++ b/panels/network/connection-editor/connection-editor.gresource.xml
@@ -1,6 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<gresources>
<gresource prefix="/org/gnome/control-center/network">
+ <file preprocess="xml-stripblanks">cc-connection-editor.ui</file>
+ <file preprocess="xml-stripblanks">ce-security-page.ui</file>
+ <file preprocess="xml-stripblanks">ce-details-page.ui</file>
+ <file preprocess="xml-stripblanks">ce-ip-page.ui</file>
+ <file preprocess="xml-stripblanks">ce-password-row.ui</file>
<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>
diff --git a/panels/network/connection-editor/meson.build b/panels/network/connection-editor/meson.build
index fd4ddf957..ff857b14c 100644
--- a/panels/network/connection-editor/meson.build
+++ b/panels/network/connection-editor/meson.build
@@ -1,6 +1,11 @@
name = 'connection-editor'
sources = files(
+ 'cc-connection-editor.c',
+ 'ce-security-page.c',
+ 'ce-details-page.c',
+ 'ce-ip-page.c',
+ 'ce-password-row.c',
'ce-ip-address-entry.c',
'ce-netmask-entry.c',
'ce-page-8021x-security.c',
@@ -17,6 +22,11 @@ sources = files(
)
resource_data = files(
+ 'cc-connection-editor.ui',
+ 'ce-security-page.ui',
+ 'ce-details-page.ui',
+ 'ce-ip-page.ui',
+ 'ce-password-row.ui',
'8021x-security-page.ui',
'connection-editor.ui',
'details-page.ui',
--
2.25.1