We now have a working preference screen and can rebind the interface!
[dhcpcd-ui] / prefs.c
1 /*
2  * dhcpcd-gtk
3  * Copyright 2009 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 <errno.h>
28
29 #include "dhcpcd-config.h"
30 #include "dhcpcd-gtk.h"
31 #include "prefs.h"
32
33 static GtkWidget *dialog, *blocks, *names, *controls, *clear, *rebind;
34 static GtkWidget *autoconf, *address, *router, *dns_servers, *dns_search;
35 static GPtrArray *config;
36 static char *block, *name;
37
38 static void
39 show_config(GPtrArray *array)
40 {
41         const char *val;
42         bool autocnf;
43
44         if (get_config_static(array, "ip_address=", &val) != -1)
45                 autocnf = false;
46         else {
47                 get_config(array, "inform", &val);
48                 autocnf = true;
49         }
50         gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(autoconf), autocnf);
51         gtk_entry_set_text(GTK_ENTRY(address), val ? val : "");
52         get_config_static(array, "routers=", &val);
53         gtk_entry_set_text(GTK_ENTRY(router), val ? val : "");
54         get_config_static(array, "domain_name_servers=", &val);
55         gtk_entry_set_text(GTK_ENTRY(dns_servers), val ? val : "");
56         get_config_static(array, "domain_search=", &val);
57         gtk_entry_set_text(GTK_ENTRY(dns_search), val ? val : "");
58 }
59
60 static char *
61 combo_active_text(GtkWidget *widget)
62 {
63         GtkListStore *store;
64         GtkTreeIter iter;
65         GValue val;
66         char *text;
67
68         store = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(widget)));
69         if (!gtk_combo_box_get_active_iter(GTK_COMBO_BOX(widget), &iter))
70                 return NULL;
71         memset(&val, 0, sizeof(val));
72         gtk_tree_model_get_value(GTK_TREE_MODEL(store), &iter, 1, &val);
73         text = g_strdup(g_value_get_string(&val));
74         g_value_unset(&val);
75         return text;
76 }
77
78 static void
79 make_config(GPtrArray *array)
80 {
81         const char *val;
82         bool a;
83
84         a = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(autoconf));
85         val = gtk_entry_get_text(GTK_ENTRY(address));
86         if (*val == '\0')
87                 val = NULL;
88         set_option(array, false, "inform", a ? val : NULL);
89         set_option(array, true, "ip_address=", a ? NULL : val);
90         
91         val = gtk_entry_get_text(GTK_ENTRY(router));
92         if (a && *val == '\0')
93                 val = NULL;
94         set_option(array, true, "routers=", val);
95         
96         val = gtk_entry_get_text(GTK_ENTRY(dns_servers));
97         if (a && *val == '\0')
98                 val = NULL;
99         set_option(array, true, "domain_name_servers=", val);
100         
101         val = gtk_entry_get_text(GTK_ENTRY(dns_search));
102         if (a && *val == '\0')
103                 val = NULL;
104         set_option(array, true, "domain_search=", val);
105 }
106
107 static void
108 set_name_active_icon(const char *iname)
109 {
110         GtkListStore *store;
111         GtkTreeIter iter;
112         GtkIconTheme *it;
113         GtkTreePath *path;
114         GdkPixbuf *pb;
115
116         store = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(names)));
117         if (!gtk_combo_box_get_active_iter(GTK_COMBO_BOX(names), &iter))
118                 return;
119         it = gtk_icon_theme_get_default();
120         pb = gtk_icon_theme_load_icon(it, iname,
121             GTK_ICON_SIZE_MENU, 0, NULL);
122         if (pb) {
123                 path = gtk_tree_model_get_path(GTK_TREE_MODEL(store), &iter);
124                 gtk_list_store_set(store, &iter, 0, pb, -1);
125                 g_object_unref(pb);
126                 gtk_tree_model_row_changed(GTK_TREE_MODEL(store), path, &iter);
127                 gtk_tree_path_free(path);
128         }
129 }
130
131 static GSList *
132 list_interfaces(void)
133 {
134         GSList *list, *l;
135         const struct if_msg *ifm;
136
137         list = NULL;
138         for (l = interfaces; l; l = l->next) {
139                 ifm = (const struct if_msg *)l->data;
140                 list = g_slist_append(list, ifm->ifname);
141         }
142         return list;
143 }
144
145 static GSList *
146 list_ssids(void)
147 {
148         GSList *list, *l, *a, *la;
149         const struct if_msg *ifm;
150         const struct if_ap *ifa;
151
152         list = NULL;
153         for (l = interfaces; l; l = l->next) {
154                 ifm = (const struct if_msg *)l->data;
155                 if (!ifm->wireless)
156                         continue;
157                 for (a = ifm->scan_results; a; a = a->next) {
158                         ifa = (const struct if_ap *)a->data;
159                         for (la = list; la; la = la->next)
160                                 if (g_strcmp0((const char *)la->data,
161                                         ifa->ssid) == 0)
162                                         break;
163                         if (la == NULL)
164                                 list = g_slist_append(list, ifa->ssid);
165                 }
166         }
167         return list;
168 }
169
170 static void
171 blocks_on_change(GtkWidget *widget)
172 {
173         GtkListStore *store;
174         GtkTreeIter iter;
175         GError *error;
176         char **list, **lp;
177         const char *iname, *nn;
178         GSList *l, *new_names;
179         GtkIconTheme *it;
180         GdkPixbuf *pb;
181         int n;
182
183         if (name) {
184                 make_config(config);
185                 save_config(block, name, config);
186                 free_config(&config);
187                 show_config(config);
188                 g_free(block);
189                 g_free(name);
190                 name = NULL;
191         }
192         block = combo_active_text(widget);
193         store = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(names)));
194         gtk_list_store_clear(store);
195         error = NULL;
196         if (!dbus_g_proxy_call(dbus, "GetConfigBlocks", &error,
197                 G_TYPE_STRING, block, G_TYPE_INVALID,
198                 G_TYPE_STRV, &list, G_TYPE_INVALID))
199         {
200                 g_warning("GetConfigBlocks: %s", error->message);
201                 g_clear_error(&error);
202                 return;
203         }
204
205         it = gtk_icon_theme_get_default();
206         if (g_strcmp0(block, "interface") == 0)
207                 new_names = list_interfaces();
208         else
209                 new_names = list_ssids();
210
211         n = 0;
212         for (l = new_names; l; l = l->next) {
213                 nn = (const char *)l->data;
214                 for (lp = list; *lp; lp++)
215                         if (g_strcmp0(nn, *lp) == 0)
216                                 break;
217                 if (*lp)
218                         iname = "document-save";
219                 else
220                         iname = "document-new";
221                 pb = gtk_icon_theme_load_icon(it, iname,
222                     GTK_ICON_SIZE_MENU, 0, &error);
223                 gtk_list_store_append(store, &iter);
224                 gtk_list_store_set(store, &iter, 0, pb, 1, nn, -1);
225                 g_object_unref(pb);
226                 n++;
227         }
228
229         for (lp = list; *lp; lp++) {
230                 for (l = new_names; l; l = l->next)
231                         if (g_strcmp0((const char *)l->data, *lp) == 0)
232                                 break;
233                 if (l != NULL)
234                         continue;
235                 pb = gtk_icon_theme_load_icon(it, "document-save",
236                     GTK_ICON_SIZE_MENU, 0, &error);
237                 gtk_list_store_append(store, &iter);
238                 gtk_list_store_set(store, &iter, 0, pb, 1, *lp, -1);
239                 g_object_unref(pb);
240                 n++;
241         }
242         gtk_widget_set_sensitive(names, n);
243         g_slist_free(new_names);
244         g_strfreev(list);
245 }
246
247 static void
248 names_on_change(void)
249 {
250         
251         if (name) {
252                 make_config(config);
253                 save_config(block, name, config);
254                 g_free(name);
255         }
256         name = combo_active_text(names);
257         free_config(&config);
258         config = load_config(block, name);
259         show_config(config);
260         gtk_widget_set_sensitive(controls, name ? true : false);
261         gtk_widget_set_sensitive(clear, name ? true : false);
262         gtk_widget_set_sensitive(rebind, name ? true : false);
263 }
264
265 static bool
266 valid_address(const char *val, bool allow_cidr)
267 {
268         char *addr, *p, *e;
269         struct in_addr in;
270         gint64 cidr;
271         bool retval;
272
273         addr = g_strdup(val);
274         if (allow_cidr) {
275                 p = strchr(addr, '/');
276                 if (p != NULL) {
277                         *p++ = '\0';
278                         errno = 0;
279                         e = NULL;
280                         cidr = g_ascii_strtoll(p, &e, 10);
281                         if (cidr < 0 || cidr > 32 ||
282                             errno != 0 || *e != '\0')
283                         {
284                                 retval = false;
285                                 goto out;
286                         }
287                 }
288         }
289         retval = inet_aton(addr, &in) == 0 ? false : true;
290         
291 out:
292         g_free(addr);
293         return retval;
294 }
295
296         
297 static bool
298 address_lost_focus(GtkEntry *entry)
299 {
300         const char *val;
301
302         val = gtk_entry_get_text(entry);
303         if (*val != '\0' && !valid_address(val, true))
304                 gtk_entry_set_text(entry, "");
305         return false;
306 }
307
308 static bool
309 entry_lost_focus(GtkEntry *entry)
310 {
311         const char *val;
312         char **a, **p;
313
314         val = gtk_entry_get_text(entry);
315         a = g_strsplit(val, " ", 0);
316         for (p = a; *p; p++) {
317                 if (**p != '\0' && !valid_address(*p, false)) {
318                         gtk_entry_set_text(entry, "");
319                         break;
320                 }
321         }
322         g_strfreev(a);
323         return false;
324 }
325
326 static void
327 on_clear(void)
328 {
329
330         free_config(&config);
331         config = g_ptr_array_new();
332         if (save_config(block, name, config)) {
333                 set_name_active_icon("document-new");
334                 show_config(config);
335         }
336 }
337
338 static void
339 rebind_interface(const char *iface)
340 {
341         GError *error;
342         
343         error = NULL;
344         if (!dbus_g_proxy_call(dbus, "Rebind", &error,
345                 G_TYPE_STRING, iface, G_TYPE_INVALID, G_TYPE_INVALID))
346         {
347                 g_critical("Rebind: %s: %s", iface, error->message);
348                 g_clear_error(&error);
349         }
350 }
351
352 static void
353 on_rebind(void)
354 {
355         GSList *l;
356         const struct if_msg *ifm;
357
358         make_config(config);
359         if (save_config(block, name, config)) {
360                 set_name_active_icon("document-save");
361                 show_config(config);
362                 if (g_strcmp0(block, "interface") == 0)
363                         rebind_interface(name);
364                 else {
365                         for (l = interfaces; l; l = l->next) {
366                                 ifm = (const struct if_msg *)l->data;
367                                 if (g_strcmp0(ifm->ssid, name) == 0)
368                                         rebind_interface(ifm->ifname);
369                         }
370                 }
371         }
372 }
373
374 static void
375 on_destroy(void)
376 {
377         
378         if (name != NULL) {
379                 make_config(config);
380                 save_config(block, name, config);
381                 g_free(block);
382                 g_free(name);
383                 block = name = NULL;
384         }
385         free_config(&config);
386         dialog = NULL;
387 }
388
389 void
390 dhcpcd_prefs_close(void)
391 {
392         
393         if (dialog != NULL) {
394                 gtk_widget_destroy(dialog);
395                 dialog = NULL;
396         }
397 }
398
399 void
400 dhcpcd_prefs_show(void)
401 {
402         GtkWidget *dialog_vbox, *hbox, *vbox, *table, *w;
403         GtkListStore *store;
404         GtkTreeIter iter;
405         GtkCellRenderer *rend;
406         GError *error;
407         GtkIconTheme *it;
408         GdkPixbuf *pb;
409         
410         if (dialog) {
411                 gtk_window_present(GTK_WINDOW(dialog));
412                 return;
413         }
414
415         dialog = gtk_window_new(GTK_WINDOW_TOPLEVEL);
416         g_signal_connect(G_OBJECT(dialog), "destroy", on_destroy, NULL);
417
418         gtk_window_set_title(GTK_WINDOW(dialog), _("dhcpcd preferences"));
419         gtk_window_set_resizable(GTK_WINDOW(dialog), false);
420         gtk_window_set_icon_name(GTK_WINDOW(dialog), "config-users");
421         gtk_window_set_position(GTK_WINDOW(dialog), GTK_WIN_POS_CENTER);
422         gtk_window_set_type_hint(GTK_WINDOW(dialog),
423             GDK_WINDOW_TYPE_HINT_DIALOG);
424
425         dialog_vbox = gtk_vbox_new(false, 10);
426         gtk_container_set_border_width(GTK_CONTAINER(dialog), 10);
427         gtk_container_add(GTK_CONTAINER(dialog), dialog_vbox);
428
429         hbox = gtk_hbox_new(false, 0);
430         gtk_box_pack_start(GTK_BOX(dialog_vbox), hbox, false, false, 3);
431         w = gtk_label_new("Configure:");
432         gtk_box_pack_start(GTK_BOX(hbox), w, false, false, 3);
433         store = gtk_list_store_new(2, GDK_TYPE_PIXBUF, G_TYPE_STRING);
434         it = gtk_icon_theme_get_default();
435         error = NULL;
436         pb = gtk_icon_theme_load_icon(it, "network-wired",
437             GTK_ICON_SIZE_MENU, 0, &error);     
438         gtk_list_store_append(store, &iter);
439         gtk_list_store_set(store, &iter, 0, pb, 1, "interface", -1);
440         g_object_unref(pb);
441         pb = gtk_icon_theme_load_icon(it, "network-wireless",
442             GTK_ICON_SIZE_MENU, 0, &error);
443         gtk_list_store_append(store, &iter);
444         gtk_list_store_set(store, &iter, 0, pb, 1, "ssid", -1);
445         g_object_unref(pb);
446         blocks = gtk_combo_box_new_with_model(GTK_TREE_MODEL(store));
447         rend = gtk_cell_renderer_pixbuf_new();
448         gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(blocks), rend, false);
449         gtk_cell_layout_add_attribute(GTK_CELL_LAYOUT(blocks),
450             rend, "pixbuf", 0);
451         rend = gtk_cell_renderer_text_new();
452         gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(blocks), rend, true);
453         gtk_cell_layout_add_attribute(GTK_CELL_LAYOUT(blocks),
454             rend, "text", 1);
455         gtk_combo_box_set_active(GTK_COMBO_BOX(blocks), 0);
456         gtk_box_pack_start(GTK_BOX(hbox), blocks, false, false, 3);
457         store = gtk_list_store_new(2, GDK_TYPE_PIXBUF, G_TYPE_STRING);
458         names = gtk_combo_box_new_with_model(GTK_TREE_MODEL(store));
459         rend = gtk_cell_renderer_pixbuf_new();
460         gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(names), rend, false);
461         gtk_cell_layout_add_attribute(GTK_CELL_LAYOUT(names),
462             rend, "pixbuf", 0);
463         rend = gtk_cell_renderer_text_new();
464         gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(names), rend, true);
465         gtk_cell_layout_add_attribute(GTK_CELL_LAYOUT(names), rend, "text", 1);
466         gtk_widget_set_sensitive(names, false);
467         gtk_box_pack_start(GTK_BOX(hbox), names, false, false, 3);
468         g_signal_connect(G_OBJECT(blocks), "changed",
469             G_CALLBACK(blocks_on_change), NULL);
470         g_signal_connect(G_OBJECT(names), "changed",
471             G_CALLBACK(names_on_change), NULL);
472         
473         w = gtk_hseparator_new();
474         gtk_box_pack_start(GTK_BOX(dialog_vbox), w, true, false, 3);
475         controls = gtk_vbox_new(false, 10);
476         gtk_widget_set_sensitive(controls, false);
477         gtk_box_pack_start(GTK_BOX(dialog_vbox), controls, true, true, 0);
478         vbox = gtk_vbox_new(false, 3);
479         gtk_box_pack_start(GTK_BOX(controls), vbox, false, false, 0);
480         autoconf = gtk_check_button_new_with_label(
481                 _("Automatically configure empty options"));
482         gtk_box_pack_start(GTK_BOX(vbox), autoconf, false, false, 3);
483         table = gtk_table_new(6, 2, false);
484         gtk_box_pack_start(GTK_BOX(controls), table, false, false, 0);
485
486 #define attach_label(a, b, c, d, e)                                           \
487         do {                                                                  \
488                 gtk_misc_set_alignment(GTK_MISC(a), 0.0, 0.5);                \
489                 gtk_table_attach(GTK_TABLE(table), a, b, c, d, e,             \
490                     GTK_FILL | GTK_SHRINK, GTK_FILL | GTK_SHRINK, 3, 3);      \
491         } while (0)
492 #define attach_entry(a, b, c, d, e)                                           \
493         gtk_table_attach(GTK_TABLE(table), a, b, c, d, e,                     \
494             GTK_EXPAND | GTK_FILL | GTK_SHRINK, GTK_FILL | GTK_SHRINK, 3, 3); \
495                                                                               \
496         w = gtk_label_new(_("IP Address:"));
497         address = gtk_entry_new();
498         gtk_entry_set_max_length(GTK_ENTRY(address), 15);
499         g_signal_connect(G_OBJECT(address), "focus-out-event",
500             G_CALLBACK(address_lost_focus), NULL);
501         attach_label(w, 0, 1, 0, 1);
502         attach_entry(address, 1, 2, 0, 1);
503
504         w = gtk_label_new(_("Router:"));
505         router = gtk_entry_new();
506         gtk_entry_set_max_length(GTK_ENTRY(router), 12);
507         g_signal_connect(G_OBJECT(router), "focus-out-event",
508             G_CALLBACK(entry_lost_focus), NULL);
509         attach_label(w, 0, 1, 2, 3);
510         attach_entry(router, 1, 2, 2, 3);
511
512         w = gtk_label_new(_("DNS Servers:"));
513         dns_servers = gtk_entry_new();
514         g_signal_connect(G_OBJECT(dns_servers), "focus-out-event",
515             G_CALLBACK(entry_lost_focus), NULL);
516         attach_label(w, 0, 1, 3, 4);
517         attach_entry(dns_servers, 1, 2, 3, 4);
518
519         w = gtk_label_new(_("DNS Search:"));
520         dns_search = gtk_entry_new();
521         attach_label(w, 0, 1, 4, 5);
522         attach_entry(dns_search, 1, 2, 4, 5);
523
524         hbox = gtk_hbox_new(false, 10);
525         gtk_box_pack_start(GTK_BOX(dialog_vbox), hbox, true, true, 3);
526         clear = gtk_button_new_from_stock(GTK_STOCK_CLEAR);
527         gtk_widget_set_sensitive(clear, false);
528         gtk_box_pack_start(GTK_BOX(hbox), clear, false, false, 0);
529         g_signal_connect(G_OBJECT(clear), "clicked", on_clear, NULL);
530         rebind = gtk_button_new_with_mnemonic(_("_Rebind"));
531         gtk_widget_set_sensitive(rebind, false);
532         w = gtk_image_new_from_stock(GTK_STOCK_EXECUTE,
533             GTK_ICON_SIZE_BUTTON);
534         gtk_button_set_image(GTK_BUTTON(rebind), w);
535         gtk_box_pack_start(GTK_BOX(hbox), rebind, false, false, 0);
536         g_signal_connect(G_OBJECT(rebind), "clicked", on_rebind, NULL);
537         w = gtk_button_new_from_stock(GTK_STOCK_CLOSE);
538         gtk_box_pack_end(GTK_BOX(hbox), w, false, false, 0);
539         g_signal_connect(G_OBJECT(w), "clicked",
540             dhcpcd_prefs_close, NULL);
541         
542         blocks_on_change(blocks);
543         show_config(NULL);
544         gtk_widget_show_all(dialog);
545 }