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