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