1 /** \file   kbd.c
2  * \brief   Native GTK3 UI keyboard stuff
3  *
4  * \author  Marco van den Heuvel <blackystardust68@yahoo.com>
5  * \author  Michael C. Martin <mcmartin@gmail.com>
6  * \author  Oliver Schaertel
7  * \author  pottendo <pottendo@gmx.net>
8  * \author  Bas Wassink <b.wassink@ziggo.nl>
9  */
10 
11 /*
12  * This file is part of VICE, the Versatile Commodore Emulator.
13  * See README for copyright notice.
14  *
15  *  This program is free software; you can redistribute it and/or modify
16  *  it under the terms of the GNU General Public License as published by
17  *  the Free Software Foundation; either version 2 of the License, or
18  *  (at your option) any later version.
19  *
20  *  This program is distributed in the hope that it will be useful,
21  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
22  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
23  *  GNU General Public License for more details.
24  *
25  *  You should have received a copy of the GNU General Public License
26  *  along with this program; if not, write to the Free Software
27  *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
28  *  02111-1307  USA.
29  *
30  */
31 
32 #include "vice.h"
33 
34 #include <stdio.h>
35 #include <gtk/gtk.h>
36 #include "debug_gtk3.h"
37 #include "lib.h"
38 #include "log.h"
39 #include "ui.h"
40 
41 /* UNIX-specific; for kbd_arch_get_host_mapping */
42 #include <locale.h>
43 #include <string.h>
44 
45 
46 #include "keyboard.h"
47 #include "kbd.h"
48 
49 
50 static gboolean kbd_hotkey_handle(GdkEvent *report);
51 
52 
53 /** \brief  Initial size of the hotkeys array
54  */
55 #define HOTKEYS_SIZE_INIT   64
56 
57 
58 /** \brief  List of custom hotkeys
59  */
60 static kbd_gtk3_hotkey_t *hotkeys_list = NULL;
61 
62 
63 /** \brief  Size of the hotkeys array
64  *
65  * This will be HOTKEYS_SIZE_INIT element after initializing and will grow
66  * by doubling its size when the array is full.
67  */
68 static int hotkeys_size = 0;
69 
70 
71 /** \brief  Number of registered hotkeys
72  */
73 static int hotkeys_count = 0;
74 
75 
76 
77 
kbd_arch_get_host_mapping(void)78 int kbd_arch_get_host_mapping(void)
79 {
80     int n;
81     char *l;
82     int maps[KBD_MAPPING_NUM] = {
83         KBD_MAPPING_US, KBD_MAPPING_UK, KBD_MAPPING_DE, KBD_MAPPING_DA,
84         KBD_MAPPING_NO, KBD_MAPPING_FI, KBD_MAPPING_IT };
85     /* TODO: This is a UNIX-specific version lifted from the SDL
86      * implementation. */
87     char str[KBD_MAPPING_NUM][6] = {
88         "en_US", "en_UK", "de", "da", "no", "fi", "it"};
89     setlocale(LC_ALL, "");
90     l = setlocale(LC_ALL, NULL);
91     if (l && (strlen(l) > 1)) {
92         for (n = 1; n < KBD_MAPPING_NUM; n++) {
93             if (strncmp(l, str[n], strlen(str[n])) == 0) {
94                 return maps[n];
95             }
96         }
97     }
98     return KBD_MAPPING_US;
99 }
100 
101 
102 /** \brief  Initialize keyboard handling
103  */
kbd_arch_init(void)104 void kbd_arch_init(void)
105 {
106     /* do NOT call kbd_hotkey_init(), keyboard.c calls this function *after*
107      * the UI init stuff is called, allocating the hotkeys array again and thus
108      * causing a memory leak
109      */
110 }
111 
112 
113 
kbd_arch_shutdown(void)114 void kbd_arch_shutdown(void)
115 {
116     /* Also don't call kbd_hotkey_shutdown() here */
117 }
118 
119 
kbd_arch_keyname_to_keynum(char * keyname)120 signed long kbd_arch_keyname_to_keynum(char *keyname)
121 {
122     guint sym = gdk_keyval_from_name(keyname);
123 
124     if (sym == GDK_KEY_VoidSymbol) {
125         return -1;
126     }
127 
128     return (signed long)sym;
129 }
130 
kbd_arch_keynum_to_keyname(signed long keynum)131 const char *kbd_arch_keynum_to_keyname(signed long keynum)
132 {
133     return gdk_keyval_name((guint)keynum);
134 }
135 
kbd_initialize_numpad_joykeys(int * joykeys)136 void kbd_initialize_numpad_joykeys(int *joykeys)
137 {
138     joykeys[0] = GDK_KEY_KP_0;
139     joykeys[1] = GDK_KEY_KP_1;
140     joykeys[2] = GDK_KEY_KP_2;
141     joykeys[3] = GDK_KEY_KP_3;
142     joykeys[4] = GDK_KEY_KP_4;
143     joykeys[5] = GDK_KEY_KP_6;
144     joykeys[6] = GDK_KEY_KP_7;
145     joykeys[7] = GDK_KEY_KP_8;
146     joykeys[8] = GDK_KEY_KP_9;
147 }
148 
kbd_event_handler(GtkWidget * w,GdkEvent * report,gpointer gp)149 static gboolean kbd_event_handler(GtkWidget *w, GdkEvent *report, gpointer gp)
150 {
151     gint key;
152 
153     key = report->key.keyval;
154     switch (report->type) {
155         case GDK_KEY_PRESS:
156             /* fprintf(stderr, "KeyPress: %d.\n", key); */
157             if (gtk_window_activate_key(GTK_WINDOW(w), (GdkEventKey *)report)) {
158                 return TRUE;
159             }
160             /* For some reason, the Alt-D of going fullscreen doesn't
161              * return true when CAPS LOCK isn't on, but only it does
162              * this. */
163             if (key == GDK_KEY_d && report->key.state & GDK_MOD1_MASK) {
164                 return TRUE;
165             }
166 
167             /* check the custom hotkeys */
168             if (kbd_hotkey_handle(report)) {
169                 return TRUE;
170             }
171 
172 #if 0
173             if ((key == GDK_KEY_p || key == GDK_KEY_P)
174                     && (report->key.state & GDK_MOD1_MASK)) {
175                 debug_gtk3("Got Alt+P");
176                 ui_toggle_pause();
177                 return TRUE;
178             }
179 #endif
180 
181             keyboard_key_pressed((signed long)key);
182             return TRUE;
183         case GDK_KEY_RELEASE:
184             /* fprintf(stderr, "KeyRelease: %d.\n", key); */
185             if (key == GDK_KEY_Shift_L || key == GDK_KEY_Shift_R ||
186                 key == GDK_KEY_ISO_Level3_Shift) {
187                 keyboard_key_clear();
188             }
189             keyboard_key_released(key);
190             break;
191         case GDK_ENTER_NOTIFY:
192         case GDK_LEAVE_NOTIFY:
193         case GDK_FOCUS_CHANGE:
194             keyboard_key_clear();
195             break;
196         default:
197             break;
198     }                           /* switch */
199     return FALSE;
200 }
201 
kbd_connect_handlers(GtkWidget * widget,void * data)202 void kbd_connect_handlers(GtkWidget *widget, void *data)
203 {
204     g_signal_connect(G_OBJECT(widget), "key-press-event", G_CALLBACK(kbd_event_handler), data);
205     g_signal_connect(G_OBJECT(widget), "key-release-event", G_CALLBACK(kbd_event_handler), data);
206     g_signal_connect(G_OBJECT(widget), "enter-notify-event", G_CALLBACK(kbd_event_handler), data);
207     g_signal_connect(G_OBJECT(widget), "leave-notify-event", G_CALLBACK(kbd_event_handler), data);
208 }
209 
210 /*
211  * Hotkeys (keyboard shortcuts not connected to any GtkMenuItem) handling
212  */
213 
214 
215 /** \brief  Initialize the hotkeys
216  *
217  * This allocates an initial hotkeys array of HOTKEYS_SIZE_INIT elements
218  */
kbd_hotkey_init(void)219 void kbd_hotkey_init(void)
220 {
221     debug_gtk3("initializing hotkeys list.");
222     hotkeys_list = lib_malloc(HOTKEYS_SIZE_INIT * sizeof *hotkeys_list);
223     hotkeys_size = HOTKEYS_SIZE_INIT;
224     hotkeys_count = 0;
225 }
226 
227 
228 
229 /** \brief  Clean up memory used by the hotkeys array
230  */
kbd_hotkey_shutdown(void)231 void kbd_hotkey_shutdown(void)
232 {
233     debug_gtk3("cleaning up memory used by the hotkeys.");
234     lib_free(hotkeys_list);
235 }
236 
237 
238 /** \brief  Find hotkey index
239  *
240  * \param[in]   code    key code
241  * \param[in]   mask    key mask
242  *
243  * \return  index in list, -1 when not found
244  */
kbd_hotkey_get_index(guint code,guint mask)245 static int kbd_hotkey_get_index(guint code, guint mask)
246 {
247     int i = 0;
248 
249     while (i < hotkeys_count) {
250         if (hotkeys_list[i].code == code && hotkeys_list[i].mask) {
251             return i;
252         }
253         i++;
254     }
255     return -1;
256 }
257 
258 
259 /** \brief  Look up the requested hotkey and trigger its callback when found
260  *
261  * \param[in]   report  GDK key press event instance
262  *
263  * \return  TRUE when the key was found and the callback triggered,
264  *          FALSE otherwise
265  */
kbd_hotkey_handle(GdkEvent * report)266 static gboolean kbd_hotkey_handle(GdkEvent *report)
267 {
268     int i = 0;
269     gint code = report->key.keyval;
270 
271     while (i < hotkeys_count) {
272         if ((hotkeys_list[i].code == code)
273                 && (report->key.state & hotkeys_list[i].mask)) {
274 
275             debug_gtk3("triggering callback of hotkey with index %d.", i);
276             hotkeys_list[i].callback();
277             return TRUE;
278         }
279         i++;
280     }
281     return FALSE;
282 }
283 
284 
285 /** \brief  Add hotkey to the list
286  *
287  * \param[in]   code        GDK key code
288  * \param[in]   mask        GDK key modifier bitmask
289  * \param[in]   callback    function to call when hotkey is triggered
290  *
291  * \return  bool
292  */
kbd_hotkey_add(guint code,guint mask,void (* callback)(void))293 gboolean kbd_hotkey_add(guint code, guint mask, void (*callback)(void))
294 {
295     if (callback == NULL) {
296         log_error(LOG_ERR, "Error: NULL passed as callback.");
297         return FALSE;
298     }
299     if (kbd_hotkey_get_index(code, mask) >= 0) {
300         log_error(LOG_ERR, "Error: hotkey already registered.");
301         return FALSE;
302     }
303 
304     /* resize list? */
305     if (hotkeys_count == hotkeys_size) {
306         int new_size = hotkeys_size * 2;
307         debug_gtk3("Resizing hotkeys list to %d items.", new_size);
308         hotkeys_list = lib_realloc(
309                 hotkeys_list,
310                 (size_t)new_size * sizeof *hotkeys_list);
311         hotkeys_size = new_size;
312     }
313 
314 
315     /* register hotkey */
316     hotkeys_list[hotkeys_count].code = code;
317     hotkeys_list[hotkeys_count].mask = mask;
318     hotkeys_list[hotkeys_count].callback = callback;
319     hotkeys_count++;
320     return TRUE;
321 }
322 
323 
324 /** \brief  Add multiple hotkeys at once
325  *
326  * Adds multiple hotkeys from \a list. Terminate the list with NULL for the
327  * callback value.
328  *
329  * \param[in]   list    list of hotkeys
330  *
331  * \return  TRUE on success, FALSE if the list was exhausted or a hotkey
332  *          was already registered
333  */
kbd_hotkey_add_list(kbd_gtk3_hotkey_t * list)334 gboolean kbd_hotkey_add_list(kbd_gtk3_hotkey_t *list)
335 {
336     int i = 0;
337 
338     while (list[i].callback != NULL) {
339         if (!kbd_hotkey_add(list[i].code, list[i].mask, list[i].callback)) {
340             return FALSE;
341         }
342         i++;
343     }
344     return TRUE;
345 }
346