Fix sorting a little.
[dhcpcd-ui] / src / dhcpcd-gtk / prefs.c
1 /*
2  * dhcpcd-gtk
3  * Copyright 2009-2012 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         GtkIconTheme *it;
213         GdkPixbuf *pb;
214         int n;
215
216         con = (DHCPCD_CONNECTION *)data;
217         if (name) {
218                 if (make_config(&config))
219                         dhcpcd_config_save(con, block, name, config);
220                 dhcpcd_config_free(config);
221                 config = NULL;
222                 show_config(config);
223                 g_free(block);
224                 g_free(name);
225                 name = NULL;
226         }
227         block = combo_active_text(widget);
228         store = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(names)));
229         gtk_list_store_clear(store);
230         list = dhcpcd_config_blocks_get(con, block);
231         
232         it = gtk_icon_theme_get_default();
233         if (g_strcmp0(block, "interface") == 0)
234                 new_names = list_interfaces(con);
235         else
236                 new_names = list_ssids(wi_scans);
237
238         n = 0;
239         for (l = new_names; l; l = l->next) {
240                 nn = (const char *)l->data;
241                 for (lp = list; *lp; lp++)
242                         if (g_strcmp0(nn, *lp) == 0)
243                                 break;
244                 if (*lp)
245                         iname = "document-save";
246                 else
247                         iname = "document-new";
248                 pb = load_icon(iname);
249                 gtk_list_store_append(store, &iter);
250                 gtk_list_store_set(store, &iter, 0, pb, 1, nn, -1);
251                 g_object_unref(pb);
252                 n++;
253         }
254
255         for (lp = list; *lp; lp++) {
256                 for (l = new_names; l; l = l->next)
257                         if (g_strcmp0((const char *)l->data, *lp) == 0)
258                                 break;
259                 if (l != NULL)
260                         continue;
261                 pb = load_icon("document-save");
262                 gtk_list_store_append(store, &iter);
263                 gtk_list_store_set(store, &iter, 0, pb, 1, *lp, -1);
264                 g_object_unref(pb);
265                 n++;
266         }
267         gtk_widget_set_sensitive(names, n);
268         g_slist_free(new_names);
269         g_strfreev(list);
270 }
271
272 static void
273 names_on_change(_unused GtkWidget *widget, gpointer data)
274 {
275         DHCPCD_CONNECTION *con;
276         DHCPCD_IF *i;
277
278         con = (DHCPCD_CONNECTION *)data;
279         if (name) {
280                 if (make_config(&config))
281                         dhcpcd_config_save(con, block, name, config);
282                 g_free(name);
283         }
284         name = combo_active_text(names);
285         dhcpcd_config_free(config);
286         iface = NULL;
287         if (g_strcmp0(block, "interface") == 0) {
288                 for (i = dhcpcd_interfaces(con); i; i = i->next)
289                         if (g_strcmp0(name, i->ifname) == 0) {
290                                 iface = i;
291                                 break;
292                         }
293         }
294         gtk_widget_set_sensitive(address,
295             iface && (iface->flags & IFF_POINTOPOINT) == 0);
296         if (block && name) {
297                 config = dhcpcd_config_load(con, block, name);
298                 if (config == NULL && dhcpcd_error(con))
299                         g_error("libdhcpcd: %s", dhcpcd_error(con));
300         } else
301                 config = NULL;
302         show_config(config);
303         gtk_widget_set_sensitive(controls, name ? true : false);
304         gtk_widget_set_sensitive(clear, name ? true : false);
305         gtk_widget_set_sensitive(rebind, name ? true : false);
306 }
307
308 static bool
309 valid_address(const char *val, bool allow_cidr)
310 {
311         char *addr, *p, *e;
312         struct in_addr in;
313         gint64 cidr;
314         bool retval;
315
316         addr = g_strdup(val);
317         if (allow_cidr) {
318                 p = strchr(addr, '/');
319                 if (p != NULL) {
320                         *p++ = '\0';
321                         errno = 0;
322                         e = NULL;
323                         cidr = g_ascii_strtoll(p, &e, 10);
324                         if (cidr < 0 || cidr > 32 ||
325                             errno != 0 || *e != '\0')
326                         {
327                                 retval = false;
328                                 goto out;
329                         }
330                 }
331         }
332         retval = inet_aton(addr, &in) == 0 ? false : true;
333         
334 out:
335         g_free(addr);
336         return retval;
337 }
338
339         
340 static bool
341 address_lost_focus(GtkEntry *entry)
342 {
343         const char *val;
344
345         val = gtk_entry_get_text(entry);
346         if (*val != '\0' && !valid_address(val, true))
347                 gtk_entry_set_text(entry, "");
348         return false;
349 }
350
351 static bool
352 entry_lost_focus(GtkEntry *entry)
353 {
354         const char *val;
355         char **a, **p;
356
357         val = gtk_entry_get_text(entry);
358         a = g_strsplit(val, " ", 0);
359         for (p = a; *p; p++) {
360                 if (**p != '\0' && !valid_address(*p, false)) {
361                         gtk_entry_set_text(entry, "");
362                         break;
363                 }
364         }
365         g_strfreev(a);
366         return false;
367 }
368
369 static void
370 on_clear(_unused GtkObject *o, gpointer data)
371 {
372         DHCPCD_CONNECTION *con;
373
374         con = (DHCPCD_CONNECTION *)data;
375         dhcpcd_config_free(config);
376         config = NULL;
377         if (dhcpcd_config_save(con, block, name, config)) {
378                 set_name_active_icon("document-new");
379                 show_config(config);
380         }
381 }
382
383 static void
384 on_rebind(_unused GtkWidget *widget, gpointer data)
385 {
386         DHCPCD_CONNECTION *con;
387         DHCPCD_IF *i;
388
389         con = (DHCPCD_CONNECTION *)data;
390         if (make_config(&config) &&
391             dhcpcd_config_save(con, block, name, config)) {
392                 set_name_active_icon(config == NULL ?
393                     "document-new" : "document-save");
394                 show_config(config);
395                 if (g_strcmp0(block, "interface") == 0)
396                         dhcpcd_rebind(con, iface);
397                 else {
398                         for (i = dhcpcd_interfaces(con); i; i = i->next) {
399                                 if (g_strcmp0(i->ssid, name) == 0)
400                                         dhcpcd_rebind(con, i);
401                         }
402                 }
403                 if (dhcpcd_error(con))
404                         g_warning("libdhcpcd: %s", dhcpcd_error(con));
405         }
406 }
407
408 static void
409 on_destroy(_unused GtkObject *o, gpointer data)
410 {
411         if (name != NULL) {
412                 if (make_config(&config))
413                         dhcpcd_config_save((DHCPCD_CONNECTION *)data,
414                             block, name, config);
415                 g_free(block);
416                 g_free(name);
417                 block = name = NULL;
418         }
419         dhcpcd_config_free(config);
420         config = NULL;
421         dialog = NULL;
422 }
423
424 static void
425 dhcpcd_prefs_close(void)
426 {
427         if (dialog != NULL) {
428                 gtk_widget_destroy(dialog);
429                 dialog = NULL;
430         }
431 }
432
433 void
434 dhcpcd_prefs_abort(void)
435 {
436         g_free(name);
437         name = NULL;
438         dhcpcd_prefs_close();
439 }
440
441 void
442 dhcpcd_prefs_show(DHCPCD_CONNECTION *con)
443 {
444         GtkWidget *dialog_vbox, *hbox, *vbox, *table, *w;
445         GtkListStore *store;
446         GtkTreeIter iter;
447         GtkCellRenderer *rend;
448         GdkPixbuf *pb;
449         
450         if (dialog) {
451                 gtk_window_present(GTK_WINDOW(dialog));
452                 return;
453         }
454
455         if (g_strcmp0(dhcpcd_status(con), "down") == 0)
456                 return;
457
458         dialog = gtk_window_new(GTK_WINDOW_TOPLEVEL);
459         g_signal_connect(G_OBJECT(dialog), "destroy",
460             G_CALLBACK(on_destroy), con);
461
462         gtk_window_set_title(GTK_WINDOW(dialog), _("Network Preferences"));
463         gtk_window_set_resizable(GTK_WINDOW(dialog), false);
464         gtk_window_set_icon_name(GTK_WINDOW(dialog),
465             "network-transmit-receive");
466         gtk_window_set_position(GTK_WINDOW(dialog), GTK_WIN_POS_CENTER);
467         gtk_window_set_type_hint(GTK_WINDOW(dialog),
468             GDK_WINDOW_TYPE_HINT_DIALOG);
469
470         dialog_vbox = gtk_vbox_new(false, 10);
471         gtk_container_set_border_width(GTK_CONTAINER(dialog), 10);
472         gtk_container_add(GTK_CONTAINER(dialog), dialog_vbox);
473
474         hbox = gtk_hbox_new(false, 0);
475         gtk_box_pack_start(GTK_BOX(dialog_vbox), hbox, false, false, 3);
476         w = gtk_label_new("Configure:");
477         gtk_box_pack_start(GTK_BOX(hbox), w, false, false, 3);
478         store = gtk_list_store_new(2, GDK_TYPE_PIXBUF, G_TYPE_STRING);
479         pb = load_icon("network-wired");
480         gtk_list_store_append(store, &iter);
481         gtk_list_store_set(store, &iter, 0, pb, 1, "interface", -1);
482         g_object_unref(pb);
483         pb = load_icon("network-wireless");
484         gtk_list_store_append(store, &iter);
485         gtk_list_store_set(store, &iter, 0, pb, 1, "ssid", -1);
486         g_object_unref(pb);
487         blocks = gtk_combo_box_new_with_model(GTK_TREE_MODEL(store));
488         rend = gtk_cell_renderer_pixbuf_new();
489         gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(blocks), rend, false);
490         gtk_cell_layout_add_attribute(GTK_CELL_LAYOUT(blocks),
491             rend, "pixbuf", 0);
492         rend = gtk_cell_renderer_text_new();
493         gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(blocks), rend, true);
494         gtk_cell_layout_add_attribute(GTK_CELL_LAYOUT(blocks),
495             rend, "text", 1);
496         gtk_combo_box_set_active(GTK_COMBO_BOX(blocks), 0);
497         gtk_box_pack_start(GTK_BOX(hbox), blocks, false, false, 3);
498         store = gtk_list_store_new(2, GDK_TYPE_PIXBUF, G_TYPE_STRING);
499         names = gtk_combo_box_new_with_model(GTK_TREE_MODEL(store));
500         rend = gtk_cell_renderer_pixbuf_new();
501         gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(names), rend, false);
502         gtk_cell_layout_add_attribute(GTK_CELL_LAYOUT(names),
503             rend, "pixbuf", 0);
504         rend = gtk_cell_renderer_text_new();
505         gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(names), rend, true);
506         gtk_cell_layout_add_attribute(GTK_CELL_LAYOUT(names), rend, "text", 1);
507         gtk_widget_set_sensitive(names, false);
508         gtk_box_pack_start(GTK_BOX(hbox), names, false, false, 3);
509         g_signal_connect(G_OBJECT(blocks), "changed",
510             G_CALLBACK(blocks_on_change), con);
511         g_signal_connect(G_OBJECT(names), "changed",
512             G_CALLBACK(names_on_change), con);
513         
514         w = gtk_hseparator_new();
515         gtk_box_pack_start(GTK_BOX(dialog_vbox), w, true, false, 3);
516         controls = gtk_vbox_new(false, 10);
517         gtk_widget_set_sensitive(controls, false);
518         gtk_box_pack_start(GTK_BOX(dialog_vbox), controls, true, true, 0);
519         vbox = gtk_vbox_new(false, 3);
520         gtk_box_pack_start(GTK_BOX(controls), vbox, false, false, 0);
521         autoconf = gtk_check_button_new_with_label(
522                 _("Automatically configure empty options"));
523         gtk_box_pack_start(GTK_BOX(vbox), autoconf, false, false, 3);
524         table = gtk_table_new(6, 2, false);
525         gtk_box_pack_start(GTK_BOX(controls), table, false, false, 0);
526
527 #define attach_label(a, b, c, d, e)                                           \
528         do {                                                                  \
529                 gtk_misc_set_alignment(GTK_MISC(a), 0.0, 0.5);                \
530                 gtk_table_attach(GTK_TABLE(table), a, b, c, d, e,             \
531                     GTK_FILL | GTK_SHRINK, GTK_FILL | GTK_SHRINK, 3, 3);      \
532         } while (0)
533 #define attach_entry(a, b, c, d, e)                                           \
534         gtk_table_attach(GTK_TABLE(table), a, b, c, d, e,                     \
535             GTK_EXPAND | GTK_FILL | GTK_SHRINK, GTK_FILL | GTK_SHRINK, 3, 3);
536         
537         w = gtk_label_new(_("IP Address:"));
538         address = gtk_entry_new();
539         gtk_entry_set_max_length(GTK_ENTRY(address), 18);
540         g_signal_connect(G_OBJECT(address), "focus-out-event",
541             G_CALLBACK(address_lost_focus), NULL);
542         attach_label(w, 0, 1, 0, 1);
543         attach_entry(address, 1, 2, 0, 1);
544
545         w = gtk_label_new(_("Router:"));
546         router = gtk_entry_new();
547         gtk_entry_set_max_length(GTK_ENTRY(router), 15);
548         g_signal_connect(G_OBJECT(router), "focus-out-event",
549             G_CALLBACK(entry_lost_focus), NULL);
550         attach_label(w, 0, 1, 2, 3);
551         attach_entry(router, 1, 2, 2, 3);
552
553         w = gtk_label_new(_("DNS Servers:"));
554         dns_servers = gtk_entry_new();
555         g_signal_connect(G_OBJECT(dns_servers), "focus-out-event",
556             G_CALLBACK(entry_lost_focus), NULL);
557         attach_label(w, 0, 1, 3, 4);
558         attach_entry(dns_servers, 1, 2, 3, 4);
559
560         w = gtk_label_new(_("DNS Search:"));
561         dns_search = gtk_entry_new();
562         attach_label(w, 0, 1, 4, 5);
563         attach_entry(dns_search, 1, 2, 4, 5);
564
565         hbox = gtk_hbox_new(false, 10);
566         gtk_box_pack_start(GTK_BOX(dialog_vbox), hbox, true, true, 3);
567         clear = gtk_button_new_from_stock(GTK_STOCK_CLEAR);
568         gtk_widget_set_sensitive(clear, false);
569         gtk_box_pack_start(GTK_BOX(hbox), clear, false, false, 0);
570         g_signal_connect(G_OBJECT(clear), "clicked",
571             G_CALLBACK(on_clear), con);
572         rebind = gtk_button_new_with_mnemonic(_("_Rebind"));
573         gtk_widget_set_sensitive(rebind, false);
574         w = gtk_image_new_from_stock(GTK_STOCK_EXECUTE,
575             GTK_ICON_SIZE_BUTTON);
576         gtk_button_set_image(GTK_BUTTON(rebind), w);
577         gtk_box_pack_start(GTK_BOX(hbox), rebind, false, false, 0);
578         g_signal_connect(G_OBJECT(rebind), "clicked",
579             G_CALLBACK(on_rebind), con);
580         w = gtk_button_new_from_stock(GTK_STOCK_CLOSE);
581         gtk_box_pack_end(GTK_BOX(hbox), w, false, false, 0);
582         g_signal_connect(G_OBJECT(w), "clicked",
583             G_CALLBACK(dhcpcd_prefs_close), NULL);
584         
585         blocks_on_change(blocks, con);
586         show_config(NULL);
587         gtk_widget_show_all(dialog);
588 }