Rejig menu order.
[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 <dbus/dbus.h>
28
29 #include "dhcpcd-gtk.h"
30 #include "prefs.h"
31
32 static GPtrArray *config;
33 static GtkWidget *dialog, *blocks, *names, *controls;
34 static GtkWidget *hostname, *fqdn, *clientid, *duid, *arp, *ipv4ll;
35
36 static void
37 free_config(GPtrArray **array)
38 {
39         GPtrArray *a;
40         guint i;
41         GValueArray *c;
42
43         a = *array;
44         if (a == NULL)
45                 return;
46         for (i = 0; i < a->len; i++) {
47                 c = g_ptr_array_index(a, i);
48                 g_value_array_free(c);
49         }
50         g_ptr_array_free(a, TRUE);
51         *array = NULL;
52 }       
53
54 static GPtrArray *
55 read_config(const char *block, const char *name)
56 {
57         GType otype;
58         GError *error;
59         GPtrArray *array;
60
61         error = NULL;
62         otype = dbus_g_type_get_struct("GValueArray",
63             G_TYPE_STRING, G_TYPE_STRING, G_TYPE_INVALID);
64         otype = dbus_g_type_get_collection("GPtrArray", otype);
65         if (!dbus_g_proxy_call(dbus, "GetConfig", &error,
66                 G_TYPE_STRING, block, G_TYPE_STRING, name, G_TYPE_INVALID,
67                 otype, &array, G_TYPE_INVALID))
68         {
69                 g_critical("GetConfig: %s", error->message);
70                 g_clear_error(&error);
71                 return NULL;
72         }
73         return array;
74 }
75
76 static void
77 save_config(const char *block, const char *name)
78 {
79         GType otype;
80         GError *error;
81
82         error = NULL;
83         otype = dbus_g_type_get_struct("GValueArray",
84             G_TYPE_STRING, G_TYPE_STRING, G_TYPE_INVALID);
85         otype = dbus_g_type_get_collection("GPtrArray", otype);
86         if (!dbus_g_proxy_call(dbus, "SetConfig", &error,
87                 G_TYPE_STRING, block, G_TYPE_STRING, name,
88                 otype, config, G_TYPE_INVALID, G_TYPE_INVALID))
89         {
90                 g_critical("SetConfig: %s", error->message);
91                 g_clear_error(&error);
92         }
93 }
94
95 static gboolean
96 get_config(GPtrArray *array, const char *option, const char **value)
97 {
98         guint i;
99         GValueArray *c;
100         GValue *val;
101         const char *str;
102
103         if (array == NULL)
104                 return FALSE;
105         for (i = 0; i < array->len; i++) {
106                 c = g_ptr_array_index(array, i);
107                 val = g_value_array_get_nth(c, 0);
108                 str = g_value_get_string(val);
109                 if (strcmp(str, option) != 0)
110                         continue;
111                 if (value != NULL) {
112                         val = g_value_array_get_nth(c, 1);
113                         str = g_value_get_string(val);
114                         if (*str == '\0')
115                                 *value = NULL;
116                         else
117                                 *value = str;
118                 }
119                 return TRUE;
120         }
121         if (value != NULL)
122                 *value = NULL;
123         return FALSE;
124 }
125
126 static gboolean
127 toggle_generic(gboolean has, const char *val)
128 {
129         return (has && (val == NULL || g_strcmp0(val, "\"\"") == 0));
130 }
131
132 static gboolean
133 toggle_generic_neg(gboolean has, _unused const char *val)
134 {
135         return !has;
136 }
137
138 static gboolean
139 toggle_fqdn(gboolean has, const char *val)
140 {
141         return (has &&
142             (val == NULL ||
143                 g_strcmp0(val, "both") == 0 ||
144                 g_strcmp0(val, "ptr") == 0));
145 }
146
147 static void
148 set_check(GtkToggleButton *button,
149     GPtrArray *global, GPtrArray *conf, const char *name,
150     gboolean (*test)(gboolean, const char *))
151 {
152         const char *val;
153         gboolean has, incons;
154         
155         if (get_config(conf, name, &val)) {
156                 has = TRUE;
157                 incons = FALSE;
158         } else if (global == NULL) {
159                 incons = FALSE;
160                 has = FALSE;
161         } else {
162                 has = get_config(global, name, &val);
163                 incons = TRUE;
164         }
165         gtk_toggle_button_set_active(button, test(has, val));
166         gtk_toggle_button_set_inconsistent(button, incons);
167 }
168
169 static void
170 show_config(const char *block, const char *name)
171 {
172         GPtrArray *global;
173
174         if (block || name)
175                 global = read_config(NULL, NULL);
176         else
177                 global = NULL;
178         
179         free_config(&config);
180         config = read_config(block, name);
181         set_check(GTK_TOGGLE_BUTTON(hostname), global, config,
182             "hostname", toggle_generic);
183         set_check(GTK_TOGGLE_BUTTON(fqdn), global, config,
184             "fqdn", toggle_fqdn);
185         set_check(GTK_TOGGLE_BUTTON(clientid), global, config,
186             "clientid", toggle_generic);
187         set_check(GTK_TOGGLE_BUTTON(duid), global, config,
188             "duid", toggle_generic);
189         gtk_widget_set_sensitive(duid,
190             gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(clientid)));
191         set_check(GTK_TOGGLE_BUTTON(arp), global, config,
192             "noarp", toggle_generic_neg);
193         set_check(GTK_TOGGLE_BUTTON(ipv4ll), global, config,
194             "noipv4ll", toggle_generic_neg);
195         gtk_widget_set_sensitive(ipv4ll,
196             gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(arp)));
197         free_config(&global);
198 }
199
200 static char *
201 combo_active_text(GtkWidget *widget)
202 {
203         GtkListStore *store;
204         GtkTreeIter iter;
205         GValue val;
206         char *text;
207
208         store = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(widget)));
209         if (!gtk_combo_box_get_active_iter(GTK_COMBO_BOX(widget), &iter))
210                 return NULL;
211         memset(&val, 0, sizeof(val));
212         gtk_tree_model_get_value(GTK_TREE_MODEL(store), &iter, 1, &val);
213         text = g_strdup(g_value_get_string(&val));
214         g_value_unset(&val);
215         return text;
216 }
217
218 static GSList *
219 list_interfaces(void)
220 {
221         GSList *list, *l;
222         const struct if_msg *ifm;
223
224         list = NULL;
225         for (l = interfaces; l; l = l->next) {
226                 ifm = (const struct if_msg *)l->data;
227                 list = g_slist_append(list, ifm->ifname);
228         }
229         return list;
230 }
231
232 static GSList *
233 list_ssids(void)
234 {
235         GSList *list, *l, *a, *la;
236         const struct if_msg *ifm;
237         const struct if_ap *ifa;
238
239         list = NULL;
240         for (l = interfaces; l; l = l->next) {
241                 ifm = (const struct if_msg *)l->data;
242                 if (!ifm->wireless)
243                         continue;
244                 for (a = ifm->scan_results; a; a = a->next) {
245                         ifa = (const struct if_ap *)a->data;
246                         for (la = list; la; la = la->next)
247                                 if (g_strcmp0((const char *)la->data,
248                                         ifa->ssid) == 0)
249                                         break;
250                         if (la == NULL)
251                                 list = g_slist_append(list, ifa->ssid);
252                 }
253         }
254         return list;
255 }
256
257 static void
258 blocks_on_change(GtkWidget *widget, _unused gpointer data)
259 {
260         GtkListStore *store;
261         GtkTreeIter iter;
262         GError *error;
263         char **list, **lp, *block;
264         const char *iname, *nn;
265         GSList *l, *new_names;
266         GtkIconTheme *it;
267         GdkPixbuf *pb;
268         gint n;
269
270         block = combo_active_text(widget);
271         store = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(names)));
272         gtk_list_store_clear(store);
273         if (strcmp(block, "global") == 0) {
274                 g_free(block);
275                 gtk_widget_set_sensitive(names, FALSE);
276                 gtk_widget_set_sensitive(controls, TRUE);
277                 show_config(NULL, NULL);
278                 return;
279         }
280         error = NULL;
281         if (!dbus_g_proxy_call(dbus, "GetConfigBlocks", &error,
282                 G_TYPE_STRING, block, G_TYPE_INVALID,
283                 G_TYPE_STRV, &list, G_TYPE_INVALID))
284         {
285                 g_free(block);
286                 g_warning("GetConfigBlocks: %s", error->message);
287                 g_clear_error(&error);
288                 return;
289         }
290
291         it = gtk_icon_theme_get_default();
292         if (g_strcmp0(block, "interface") == 0)
293                 new_names = list_interfaces();
294         else
295                 new_names = list_ssids();
296
297         n = 0;
298         for (l = new_names; l; l = l->next) {
299                 nn = (const char *)l->data;
300                 for (lp = list; *lp; lp++)
301                         if (g_strcmp0(nn, *lp) == 0)
302                                 break;
303                 if (*lp)
304                         iname = "document-save";
305                 else
306                         iname = "document-new";
307                 pb = gtk_icon_theme_load_icon(it, iname,
308                     GTK_ICON_SIZE_MENU, 0, &error);
309                 gtk_list_store_append(store, &iter);
310                 gtk_list_store_set(store, &iter, 0, pb, 1, nn, -1);
311                 g_object_unref(pb);
312                 n++;
313         }
314
315         for (lp = list; *lp; lp++) {
316                 for (l = new_names; l; l = l->next)
317                         if (g_strcmp0((const char *)l->data, *lp) == 0)
318                                 break;
319                 if (l != NULL)
320                         continue;
321                 pb = gtk_icon_theme_load_icon(it, "document-save",
322                     GTK_ICON_SIZE_MENU, 0, &error);
323                 gtk_list_store_append(store, &iter);
324                 gtk_list_store_set(store, &iter, 0, pb, 1, *lp, -1);
325                 g_object_unref(pb);
326                 n++;
327         }
328         gtk_widget_set_sensitive(names, n);
329         gtk_widget_set_sensitive(controls, FALSE);
330         g_slist_free(new_names);
331         g_strfreev(list);
332         g_free(block);
333 }
334
335 static void
336 names_on_change(GtkWidget *widget, _unused gpointer data)
337 {
338         char *block, *name;
339
340         block = combo_active_text(blocks);
341         name = combo_active_text(widget);
342         gtk_widget_set_sensitive(controls, TRUE);
343         show_config(block, name);
344         g_free(block);
345         g_free(name);
346 }
347
348 static void
349 on_toggle(GtkWidget *widget, gpointer data)
350 {
351         gboolean active;
352
353         gtk_toggle_button_set_inconsistent(GTK_TOGGLE_BUTTON(widget), FALSE);
354         if (data == NULL)
355                 return;
356         active = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget));
357         gtk_widget_set_sensitive(GTK_WIDGET((GtkWidget *)data), active);
358 }
359
360 static void
361 on_destroy(void)
362 {
363         free_config(&config);
364         dialog = NULL;
365 }
366
367 void
368 dhcpcd_prefs_close(void)
369 {
370         if (dialog) {
371                 gtk_widget_destroy(dialog);
372                 dialog = NULL;
373         }
374 }
375
376 static void
377 on_redo(void)
378 {
379         char *block, *name;
380         GValueArray *array;
381         GValue val;
382
383         block = combo_active_text(blocks);
384         name = combo_active_text(names);
385         free_config(&config);
386         if (g_strcmp0(block, "global") == 0) {
387                 config = g_ptr_array_sized_new(3);
388                 memset(&val, 0, sizeof(val));
389                 g_value_init(&val, G_TYPE_STRING);
390                 array = g_value_array_new(2);
391                 g_value_set_static_string(&val, "hostname");
392                 array = g_value_array_append(array, &val);
393                 g_value_set_static_string(&val, "");
394                 array = g_value_array_append(array, &val);
395                 g_ptr_array_add(config, array);
396                 array = g_value_array_new(2);
397                 g_value_set_static_string(&val, "option");
398                 array = g_value_array_append(array, &val);
399                 g_value_set_static_string(&val, "domain_name_servers, "
400                     "domain_name, domain_search, host_name");
401                 array = g_value_array_append(array, &val);
402                 g_ptr_array_add(config, array);
403                 array = g_value_array_new(2);
404                 g_value_set_static_string(&val, "option");
405                 array = g_value_array_append(array, &val);
406                 g_value_set_static_string(&val, "ntp_servers");
407                 array = g_value_array_append(array, &val);
408                 g_ptr_array_add(config, array);
409         } else
410                 config = g_ptr_array_new();
411         save_config(name ? block : NULL, name);
412         show_config(name ? block : NULL, name);
413         g_free(block);
414         g_free(name);
415 }
416
417 void
418 dhcpcd_prefs_show(void)
419 {
420         GtkWidget *dialog_vbox, *hbox, *vbox, *label;
421         GtkListStore *store;
422         GtkTreeIter iter;
423         GtkCellRenderer *rend;
424         GError *error;
425         GtkIconTheme *it;
426         GdkPixbuf *pb;
427         
428         if (dialog) {
429                 gtk_window_present(GTK_WINDOW(dialog));
430                 return;
431         }
432
433         dialog = gtk_window_new(GTK_WINDOW_TOPLEVEL);
434         gtk_signal_connect(GTK_OBJECT(dialog), "destroy", on_destroy, NULL);
435
436         gtk_window_set_title(GTK_WINDOW(dialog), _("dhcpcd preferences"));
437         gtk_window_set_resizable(GTK_WINDOW(dialog), FALSE);
438         gtk_window_set_icon_name(GTK_WINDOW(dialog), "config-users");
439         gtk_window_set_position(GTK_WINDOW(dialog), GTK_WIN_POS_CENTER);
440         gtk_window_set_type_hint(GTK_WINDOW(dialog),
441                                  GDK_WINDOW_TYPE_HINT_DIALOG);
442
443         dialog_vbox = gtk_vbox_new(FALSE, 3);
444         gtk_container_add(GTK_CONTAINER(dialog), dialog_vbox);
445
446         hbox = gtk_hbox_new(FALSE, 10);
447         gtk_box_pack_start(GTK_BOX(dialog_vbox), hbox, FALSE, FALSE, 3);
448         label = gtk_label_new("Configuration block:");
449         gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 3);
450         store = gtk_list_store_new(2, GDK_TYPE_PIXBUF, G_TYPE_STRING);
451         it = gtk_icon_theme_get_default();
452         error = NULL;
453         pb = gtk_icon_theme_load_icon(it, "config-users",
454             GTK_ICON_SIZE_MENU, 0, &error);     
455         gtk_list_store_append(store, &iter);
456         gtk_list_store_set(store, &iter, 0, pb, 1, "global", -1);
457         g_object_unref(pb);
458         pb = gtk_icon_theme_load_icon(it, "network-wired",
459             GTK_ICON_SIZE_MENU, 0, &error);     
460         gtk_list_store_append(store, &iter);
461         gtk_list_store_set(store, &iter, 0, pb, 1, "interface", -1);
462         g_object_unref(pb);
463         pb = gtk_icon_theme_load_icon(it, "network-wireless",
464             GTK_ICON_SIZE_MENU, 0, &error);
465         gtk_list_store_append(store, &iter);
466         gtk_list_store_set(store, &iter, 0, pb, 1, "ssid", -1);
467         g_object_unref(pb);
468         blocks = gtk_combo_box_new_with_model(GTK_TREE_MODEL(store));
469         rend = gtk_cell_renderer_pixbuf_new();
470         gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(blocks), rend, FALSE);
471         gtk_cell_layout_add_attribute(GTK_CELL_LAYOUT(blocks),
472             rend, "pixbuf", 0);
473         rend = gtk_cell_renderer_text_new();
474         gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(blocks), rend, TRUE);
475         gtk_cell_layout_add_attribute(GTK_CELL_LAYOUT(blocks),
476             rend, "text", 1);
477         gtk_combo_box_set_active(GTK_COMBO_BOX(blocks), 0);
478         gtk_box_pack_start(GTK_BOX(hbox), blocks, FALSE, FALSE, 3);
479         label = gtk_label_new("Block name:");
480         gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 3);
481         store = gtk_list_store_new(2, GDK_TYPE_PIXBUF, G_TYPE_STRING);
482         names = gtk_combo_box_new_with_model(GTK_TREE_MODEL(store));
483         rend = gtk_cell_renderer_pixbuf_new();
484         gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(names), rend, FALSE);
485         gtk_cell_layout_add_attribute(GTK_CELL_LAYOUT(names),
486             rend, "pixbuf", 0);
487         rend = gtk_cell_renderer_text_new();
488         gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(names), rend, TRUE);
489         gtk_cell_layout_add_attribute(GTK_CELL_LAYOUT(names), rend, "text", 1);
490         gtk_widget_set_sensitive(names, FALSE);
491         gtk_box_pack_start(GTK_BOX(hbox), names, FALSE, FALSE, 3);
492         gtk_signal_connect(GTK_OBJECT(blocks), "changed",
493             G_CALLBACK(blocks_on_change), NULL);
494         gtk_signal_connect(GTK_OBJECT(names), "changed",
495             G_CALLBACK(names_on_change), NULL);
496         
497         label = gtk_hseparator_new();
498         gtk_box_pack_start(GTK_BOX(dialog_vbox), label, TRUE, FALSE, 3);
499         controls = gtk_hbox_new(FALSE, 10);
500         gtk_box_pack_start(GTK_BOX(dialog_vbox), controls, TRUE, TRUE, 0);
501         vbox = gtk_vbox_new(FALSE, 3);
502         gtk_box_pack_start(GTK_BOX(controls), vbox, FALSE, FALSE, 0);
503         hostname = gtk_check_button_new_with_label(_("Send Hostname"));
504         gtk_signal_connect(GTK_OBJECT(hostname), "toggled",
505             G_CALLBACK(on_toggle), NULL);
506         gtk_box_pack_start(GTK_BOX(vbox), hostname, FALSE, FALSE, 3);
507         fqdn = gtk_check_button_new_with_label(_("Send FQDN"));
508         gtk_signal_connect(GTK_OBJECT(fqdn), "toggled",
509             G_CALLBACK(on_toggle), NULL);
510         gtk_box_pack_start(GTK_BOX(vbox), fqdn, FALSE, FALSE, 3);
511         clientid = gtk_check_button_new_with_label(_("Send ClientID"));
512         gtk_box_pack_start(GTK_BOX(vbox), clientid, FALSE, FALSE, 3);
513         duid = gtk_check_button_new_with_label(_("Send DUID"));
514         gtk_signal_connect(GTK_OBJECT(clientid), "toggled",
515             G_CALLBACK(on_toggle), duid);
516         gtk_signal_connect(GTK_OBJECT(duid), "toggled",
517             G_CALLBACK(on_toggle), NULL);
518         gtk_box_pack_start(GTK_BOX(vbox), duid, FALSE, FALSE, 3);
519         arp = gtk_check_button_new_with_label(_("Enable ARP checking"));
520         gtk_box_pack_start(GTK_BOX(vbox), arp, FALSE, FALSE, 3);
521         ipv4ll = gtk_check_button_new_with_label(_("Enable Zeroconf"));
522         gtk_box_pack_start(GTK_BOX(vbox), ipv4ll, FALSE, FALSE, 3);
523         gtk_signal_connect(GTK_OBJECT(arp), "toggled",
524             G_CALLBACK(on_toggle), ipv4ll);
525         gtk_signal_connect(GTK_OBJECT(ipv4ll), "toggled",
526             G_CALLBACK(on_toggle), NULL);
527
528         hbox = gtk_hbox_new(FALSE, 0);
529         gtk_box_pack_start(GTK_BOX(dialog_vbox), hbox, TRUE, TRUE, 3);
530         label = gtk_button_new_from_stock(GTK_STOCK_REDO);
531         gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
532         gtk_signal_connect(GTK_OBJECT(label), "clicked", on_redo, NULL);
533         label = gtk_button_new_from_stock(GTK_STOCK_CLOSE);
534         gtk_box_pack_end(GTK_BOX(hbox), label, FALSE, FALSE, 0);
535         gtk_signal_connect(GTK_OBJECT(label), "clicked",
536                            dhcpcd_prefs_close, NULL);
537
538         show_config(NULL, NULL);
539         gtk_widget_show_all(dialog);
540 }