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