gnome-control-center/panels/network/connection-editor/net-connection-editor.c
Michael Catanzaro be535df39b Don't warn about failure to apply changes on inactive device
If the NMDevice is not active (i.e. if we are editing a connection that
is not active) then don't warn when failing to reapply changes to the
device. That's expected and not something we should warn about.

We could perhaps not even try, but the device could become inactive
between the time of our check and this error, so better ignore the error
regardless.
2023-08-30 08:44:55 +00:00

993 lines
35 KiB
C

/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
*
* Copyright (C) 2012 Red Hat, Inc
*
* Licensed under the GNU General Public License Version 2
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "config.h"
#include <glib-object.h>
#include <glib/gi18n.h>
#include <NetworkManager.h>
#include "net-connection-editor.h"
#include "net-connection-editor-resources.h"
#include "ce-page.h"
#include "ce-page-details.h"
#include "ce-page-wifi.h"
#include "ce-page-ip4.h"
#include "ce-page-ip6.h"
#include "ce-page-security.h"
#include "ce-page-ethernet.h"
#include "ce-page-8021x-security.h"
#include "ce-page-vpn.h"
#include "ce-page-wireguard.h"
#include "vpn-helpers.h"
enum {
DONE,
LAST_SIGNAL
};
static guint signals[LAST_SIGNAL] = { 0 };
struct _NetConnectionEditor
{
GtkDialog parent;
GtkBox *add_connection_box;
AdwBin *add_connection_frame;
GtkButton *apply_button;
GtkButton *cancel_button;
GtkNotebook *notebook;
AdwToastOverlay *toast_overlay;
GtkStack *toplevel_stack;
NMClient *client;
NMDevice *device;
NMConnection *connection;
NMConnection *orig_connection;
gboolean is_new_connection;
gboolean is_changed;
NMAccessPoint *ap;
GCancellable *cancellable;
GSList *initializing_pages;
NMClientPermissionResult can_modify;
gboolean title_set;
};
G_DEFINE_TYPE (NetConnectionEditor, net_connection_editor, GTK_TYPE_DIALOG)
/* Used as both GSettings keys and GObject data tags */
#define IGNORE_CA_CERT_TAG "ignore-ca-cert"
#define IGNORE_PHASE2_CA_CERT_TAG "ignore-phase2-ca-cert"
static GSettings *
_get_ca_ignore_settings (NMConnection *connection)
{
GSettings *settings;
g_autofree gchar *path = NULL;
const char *uuid;
g_return_val_if_fail (connection, NULL);
uuid = nm_connection_get_uuid (connection);
g_return_val_if_fail (uuid && *uuid, NULL);
path = g_strdup_printf ("/org/gnome/nm-applet/eap/%s/", uuid);
settings = g_settings_new_with_path ("org.gnome.nm-applet.eap", path);
return settings;
}
/**
* eap_method_ca_cert_ignore_save:
* @connection: the connection for which to save CA cert ignore values to GSettings
*
* Reads the CA cert ignore tags from the 802.1x setting GObject data and saves
* then to GSettings if present, using the connection UUID as the index.
*/
static void
eap_method_ca_cert_ignore_save (NMConnection *connection)
{
NMSetting8021x *s_8021x;
g_autoptr(GSettings) settings = NULL;
gboolean ignore = FALSE, phase2_ignore = FALSE;
g_return_if_fail (connection);
s_8021x = nm_connection_get_setting_802_1x (connection);
if (s_8021x) {
ignore = !!g_object_get_data (G_OBJECT (s_8021x), IGNORE_CA_CERT_TAG);
phase2_ignore = !!g_object_get_data (G_OBJECT (s_8021x), IGNORE_PHASE2_CA_CERT_TAG);
}
settings = _get_ca_ignore_settings (connection);
if (!settings)
return;
g_settings_set_boolean (settings, IGNORE_CA_CERT_TAG, ignore);
g_settings_set_boolean (settings, IGNORE_PHASE2_CA_CERT_TAG, phase2_ignore);
}
/**
* eap_method_ca_cert_ignore_load:
* @connection: the connection for which to load CA cert ignore values to GSettings
*
* Reads the CA cert ignore tags from the 802.1x setting GObject data and saves
* then to GSettings if present, using the connection UUID as the index.
*/
static void
eap_method_ca_cert_ignore_load (NMConnection *connection)
{
g_autoptr(GSettings) settings = NULL;
NMSetting8021x *s_8021x;
gboolean ignore, phase2_ignore;
g_return_if_fail (connection);
s_8021x = nm_connection_get_setting_802_1x (connection);
if (!s_8021x)
return;
settings = _get_ca_ignore_settings (connection);
if (!settings)
return;
ignore = g_settings_get_boolean (settings, IGNORE_CA_CERT_TAG);
phase2_ignore = g_settings_get_boolean (settings, IGNORE_PHASE2_CA_CERT_TAG);
g_object_set_data (G_OBJECT (s_8021x),
IGNORE_CA_CERT_TAG,
GUINT_TO_POINTER (ignore));
g_object_set_data (G_OBJECT (s_8021x),
IGNORE_PHASE2_CA_CERT_TAG,
GUINT_TO_POINTER (phase2_ignore));
}
static void page_changed (NetConnectionEditor *self);
static void
cancel_editing (NetConnectionEditor *self)
{
g_signal_emit (self, signals[DONE], 0, FALSE);
gtk_window_destroy (GTK_WINDOW (self));
}
static void
close_request_cb (NetConnectionEditor *self)
{
cancel_editing (self);
}
static void
cancel_clicked_cb (NetConnectionEditor *self)
{
cancel_editing (self);
}
static void
update_connection (NetConnectionEditor *self)
{
g_autoptr(GVariant) settings = NULL;
settings = nm_connection_to_dbus (self->connection, NM_CONNECTION_SERIALIZE_ALL);
nm_connection_replace_settings (self->orig_connection, settings, NULL);
}
static void
update_complete (NetConnectionEditor *self,
gboolean success)
{
g_signal_emit (self, signals[DONE], 0, success);
gtk_window_destroy (GTK_WINDOW (self));
}
static void
device_reapply_cb (GObject *source_object,
GAsyncResult *res,
gpointer user_data)
{
NetConnectionEditor *self = user_data;
g_autoptr(GError) error = NULL;
gboolean success = TRUE;
if (!nm_device_reapply_finish (NM_DEVICE (source_object), res, &error)) {
if (!g_error_matches (error, NM_DEVICE_ERROR, NM_DEVICE_ERROR_NOT_ACTIVE))
g_warning ("Failed to reapply changes on device: %s", error->message);
success = FALSE;
}
update_complete (self, success);
g_object_unref (self);
}
static void
updated_connection_cb (GObject *source_object,
GAsyncResult *res,
gpointer user_data)
{
NetConnectionEditor *self = user_data;
g_autoptr(GError) error = NULL;
if (!nm_remote_connection_commit_changes_finish (NM_REMOTE_CONNECTION (source_object),
res, &error)) {
g_warning ("Failed to commit changes: %s", error->message);
update_complete (self, FALSE);
g_object_unref (self);
return;
}
nm_connection_clear_secrets (NM_CONNECTION (source_object));
nm_device_reapply_async (self->device, NM_CONNECTION (self->orig_connection),
0, 0, NULL, device_reapply_cb, self /* owned */);
}
static void
added_connection_cb (GObject *source_object,
GAsyncResult *res,
gpointer user_data)
{
NetConnectionEditor *self = user_data;
g_autoptr(GError) error = NULL;
gboolean success = TRUE;
if (!nm_client_add_connection_finish (NM_CLIENT (source_object), res, &error)) {
g_warning ("Failed to add connection: %s", error->message);
success = FALSE;
update_complete (self, success);
g_object_unref (self);
return;
}
nm_device_reapply_async (self->device, NM_CONNECTION (self->orig_connection),
0, 0, NULL, device_reapply_cb, self /* owned */);
}
static void
apply_clicked_cb (NetConnectionEditor *self)
{
update_connection (self);
eap_method_ca_cert_ignore_save (self->connection);
if (!self->device) {
update_complete (self, TRUE);
return;
}
if (self->is_new_connection) {
nm_client_add_connection_async (self->client,
self->orig_connection,
TRUE,
NULL,
added_connection_cb,
g_object_ref (self));
} else {
nm_remote_connection_commit_changes_async (NM_REMOTE_CONNECTION (self->orig_connection),
TRUE,
NULL,
updated_connection_cb,
g_object_ref (self));
}
gtk_widget_set_visible (GTK_WIDGET (self), FALSE);
}
static void
net_connection_editor_init (NetConnectionEditor *self)
{
gtk_widget_init_template (GTK_WIDGET (self));
}
static void
net_connection_editor_finalize (GObject *object)
{
NetConnectionEditor *self = NET_CONNECTION_EDITOR (object);
g_clear_object (&self->connection);
g_clear_object (&self->orig_connection);
g_clear_object (&self->device);
g_clear_object (&self->client);
g_clear_object (&self->ap);
g_cancellable_cancel (self->cancellable);
g_clear_object (&self->cancellable);
G_OBJECT_CLASS (net_connection_editor_parent_class)->finalize (object);
}
static void
net_connection_editor_class_init (NetConnectionEditorClass *class)
{
GObjectClass *object_class = G_OBJECT_CLASS (class);
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class);
g_resources_register (net_connection_editor_get_resource ());
object_class->finalize = net_connection_editor_finalize;
signals[DONE] = g_signal_new ("done",
G_OBJECT_CLASS_TYPE (object_class),
G_SIGNAL_RUN_FIRST,
0,
NULL, NULL,
NULL,
G_TYPE_NONE, 1, G_TYPE_BOOLEAN);
gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/control-center/network/connection-editor.ui");
gtk_widget_class_bind_template_child (widget_class, NetConnectionEditor, add_connection_box);
gtk_widget_class_bind_template_child (widget_class, NetConnectionEditor, add_connection_frame);
gtk_widget_class_bind_template_child (widget_class, NetConnectionEditor, apply_button);
gtk_widget_class_bind_template_child (widget_class, NetConnectionEditor, cancel_button);
gtk_widget_class_bind_template_child (widget_class, NetConnectionEditor, notebook);
gtk_widget_class_bind_template_child (widget_class, NetConnectionEditor, toast_overlay);
gtk_widget_class_bind_template_child (widget_class, NetConnectionEditor, toplevel_stack);
gtk_widget_class_bind_template_callback (widget_class, cancel_clicked_cb);
gtk_widget_class_bind_template_callback (widget_class, close_request_cb);
gtk_widget_class_bind_template_callback (widget_class, apply_clicked_cb);
}
static void
net_connection_editor_do_fallback (NetConnectionEditor *self, const gchar *type)
{
g_autofree gchar *cmdline = NULL;
g_autoptr(GError) error = NULL;
if (self->is_new_connection) {
cmdline = g_strdup_printf ("nm-connection-editor --type='%s' --create", type);
} else {
cmdline = g_strdup_printf ("nm-connection-editor --edit='%s'",
nm_connection_get_uuid (self->connection));
}
g_spawn_command_line_async (cmdline, &error);
if (error) {
AdwToast *toast;
g_autofree gchar *message = NULL;
message = g_strdup_printf (_("Unable to open connection editor: %s"), error->message);
toast = adw_toast_new (message);
adw_toast_overlay_add_toast (self->toast_overlay, toast);
}
g_signal_emit (self, signals[DONE], 0, FALSE);
}
static void
net_connection_editor_update_title (NetConnectionEditor *self)
{
g_autofree gchar *id = NULL;
if (self->title_set)
return;
if (self->is_new_connection) {
if (self->device) {
id = g_strdup (_("New Profile"));
} else {
/* Leave it set to "Add New Connection" */
return;
}
} else {
NMSettingWireless *sw;
sw = nm_connection_get_setting_wireless (self->connection);
if (sw) {
GBytes *ssid;
ssid = nm_setting_wireless_get_ssid (sw);
id = nm_utils_ssid_to_utf8 (g_bytes_get_data (ssid, NULL), g_bytes_get_size (ssid));
} else {
id = g_strdup (nm_connection_get_id (self->connection));
}
}
gtk_window_set_title (GTK_WINDOW (self), id);
}
static gboolean
editor_is_initialized (NetConnectionEditor *self)
{
return self->initializing_pages == NULL;
}
static void
update_sensitivity (NetConnectionEditor *self)
{
NMSettingConnection *sc;
gboolean sensitive;
gint i;
if (!editor_is_initialized (self))
return;
sc = nm_connection_get_setting_connection (self->connection);
if (nm_setting_connection_get_read_only (sc)) {
sensitive = FALSE;
} else {
sensitive = self->can_modify;
}
for (i = 0; i < gtk_notebook_get_n_pages (self->notebook); i++) {
GtkWidget *page = gtk_notebook_get_nth_page (self->notebook, i);
gtk_widget_set_sensitive (page, sensitive);
}
}
static void
validate (NetConnectionEditor *self)
{
gboolean valid = FALSE;
g_autofree gchar *apply_tooltip = NULL;
gint i;
if (!editor_is_initialized (self))
goto done;
valid = TRUE;
for (i = 0; i < gtk_notebook_get_n_pages (self->notebook); i++) {
CEPage *page = CE_PAGE (gtk_notebook_get_nth_page (self->notebook, i));
g_autoptr(GError) error = NULL;
if (!ce_page_validate (page, self->connection, &error)) {
valid = FALSE;
if (error) {
apply_tooltip = g_strdup_printf (_("Invalid setting %s: %s"), ce_page_get_title (page), error->message);
g_debug ("%s", apply_tooltip);
} else {
apply_tooltip = g_strdup_printf (_("Invalid setting %s"), ce_page_get_title (page));
g_debug ("%s", apply_tooltip);
}
}
}
update_sensitivity (self);
done:
if (apply_tooltip != NULL)
gtk_widget_set_tooltip_text(GTK_WIDGET (self->apply_button), apply_tooltip);
gtk_widget_set_sensitive (GTK_WIDGET (self->apply_button), valid && self->is_changed);
}
static void
page_changed (NetConnectionEditor *self)
{
if (editor_is_initialized (self))
self->is_changed = TRUE;
validate (self);
}
static gboolean
idle_validate (gpointer user_data)
{
validate (NET_CONNECTION_EDITOR (user_data));
return G_SOURCE_REMOVE;
}
static void
recheck_initialization (NetConnectionEditor *self)
{
if (!editor_is_initialized (self))
return;
gtk_stack_set_visible_child (self->toplevel_stack, GTK_WIDGET (self->notebook));
gtk_notebook_set_current_page (self->notebook, 0);
g_idle_add (idle_validate, self);
if (self->is_new_connection)
adw_bin_set_child (self->add_connection_frame, NULL);
}
static void
page_initialized (NetConnectionEditor *self, GError *error, CEPage *page)
{
GtkWidget *label;
gint position;
gint i;
position = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (page), "position"));
g_object_set_data (G_OBJECT (page), "position", GINT_TO_POINTER (position));
for (i = 0; i < gtk_notebook_get_n_pages (self->notebook); i++) {
GtkWidget *page = gtk_notebook_get_nth_page (self->notebook, i);
gint pos = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (page), "position"));
if (pos > position)
break;
}
label = gtk_label_new (ce_page_get_title (page));
gtk_notebook_insert_page (self->notebook, GTK_WIDGET (page), label, i);
self->initializing_pages = g_slist_remove (self->initializing_pages, page);
recheck_initialization (self);
}
typedef struct {
NetConnectionEditor *editor;
CEPage *page;
const gchar *setting_name;
} GetSecretsInfo;
static void
get_secrets_cb (GObject *source_object,
GAsyncResult *res,
gpointer user_data)
{
NMRemoteConnection *connection;
g_autofree GetSecretsInfo *info = user_data;
g_autoptr(GError) error = NULL;
g_autoptr(GVariant) variant = NULL;
connection = NM_REMOTE_CONNECTION (source_object);
variant = nm_remote_connection_get_secrets_finish (connection, res, &error);
if (!variant) {
if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
return;
g_warning ("Failed to get secrets: %s", error->message);
}
ce_page_complete_init (info->page, info->editor->connection, info->setting_name, variant, g_steal_pointer (&error));
}
static void
get_secrets_for_page (NetConnectionEditor *self,
CEPage *page,
const gchar *setting_name)
{
GetSecretsInfo *info;
info = g_new0 (GetSecretsInfo, 1);
info->editor = self;
info->page = page;
info->setting_name = setting_name;
nm_remote_connection_get_secrets_async (NM_REMOTE_CONNECTION (self->orig_connection),
setting_name,
self->cancellable,
get_secrets_cb,
info);
}
static void
add_page (NetConnectionEditor *self, CEPage *page)
{
gint position;
position = g_slist_length (self->initializing_pages);
g_object_set_data (G_OBJECT (page), "position", GINT_TO_POINTER (position));
self->initializing_pages = g_slist_append (self->initializing_pages, page);
g_signal_connect_object (page, "changed", G_CALLBACK (page_changed), self, G_CONNECT_SWAPPED);
g_signal_connect_object (page, "initialized", G_CALLBACK (page_initialized), self, G_CONNECT_SWAPPED);
}
static void
net_connection_editor_set_connection (NetConnectionEditor *self,
NMConnection *connection)
{
GSList *pages, *l;
NMSettingConnection *sc;
const gchar *type;
gboolean is_wired;
gboolean is_wifi;
gboolean is_vpn;
gboolean is_wireguard;
self->is_new_connection = !nm_client_get_connection_by_uuid (self->client,
nm_connection_get_uuid (connection));
if (self->is_new_connection) {
gtk_button_set_label (self->apply_button, _("_Add"));
self->is_changed = TRUE;
}
self->connection = nm_simple_connection_new_clone (connection);
self->orig_connection = g_object_ref (connection);
net_connection_editor_update_title (self);
eap_method_ca_cert_ignore_load (self->connection);
sc = nm_connection_get_setting_connection (connection);
type = nm_setting_connection_get_connection_type (sc);
is_wired = g_str_equal (type, NM_SETTING_WIRED_SETTING_NAME);
is_wifi = g_str_equal (type, NM_SETTING_WIRELESS_SETTING_NAME);
is_vpn = g_str_equal (type, NM_SETTING_VPN_SETTING_NAME);
is_wireguard = g_str_equal (type, NM_SETTING_WIREGUARD_SETTING_NAME);
if (!self->is_new_connection)
add_page (self, CE_PAGE (ce_page_details_new (self->connection, self->device, self->ap, self)));
if (is_wifi)
add_page (self, CE_PAGE (ce_page_wifi_new (self->connection, self->client)));
else if (is_wired)
add_page (self, CE_PAGE (ce_page_ethernet_new (self->connection, self->client)));
else if (is_vpn)
add_page (self, CE_PAGE (ce_page_vpn_new (self->connection)));
else if (is_wireguard)
add_page (self, CE_PAGE (ce_page_wireguard_new (self->connection)));
else {
/* Unsupported type */
net_connection_editor_do_fallback (self, type);
return;
}
add_page (self, CE_PAGE (ce_page_ip4_new (self->connection, self->client)));
add_page (self, CE_PAGE (ce_page_ip6_new (self->connection, self->client)));
if (is_wifi)
add_page (self, CE_PAGE (ce_page_security_new (self->connection)));
else if (is_wired)
add_page (self, CE_PAGE (ce_page_8021x_security_new (self->connection)));
pages = g_slist_copy (self->initializing_pages);
for (l = pages; l; l = l->next) {
CEPage *page = l->data;
const gchar *security_setting;
security_setting = ce_page_get_security_setting (page);
if (!security_setting || self->is_new_connection) {
ce_page_complete_init (page, NULL, NULL, NULL, NULL);
} else {
get_secrets_for_page (self, page, security_setting);
}
}
g_slist_free (pages);
}
static NMConnection *
complete_vpn_connection (NetConnectionEditor *self,
NMConnection *connection,
GType setting_type)
{
NMSettingConnection *s_con;
NMSetting *s_type;
if (!connection)
connection = nm_simple_connection_new ();
s_con = nm_connection_get_setting_connection (connection);
if (!s_con) {
s_con = NM_SETTING_CONNECTION (nm_setting_connection_new ());
nm_connection_add_setting (connection, NM_SETTING (s_con));
}
if (!nm_setting_connection_get_uuid (s_con)) {
g_autofree gchar *uuid = nm_utils_uuid_generate ();
g_object_set (s_con,
NM_SETTING_CONNECTION_UUID, uuid,
NULL);
}
if (!nm_setting_connection_get_id (s_con)) {
const GPtrArray *connections;
g_autofree gchar *id = NULL;
connections = nm_client_get_connections (self->client);
id = ce_page_get_next_available_name (connections, NAME_FORMAT_TYPE, _("VPN"));
g_object_set (s_con,
NM_SETTING_CONNECTION_ID, id,
NULL);
}
s_type = nm_connection_get_setting (connection, setting_type);
if (!s_type) {
s_type = g_object_new (setting_type, NULL);
nm_connection_add_setting (connection, s_type);
}
if (!nm_setting_connection_get_connection_type (s_con)) {
g_object_set (s_con,
NM_SETTING_CONNECTION_TYPE, nm_setting_get_name (s_type),
NULL);
}
return connection;
}
static void
finish_add_connection (NetConnectionEditor *self, NMConnection *connection)
{
gtk_widget_set_visible (GTK_WIDGET (self->apply_button), TRUE);
if (connection)
net_connection_editor_set_connection (self, connection);
}
static void
vpn_import_complete (NMConnection *connection, gpointer user_data)
{
NetConnectionEditor *self = user_data;
NMSetting *s_type = NULL;
NMSettingConnection *s_con;
if (!connection) {
AdwToast *toast;
g_autofree gchar *message = NULL;
message = g_strdup_printf (_("Invalid VPN configuration file"));
toast = adw_toast_new (message);
adw_toast_overlay_add_toast (self->toast_overlay, toast);
g_signal_emit (self, signals[DONE], 0, FALSE);
return;
}
s_type = nm_connection_get_setting (connection, NM_TYPE_SETTING_WIREGUARD);
if (s_type)
complete_vpn_connection (self, connection, NM_TYPE_SETTING_WIREGUARD);
else
complete_vpn_connection (self, connection, NM_TYPE_SETTING_VPN);
/* Mark the connection as private to this user, and non-autoconnect */
s_con = nm_connection_get_setting_connection (connection);
g_object_set (s_con, NM_SETTING_CONNECTION_AUTOCONNECT, FALSE, NULL);
nm_setting_connection_add_permission (s_con, "user", g_get_user_name (), NULL);
finish_add_connection (self, connection);
}
static void
vpn_type_activated (NetConnectionEditor *self, GtkWidget *row)
{
const char *service_name = g_object_get_data (G_OBJECT (row), "service_name");
NMConnection *connection;
NMSettingVpn *s_vpn = NULL;
NMSettingConnection *s_con;
GType s_type = NM_TYPE_SETTING_VPN;
if (!strcmp (service_name, "import")) {
vpn_import (GTK_WINDOW (self), vpn_import_complete, self);
return;
} else if (!strcmp (service_name, "wireguard")) {
s_type = NM_TYPE_SETTING_WIREGUARD;
}
connection = complete_vpn_connection (self, NULL, s_type);
if (s_type == NM_TYPE_SETTING_VPN) {
s_vpn = nm_connection_get_setting_vpn (connection);
g_object_set (s_vpn, NM_SETTING_VPN_SERVICE_TYPE, service_name, NULL);
}
/* Mark the connection as private to this user, and non-autoconnect */
s_con = nm_connection_get_setting_connection (connection);
g_object_set (s_con, NM_SETTING_CONNECTION_AUTOCONNECT, FALSE, NULL);
nm_setting_connection_add_permission (s_con, "user", g_get_user_name (), NULL);
finish_add_connection (self, connection);
}
static void
select_vpn_type (NetConnectionEditor *self, GtkListBox *list)
{
GSList *vpn_plugins, *iter;
GtkWidget *row, *row_box;
GtkWidget *name_label, *desc_label;
GtkWidget *child;
/* Get the available VPN types */
vpn_plugins = vpn_get_plugins ();
/* Remove the previous menu contents */
while ((child = gtk_widget_get_first_child (GTK_WIDGET (list))) != NULL)
gtk_list_box_remove (list, child);
/* Add the VPN types */
for (iter = vpn_plugins; iter; iter = iter->next) {
NMVpnEditorPlugin *plugin = nm_vpn_plugin_info_get_editor_plugin (iter->data);
g_autofree gchar *name = NULL;
g_autofree gchar *desc = NULL;
g_autofree gchar *desc_markup = NULL;
g_autofree gchar *service_name = NULL;
g_object_get (plugin,
NM_VPN_EDITOR_PLUGIN_NAME, &name,
NM_VPN_EDITOR_PLUGIN_DESCRIPTION, &desc,
NM_VPN_EDITOR_PLUGIN_SERVICE, &service_name,
NULL);
desc_markup = g_markup_printf_escaped ("<span size='smaller'>%s</span>", desc);
row = gtk_list_box_row_new ();
row_box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6);
gtk_widget_set_margin_start (row_box, 12);
gtk_widget_set_margin_end (row_box, 12);
gtk_widget_set_margin_top (row_box, 12);
gtk_widget_set_margin_bottom (row_box, 12);
name_label = gtk_label_new (name);
gtk_widget_set_halign (name_label, GTK_ALIGN_START);
gtk_box_append (GTK_BOX (row_box), name_label);
desc_label = gtk_label_new (NULL);
gtk_label_set_markup (GTK_LABEL (desc_label), desc_markup);
gtk_label_set_wrap (GTK_LABEL (desc_label), TRUE);
gtk_widget_set_halign (desc_label, GTK_ALIGN_START);
gtk_widget_add_css_class (desc_label, "dim-label");
gtk_box_append (GTK_BOX (row_box), desc_label);
gtk_list_box_row_set_child (GTK_LIST_BOX_ROW (row), row_box);
g_object_set_data_full (G_OBJECT (row), "service_name", g_steal_pointer (&service_name), g_free);
gtk_list_box_append (list, row);
}
/* Translators: VPN add dialog Wireguard description */
gchar *desc = _("Free and open-source VPN solution designed for ease "
"of use, high speed performance and low attack surface.");
gchar *desc_markup = g_markup_printf_escaped ("<span size='smaller'>%s</span>", desc);
row = gtk_list_box_row_new ();
row_box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6);
gtk_widget_set_margin_start (row_box, 12);
gtk_widget_set_margin_end (row_box, 12);
gtk_widget_set_margin_top (row_box, 12);
gtk_widget_set_margin_bottom (row_box, 12);
name_label = gtk_label_new (_("WireGuard"));
gtk_widget_set_halign (name_label, GTK_ALIGN_START);
gtk_box_append (GTK_BOX (row_box), name_label);
desc_label = gtk_label_new (NULL);
gtk_label_set_markup (GTK_LABEL (desc_label), desc_markup);
gtk_label_set_wrap (GTK_LABEL (desc_label), TRUE);
gtk_widget_set_halign (desc_label, GTK_ALIGN_START);
gtk_widget_add_css_class (desc_label, "dim-label");
gtk_box_append (GTK_BOX (row_box), desc_label);
gtk_list_box_row_set_child (GTK_LIST_BOX_ROW (row), row_box);
g_object_set_data (G_OBJECT (row), "service_name", "wireguard");
gtk_list_box_append (list, row);
/* Import */
row = gtk_list_box_row_new ();
row_box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6);
gtk_widget_set_margin_start (row_box, 12);
gtk_widget_set_margin_end (row_box, 12);
gtk_widget_set_margin_top (row_box, 12);
gtk_widget_set_margin_bottom (row_box, 12);
name_label = gtk_label_new (_("Import from file…"));
gtk_widget_set_halign (name_label, GTK_ALIGN_START);
gtk_box_append (GTK_BOX (row_box), name_label);
gtk_list_box_row_set_child (GTK_LIST_BOX_ROW (row), row_box);
g_object_set_data (G_OBJECT (row), "service_name", "import");
gtk_list_box_append (list, row);
g_signal_connect_object (list, "row-activated",
G_CALLBACK (vpn_type_activated), self, G_CONNECT_SWAPPED);
}
static void
net_connection_editor_add_connection (NetConnectionEditor *self)
{
GtkListBox *list;
list = GTK_LIST_BOX (gtk_list_box_new ());
gtk_list_box_set_selection_mode (list, GTK_SELECTION_NONE);
select_vpn_type (self, list);
adw_bin_set_child (self->add_connection_frame, GTK_WIDGET (list));
gtk_stack_set_visible_child (self->toplevel_stack, GTK_WIDGET (self->add_connection_box));
gtk_widget_set_visible (GTK_WIDGET (self->apply_button), FALSE);
gtk_window_set_title (GTK_WINDOW (self), _("Add VPN"));
}
static void
permission_changed (NetConnectionEditor *self,
NMClientPermission permission,
NMClientPermissionResult result)
{
if (permission != NM_CLIENT_PERMISSION_SETTINGS_MODIFY_SYSTEM)
return;
if (result == NM_CLIENT_PERMISSION_RESULT_YES || result == NM_CLIENT_PERMISSION_RESULT_AUTH)
self->can_modify = TRUE;
else
self->can_modify = FALSE;
validate (self);
}
NetConnectionEditor *
net_connection_editor_new (NMConnection *connection,
NMDevice *device,
NMAccessPoint *ap,
NMClient *client)
{
NetConnectionEditor *self;
self = g_object_new (net_connection_editor_get_type (),
/* This doesn't seem to work for a template, so it is also hardcoded. */
"use-header-bar", 1,
NULL);
self->cancellable = g_cancellable_new ();
if (ap)
self->ap = g_object_ref (ap);
if (device)
self->device = g_object_ref (device);
self->client = g_object_ref (client);
self->can_modify = nm_client_get_permission_result (client, NM_CLIENT_PERMISSION_SETTINGS_MODIFY_SYSTEM);
g_signal_connect_object (self->client, "permission-changed",
G_CALLBACK (permission_changed), self, G_CONNECT_SWAPPED);
if (connection)
net_connection_editor_set_connection (self, connection);
else
net_connection_editor_add_connection (self);
return self;
}
static void
forgotten_cb (GObject *source_object,
GAsyncResult *res,
gpointer user_data)
{
NMRemoteConnection *connection = NM_REMOTE_CONNECTION (source_object);
NetConnectionEditor *self = user_data;
g_autoptr(GError) error = NULL;
if (!nm_remote_connection_delete_finish (connection, res, &error)) {
if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
g_warning ("Failed to delete connection %s: %s",
nm_connection_get_id (NM_CONNECTION (connection)),
error->message);
return;
}
cancel_editing (self);
}
void
net_connection_editor_forget (NetConnectionEditor *self)
{
nm_remote_connection_delete_async (NM_REMOTE_CONNECTION (self->orig_connection),
self->cancellable, forgotten_cb, self);
}
void
net_connection_editor_set_title (NetConnectionEditor *self,
const gchar *title)
{
gtk_window_set_title (GTK_WINDOW (self), title);
self->title_set = TRUE;
}