nm-connection-list.c 46.3 KB
Newer Older
1
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
Rodrigo Moya's avatar
Rodrigo Moya committed
2
3
4
/* NetworkManager Connection editor -- Connection editor for NetworkManager
 *
 * Rodrigo Moya <rodrigo@gnome-db.org>
5
 * Dan Williams <dcbw@redhat.com>
Rodrigo Moya's avatar
Rodrigo Moya committed
6
7
8
9
10
11
12
13
14
15
16
 *
 * 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.
 *
17
18
19
 * 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.
Rodrigo Moya's avatar
Rodrigo Moya committed
20
 *
21
 * (C) Copyright 2007 - 2009 Red Hat, Inc.
Rodrigo Moya's avatar
Rodrigo Moya committed
22
23
 */

24
#include <config.h>
25
#include <string.h>
26
27
#include <sys/types.h>
#include <unistd.h>
28

29
#include <gtk/gtk.h>
30
#include <gconf/gconf-client.h>
31
#include <gdk/gdkx.h>
32
33
#include <glib/gi18n.h>

34
#include <nm-setting-connection.h>
35
36
#include <nm-connection.h>
#include <nm-setting.h>
37
38
39
40
41
#include <nm-setting-wired.h>
#include <nm-setting-wireless.h>
#include <nm-setting-vpn.h>
#include <nm-setting-gsm.h>
#include <nm-setting-cdma.h>
Tambet Ingo's avatar
Tambet Ingo committed
42
#include <nm-setting-pppoe.h>
43
#include <nm-setting-ppp.h>
44
#include <nm-setting-serial.h>
45
#include <nm-vpn-plugin-ui-interface.h>
46
#include <nm-utils.h>
47
#include <nm-settings-system-interface.h>
48

49
50
51
52
53
54
#include "ce-page.h"
#include "page-wired.h"
#include "page-wireless.h"
#include "page-mobile.h"
#include "page-dsl.h"
#include "page-vpn.h"
Rodrigo Moya's avatar
Rodrigo Moya committed
55
56
#include "nm-connection-editor.h"
#include "nm-connection-list.h"
57
#include "gconf-helpers.h"
58
#include "utils.h"
59
#include "vpn-helpers.h"
60
#include "ce-polkit-button.h"
Rodrigo Moya's avatar
Rodrigo Moya committed
61
62
63

G_DEFINE_TYPE (NMConnectionList, nm_connection_list, G_TYPE_OBJECT)

64
65
66
67
68
69
70
enum {
	LIST_DONE,
	LIST_LAST_SIGNAL
};

static guint list_signals[LIST_LAST_SIGNAL] = { 0 };

71
#define COL_ID 			0
72
73
74
#define COL_LAST_USED	1
#define COL_TIMESTAMP	2
#define COL_CONNECTION	3
75

76
77
78
typedef struct {
	NMConnectionList *list;
	GtkTreeView *treeview;
79
	GtkWindow *list_window;
80
	GtkWidget *button;
81
	PageNewConnectionFunc new_func;
82
83
} ActionInfo;

84
static void
85
error_dialog (GtkWindow *parent, const char *heading, const char *format, ...)
86
87
88
{
	GtkWidget *dialog;
	va_list args;
89
90
91
92
93
94
95
	char *message;

	dialog = gtk_message_dialog_new (parent,
	                                 GTK_DIALOG_DESTROY_WITH_PARENT,
	                                 GTK_MESSAGE_ERROR,
	                                 GTK_BUTTONS_CLOSE,
	                                 "%s", heading);
96
97

	va_start (args, format);
98
	message = g_strdup_vprintf (format, args);
99
100
	va_end (args);

101
	gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog), "%s", message);
102
	g_free (message);
103

104
105
	gtk_widget_show_all (dialog);
	gtk_window_present (GTK_WINDOW (dialog));
106
107
108
109
	gtk_dialog_run (GTK_DIALOG (dialog));
	gtk_widget_destroy (dialog);
}

110
static NMSettingsConnectionInterface *
111
get_active_connection (GtkTreeView *treeview)
112
{
113
114
115
116
	GtkTreeSelection *selection;
	GList *selected_rows;
	GtkTreeModel *model = NULL;
	GtkTreeIter iter;
117
	NMSettingsConnectionInterface *connection = NULL;
118

119
120
121
122
	selection = gtk_tree_view_get_selection (treeview);
	selected_rows = gtk_tree_selection_get_selected_rows (selection, &model);
	if (!selected_rows)
		return NULL;
123

124
	if (gtk_tree_model_get_iter (model, &iter, (GtkTreePath *) selected_rows->data))
125
		gtk_tree_model_get (model, &iter, COL_CONNECTION, &connection, -1);
126

127
128
129
	/* free memory */
	g_list_foreach (selected_rows, (GFunc) gtk_tree_path_free, NULL);
	g_list_free (selected_rows);
130

131
	return connection;
132
}
133

134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
#define TV_TYPE_TAG "ctype"

static GtkTreeView *
get_treeview_for_type (NMConnectionList *list, GType ctype)
{
	GSList *iter;

	for (iter = list->treeviews; iter; iter = g_slist_next (iter)) {
		GtkTreeView *candidate = GTK_TREE_VIEW (iter->data);
		GType candidate_type;

		candidate_type = GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (candidate), TV_TYPE_TAG));
		if (candidate_type == ctype)
			return candidate;
	}

	return NULL;
}

153
static GtkListStore *
154
get_model_for_connection (NMConnectionList *list, NMSettingsConnectionInterface *connection)
155
156
157
158
{
	NMSettingConnection *s_con;
	GtkTreeView *treeview;
	GtkTreeModel *model;
159
	const char *str_type;
160

161
162
163
	s_con = (NMSettingConnection *) nm_connection_get_setting (NM_CONNECTION (connection), NM_TYPE_SETTING_CONNECTION);
	g_assert (s_con);
	str_type = nm_setting_connection_get_connection_type (s_con);
164

165
	if (!str_type) {
166
167
168
		g_warning ("Ignoring incomplete connection");
		return NULL;
	}
169

170
171
172
173
	if (!strcmp (str_type, NM_SETTING_CDMA_SETTING_NAME))
		str_type = NM_SETTING_GSM_SETTING_NAME;

	treeview = get_treeview_for_type (list, nm_connection_lookup_setting_type (str_type));
174
	if (!treeview) {
175
		g_warning ("No registered treeview for connection type '%s'", str_type);
176
177
178
179
180
181
182
183
		return NULL;
	}

	model = gtk_tree_view_get_model (treeview);
	if (GTK_IS_TREE_MODEL_SORT (model))
		return GTK_LIST_STORE (gtk_tree_model_sort_get_model (GTK_TREE_MODEL_SORT (model)));

	return GTK_LIST_STORE (model);
184
185
}

186
187
static gboolean
get_iter_for_connection (GtkTreeModel *model,
188
189
                         NMSettingsConnectionInterface *connection,
                         GtkTreeIter *iter)
190
{
191
192
	GtkTreeIter temp_iter;
	gboolean found = FALSE;
193

194
195
	if (!gtk_tree_model_get_iter_first (model, &temp_iter))
		return FALSE;
196

197
	do {
198
		NMSettingsConnectionInterface *candidate = NULL;
199

200
		gtk_tree_model_get (model, &temp_iter, COL_CONNECTION, &candidate, -1);
201
		if (candidate && (candidate == connection)) {
202
203
204
205
206
			*iter = temp_iter;
			found = TRUE;
			break;
		}
	} while (gtk_tree_model_iter_next (model, &temp_iter));
207

208
	return found;
209
210
}

211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
static char *
format_last_used (guint64 timestamp)
{
	GTimeVal now_tv;
	GDate *now, *last;
	char *last_used = NULL;

	if (!timestamp)
		return g_strdup (_("never"));

	g_get_current_time (&now_tv);
	now = g_date_new ();
	g_date_set_time_val (now, &now_tv);

	last = g_date_new ();
	g_date_set_time_t (last, (time_t) timestamp);

	/* timestamp is now or in the future */
	if (now_tv.tv_sec <= timestamp) {
		last_used = g_strdup (_("now"));
		goto out;
	}

	if (g_date_compare (now, last) <= 0) {
		guint minutes, hours;

		/* Same day */

		minutes = (now_tv.tv_sec - timestamp) / 60;
		if (minutes == 0) {
			last_used = g_strdup (_("now"));
			goto out;
		}

		hours = (now_tv.tv_sec - timestamp) / 3600;
		if (hours == 0) {
			/* less than an hour ago */
			last_used = g_strdup_printf (ngettext ("%d minute ago", "%d minutes ago", minutes), minutes);
			goto out;
		}

		last_used = g_strdup_printf (ngettext ("%d hour ago", "%d hours ago", hours), hours);
	} else {
		guint days, months, years;

		days = g_date_get_julian (now) - g_date_get_julian (last);
		if (days == 0) {
			last_used = g_strdup ("today");
			goto out;
		}

		months = days / 30;
		if (months == 0) {
			last_used = g_strdup_printf (ngettext ("%d day ago", "%d days ago", days), days);
			goto out;
		}

		years = days / 365;
		if (years == 0) {
			last_used = g_strdup_printf (ngettext ("%d month ago", "%d months ago", months), months);
			goto out;
		}

		last_used = g_strdup_printf (ngettext ("%d year ago", "%d years ago", years), years);
	}

out:
	g_date_free (now);
	g_date_free (last);
	return last_used;
}

283
static void
284
update_connection_row (GtkListStore *store,
285
286
                       GtkTreeIter *iter,
                       NMSettingsConnectionInterface *connection)
287
{
288
289
	NMSettingConnection *s_con;
	char *last_used;
290

291
	s_con = NM_SETTING_CONNECTION (nm_connection_get_setting (NM_CONNECTION (connection), NM_TYPE_SETTING_CONNECTION));
292
	g_assert (s_con);
293

294
	last_used = format_last_used (nm_setting_connection_get_timestamp (s_con));
295
	gtk_list_store_set (store, iter,
296
297
298
299
300
	                    COL_ID, nm_setting_connection_get_id (s_con),
	                    COL_LAST_USED, last_used,
	                    COL_TIMESTAMP, nm_setting_connection_get_timestamp (s_con),
	                    COL_CONNECTION, connection,
	                    -1);
301
302
	g_free (last_used);
}
303

304
305

/**********************************************/
306
/* Connection deleting */
307

308
309
310
typedef void (*DeleteResultFunc) (NMConnectionList *list,
                                  GError *error,
                                  gpointer user_data);
311

312
typedef struct {
313
314
315
316
317
	NMConnectionList *list;
	NMSettingsConnectionInterface *original;
	DeleteResultFunc callback;
	gpointer callback_data;
} DeleteInfo;
318
319

static void
320
321
322
delete_cb (NMSettingsConnectionInterface *connection_iface,
           GError *error,
           gpointer user_data)
323
{
324
325
326
	DeleteInfo *info = user_data;
	NMConnection *connection = NM_CONNECTION (connection_iface);
	NMConnectionScope scope;
327

328
329
	scope = nm_connection_get_scope (connection);
	if (!error && (scope == NM_CONNECTION_SCOPE_USER)) {
330
331
332
		NMSettingConnection *s_con;
		NMSettingVPN *s_vpn;
		NMVpnPluginUiInterface *plugin;
333
334
		GError *vpn_error = NULL;

335
336
337
338
		s_con = (NMSettingConnection *) nm_connection_get_setting (connection, NM_TYPE_SETTING_CONNECTION);
		g_assert (s_con);

		/* Clean up VPN secrets and any plugin-specific data */
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
		if (strcmp (nm_setting_connection_get_connection_type (s_con), NM_SETTING_VPN_SETTING_NAME))
			goto done;

		s_vpn = (NMSettingVPN *) nm_connection_get_setting (connection, NM_TYPE_SETTING_VPN);
		if (!s_vpn)
			goto done;

		plugin = vpn_get_plugin_by_service (nm_setting_vpn_get_service_type (s_vpn));
		if (!plugin)
			goto done;

		if (!nm_vpn_plugin_ui_interface_delete_connection (plugin, connection, &vpn_error)) {
			g_warning ("%s: couldn't clean up VPN connection on delete: (%d) %s",
			           __func__,
			           vpn_error ? vpn_error->code : -1,
			           (vpn_error && vpn_error->message) ? vpn_error->message : "unknown");
			g_clear_error (&vpn_error);
356
		}
357
358
	}

359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
done:
	info->callback (info->list, error, info->callback_data);
	g_free (info);
}

static void
delete_connection (NMConnectionList *list,
                   NMSettingsConnectionInterface *connection,
                   DeleteResultFunc callback,
                   gpointer user_data)
{
	DeleteInfo *info;

	info = g_malloc0 (sizeof (DeleteInfo));
	info->list = list;
	info->callback = callback;
	info->callback_data = user_data;

	nm_settings_connection_interface_delete (connection, delete_cb, info);
378
379
380
381
382
}

/**********************************************/
/* Connection adding */

383
384
385
typedef void (*AddResultFunc) (NMConnectionList *list,
                               GError *error,
                               gpointer user_data);
386
387
388

typedef struct {
	NMConnectionList *list;
389
	NMConnectionEditor *editor;
390
391
392
	AddResultFunc callback;
	gpointer callback_data;
} AddInfo;
393
394

static void
395
396
397
add_cb (NMSettingsInterface *settings,
        GError *error,
        gpointer user_data)
398
{
399
400
	AddInfo *info = user_data;
	NMConnection *connection;
401

402
403
404
405
406
407
	if (!error) {
		/* Let the VPN plugin save its secrets */
		connection = nm_connection_editor_get_connection (info->editor);
		if (nm_connection_get_scope (connection) == NM_CONNECTION_SCOPE_USER)
			nm_connection_editor_save_vpn_secrets (info->editor);
	}
408

409
410
	info->callback (info->list, error, info->callback_data);
	g_free (info);
411
412
413
414
}

static void
add_connection (NMConnectionList *self,
415
                NMConnectionEditor *editor,
416
417
                AddResultFunc callback,
                gpointer callback_data)
418
{
419
	NMSettingsInterface *settings = NULL;
420
	NMConnection *connection;
421
	NMConnectionScope scope;
422
	AddInfo *info;
423

424
425
426
427
428
429
	info = g_malloc0 (sizeof (AddInfo));
	info->list = self;
	info->editor = editor;
	info->callback = callback;
	info->callback_data = callback_data;

430
	connection = nm_connection_editor_get_connection (editor);
431
	g_assert (connection);
432
	scope = nm_connection_get_scope (connection);
433
434
435
436
437
438
439
440
	if (scope == NM_CONNECTION_SCOPE_SYSTEM)
		settings = NM_SETTINGS_INTERFACE (self->system_settings);
	else if (scope == NM_CONNECTION_SCOPE_USER)
		settings = NM_SETTINGS_INTERFACE (self->gconf_settings);
	else
		g_assert_not_reached ();
	
	nm_settings_interface_add_connection (settings, connection, add_cb, info);
441
}
442

443
444
/**********************************************/
/* Connection updating */
445

446
typedef void (*UpdateResultFunc) (NMConnectionList *list,
447
                                  NMSettingsConnectionInterface *connection,
448
449
                                  GError *error,
                                  gpointer user_data);
450

451
452
typedef struct {
	NMConnectionList *list;
453
	NMConnectionEditor *editor;
454
	NMSettingsConnectionInterface *connection;
455
456
457
	UpdateResultFunc callback;
	gpointer callback_data;
} UpdateInfo;
458

459
static void
460
update_complete (UpdateInfo *info, GError *error)
461
{
462
463
	info->callback (info->list, info->connection, error, info->callback_data);
	g_object_unref (info->connection);
464
	g_free (info);
465
}
466

467
static void
468
469
470
update_remove_result_cb (NMConnectionList *list,
                         GError *error,
                         gpointer user_data)
471
{
472
	UpdateInfo *info = user_data;
473

474
	update_complete (info, error);
475
}
476

477
static void
478
update_add_result_cb (NMConnectionList *list, GError *error, gpointer user_data)
479
{
480
481
482
483
484
485
486
487
	UpdateInfo *info = user_data;

	if (error) {
		update_complete (info, error);
		return;
	}

	/* Now try to remove the original connection */
488
	delete_connection (list, info->connection, update_remove_result_cb, info);
489
490
491
}

static void
492
493
494
update_cb (NMSettingsConnectionInterface *connection,
           GError *error,
           gpointer user_data)
495
{
496
	UpdateInfo *info = user_data;
497

498
499
500
501
	if (!error) {
		/* Save user-connection vpn secrets */
		if (nm_connection_get_scope (NM_CONNECTION (connection)) == NM_CONNECTION_SCOPE_USER)
			nm_connection_editor_save_vpn_secrets (info->editor);
502
	}
503

504
505
506
507
508
	/* Clear secrets so they don't lay around in memory; they'll get requested
	 * again anyway next time the connection is edited.
	 */
	nm_connection_clear_secrets (NM_CONNECTION (connection));

509
	update_complete (info, error);
510
511
512
513
}

static void
update_connection (NMConnectionList *list,
514
                   NMConnectionEditor *editor,
515
516
                   NMSettingsConnectionInterface *connection,
                   NMConnectionScope orig_scope,
517
                   UpdateResultFunc callback,
518
                   gpointer user_data)
519
{
520
	NMConnectionScope new_scope;
521
	UpdateInfo *info;
522

523
	info = g_malloc0 (sizeof (UpdateInfo));
524
	info->list = list;
525
	info->editor = editor;
526
	info->connection = g_object_ref (connection);
527
	info->callback = callback;
528
	info->callback_data = user_data;
529

530
531
532
	new_scope = nm_connection_get_scope (NM_CONNECTION (connection));
	if (new_scope == orig_scope) {
		/* The easy part: Connection is just updated and has the same scope */
533
534
535
		GHashTable *new_settings;
		GError *error = NULL;

536
537
538
539
540
541
542
543
544
545
546
547
548
		/* System connections need the certificates filled because the
		 * applet private values that we use to store the path to certificates
		 * and private keys don't go through D-Bus; they are private of course!
		 */
		if (new_scope == NM_CONNECTION_SCOPE_SYSTEM) {
			new_settings = nm_connection_to_hash (NM_CONNECTION (connection));
			if (!nm_connection_replace_settings (NM_CONNECTION (connection),
			                                     new_settings,
			                                     &error)) {
				update_complete (info, error);
				g_error_free (error);
				return;
			}
549
		}
550
551
552
553
554

		/* Update() actually saves the connection settings to backing storage,
		 * either GConf or over D-Bus.
		 */
		nm_settings_connection_interface_update (connection, update_cb, info);
555
556
	} else {
		/* The hard part: Connection scope changed:
557
558
559
560
		 * Add the modified connection to the new settings service, then delete
		 * the original connection from the old settings service.
		 */
		add_connection (list, editor, update_add_result_cb, info);
561
	}
562
563
}

564
565
566
/**********************************************/
/* dialog/UI handling stuff */

567
static void
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
add_finished_cb (NMConnectionList *list, GError *error, gpointer user_data)
{
	NMConnectionEditor *editor = NM_CONNECTION_EDITOR (user_data);
	GtkWindow *parent;

	if (error) {
		parent = nm_connection_editor_get_window (editor);
		error_dialog (parent, _("Connection add failed"), "%s", error->message);
	}

	g_hash_table_remove (list->editors, nm_connection_editor_get_connection (editor));
}


static void
add_response_cb (NMConnectionEditor *editor, gint response, GError *error, gpointer user_data)
584
585
{
	ActionInfo *info = (ActionInfo *) user_data;
586
	GError *add_error = NULL;
587

588
	if (response == GTK_RESPONSE_OK) {
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
		/* Verify and commit user changes */
		if (nm_connection_editor_update_connection (editor, &add_error)) {
			/* Yay we can try to add the connection; it'll get removed from
			 * list->editors when the add finishes.
			 */
			add_connection (info->list, editor, add_finished_cb, editor);
			return;
		} else {
			error_dialog (GTK_WINDOW (editor->window),
			              _("Error editing connection: property '%s' / '%s' invalid: %d"),
			              g_type_name (nm_connection_lookup_setting_type_by_quark (add_error->domain)),
			              (add_error && add_error->message) ? add_error->message : "(unknown)",
			              add_error ? add_error->code : -1);
			g_clear_error (&add_error);
		}
604
	} else if (response == GTK_RESPONSE_NONE) {
605
606
		const char *message = _("An unknown error ocurred.");

607
608
609
610
611
612
		if (error && error->message)
			message = error->message;
		error_dialog (GTK_WINDOW (editor->window),
		              _("Error initializing editor"),
		              "%s", message);
	}
613

614
	g_hash_table_remove (info->list->editors, nm_connection_editor_get_connection (editor));
615
616
}

617
static void
618
619
620
621
really_add_connection (NMConnection *connection,
                       gboolean canceled,
                       GError *error,
                       gpointer user_data)
622
{
623
624
625
626
	ActionInfo *info = user_data;
	NMConnectionEditor *editor;
	GError *editor_error = NULL;
	const char *message = _("The connection editor dialog could not be initialized due to an unknown error.");
627

628
	g_return_if_fail (info != NULL);
629

630
631
	if (canceled)
		return;
632

633
634
635
636
637
638
	if (!connection) {
		error_dialog (info->list_window,
		              _("Could not create new connection"),
		              "%s",
		              (error && error->message) ? error->message : message);
		return;
639
640
	}

641
	editor = nm_connection_editor_new (connection, info->list->system_settings, &error);
642
	if (!editor) {
643
644
		g_object_unref (connection);

645
646
647
648
649
650
651
		error_dialog (info->list_window,
		              _("Could not edit new connection"),
		              "%s",
		              (editor_error && editor_error->message) ? editor_error->message : message);
		g_clear_error (&editor_error);
		return;
	}
652

653
	g_signal_connect (editor, "done", G_CALLBACK (add_response_cb), info);
654
	g_hash_table_insert (info->list->editors, connection, editor);
655

656
	nm_connection_editor_run (editor);
657
658
}

659
660
static GSList *
page_get_connections (gpointer user_data)
661
{
662
	ActionInfo *info = (ActionInfo *) user_data;
663

664
665
	return g_slist_concat (nm_settings_interface_list_connections (NM_SETTINGS_INTERFACE (info->list->system_settings)),
	                       nm_settings_interface_list_connections (NM_SETTINGS_INTERFACE (info->list->gconf_settings)));
666
667
}

Rodrigo Moya's avatar
Rodrigo Moya committed
668
static void
669
add_clicked (GtkButton *button, gpointer user_data)
Rodrigo Moya's avatar
Rodrigo Moya committed
670
{
671
	ActionInfo *info = (ActionInfo *) user_data;
672
673
	NMConnectionList *list = info->list;
	GType ctype;
674

675
676
677
678
	if (!info->new_func) {
		ctype = GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (info->treeview), TV_TYPE_TAG));
		g_warning ("No new-connection function registered for type '%s'",
		           g_type_name (ctype));
679
680
681
		return;
	}

682
683
684
685
	(*(info->new_func)) (GTK_WINDOW (list->dialog),
	                     really_add_connection,
	                     page_get_connections,
	                     info);
686
687
}

688
689
typedef struct {
	NMConnectionList *list;
690
	NMConnectionEditor *editor;
691
	NMConnectionScope orig_scope;
692
} EditInfo;
693

694
static void
695
connection_updated_cb (NMConnectionList *list,
696
                       NMSettingsConnectionInterface *connection,
697
698
                       GError *error,
                       gpointer user_data)
699
{
700
	EditInfo *info = user_data;
701

702
	if (!error) {
703
		GtkListStore *store;
704
705
		GtkTreeIter iter;

706
		store = get_model_for_connection (list, connection);
707
		g_assert (store);
708
709
		if (get_iter_for_connection (GTK_TREE_MODEL (store), connection, &iter))
			update_connection_row (store, &iter, connection);
710
	}
711
712

	g_hash_table_remove (list->editors, connection);
713
714
715
716
	g_free (info);
}

static void
717
edit_done_cb (NMConnectionEditor *editor, gint response, GError *error, gpointer user_data)
718
{
719
	EditInfo *info = user_data;
720
	const char *message = _("An unknown error ocurred.");
721
	NMConnection *connection;
722
	GError *edit_error = NULL;
723

724
725
	connection = nm_connection_editor_get_connection (editor);
	g_assert (connection);
726

727
728
	switch (response) {
	case GTK_RESPONSE_OK:
729
		/* Verify and commit user changes */
730
		if (nm_connection_editor_update_connection (editor, &edit_error)) {
731
			/* Save the connection to backing storage */
732
733
734
735
736
737
			update_connection (info->list,
			                   editor,
			                   NM_SETTINGS_CONNECTION_INTERFACE (connection),
			                   info->orig_scope,
			                   connection_updated_cb,
			                   info);
738
739
740
741
		} else {
			g_warning ("%s: invalid connection after update: bug in the "
			           "'%s' / '%s' invalid: %d",
			           __func__,
742
743
			           g_type_name (nm_connection_lookup_setting_type_by_quark (edit_error->domain)),
			           edit_error->message, edit_error->code);
744
745
746
747
			connection_updated_cb (info->list,
			                       NM_SETTINGS_CONNECTION_INTERFACE (connection),
			                       edit_error,
			                       info);
748
			g_error_free (edit_error);
749
		}
750
751
		break;
	case GTK_RESPONSE_NONE:
Dan Williams's avatar
Dan Williams committed
752
753
754
755
756
		/* Show an error dialog if the editor initialization failed */
		if (error && error->message)
			message = error->message;
		error_dialog (GTK_WINDOW (editor->window), _("Error initializing editor"), "%s", message);
		/* fall through */
757
758
759
760
761
	case GTK_RESPONSE_CANCEL:
	default:
		g_hash_table_remove (info->list->editors, connection);
		g_free (info);
		break;
762
	}
763
764
765
}

static void
766
do_edit (ActionInfo *info)
767
{
768
	NMSettingsConnectionInterface *connection;
769
	NMConnectionEditor *editor;
770
	EditInfo *edit_info;
771
772
	GError *error = NULL;
	const char *message = _("The connection editor dialog could not be initialized due to an unknown error.");
773

774
775
	connection = get_active_connection (info->treeview);
	g_return_if_fail (connection != NULL);
776
777

	/* Don't allow two editors for the same connection */
778
	editor = (NMConnectionEditor *) g_hash_table_lookup (info->list->editors, connection);
779
780
781
782
783
	if (editor) {
		nm_connection_editor_present (editor);
		return;
	}

784
	editor = nm_connection_editor_new (NM_CONNECTION (connection), info->list->system_settings, &error);
785
786
787
788
789
790
791
792
	if (!editor) {
		error_dialog (info->list_window,
		              _("Could not edit connection"),
		              "%s",
		              (error && error->message) ? error->message : message);
		return;
	}

793
	edit_info = g_malloc0 (sizeof (EditInfo));
794
	edit_info->list = info->list;
795
	edit_info->editor = editor;
796
	edit_info->orig_scope = nm_connection_get_scope (NM_CONNECTION (connection));
797
798

	g_signal_connect (editor, "done", G_CALLBACK (edit_done_cb), edit_info);
799
	g_hash_table_insert (info->list->editors, connection, editor);
800
801

	nm_connection_editor_run (editor);
Rodrigo Moya's avatar
Rodrigo Moya committed
802
803
}

804
static void
805
806
807
delete_result_cb (NMConnectionList *list,
                  GError *error,
                  gpointer user_data)
808
{
809
	GtkWindow *parent = GTK_WINDOW (user_data);
810

811
812
	if (error)
		error_dialog (parent, _("Connection delete failed"), "%s", error->message);
813
814
}

Rodrigo Moya's avatar
Rodrigo Moya committed
815
static void
816
delete_clicked (GtkButton *button, gpointer user_data)
Rodrigo Moya's avatar
Rodrigo Moya committed
817
{
818
	ActionInfo *info = (ActionInfo *) user_data;
819
	NMSettingsConnectionInterface *connection;
820
	NMSettingConnection *s_con;
821
	GtkWidget *dialog;
822
	const char *id;
823
	guint result;
824

825
826
	connection = get_active_connection (info->treeview);
	g_return_if_fail (connection != NULL);
827

828
829
830
	s_con = (NMSettingConnection *) nm_connection_get_setting (NM_CONNECTION (connection), NM_TYPE_SETTING_CONNECTION);
	g_assert (s_con);
	id = nm_setting_connection_get_id (s_con);
831

832
	dialog = gtk_message_dialog_new (GTK_WINDOW (info->list->dialog),
833
834
835
836
	                                 GTK_DIALOG_DESTROY_WITH_PARENT,
	                                 GTK_MESSAGE_QUESTION,
	                                 GTK_BUTTONS_NONE,
	                                 _("Are you sure you wish to delete the connection %s?"),
837
	                                 id);
838
839
840
841
	gtk_dialog_add_buttons (GTK_DIALOG (dialog),
	                        GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
	                        GTK_STOCK_DELETE, GTK_RESPONSE_YES,
	                        NULL);
842
	gtk_window_set_transient_for (GTK_WINDOW (dialog), GTK_WINDOW (info->list->dialog));
843
844
845
846

	result = gtk_dialog_run (GTK_DIALOG (dialog));
	gtk_widget_destroy (dialog);

847
	if (result == GTK_RESPONSE_YES)
848
		delete_connection (info->list, connection, delete_result_cb, GTK_WINDOW (info->list->dialog));
849
850
}

851
static void
852
pk_button_selection_changed_cb (GtkTreeSelection *selection, gpointer user_data)
853
854
855
856
{
	ActionInfo *info = (ActionInfo *) user_data;
	GtkTreeIter iter;
	GtkTreeModel *model;
857
	NMSettingsConnectionInterface *connection;
858
	NMSettingConnection *s_con;
859
860
861
	NMConnectionScope scope;
	gboolean sensitive = FALSE;
	gboolean use_polkit = FALSE;
862
863
864
865

	if (!gtk_tree_selection_get_selected (selection, &model, &iter))
		goto done;

866
	connection = get_active_connection (info->treeview);
867
868
869
	if (!connection)
		goto done;

870
871
872
	scope = nm_connection_get_scope (NM_CONNECTION (connection));
	use_polkit = (scope == NM_CONNECTION_SCOPE_SYSTEM);

873
874
	s_con = (NMSettingConnection *) nm_connection_get_setting (NM_CONNECTION (connection),
	                                                           NM_TYPE_SETTING_CONNECTION);
875
876
	g_assert (s_con);

877
	sensitive = !nm_setting_connection_get_read_only (s_con);
878
879

done:
880
881
	ce_polkit_button_set_use_polkit (CE_POLKIT_BUTTON (info->button), use_polkit);
	ce_polkit_button_set_master_sensitive (CE_POLKIT_BUTTON (info->button), sensitive);
882
883
}

884
885
886
887
888
static void
vpn_list_selection_changed_cb (GtkTreeSelection *selection, gpointer user_data)
{
	ActionInfo *info = (ActionInfo *) user_data;
	NMVpnPluginUiInterface *plugin;
889
	NMSettingsConnectionInterface *connection;
890
	NMSettingVPN *s_vpn;
891
	const char *service_type;
892
893
894
895
896
897
898
899
	GtkTreeIter iter;
	GtkTreeModel *model;
	guint32 caps;
	gboolean supported = FALSE;

	if (!gtk_tree_selection_get_selected (selection, &model, &iter))
		goto done;

900
	connection = get_active_connection (info->treeview);
901
902
903
	if (!connection)
		goto done;

904
	s_vpn = NM_SETTING_VPN (nm_connection_get_setting (NM_CONNECTION (connection), NM_TYPE_SETTING_VPN));
905
906
907
	service_type = s_vpn ? nm_setting_vpn_get_service_type (s_vpn) : NULL;

	if (!service_type)
908
909
		goto done;

910
	plugin = vpn_get_plugin_by_service (service_type);
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
	if (!plugin)
		goto done;

	caps = nm_vpn_plugin_ui_interface_get_capabilities (plugin);
	if (caps & NM_VPN_PLUGIN_UI_CAPABILITY_EXPORT)
		supported = TRUE;

done:
	gtk_widget_set_sensitive (info->button, supported);
}

static void
import_success_cb (NMConnection *connection, gpointer user_data)
{
	ActionInfo *info = (ActionInfo *) user_data;
	NMConnectionEditor *editor;
	NMSettingConnection *s_con;
	NMSettingVPN *s_vpn;
929
	const char *service_type;
930
	char *s;
931
932
	GError *error = NULL;
	const char *message = _("The connection editor dialog could not be initialized due to an unknown error.");
933
934
935
936
937
938
939

	/* Basic sanity checks of the connection */
	s_con = NM_SETTING_CONNECTION (nm_connection_get_setting (connection, NM_TYPE_SETTING_CONNECTION));
	if (!s_con) {
		s_con = NM_SETTING_CONNECTION (nm_setting_connection_new ());
		nm_connection_add_setting (connection, NM_SETTING (s_con));
	}
940
941
942

	s = (char *) nm_setting_connection_get_id (s_con);
	if (!s) {
943
944
		GSList *connections;

945
		connections = nm_settings_interface_list_connections (NM_SETTINGS_INTERFACE (info->list->system_settings));
946
		connections = g_slist_concat (connections,
947
		                              nm_settings_interface_list_connections (NM_SETTINGS_INTERFACE (info->list->gconf_settings)));
948
949

		s = utils_next_available_name (connections, _("VPN connection %d"));
950
951
		g_object_set (s_con, NM_SETTING_CONNECTION_ID, s, NULL);
		g_free (s);
952
953

		g_slist_free (connections);
954
955
956
957
958
959
960
961
962
963
964
	}

	s = (char *) nm_setting_connection_get_connection_type (s_con);
	if (!s || strcmp (s, NM_SETTING_VPN_SETTING_NAME))
		g_object_set (s_con, NM_SETTING_CONNECTION_TYPE, NM_SETTING_VPN_SETTING_NAME, NULL);

	s = (char *) nm_setting_connection_get_uuid (s_con);
	if (!s) {
		s = nm_utils_uuid_generate ();
		g_object_set (s_con, NM_SETTING_CONNECTION_UUID, s, NULL);
		g_free (s);
965
966
967
	}

	s_vpn = NM_SETTING_VPN (nm_connection_get_setting (connection, NM_TYPE_SETTING_VPN));
968
969
970
	service_type = s_vpn ? nm_setting_vpn_get_service_type (s_vpn) : NULL;

	if (!service_type || !strlen (service_type)) {
971
972
		GtkWidget *dialog;

973
974
		g_object_unref (connection);

975
976
977
978
979
980
981
		dialog = gtk_message_dialog_new (NULL,
		                                 GTK_DIALOG_DESTROY_WITH_PARENT,
		                                 GTK_MESSAGE_ERROR,
		                                 GTK_BUTTONS_OK,
		                                 _("Cannot import VPN connection"));
		gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
		                                 _("The VPN plugin failed to import the VPN connection correctly\n\nError: no VPN service type."));
982
		gtk_window_set_transient_for (GTK_WINDOW (dialog), info->list_window);
983
984
985
986
987
988
989
		g_signal_connect (dialog, "delete-event", G_CALLBACK (gtk_widget_destroy), NULL);
		g_signal_connect (dialog, "response", G_CALLBACK (gtk_widget_destroy), NULL);
		gtk_widget_show_all (dialog);
		gtk_window_present (GTK_WINDOW (dialog));
		return;
	}

990
	editor = nm_connection_editor_new (connection, info->list->system_settings, &error);
991
	if (!editor) {
992
		g_object_unref (connection);
993
994
995
996
997
998
999
		error_dialog (info->list_window,
		              _("Could not edit imported connection"),
		              "%s",
		              (error && error->message) ? error->message : message);
		return;
	}

1000
	g_signal_connect (editor, "done", G_CALLBACK (add_response_cb), info);
1001
	g_hash_table_insert (info->list->editors, connection, editor);
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015

	nm_connection_editor_run (editor);
}

static void
import_vpn_cb (GtkButton *button, gpointer user_data)
{
	vpn_import (import_success_cb, (ActionInfo *) user_data);
}

static void
export_vpn_cb (GtkButton *button, gpointer user_data)
{
	ActionInfo *info = (ActionInfo *) user_data;
1016
	NMSettingsConnectionInterface *connection;
1017

1018
1019
1020