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