1 /*
2  *  This file is part of audacious-hotkey plugin for audacious
3  *
4  *  Copyright (c) 2007 - 2008  Sascha Hlusiak <contact@saschahlusiak.de>
5  *  Name: gui.c
6  *  Description: gui.c
7  *
8  *  Part of this code is from itouch-ctrl plugin.
9  *  Authors of itouch-ctrl are listed below:
10  *
11  *  Copyright (c) 2006 - 2007 Vladimir Paskov <vlado.paskov@gmail.com>
12  *
13  *  Part of this code are from xmms-itouch plugin.
14  *  Authors of xmms-itouch are listed below:
15  *
16  *  Copyright (C) 2000-2002 Ville Syrjälä <syrjala@sci.fi>
17  *                         Bryn Davies <curious@ihug.com.au>
18  *                         Jonathan A. Davis <davis@jdhouse.org>
19  *                         Jeremy Tan <nsx@nsx.homeip.net>
20  *
21  *  audacious-hotkey is free software; you can redistribute it and/or modify
22  *  it under the terms of the GNU General Public License as published by
23  *  the Free Software Foundation; either version 2 of the License, or
24  *  (at your option) any later version.
25  *
26  *  audacious-hotkey is distributed in the hope that it will be useful,
27  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
28  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
29  *  GNU General Public License for more details.
30  *
31  *  You should have received a copy of the GNU General Public License
32  *  along with audacious-hotkey; if not, write to the Free Software
33  *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
34  * USA.
35  */
36 
37 #include <libaudcore/i18n.h>
38 #include <libaudcore/preferences.h>
39 
40 #include <libaudgui/libaudgui-gtk.h>
41 
42 #include <gdk/gdkkeysyms-compat.h>
43 #include <gdk/gdkx.h>
44 #include <gtk/gtk.h>
45 
46 #include <X11/XKBlib.h>
47 
48 #include "grab.h"
49 #include "gui.h"
50 #include "plugin.h"
51 
52 typedef struct _KeyControls
53 {
54     GtkWidget * keytext;
55     GtkWidget * grid;
56     GtkWidget * button;
57     GtkWidget * combobox;
58 
59     HotkeyConfiguration hotkey;
60     struct _KeyControls *next, *prev, *first;
61 } KeyControls;
62 
63 static KeyControls * first_controls;
64 
65 static void clear_keyboard(GtkWidget * widget, void * data);
66 static void add_callback(GtkWidget * widget, void * data);
67 static void destroy_callback();
68 static void ok_callback();
69 
70 static const char * event_desc[] = {N_("Previous track"),
71                                     N_("Play"),
72                                     N_("Pause/Resume"),
73                                     N_("Stop"),
74                                     N_("Next track"),
75                                     N_("Step forward"),
76                                     N_("Step backward"),
77                                     N_("Mute"),
78                                     N_("Volume up"),
79                                     N_("Volume down"),
80                                     N_("Jump to file"),
81                                     N_("Toggle player window(s)"),
82                                     N_("Show On-Screen-Display"),
83                                     N_("Toggle repeat"),
84                                     N_("Toggle shuffle"),
85                                     N_("Toggle stop after current"),
86                                     N_("Raise player window(s)")};
87 
88 static_assert(aud::n_elems(event_desc) == EVENT_MAX,
89               "event_desc table is not up to date");
90 
set_keytext(GtkWidget * entry,int key,int mask,int type)91 static void set_keytext(GtkWidget * entry, int key, int mask, int type)
92 {
93     char * text = nullptr;
94 
95     if (key == 0 && mask == 0)
96     {
97         text = g_strdup(_("(none)"));
98     }
99     else
100     {
101         static const char * modifier_string[] = {
102             "Control", "Shift", "Alt", "Mod2", "Mod3", "Super", "Mod5"};
103         static const unsigned int modifiers[] = {
104             ControlMask, ShiftMask, Mod1Mask, Mod2Mask,
105             Mod3Mask,    Mod4Mask,  Mod5Mask};
106         const char * strings[9];
107         char * keytext = nullptr;
108         int i, j;
109         if (type == TYPE_KEY)
110         {
111             KeySym keysym;
112             keysym = XkbKeycodeToKeysym(
113                 GDK_DISPLAY_XDISPLAY(gdk_display_get_default()), key, 0, 0);
114             if (keysym == 0 || keysym == NoSymbol)
115             {
116                 keytext = g_strdup_printf("#%d", key);
117             }
118             else
119             {
120                 keytext = g_strdup(XKeysymToString(keysym));
121             }
122         }
123         if (type == TYPE_MOUSE)
124         {
125             keytext = g_strdup_printf("Button%d", key);
126         }
127 
128         for (i = 0, j = 0; j < 7; j++)
129         {
130             if (mask & modifiers[j])
131                 strings[i++] = modifier_string[j];
132         }
133         if (key != 0)
134             strings[i++] = keytext;
135         strings[i] = nullptr;
136 
137         text = g_strjoinv(" + ", (char **)strings);
138         g_free(keytext);
139     }
140 
141     gtk_entry_set_text(GTK_ENTRY(entry), text);
142     gtk_editable_set_position(GTK_EDITABLE(entry), -1);
143     if (text)
144         g_free(text);
145 }
146 
on_entry_key_press_event(GtkWidget * widget,GdkEventKey * event,void * user_data)147 static gboolean on_entry_key_press_event(GtkWidget * widget,
148                                          GdkEventKey * event, void * user_data)
149 {
150     KeyControls * controls = (KeyControls *)user_data;
151     int is_mod;
152     int mod;
153 
154     if (event->keyval == GDK_Tab)
155         return false;
156     if (event->keyval == GDK_Escape && ((event->state & ~GDK_LOCK_MASK) == 0))
157         return false;
158     if (event->keyval == GDK_Return && ((event->state & ~GDK_LOCK_MASK) == 0))
159         return false;
160     if (event->keyval == GDK_ISO_Left_Tab)
161     {
162         set_keytext(controls->keytext, controls->hotkey.key,
163                     controls->hotkey.mask, controls->hotkey.type);
164         return false;
165     }
166     if (event->keyval == GDK_Up && ((event->state & ~GDK_LOCK_MASK) == 0))
167         return false;
168     if (event->keyval == GDK_Down && ((event->state & ~GDK_LOCK_MASK) == 0))
169         return false;
170 
171     mod = 0;
172     is_mod = 0;
173 
174     if ((event->state & GDK_CONTROL_MASK) |
175         (!is_mod && (is_mod = (event->keyval == GDK_Control_L ||
176                                event->keyval == GDK_Control_R))))
177         mod |= ControlMask;
178 
179     if ((event->state & GDK_MOD1_MASK) |
180         (!is_mod &&
181          (is_mod = (event->keyval == GDK_Alt_L || event->keyval == GDK_Alt_R))))
182         mod |= Mod1Mask;
183 
184     if ((event->state & GDK_SHIFT_MASK) |
185         (!is_mod && (is_mod = (event->keyval == GDK_Shift_L ||
186                                event->keyval == GDK_Shift_R))))
187         mod |= ShiftMask;
188 
189     if ((event->state & GDK_MOD5_MASK) |
190         (!is_mod && (is_mod = (event->keyval == GDK_ISO_Level3_Shift))))
191         mod |= Mod5Mask;
192 
193     if ((event->state & GDK_MOD4_MASK) |
194         (!is_mod && (is_mod = (event->keyval == GDK_Super_L ||
195                                event->keyval == GDK_Super_R))))
196         mod |= Mod4Mask;
197 
198     if (!is_mod)
199     {
200         controls->hotkey.key = event->hardware_keycode;
201         controls->hotkey.mask = mod;
202         controls->hotkey.type = TYPE_KEY;
203         if (controls->next == nullptr)
204             add_callback(nullptr, (void *)controls);
205         else
206             gtk_widget_grab_focus(GTK_WIDGET(controls->next->keytext));
207     }
208 
209     set_keytext(controls->keytext, is_mod ? 0 : event->hardware_keycode, mod,
210                 TYPE_KEY);
211     return true;
212 }
213 
on_entry_key_release_event(GtkWidget * widget,GdkEventKey * event,void * user_data)214 static gboolean on_entry_key_release_event(GtkWidget * widget,
215                                            GdkEventKey * event,
216                                            void * user_data)
217 {
218     KeyControls * controls = (KeyControls *)user_data;
219     if (!gtk_widget_is_focus(widget))
220         return false;
221     set_keytext(controls->keytext, controls->hotkey.key, controls->hotkey.mask,
222                 controls->hotkey.type);
223 
224     return true;
225 }
226 
on_entry_button_press_event(GtkWidget * widget,GdkEventButton * event,void * user_data)227 static gboolean on_entry_button_press_event(GtkWidget * widget,
228                                             GdkEventButton * event,
229                                             void * user_data)
230 {
231     KeyControls * controls = (KeyControls *)user_data;
232     int mod;
233 
234     if (!gtk_widget_is_focus(widget))
235         return false;
236 
237     mod = 0;
238     if (event->state & GDK_CONTROL_MASK)
239         mod |= ControlMask;
240 
241     if (event->state & GDK_MOD1_MASK)
242         mod |= Mod1Mask;
243 
244     if (event->state & GDK_SHIFT_MASK)
245         mod |= ShiftMask;
246 
247     if (event->state & GDK_MOD5_MASK)
248         mod |= Mod5Mask;
249 
250     if (event->state & GDK_MOD4_MASK)
251         mod |= Mod4Mask;
252 
253     if ((event->button <= 3) && (mod == 0))
254     {
255         GtkWidget * dialog;
256         int response;
257         dialog = gtk_message_dialog_new(
258             GTK_WINDOW(gtk_widget_get_toplevel(widget)), GTK_DIALOG_MODAL,
259             GTK_MESSAGE_WARNING, GTK_BUTTONS_YES_NO,
260             _("It is not recommended to bind the primary mouse buttons without "
261               "modifiers.\n\n"
262               "Do you want to continue?"));
263         gtk_window_set_title(GTK_WINDOW(dialog), _("Binding mouse buttons"));
264         response = gtk_dialog_run(GTK_DIALOG(dialog));
265         gtk_widget_destroy(dialog);
266         if (response != GTK_RESPONSE_YES)
267             return true;
268     }
269 
270     controls->hotkey.key = event->button;
271     controls->hotkey.mask = mod;
272     controls->hotkey.type = TYPE_MOUSE;
273     set_keytext(controls->keytext, controls->hotkey.key, controls->hotkey.mask,
274                 controls->hotkey.type);
275     if (controls->next == nullptr)
276         add_callback(nullptr, (void *)controls);
277 
278     return true;
279 }
280 
on_entry_scroll_event(GtkWidget * widget,GdkEventScroll * event,void * user_data)281 static gboolean on_entry_scroll_event(GtkWidget * widget,
282                                       GdkEventScroll * event, void * user_data)
283 {
284     KeyControls * controls = (KeyControls *)user_data;
285     int mod;
286 
287     if (!gtk_widget_is_focus(widget))
288         return false;
289 
290     mod = 0;
291     if (event->state & GDK_CONTROL_MASK)
292         mod |= ControlMask;
293 
294     if (event->state & GDK_MOD1_MASK)
295         mod |= Mod1Mask;
296 
297     if (event->state & GDK_SHIFT_MASK)
298         mod |= ShiftMask;
299 
300     if (event->state & GDK_MOD5_MASK)
301         mod |= Mod5Mask;
302 
303     if (event->state & GDK_MOD4_MASK)
304         mod |= Mod4Mask;
305 
306     if (event->direction == GDK_SCROLL_UP)
307         controls->hotkey.key = 4;
308     else if (event->direction == GDK_SCROLL_DOWN)
309         controls->hotkey.key = 5;
310     else if (event->direction == GDK_SCROLL_LEFT)
311         controls->hotkey.key = 6;
312     else if (event->direction == GDK_SCROLL_RIGHT)
313         controls->hotkey.key = 7;
314     else
315         return false;
316 
317     controls->hotkey.mask = mod;
318     controls->hotkey.type = TYPE_MOUSE;
319     set_keytext(controls->keytext, controls->hotkey.key, controls->hotkey.mask,
320                 controls->hotkey.type);
321     if (controls->next == nullptr)
322         add_callback(nullptr, (void *)controls);
323     return true;
324 }
325 
add_event_controls(KeyControls * list,GtkWidget * grid,int row,HotkeyConfiguration * hotkey)326 KeyControls * add_event_controls(KeyControls * list, GtkWidget * grid, int row,
327                                  HotkeyConfiguration * hotkey)
328 {
329     KeyControls * controls;
330     int i;
331 
332     controls = (KeyControls *)g_malloc(sizeof(KeyControls));
333     controls->next = nullptr;
334     controls->prev = list;
335     controls->first = list->first;
336     controls->grid = grid;
337     list->next = controls;
338 
339     if (hotkey)
340     {
341         controls->hotkey.key = hotkey->key;
342         controls->hotkey.mask = hotkey->mask;
343         controls->hotkey.type = hotkey->type;
344         controls->hotkey.event = hotkey->event;
345         if (controls->hotkey.key == 0)
346             controls->hotkey.mask = 0;
347     }
348     else
349     {
350         controls->hotkey.key = 0;
351         controls->hotkey.mask = 0;
352         controls->hotkey.type = TYPE_KEY;
353         controls->hotkey.event = (EVENT)0;
354     }
355 
356     controls->combobox = gtk_combo_box_text_new();
357     for (i = 0; i < EVENT_MAX; i++)
358     {
359         gtk_combo_box_text_append_text((GtkComboBoxText *)controls->combobox,
360                                        _(event_desc[i]));
361     }
362     gtk_combo_box_set_active(GTK_COMBO_BOX(controls->combobox),
363                              controls->hotkey.event);
364     gtk_table_attach_defaults(GTK_TABLE(grid), controls->combobox, 0, 1, row,
365                               row + 1);
366 
367     controls->keytext = gtk_entry_new();
368     gtk_table_attach_defaults(GTK_TABLE(grid), controls->keytext, 1, 2, row,
369                               row + 1);
370     gtk_editable_set_editable(GTK_EDITABLE(controls->keytext), false);
371 
372     set_keytext(controls->keytext, controls->hotkey.key, controls->hotkey.mask,
373                 controls->hotkey.type);
374     g_signal_connect((void *)controls->keytext, "key_press_event",
375                      G_CALLBACK(on_entry_key_press_event), controls);
376     g_signal_connect((void *)controls->keytext, "key_release_event",
377                      G_CALLBACK(on_entry_key_release_event), controls);
378     g_signal_connect((void *)controls->keytext, "button_press_event",
379                      G_CALLBACK(on_entry_button_press_event), controls);
380     g_signal_connect((void *)controls->keytext, "scroll_event",
381                      G_CALLBACK(on_entry_scroll_event), controls);
382 
383     controls->button = gtk_button_new();
384     gtk_button_set_image(
385         GTK_BUTTON(controls->button),
386         gtk_image_new_from_icon_name("edit-delete", GTK_ICON_SIZE_BUTTON));
387     gtk_table_attach_defaults(GTK_TABLE(grid), controls->button, 2, 3, row,
388                               row + 1);
389     g_signal_connect(G_OBJECT(controls->button), "clicked",
390                      G_CALLBACK(clear_keyboard), controls);
391 
392     gtk_widget_grab_focus(GTK_WIDGET(controls->keytext));
393     return controls;
394 }
395 
make_config_widget()396 void * make_config_widget()
397 {
398     KeyControls * current_controls;
399     GtkWidget *main_vbox, *hbox;
400     GtkWidget * alignment;
401     GtkWidget * frame;
402     GtkWidget * label;
403     GtkWidget * image;
404     GtkWidget * grid;
405     GtkWidget *button_box, *button;
406     PluginConfig * plugin_cfg;
407     HotkeyConfiguration *hotkey, temphotkey;
408     int i;
409 
410     load_config();
411 
412     plugin_cfg = get_config();
413 
414     ungrab_keys();
415 
416     main_vbox = gtk_vbox_new(false, 4);
417 
418     alignment = gtk_alignment_new(0.5, 0.5, 1, 1);
419     gtk_box_pack_start(GTK_BOX(main_vbox), alignment, false, true, 0);
420     gtk_alignment_set_padding(GTK_ALIGNMENT(alignment), 4, 0, 0, 0);
421     hbox = gtk_hbox_new(false, 2);
422     gtk_container_add(GTK_CONTAINER(alignment), hbox);
423     image = gtk_image_new_from_icon_name("dialog-information",
424                                          GTK_ICON_SIZE_DIALOG);
425     gtk_box_pack_start(GTK_BOX(hbox), image, false, true, 0);
426     label = gtk_label_new(_("Press a key combination inside a text field.\nYou "
427                             "can also bind mouse buttons."));
428     gtk_box_pack_start(GTK_BOX(hbox), label, true, true, 0);
429     gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
430 
431     label = gtk_label_new(nullptr);
432     gtk_label_set_markup(GTK_LABEL(label), _("Hotkeys:"));
433     frame = gtk_frame_new(nullptr);
434     gtk_frame_set_label_widget(GTK_FRAME(frame), label);
435     gtk_box_pack_start(GTK_BOX(main_vbox), frame, true, true, 0);
436     gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_ETCHED_IN);
437     alignment = gtk_alignment_new(0, 0, 1, 0);
438     gtk_container_add(GTK_CONTAINER(frame), alignment);
439     gtk_alignment_set_padding(GTK_ALIGNMENT(alignment), 3, 3, 3, 3);
440 
441     grid = gtk_table_new(0, 0, false);
442     gtk_table_set_col_spacings(GTK_TABLE(grid), 2);
443     gtk_container_add(GTK_CONTAINER(alignment), grid);
444 
445     label = gtk_label_new(nullptr);
446     gtk_label_set_justify(GTK_LABEL(label), GTK_JUSTIFY_CENTER);
447     gtk_misc_set_alignment(GTK_MISC(label), 0.5, 0.5);
448     gtk_label_set_markup(GTK_LABEL(label), _("<b>Action:</b>"));
449     gtk_table_attach_defaults(GTK_TABLE(grid), label, 0, 1, 0, 1);
450 
451     label = gtk_label_new(nullptr);
452     gtk_label_set_justify(GTK_LABEL(label), GTK_JUSTIFY_CENTER);
453     gtk_misc_set_alignment(GTK_MISC(label), 0.5, 0.5);
454     gtk_label_set_markup(GTK_LABEL(label), _("<b>Key Binding:</b>"));
455     gtk_table_attach_defaults(GTK_TABLE(grid), label, 1, 2, 0, 1);
456 
457     hotkey = &(plugin_cfg->first);
458     i = 1;
459     first_controls = (KeyControls *)g_malloc(sizeof(KeyControls));
460     first_controls->next = nullptr;
461     first_controls->prev = nullptr;
462     first_controls->grid = grid;
463     first_controls->button = nullptr;
464     first_controls->combobox = nullptr;
465     first_controls->keytext = nullptr;
466     first_controls->first = first_controls;
467     first_controls->hotkey.key = 0;
468     first_controls->hotkey.mask = 0;
469     first_controls->hotkey.event = (EVENT)0;
470     first_controls->hotkey.type = TYPE_KEY;
471     current_controls = first_controls;
472     if (hotkey->key != 0)
473     {
474         while (hotkey)
475         {
476             current_controls =
477                 add_event_controls(current_controls, grid, i, hotkey);
478             hotkey = hotkey->next;
479             i++;
480         }
481     }
482     temphotkey.key = 0;
483     temphotkey.mask = 0;
484     temphotkey.type = TYPE_KEY;
485     if (current_controls != first_controls)
486         temphotkey.event = (EVENT)(current_controls->hotkey.event + 1);
487     else
488         temphotkey.event = (EVENT)0;
489     if (temphotkey.event >= EVENT_MAX)
490         temphotkey.event = (EVENT)0;
491     add_event_controls(current_controls, grid, i, &temphotkey);
492 
493     hbox = gtk_hbox_new(false, 0);
494     gtk_box_pack_start(GTK_BOX(main_vbox), hbox, false, true, 0);
495 
496     button_box = gtk_hbutton_box_new();
497     gtk_box_pack_start(GTK_BOX(hbox), button_box, false, true, 0);
498     gtk_button_box_set_layout(GTK_BUTTON_BOX(button_box), GTK_BUTTONBOX_START);
499     gtk_box_set_spacing(GTK_BOX(button_box), 4);
500 
501     button = audgui_button_new(_("_Add"), "list-add", nullptr, nullptr);
502     gtk_container_add(GTK_CONTAINER(button_box), button);
503     g_signal_connect(G_OBJECT(button), "clicked", G_CALLBACK(add_callback),
504                      first_controls);
505 
506     return main_vbox;
507 }
508 
clear_keyboard(GtkWidget * widget,void * data)509 static void clear_keyboard(GtkWidget * widget, void * data)
510 {
511     KeyControls * controls = (KeyControls *)data;
512 
513     if ((controls->next == nullptr) && (controls->prev->keytext == nullptr))
514     {
515         controls->hotkey.key = 0;
516         controls->hotkey.mask = 0;
517         controls->hotkey.type = TYPE_KEY;
518         set_keytext(controls->keytext, 0, 0, TYPE_KEY);
519         gtk_combo_box_set_active(GTK_COMBO_BOX(controls->combobox), 0);
520         return;
521     }
522 
523     if (controls->prev)
524     {
525         KeyControls * c;
526         GtkWidget * grid;
527         int row;
528 
529         gtk_widget_destroy(GTK_WIDGET(controls->button));
530         gtk_widget_destroy(GTK_WIDGET(controls->keytext));
531         gtk_widget_destroy(GTK_WIDGET(controls->combobox));
532 
533         row = 0;
534         c = controls->first;
535         while (c)
536         {
537             if (c == controls)
538                 break;
539             row++;
540             c = c->next;
541         }
542         c = controls->next;
543         controls->prev->next = controls->next;
544         if (controls->next)
545             controls->next->prev = controls->prev;
546         g_free(controls);
547         if (c)
548             grid = c->grid;
549         else
550             grid = nullptr;
551         while (c)
552         {
553             g_object_ref(c->combobox);
554             g_object_ref(c->keytext);
555             g_object_ref(c->button);
556 
557             gtk_container_remove(GTK_CONTAINER(c->grid), c->combobox);
558             gtk_container_remove(GTK_CONTAINER(c->grid), c->keytext);
559             gtk_container_remove(GTK_CONTAINER(c->grid), c->button);
560 
561             gtk_table_attach_defaults(GTK_TABLE(c->grid), c->combobox, 0, 1,
562                                       row, row + 1);
563             gtk_table_attach_defaults(GTK_TABLE(c->grid), c->keytext, 1, 2, row,
564                                       row + 1);
565             gtk_table_attach_defaults(GTK_TABLE(c->grid), c->button, 2, 3, row,
566                                       row + 1);
567 
568             g_object_unref(c->combobox);
569             g_object_unref(c->keytext);
570             g_object_unref(c->button);
571 
572             c = c->next;
573             row++;
574         }
575         if (grid)
576             gtk_widget_show_all(GTK_WIDGET(grid));
577 
578         return;
579     }
580 }
581 
add_callback(GtkWidget * widget,void * data)582 void add_callback(GtkWidget * widget, void * data)
583 {
584     KeyControls * controls = (KeyControls *)data;
585     HotkeyConfiguration temphotkey;
586     int count;
587     if (controls == nullptr)
588         return;
589     if ((controls->next == nullptr) &&
590         (controls->hotkey.event + 1 == EVENT_MAX))
591         return;
592     controls = controls->first;
593     if (controls == nullptr)
594         return;
595     count = 1;
596     while (controls->next)
597     {
598         controls = controls->next;
599         count = count + 1;
600     }
601     temphotkey.key = 0;
602     temphotkey.mask = 0;
603     temphotkey.type = TYPE_KEY;
604     temphotkey.event = (EVENT)(controls->hotkey.event + 1);
605     if (temphotkey.event >= EVENT_MAX)
606         temphotkey.event = (EVENT)0;
607     add_event_controls(controls, controls->grid, count, &temphotkey);
608     gtk_widget_show_all(GTK_WIDGET(controls->grid));
609 }
610 
destroy_callback()611 void destroy_callback()
612 {
613     KeyControls * controls = first_controls;
614 
615     grab_keys();
616 
617     while (controls)
618     {
619         KeyControls * old;
620         old = controls;
621         controls = controls->next;
622         g_free(old);
623     }
624 
625     first_controls = nullptr;
626 }
627 
ok_callback()628 void ok_callback()
629 {
630     KeyControls * controls = first_controls;
631     PluginConfig * plugin_cfg = get_config();
632     HotkeyConfiguration * hotkey;
633 
634     hotkey = &(plugin_cfg->first);
635     hotkey = hotkey->next;
636     while (hotkey)
637     {
638         HotkeyConfiguration * old;
639         old = hotkey;
640         hotkey = hotkey->next;
641         g_free(old);
642     }
643     plugin_cfg->first.next = nullptr;
644     plugin_cfg->first.key = 0;
645     plugin_cfg->first.event = (EVENT)0;
646     plugin_cfg->first.mask = 0;
647 
648     hotkey = &(plugin_cfg->first);
649     while (controls)
650     {
651         if (controls->hotkey.key)
652         {
653             if (hotkey->key)
654             {
655                 hotkey->next = g_new(HotkeyConfiguration, 1);
656                 hotkey = hotkey->next;
657                 hotkey->next = nullptr;
658             }
659             hotkey->key = controls->hotkey.key;
660             hotkey->mask = controls->hotkey.mask;
661             hotkey->event = (EVENT)gtk_combo_box_get_active(
662                 GTK_COMBO_BOX(controls->combobox));
663             hotkey->type = controls->hotkey.type;
664         }
665         controls = controls->next;
666     }
667 
668     save_config();
669 }
670 
671 static const PreferencesWidget hotkey_widgets[] = {
672     WidgetCustomGTK(make_config_widget)};
673 
674 const PluginPreferences hotkey_prefs = {{hotkey_widgets},
675                                         nullptr, // init
676                                         ok_callback,
677                                         destroy_callback};
678