855b518c2d053c434baec79e756ee3f36920136f
[dhcpcd-ui] / src / dhcpcd-gtk / prefs.c
1 /*
2  * dhcpcd-gtk
3  * Copyright 2009-2014 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 <sys/types.h>
28 #include <arpa/inet.h>
29 #include <net/if.h>
30
31 #include <errno.h>
32
33 #include "dhcpcd-gtk.h"
34
35 static GtkWidget *dialog, *blocks, *names, *controls, *clear, *rebind;
36 static GtkWidget *autoconf, *address, *router, *dns_servers, *dns_search;
37 static DHCPCD_OPTION *config;
38 static char *block, *name;
39 static DHCPCD_IF *iface;
40
41 static void
42 config_err_dialog(DHCPCD_CONNECTION *con, bool writing, const char *txt)
43 {
44         GtkWidget *edialog;
45         char *t;
46
47         t = g_strconcat(_(writing ? "Error saving" : "Error reading"), " ",
48             dhcpcd_cffile(con), "\n\n", txt, NULL);
49         edialog = gtk_message_dialog_new(NULL, GTK_DIALOG_MODAL,
50             GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, "%s", t);
51         gtk_window_set_title(GTK_WINDOW(edialog), _("Config error"));
52         gtk_dialog_run(GTK_DIALOG(edialog));
53         gtk_widget_destroy(edialog);
54         g_free(t);
55 }
56
57 static void
58 show_config(DHCPCD_OPTION *conf)
59 {
60         const char *val;
61         bool autocnf;
62
63         if ((val = dhcpcd_config_get_static(conf, "ip_address=")) != NULL)
64                 autocnf = false;
65         else {
66                 if ((val = dhcpcd_config_get(conf, "inform")) == NULL &&
67                     (iface && iface->flags & IFF_POINTOPOINT))
68                         autocnf = false;
69                 else
70                         autocnf = true;
71         }
72         gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(autoconf), autocnf);
73         gtk_entry_set_text(GTK_ENTRY(address), val ? val : "");
74         val = dhcpcd_config_get_static(conf, "routers=");
75         gtk_entry_set_text(GTK_ENTRY(router), val ? val : "");
76         val = dhcpcd_config_get_static(conf, "domain_name_servers=");
77         gtk_entry_set_text(GTK_ENTRY(dns_servers), val ? val : "");
78         val = dhcpcd_config_get_static(conf, "domain_search=");
79         gtk_entry_set_text(GTK_ENTRY(dns_search), val ? val : "");
80 }
81
82 static char *
83 combo_active_text(GtkWidget *widget)
84 {
85         GtkListStore *store;
86         GtkTreeIter iter;
87         GValue val;
88         char *text;
89
90         store = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(widget)));
91         if (!gtk_combo_box_get_active_iter(GTK_COMBO_BOX(widget), &iter))
92                 return NULL;
93         memset(&val, 0, sizeof(val));
94         gtk_tree_model_get_value(GTK_TREE_MODEL(store), &iter, 1, &val);
95         text = g_strdup(g_value_get_string(&val));
96         g_value_unset(&val);
97         return text;
98 }
99
100 static bool
101 set_option(DHCPCD_OPTION **conf, bool s, const char *opt, const char *val,
102         bool *ret)
103 {
104
105         if (s) {
106                 if (!dhcpcd_config_set_static(conf, opt, val))
107                         g_critical("dhcpcd_config_set_static: %s",
108                             strerror(errno));
109                 else
110                         return true;
111         } else {
112                 if (!dhcpcd_config_set(conf, opt, val))
113                         g_critical("dhcpcd_config_set: %s",
114                             strerror(errno));
115                 else
116                         return true;
117         }
118
119         if (ret)
120                 *ret = false;
121         return false;
122 }
123
124 static bool
125 make_config(DHCPCD_OPTION **conf)
126 {
127         const char *val, ns[] = "";
128         bool a, ret;
129
130         ret = true;
131         a = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(autoconf));
132         if (iface && iface->flags & IFF_POINTOPOINT)
133                 set_option(conf, true, "ip_address=", a ? NULL : ns, &ret);
134         else {
135                 val = gtk_entry_get_text(GTK_ENTRY(address));
136                 if (*val == '\0')
137                         val = NULL;
138                 set_option(conf, false, "inform", a ? val : NULL, &ret);
139                 set_option(conf, true, "ip_address=", a ? NULL : val, &ret);
140         }
141
142         val = gtk_entry_get_text(GTK_ENTRY(router));
143         if (a && *val == '\0')
144                 val = NULL;
145         set_option(conf, true, "routers=", val, &ret);
146
147         val = gtk_entry_get_text(GTK_ENTRY(dns_servers));
148         if (a && *val == '\0')
149                 val = NULL;
150         set_option(conf, true, "domain_name_servers=", val, &ret);
151
152         val = gtk_entry_get_text(GTK_ENTRY(dns_search));
153         if (a && *val == '\0')
154                 val = NULL;
155         set_option(conf, true, "domain_search=", val, &ret);
156
157         return ret;
158 }
159
160 static bool
161 write_config(DHCPCD_CONNECTION *con, DHCPCD_OPTION **conf)
162 {
163
164         if (make_config(conf) &&
165             !dhcpcd_config_write(con, block, name, *conf))
166         {
167                 const char *s;
168
169                 s = strerror(errno);
170                 g_warning("dhcpcd_config_write: %s", s);
171                 config_err_dialog(con, true, s);
172                 return false;
173         }
174         return true;
175 }
176
177 static GdkPixbuf *
178 load_icon(const char *iname)
179 {
180         int width, height;
181
182         if (!gtk_icon_size_lookup(GTK_ICON_SIZE_MENU, &width, &height))
183                 return NULL;
184         return gtk_icon_theme_load_icon(gtk_icon_theme_get_default(),
185             iname, MIN(width, height), 0, NULL);
186 }
187
188 static void
189 set_name_active_icon(const char *iname)
190 {
191         GtkListStore *store;
192         GtkTreeIter iter;
193         GtkTreePath *path;
194         GdkPixbuf *pb;
195
196         store = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(names)));
197         if (!gtk_combo_box_get_active_iter(GTK_COMBO_BOX(names), &iter))
198                 return;
199         pb = load_icon(iname);
200         if (pb) {
201                 path = gtk_tree_model_get_path(GTK_TREE_MODEL(store), &iter);
202                 gtk_list_store_set(store, &iter, 0, pb, -1);
203                 g_object_unref(pb);
204                 gtk_tree_model_row_changed(GTK_TREE_MODEL(store), path, &iter);
205                 gtk_tree_path_free(path);
206         }
207 }
208
209 static GSList *
210 list_interfaces(DHCPCD_CONNECTION *con)
211 {
212         GSList *list;
213         DHCPCD_IF *i;
214
215         list = NULL;
216         for (i = dhcpcd_interfaces(con); i; i = i->next)
217                 if (strcmp(i->type, "ipv4") == 0)
218                         list = g_slist_append(list, UNCONST(i->ifname));
219         return list;
220 }
221
222 static GSList *
223 list_ssids(WI_SCANS *scans)
224 {
225         GSList *list, *l;
226         WI_SCAN *w;
227         DHCPCD_WI_SCAN *wis;
228
229         list = NULL;
230         TAILQ_FOREACH(w, scans, next) {
231                 for (wis = w->scans; wis; wis = wis->next) {
232                         for (l = list; l; l = l->next)
233                                 if (g_strcmp0((const char *)l->data,
234                                         wis->ssid) == 0)
235                                         break;
236                         if (l == NULL)
237                                 list = g_slist_append(list, wis->ssid);
238                 }
239         }
240         return list;
241 }
242
243 static void
244 blocks_on_change(GtkWidget *widget, gpointer data)
245 {
246         DHCPCD_CONNECTION *con;
247         GtkListStore *store;
248         GtkTreeIter iter;
249         char **list, **lp;
250         const char *iname, *nn;
251         GSList *l, *new_names;
252         GdkPixbuf *pb;
253         int n;
254
255         con = (DHCPCD_CONNECTION *)data;
256         if (name) {
257                 write_config(con, &config);
258                 dhcpcd_config_free(config);
259                 config = NULL;
260                 show_config(config);
261                 g_free(block);
262                 g_free(name);
263                 name = NULL;
264         }
265         block = combo_active_text(widget);
266         store = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(names)));
267         gtk_list_store_clear(store);
268         list = dhcpcd_config_blocks(con, block);
269
270         if (g_strcmp0(block, "interface") == 0)
271                 new_names = list_interfaces(con);
272         else
273                 new_names = list_ssids(&wi_scans);
274
275         n = 0;
276         for (l = new_names; l; l = l->next) {
277                 nn = (const char *)l->data;
278                 if (list) {
279                         for (lp = list; *lp; lp++)
280                                 if (g_strcmp0(nn, *lp) == 0)
281                                         break;
282                         if (*lp)
283                                 iname = "document-save";
284                         else
285                                 iname = "document-new";
286                 } else
287                         iname = "document-new";
288                 pb = load_icon(iname);
289                 gtk_list_store_append(store, &iter);
290                 gtk_list_store_set(store, &iter, 0, pb, 1, nn, -1);
291                 g_object_unref(pb);
292                 n++;
293         }
294
295         for (lp = list; lp && *lp; lp++) {
296                 for (l = new_names; l; l = l->next)
297                         if (g_strcmp0((const char *)l->data, *lp) == 0)
298                                 break;
299                 if (l != NULL)
300                         continue;
301                 pb = load_icon("document-save");
302                 gtk_list_store_append(store, &iter);
303                 gtk_list_store_set(store, &iter, 0, pb, 1, *lp, -1);
304                 g_object_unref(pb);
305                 n++;
306         }
307         gtk_widget_set_sensitive(names, n);
308         g_slist_free(new_names);
309         dhcpcd_config_blocks_free(list);
310 }
311
312 static void
313 names_on_change(_unused GtkWidget *widget, gpointer data)
314 {
315         DHCPCD_CONNECTION *con;
316         DHCPCD_IF *i;
317
318         con = (DHCPCD_CONNECTION *)data;
319         if (name) {
320                 write_config(con, &config);
321                 g_free(name);
322         }
323         name = combo_active_text(names);
324         dhcpcd_config_free(config);
325         iface = NULL;
326         if (g_strcmp0(block, "interface") == 0) {
327                 for (i = dhcpcd_interfaces(con); i; i = i->next)
328                         if (g_strcmp0(name, i->ifname) == 0) {
329                                 iface = i;
330                                 break;
331                         }
332         }
333         gtk_widget_set_sensitive(address,
334             iface && (iface->flags & IFF_POINTOPOINT) == 0);
335         if (block && name) {
336                 errno = 0;
337                 config = dhcpcd_config_read(con, block, name);
338                 if (config == NULL && errno) {
339                         const char *s;
340
341                         s = strerror(errno);
342                         g_warning("dhcpcd_config_read: %s", s);
343                         config_err_dialog(con, false, s);
344                 }
345         } else
346                 config = NULL;
347         show_config(config);
348         gtk_widget_set_sensitive(controls, name ? true : false);
349         gtk_widget_set_sensitive(clear, name ? true : false);
350         gtk_widget_set_sensitive(rebind, name ? true : false);
351 }
352
353 static bool
354 valid_address(const char *val, bool allow_cidr)
355 {
356         char *addr, *p, *e;
357         struct in_addr in;
358         gint64 cidr;
359         bool retval;
360
361         addr = g_strdup(val);
362         if (allow_cidr) {
363                 p = strchr(addr, '/');
364                 if (p != NULL) {
365                         *p++ = '\0';
366                         errno = 0;
367                         e = NULL;
368                         cidr = g_ascii_strtoll(p, &e, 10);
369                         if (cidr < 0 || cidr > 32 ||
370                             errno != 0 || *e != '\0')
371                         {
372                                 retval = false;
373                                 goto out;
374                         }
375                 }
376         }
377         retval = inet_aton(addr, &in) == 0 ? false : true;
378
379 out:
380         g_free(addr);
381         return retval;
382 }
383
384 static bool
385 address_lost_focus(GtkEntry *entry)
386 {
387         const char *val;
388
389         val = gtk_entry_get_text(entry);
390         if (*val != '\0' && !valid_address(val, true))
391                 gtk_entry_set_text(entry, "");
392         return false;
393 }
394
395 static bool
396 entry_lost_focus(GtkEntry *entry)
397 {
398         const char *val;
399         char **a, **p;
400
401         val = gtk_entry_get_text(entry);
402         a = g_strsplit(val, " ", 0);
403         for (p = a; *p; p++) {
404                 if (**p != '\0' && !valid_address(*p, false)) {
405                         gtk_entry_set_text(entry, "");
406                         break;
407                 }
408         }
409         g_strfreev(a);
410         return false;
411 }
412
413 static void
414 on_clear(_unused GtkWidget *o, gpointer data)
415 {
416         DHCPCD_CONNECTION *con;
417
418         con = (DHCPCD_CONNECTION *)data;
419         dhcpcd_config_free(config);
420         config = NULL;
421         if (dhcpcd_config_write(con, block, name, config)) {
422                 set_name_active_icon("document-new");
423                 show_config(config);
424         } else
425                 g_critical("dhcpcd_config_write: %s", strerror(errno));
426 }
427
428 static void
429 on_rebind(_unused GObject *widget, gpointer data)
430 {
431         DHCPCD_CONNECTION *con;
432         DHCPCD_IF *i;
433
434         con = (DHCPCD_CONNECTION *)data;
435         if (write_config(con, &config)) {
436                 set_name_active_icon(config == NULL ?
437                     "document-new" : "document-save");
438                 show_config(config);
439                 if (g_strcmp0(block, "interface") == 0) {
440                         if (dhcpcd_rebind(con, iface->ifname) == -1)
441                                 g_critical("dhcpcd_rebind %s: %s",
442                                     iface->ifname, strerror(errno));
443                 } else {
444                         for (i = dhcpcd_interfaces(con); i; i = i->next) {
445                                 if (g_strcmp0(i->ssid, name) == 0) {
446                                         if (dhcpcd_rebind(con, i->ifname) == -1)
447                                                 g_critical(
448                                                     "dhcpcd_rebind %s: %s",
449                                                     i->ifname,
450                                                     strerror(errno));
451                                 }
452                         }
453                 }
454         }
455 }
456
457 static void
458 on_destroy(_unused GObject *o, gpointer data)
459 {
460
461         if (name != NULL) {
462                 write_config((DHCPCD_CONNECTION *)data, &config);
463                 g_free(block);
464                 g_free(name);
465                 block = name = NULL;
466         }
467         dhcpcd_config_free(config);
468         config = NULL;
469         dialog = NULL;
470 }
471
472 static void
473 prefs_close(void)
474 {
475         if (dialog != NULL) {
476                 gtk_widget_destroy(dialog);
477                 dialog = NULL;
478         }
479 }
480
481 void
482 prefs_abort(void)
483 {
484         g_free(name);
485         name = NULL;
486         prefs_close();
487 }
488
489 #if GTK_MAJOR_VERSION == 2
490 static GtkWidget *
491 gtk_separator_new(GtkOrientation o)
492 {
493
494         if (o == GTK_ORIENTATION_HORIZONTAL)
495                 return gtk_hseparator_new();
496         else
497                 return gtk_vseparator_new();
498 }
499 #endif
500
501 void
502 prefs_show(DHCPCD_CONNECTION *con)
503 {
504         GtkWidget *dialog_vbox, *hbox, *vbox, *table, *w;
505         GtkListStore *store;
506         GtkTreeIter iter;
507         GtkCellRenderer *rend;
508         GdkPixbuf *pb;
509
510         if (dialog) {
511                 gtk_window_present(GTK_WINDOW(dialog));
512                 return;
513         }
514
515         if (g_strcmp0(dhcpcd_status(con), "down") == 0)
516                 return;
517
518         dialog = gtk_window_new(GTK_WINDOW_TOPLEVEL);
519         g_signal_connect(G_OBJECT(dialog), "destroy",
520             G_CALLBACK(on_destroy), con);
521
522         gtk_window_set_title(GTK_WINDOW(dialog), _("Network Preferences"));
523         gtk_window_set_resizable(GTK_WINDOW(dialog), false);
524         gtk_window_set_icon_name(GTK_WINDOW(dialog),
525             "preferences-system-network");
526         gtk_window_set_position(GTK_WINDOW(dialog), GTK_WIN_POS_CENTER);
527         gtk_window_set_type_hint(GTK_WINDOW(dialog),
528             GDK_WINDOW_TYPE_HINT_DIALOG);
529
530         dialog_vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 10);
531         gtk_container_set_border_width(GTK_CONTAINER(dialog), 10);
532         gtk_container_add(GTK_CONTAINER(dialog), dialog_vbox);
533
534         hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
535         gtk_box_pack_start(GTK_BOX(dialog_vbox), hbox, false, false, 3);
536         w = gtk_label_new("Configure:");
537         gtk_box_pack_start(GTK_BOX(hbox), w, false, false, 3);
538         store = gtk_list_store_new(2, GDK_TYPE_PIXBUF, G_TYPE_STRING);
539         pb = load_icon("network-wired");
540         gtk_list_store_append(store, &iter);
541         gtk_list_store_set(store, &iter, 0, pb, 1, "interface", -1);
542         g_object_unref(pb);
543         pb = load_icon("network-wireless");
544         gtk_list_store_append(store, &iter);
545         gtk_list_store_set(store, &iter, 0, pb, 1, "SSID", -1);
546         g_object_unref(pb);
547         blocks = gtk_combo_box_new_with_model(GTK_TREE_MODEL(store));
548         rend = gtk_cell_renderer_pixbuf_new();
549         gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(blocks), rend, false);
550         gtk_cell_layout_add_attribute(GTK_CELL_LAYOUT(blocks),
551             rend, "pixbuf", 0);
552         rend = gtk_cell_renderer_text_new();
553         gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(blocks), rend, true);
554         gtk_cell_layout_add_attribute(GTK_CELL_LAYOUT(blocks),
555             rend, "text", 1);
556         gtk_combo_box_set_active(GTK_COMBO_BOX(blocks), 0);
557         gtk_box_pack_start(GTK_BOX(hbox), blocks, false, false, 3);
558         store = gtk_list_store_new(2, GDK_TYPE_PIXBUF, G_TYPE_STRING);
559         names = gtk_combo_box_new_with_model(GTK_TREE_MODEL(store));
560         rend = gtk_cell_renderer_pixbuf_new();
561         gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(names), rend, false);
562         gtk_cell_layout_add_attribute(GTK_CELL_LAYOUT(names),
563             rend, "pixbuf", 0);
564         rend = gtk_cell_renderer_text_new();
565         gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(names), rend, true);
566         gtk_cell_layout_add_attribute(GTK_CELL_LAYOUT(names), rend, "text", 1);
567         gtk_widget_set_sensitive(names, false);
568         gtk_box_pack_start(GTK_BOX(hbox), names, false, false, 3);
569         g_signal_connect(G_OBJECT(blocks), "changed",
570             G_CALLBACK(blocks_on_change), con);
571         g_signal_connect(G_OBJECT(names), "changed",
572             G_CALLBACK(names_on_change), con);
573
574         w = gtk_separator_new(GTK_ORIENTATION_HORIZONTAL);
575         gtk_box_pack_start(GTK_BOX(dialog_vbox), w, true, false, 3);
576         controls = gtk_box_new(GTK_ORIENTATION_VERTICAL, 10);
577         gtk_widget_set_sensitive(controls, false);
578         gtk_box_pack_start(GTK_BOX(dialog_vbox), controls, true, true, 0);
579         vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 3);
580         gtk_box_pack_start(GTK_BOX(controls), vbox, false, false, 0);
581         autoconf = gtk_check_button_new_with_label(
582                 _("Automatically configure empty options"));
583         gtk_box_pack_start(GTK_BOX(vbox), autoconf, false, false, 3);
584         table = gtk_table_new(6, 2, false);
585         gtk_box_pack_start(GTK_BOX(controls), table, false, false, 0);
586
587 #define attach_label(a, b, c, d, e)                                           \
588         do {                                                                  \
589                 gtk_misc_set_alignment(GTK_MISC(a), 0.0, 0.5);                \
590                 gtk_table_attach(GTK_TABLE(table), a, b, c, d, e,             \
591                     GTK_FILL | GTK_SHRINK, GTK_FILL | GTK_SHRINK, 3, 3);      \
592         } while (0)
593 #define attach_entry(a, b, c, d, e)                                           \
594         gtk_table_attach(GTK_TABLE(table), a, b, c, d, e,                     \
595             GTK_EXPAND | GTK_FILL | GTK_SHRINK, GTK_FILL | GTK_SHRINK, 3, 3);
596
597         w = gtk_label_new(_("IP Address:"));
598         address = gtk_entry_new();
599         gtk_entry_set_max_length(GTK_ENTRY(address), 18);
600         g_signal_connect(G_OBJECT(address), "focus-out-event",
601             G_CALLBACK(address_lost_focus), NULL);
602         attach_label(w, 0, 1, 0, 1);
603         attach_entry(address, 1, 2, 0, 1);
604
605         w = gtk_label_new(_("Router:"));
606         router = gtk_entry_new();
607         gtk_entry_set_max_length(GTK_ENTRY(router), 15);
608         g_signal_connect(G_OBJECT(router), "focus-out-event",
609             G_CALLBACK(entry_lost_focus), NULL);
610         attach_label(w, 0, 1, 2, 3);
611         attach_entry(router, 1, 2, 2, 3);
612
613         w = gtk_label_new(_("DNS Servers:"));
614         dns_servers = gtk_entry_new();
615         g_signal_connect(G_OBJECT(dns_servers), "focus-out-event",
616             G_CALLBACK(entry_lost_focus), NULL);
617         attach_label(w, 0, 1, 3, 4);
618         attach_entry(dns_servers, 1, 2, 3, 4);
619
620         w = gtk_label_new(_("DNS Search:"));
621         dns_search = gtk_entry_new();
622         attach_label(w, 0, 1, 4, 5);
623         attach_entry(dns_search, 1, 2, 4, 5);
624
625         hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 10);
626         gtk_box_pack_start(GTK_BOX(dialog_vbox), hbox, true, true, 3);
627         clear = gtk_button_new_from_stock(GTK_STOCK_CLEAR);
628         gtk_widget_set_sensitive(clear, false);
629         gtk_box_pack_start(GTK_BOX(hbox), clear, false, false, 0);
630         g_signal_connect(G_OBJECT(clear), "clicked",
631             G_CALLBACK(on_clear), con);
632         rebind = gtk_button_new_with_mnemonic(_("_Rebind"));
633         gtk_widget_set_sensitive(rebind, false);
634         w = gtk_image_new_from_stock(GTK_STOCK_EXECUTE,
635             GTK_ICON_SIZE_BUTTON);
636         gtk_button_set_image(GTK_BUTTON(rebind), w);
637         gtk_box_pack_start(GTK_BOX(hbox), rebind, false, false, 0);
638         g_signal_connect(G_OBJECT(rebind), "clicked",
639             G_CALLBACK(on_rebind), con);
640         w = gtk_button_new_from_stock(GTK_STOCK_CLOSE);
641         gtk_box_pack_end(GTK_BOX(hbox), w, false, false, 0);
642         g_signal_connect(G_OBJECT(w), "clicked",
643             G_CALLBACK(prefs_close), NULL);
644
645         blocks_on_change(blocks, con);
646         show_config(NULL);
647         gtk_widget_show_all(dialog);
648 }