642d5ffa151bc41c4fbeaebedb68968b3646904c
[dhcpcd-ui] / src / dhcpcd-gtk / prefs.c
1 /*
2  * dhcpcd-gtk
3  * Copyright 2009-2014 Roy Marples <roy@marples.name>
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  *
14  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24  * SUCH DAMAGE.
25  */
26
27 #include <sys/types.h>
28 #include <net/if.h>
29
30 #include <errno.h>
31
32 #include "dhcpcd-gtk.h"
33
34 static GtkWidget *dialog, *blocks, *names, *controls, *clear, *rebind;
35 static GtkWidget *autoconf, *address, *router, *dns_servers, *dns_search;
36 static DHCPCD_CONFIG *config;
37 static char *block, *name;
38 static DHCPCD_IF *iface;
39
40 static void
41 show_config(DHCPCD_CONFIG *conf)
42 {
43         const char *val;
44         bool autocnf;
45
46         if ((val = dhcpcd_config_get_static(conf, "ip_address=")) != NULL)
47                 autocnf = false;
48         else {
49                 if ((val = dhcpcd_config_get(conf, "inform")) == NULL &&
50                     (iface && iface->flags & IFF_POINTOPOINT))
51                         autocnf = false;
52                 else
53                         autocnf = true;
54         }
55         gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(autoconf), autocnf);
56         gtk_entry_set_text(GTK_ENTRY(address), val ? val : "");
57         val = dhcpcd_config_get_static(conf, "routers=");
58         gtk_entry_set_text(GTK_ENTRY(router), val ? val : "");
59         val = dhcpcd_config_get_static(conf, "domain_name_servers=");
60         gtk_entry_set_text(GTK_ENTRY(dns_servers), val ? val : "");
61         val = dhcpcd_config_get_static(conf, "domain_search=");
62         gtk_entry_set_text(GTK_ENTRY(dns_search), val ? val : "");
63 }
64
65 static char *
66 combo_active_text(GtkWidget *widget)
67 {
68         GtkListStore *store;
69         GtkTreeIter iter;
70         GValue val;
71         char *text;
72
73         store = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(widget)));
74         if (!gtk_combo_box_get_active_iter(GTK_COMBO_BOX(widget), &iter))
75                 return NULL;
76         memset(&val, 0, sizeof(val));
77         gtk_tree_model_get_value(GTK_TREE_MODEL(store), &iter, 1, &val);
78         text = g_strdup(g_value_get_string(&val));
79         g_value_unset(&val);
80         return text;
81 }
82
83 static bool
84 set_option(DHCPCD_CONFIG **conf, bool s, const char *opt, const char *val,
85         bool *ret)
86 {
87         bool r;
88
89         if (s)
90                 r = dhcpcd_config_set_static(conf, opt, val);
91         else
92                 r = dhcpcd_config_set(conf, opt, val);
93         if (r)
94                 return true;
95         g_error("libdhcpcd: %s", strerror(errno));
96         if (ret)
97                 *ret = false;
98         return false;
99 }
100
101 static bool
102 make_config(DHCPCD_CONFIG **conf)
103 {
104         const char *val, ns[] = "";
105         bool a, ret;
106
107         ret = true;
108         a = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(autoconf));
109         if (iface && iface->flags & IFF_POINTOPOINT)
110                 set_option(conf, true, "ip_address=", a ? NULL : ns, &ret);
111         else {
112                 val = gtk_entry_get_text(GTK_ENTRY(address));
113                 if (*val == '\0')
114                         val = NULL;
115                 set_option(conf, false, "inform", a ? val : NULL, &ret);
116                 set_option(conf, true, "ip_address=", a ? NULL : val, &ret);
117         }
118
119         val = gtk_entry_get_text(GTK_ENTRY(router));
120         if (a && *val == '\0')
121                 val = NULL;
122         set_option(conf, true, "routers=", val, &ret);
123
124         val = gtk_entry_get_text(GTK_ENTRY(dns_servers));
125         if (a && *val == '\0')
126                 val = NULL;
127         set_option(conf, true, "domain_name_servers=", val, &ret);
128
129         val = gtk_entry_get_text(GTK_ENTRY(dns_search));
130         if (a && *val == '\0')
131                 val = NULL;
132         set_option(conf, true, "domain_search=", val, &ret);
133
134         return ret;
135 }
136
137 static GdkPixbuf *
138 load_icon(const char *iname)
139 {
140         int width, height;
141
142         if (!gtk_icon_size_lookup(GTK_ICON_SIZE_MENU, &width, &height))
143                 return NULL;
144         return gtk_icon_theme_load_icon(gtk_icon_theme_get_default(),
145             iname, MIN(width, height), 0, NULL);
146 }
147
148 static void
149 set_name_active_icon(const char *iname)
150 {
151         GtkListStore *store;
152         GtkTreeIter iter;
153         GtkTreePath *path;
154         GdkPixbuf *pb;
155
156         store = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(names)));
157         if (!gtk_combo_box_get_active_iter(GTK_COMBO_BOX(names), &iter))
158                 return;
159         pb = load_icon(iname);
160         if (pb) {
161                 path = gtk_tree_model_get_path(GTK_TREE_MODEL(store), &iter);
162                 gtk_list_store_set(store, &iter, 0, pb, -1);
163                 g_object_unref(pb);
164                 gtk_tree_model_row_changed(GTK_TREE_MODEL(store), path, &iter);
165                 gtk_tree_path_free(path);
166         }
167 }
168
169 static GSList *
170 list_interfaces(DHCPCD_CONNECTION *con)
171 {
172         GSList *list;
173         DHCPCD_IF *i;
174
175         list = NULL;
176         for (i = dhcpcd_interfaces(con); i; i = i->next)
177                 if (strcmp(i->type, "ipv4") == 0)
178                         list = g_slist_append(list, i->ifname);
179         return list;
180 }
181
182 static GSList *
183 list_ssids(WI_SCAN *scans)
184 {
185         GSList *list, *l;
186         WI_SCAN *w;
187         DHCPCD_WI_SCAN *wis;
188
189         list = NULL;
190         for (w = scans; w; w = w->next) {
191                 for (wis = w->scans; wis; wis = wis->next) {
192                         for (l = list; l; l = l->next)
193                                 if (g_strcmp0((const char *)l->data,
194                                         wis->ssid) == 0)
195                                         break;
196                         if (l == NULL)
197                                 list = g_slist_append(list, wis->ssid);
198                 }
199         }
200         return list;
201 }
202
203 static void
204 blocks_on_change(GtkWidget *widget, gpointer data)
205 {
206         DHCPCD_CONNECTION *con;
207         GtkListStore *store;
208         GtkTreeIter iter;
209         char **list, **lp;
210         const char *iname, *nn;
211         GSList *l, *new_names;
212         GdkPixbuf *pb;
213         int n;
214
215         con = (DHCPCD_CONNECTION *)data;
216         if (name) {
217                 if (make_config(&config))
218                         dhcpcd_config_save(con, block, name, config);
219                 dhcpcd_config_free(config);
220                 config = NULL;
221                 show_config(config);
222                 g_free(block);
223                 g_free(name);
224                 name = NULL;
225         }
226         block = combo_active_text(widget);
227         store = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(names)));
228         gtk_list_store_clear(store);
229         list = dhcpcd_config_blocks_get(con, block);
230
231         if (g_strcmp0(block, "interface") == 0)
232                 new_names = list_interfaces(con);
233         else
234                 new_names = list_ssids(wi_scans);
235
236         n = 0;
237         for (l = new_names; l; l = l->next) {
238                 nn = (const char *)l->data;
239                 for (lp = list; *lp; lp++)
240                         if (g_strcmp0(nn, *lp) == 0)
241                                 break;
242                 if (*lp)
243                         iname = "document-save";
244                 else
245                         iname = "document-new";
246                 pb = load_icon(iname);
247                 gtk_list_store_append(store, &iter);
248                 gtk_list_store_set(store, &iter, 0, pb, 1, nn, -1);
249                 g_object_unref(pb);
250                 n++;
251         }
252
253         for (lp = list; *lp; lp++) {
254                 for (l = new_names; l; l = l->next)
255                         if (g_strcmp0((const char *)l->data, *lp) == 0)
256                                 break;
257                 if (l != NULL)
258                         continue;
259                 pb = load_icon("document-save");
260                 gtk_list_store_append(store, &iter);
261                 gtk_list_store_set(store, &iter, 0, pb, 1, *lp, -1);
262                 g_object_unref(pb);
263                 n++;
264         }
265         gtk_widget_set_sensitive(names, n);
266         g_slist_free(new_names);
267         g_strfreev(list);
268 }
269
270 static void
271 names_on_change(_unused GtkWidget *widget, gpointer data)
272 {
273         DHCPCD_CONNECTION *con;
274         DHCPCD_IF *i;
275
276         con = (DHCPCD_CONNECTION *)data;
277         if (name) {
278                 if (make_config(&config))
279                         dhcpcd_config_save(con, block, name, config);
280                 g_free(name);
281         }
282         name = combo_active_text(names);
283         dhcpcd_config_free(config);
284         iface = NULL;
285         if (g_strcmp0(block, "interface") == 0) {
286                 for (i = dhcpcd_interfaces(con); i; i = i->next)
287                         if (g_strcmp0(name, i->ifname) == 0) {
288                                 iface = i;
289                                 break;
290                         }
291         }
292         gtk_widget_set_sensitive(address,
293             iface && (iface->flags & IFF_POINTOPOINT) == 0);
294         if (block && name) {
295                 config = dhcpcd_config_load(con, block, name);
296                 if (config == NULL && dhcpcd_error(con))
297                         g_error("libdhcpcd: %s", dhcpcd_error(con));
298         } else
299                 config = NULL;
300         show_config(config);
301         gtk_widget_set_sensitive(controls, name ? true : false);
302         gtk_widget_set_sensitive(clear, name ? true : false);
303         gtk_widget_set_sensitive(rebind, name ? true : false);
304 }
305
306 static bool
307 valid_address(const char *val, bool allow_cidr)
308 {
309         char *addr, *p, *e;
310         struct in_addr in;
311         gint64 cidr;
312         bool retval;
313
314         addr = g_strdup(val);
315         if (allow_cidr) {
316                 p = strchr(addr, '/');
317                 if (p != NULL) {
318                         *p++ = '\0';
319                         errno = 0;
320                         e = NULL;
321                         cidr = g_ascii_strtoll(p, &e, 10);
322                         if (cidr < 0 || cidr > 32 ||
323                             errno != 0 || *e != '\0')
324                         {
325                                 retval = false;
326                                 goto out;
327                         }
328                 }
329         }
330         retval = inet_aton(addr, &in) == 0 ? false : true;
331
332 out:
333         g_free(addr);
334         return retval;
335 }
336
337 static bool
338 address_lost_focus(GtkEntry *entry)
339 {
340         const char *val;
341
342         val = gtk_entry_get_text(entry);
343         if (*val != '\0' && !valid_address(val, true))
344                 gtk_entry_set_text(entry, "");
345         return false;
346 }
347
348 static bool
349 entry_lost_focus(GtkEntry *entry)
350 {
351         const char *val;
352         char **a, **p;
353
354         val = gtk_entry_get_text(entry);
355         a = g_strsplit(val, " ", 0);
356         for (p = a; *p; p++) {
357                 if (**p != '\0' && !valid_address(*p, false)) {
358                         gtk_entry_set_text(entry, "");
359                         break;
360                 }
361         }
362         g_strfreev(a);
363         return false;
364 }
365
366 static void
367 on_clear(_unused GtkWidget *o, gpointer data)
368 {
369         DHCPCD_CONNECTION *con;
370
371         con = (DHCPCD_CONNECTION *)data;
372         dhcpcd_config_free(config);
373         config = NULL;
374         if (dhcpcd_config_save(con, block, name, config)) {
375                 set_name_active_icon("document-new");
376                 show_config(config);
377         }
378 }
379
380 static void
381 on_rebind(_unused GObject *widget, gpointer data)
382 {
383         DHCPCD_CONNECTION *con;
384         DHCPCD_IF *i;
385
386         con = (DHCPCD_CONNECTION *)data;
387         if (make_config(&config) &&
388             dhcpcd_config_save(con, block, name, config)) {
389                 set_name_active_icon(config == NULL ?
390                     "document-new" : "document-save");
391                 show_config(config);
392                 if (g_strcmp0(block, "interface") == 0)
393                         dhcpcd_rebind(con, iface);
394                 else {
395                         for (i = dhcpcd_interfaces(con); i; i = i->next) {
396                                 if (g_strcmp0(i->ssid, name) == 0)
397                                         dhcpcd_rebind(con, i);
398                         }
399                 }
400                 if (dhcpcd_error(con))
401                         g_warning("libdhcpcd: %s", dhcpcd_error(con));
402         }
403 }
404
405 static void
406 on_destroy(_unused GObject *o, gpointer data)
407 {
408
409         if (name != NULL) {
410                 if (make_config(&config))
411                         dhcpcd_config_save((DHCPCD_CONNECTION *)data,
412                             block, name, config);
413                 g_free(block);
414                 g_free(name);
415                 block = name = NULL;
416         }
417         dhcpcd_config_free(config);
418         config = NULL;
419         dialog = NULL;
420 }
421
422 static void
423 dhcpcd_prefs_close(void)
424 {
425         if (dialog != NULL) {
426                 gtk_widget_destroy(dialog);
427                 dialog = NULL;
428         }
429 }
430
431 void
432 dhcpcd_prefs_abort(void)
433 {
434         g_free(name);
435         name = NULL;
436         dhcpcd_prefs_close();
437 }
438
439 #if GTK_MAJOR_VERSION == 2
440 static GtkWidget *
441 gtk_separator_new(GtkOrientation o)
442 {
443
444         if (o == GTK_ORIENTATION_HORIZONTAL)
445                 return gtk_hseparator_new();
446         else
447                 return gtk_vseparator_new();
448 }
449 #endif
450
451 void
452 dhcpcd_prefs_show(DHCPCD_CONNECTION *con)
453 {
454         GtkWidget *dialog_vbox, *hbox, *vbox, *table, *w;
455         GtkListStore *store;
456         GtkTreeIter iter;
457         GtkCellRenderer *rend;
458         GdkPixbuf *pb;
459
460         if (dialog) {
461                 gtk_window_present(GTK_WINDOW(dialog));
462                 return;
463         }
464
465         if (g_strcmp0(dhcpcd_status(con), "down") == 0)
466                 return;
467
468         dialog = gtk_window_new(GTK_WINDOW_TOPLEVEL);
469         g_signal_connect(G_OBJECT(dialog), "destroy",
470             G_CALLBACK(on_destroy), con);
471
472         gtk_window_set_title(GTK_WINDOW(dialog), _("Network Preferences"));
473         gtk_window_set_resizable(GTK_WINDOW(dialog), false);
474         gtk_window_set_icon_name(GTK_WINDOW(dialog),
475             "network-transmit-receive");
476         gtk_window_set_position(GTK_WINDOW(dialog), GTK_WIN_POS_CENTER);
477         gtk_window_set_type_hint(GTK_WINDOW(dialog),
478             GDK_WINDOW_TYPE_HINT_DIALOG);
479
480         dialog_vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 10);
481         gtk_container_set_border_width(GTK_CONTAINER(dialog), 10);
482         gtk_container_add(GTK_CONTAINER(dialog), dialog_vbox);
483
484         hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
485         gtk_box_pack_start(GTK_BOX(dialog_vbox), hbox, false, false, 3);
486         w = gtk_label_new("Configure:");
487         gtk_box_pack_start(GTK_BOX(hbox), w, false, false, 3);
488         store = gtk_list_store_new(2, GDK_TYPE_PIXBUF, G_TYPE_STRING);
489         pb = load_icon("network-wired");
490         gtk_list_store_append(store, &iter);
491         gtk_list_store_set(store, &iter, 0, pb, 1, "interface", -1);
492         g_object_unref(pb);
493         pb = load_icon("network-wireless");
494         gtk_list_store_append(store, &iter);
495         gtk_list_store_set(store, &iter, 0, pb, 1, "ssid", -1);
496         g_object_unref(pb);
497         blocks = gtk_combo_box_new_with_model(GTK_TREE_MODEL(store));
498         rend = gtk_cell_renderer_pixbuf_new();
499         gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(blocks), rend, false);
500         gtk_cell_layout_add_attribute(GTK_CELL_LAYOUT(blocks),
501             rend, "pixbuf", 0);
502         rend = gtk_cell_renderer_text_new();
503         gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(blocks), rend, true);
504         gtk_cell_layout_add_attribute(GTK_CELL_LAYOUT(blocks),
505             rend, "text", 1);
506         gtk_combo_box_set_active(GTK_COMBO_BOX(blocks), 0);
507         gtk_box_pack_start(GTK_BOX(hbox), blocks, false, false, 3);
508         store = gtk_list_store_new(2, GDK_TYPE_PIXBUF, G_TYPE_STRING);
509         names = gtk_combo_box_new_with_model(GTK_TREE_MODEL(store));
510         rend = gtk_cell_renderer_pixbuf_new();
511         gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(names), rend, false);
512         gtk_cell_layout_add_attribute(GTK_CELL_LAYOUT(names),
513             rend, "pixbuf", 0);
514         rend = gtk_cell_renderer_text_new();
515         gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(names), rend, true);
516         gtk_cell_layout_add_attribute(GTK_CELL_LAYOUT(names), rend, "text", 1);
517         gtk_widget_set_sensitive(names, false);
518         gtk_box_pack_start(GTK_BOX(hbox), names, false, false, 3);
519         g_signal_connect(G_OBJECT(blocks), "changed",
520             G_CALLBACK(blocks_on_change), con);
521         g_signal_connect(G_OBJECT(names), "changed",
522             G_CALLBACK(names_on_change), con);
523
524         w = gtk_separator_new(GTK_ORIENTATION_HORIZONTAL);
525         gtk_box_pack_start(GTK_BOX(dialog_vbox), w, true, false, 3);
526         controls = gtk_box_new(GTK_ORIENTATION_VERTICAL, 10);
527         gtk_widget_set_sensitive(controls, false);
528         gtk_box_pack_start(GTK_BOX(dialog_vbox), controls, true, true, 0);
529         vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 3);
530         gtk_box_pack_start(GTK_BOX(controls), vbox, false, false, 0);
531         autoconf = gtk_check_button_new_with_label(
532                 _("Automatically configure empty options"));
533         gtk_box_pack_start(GTK_BOX(vbox), autoconf, false, false, 3);
534         table = gtk_table_new(6, 2, false);
535         gtk_box_pack_start(GTK_BOX(controls), table, false, false, 0);
536
537 #define attach_label(a, b, c, d, e)                                           \
538         do {                                                                  \
539                 gtk_misc_set_alignment(GTK_MISC(a), 0.0, 0.5);                \
540                 gtk_table_attach(GTK_TABLE(table), a, b, c, d, e,             \
541                     GTK_FILL | GTK_SHRINK, GTK_FILL | GTK_SHRINK, 3, 3);      \
542         } while (0)
543 #define attach_entry(a, b, c, d, e)                                           \
544         gtk_table_attach(GTK_TABLE(table), a, b, c, d, e,                     \
545             GTK_EXPAND | GTK_FILL | GTK_SHRINK, GTK_FILL | GTK_SHRINK, 3, 3);
546
547         w = gtk_label_new(_("IP Address:"));
548         address = gtk_entry_new();
549         gtk_entry_set_max_length(GTK_ENTRY(address), 18);
550         g_signal_connect(G_OBJECT(address), "focus-out-event",
551             G_CALLBACK(address_lost_focus), NULL);
552         attach_label(w, 0, 1, 0, 1);
553         attach_entry(address, 1, 2, 0, 1);
554
555         w = gtk_label_new(_("Router:"));
556         router = gtk_entry_new();
557         gtk_entry_set_max_length(GTK_ENTRY(router), 15);
558         g_signal_connect(G_OBJECT(router), "focus-out-event",
559             G_CALLBACK(entry_lost_focus), NULL);
560         attach_label(w, 0, 1, 2, 3);
561         attach_entry(router, 1, 2, 2, 3);
562
563         w = gtk_label_new(_("DNS Servers:"));
564         dns_servers = gtk_entry_new();
565         g_signal_connect(G_OBJECT(dns_servers), "focus-out-event",
566             G_CALLBACK(entry_lost_focus), NULL);
567         attach_label(w, 0, 1, 3, 4);
568         attach_entry(dns_servers, 1, 2, 3, 4);
569
570         w = gtk_label_new(_("DNS Search:"));
571         dns_search = gtk_entry_new();
572         attach_label(w, 0, 1, 4, 5);
573         attach_entry(dns_search, 1, 2, 4, 5);
574
575         hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 10);
576         gtk_box_pack_start(GTK_BOX(dialog_vbox), hbox, true, true, 3);
577         clear = gtk_button_new_from_stock(GTK_STOCK_CLEAR);
578         gtk_widget_set_sensitive(clear, false);
579         gtk_box_pack_start(GTK_BOX(hbox), clear, false, false, 0);
580         g_signal_connect(G_OBJECT(clear), "clicked",
581             G_CALLBACK(on_clear), con);
582         rebind = gtk_button_new_with_mnemonic(_("_Rebind"));
583         gtk_widget_set_sensitive(rebind, false);
584         w = gtk_image_new_from_stock(GTK_STOCK_EXECUTE,
585             GTK_ICON_SIZE_BUTTON);
586         gtk_button_set_image(GTK_BUTTON(rebind), w);
587         gtk_box_pack_start(GTK_BOX(hbox), rebind, false, false, 0);
588         g_signal_connect(G_OBJECT(rebind), "clicked",
589             G_CALLBACK(on_rebind), con);
590         w = gtk_button_new_from_stock(GTK_STOCK_CLOSE);
591         gtk_box_pack_end(GTK_BOX(hbox), w, false, false, 0);
592         g_signal_connect(G_OBJECT(w), "clicked",
593             G_CALLBACK(dhcpcd_prefs_close), NULL);
594
595         blocks_on_change(blocks, con);
596         show_config(NULL);
597         gtk_widget_show_all(dialog);
598 }