]> git.sesse.net Git - vlc/blob - src/config/keys.c
Store hotkey mappings in a tree instead of a table
[vlc] / src / config / keys.c
1 /*****************************************************************************
2  * keys.c: keys configuration
3  *****************************************************************************
4  * Copyright (C) 2003-2009 the VideoLAN team
5  *
6  * Authors: Sigmund Augdal Helberg <dnumgis@videolan.org>
7  *
8  * This program is free software; you can redistribute it and/or modify
9  * it under the terms of the GNU General Public License as published by
10  * the Free Software Foundation; either version 2 of the License, or
11  * (at your option) any later version.
12  *
13  * This program is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  * GNU General Public License for more details.
17  *
18  * You should have received a copy of the GNU General Public License
19  * along with this program; if not, write to the Free Software
20  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
21  *****************************************************************************/
22
23 #ifdef HAVE_CONFIG_H
24 # include <config.h>
25 #endif
26
27 /**
28  * \file
29  * This file defines functions and structures for hotkey handling in vlc
30  */
31
32 #ifdef HAVE_CONFIG_H
33 # include <config.h>
34 #endif
35
36 #include <stdlib.h>
37 #include <limits.h>
38 #include <search.h>
39
40 #include <vlc_common.h>
41 #include <vlc_keys.h>
42 #include "configuration.h"
43 #include "libvlc.h"
44
45 typedef struct key_descriptor_s
46 {
47     const char psz_key_string[20];
48     uint32_t i_key_code;
49 } key_descriptor_t;
50
51 static const struct key_descriptor_s vlc_keys[] =
52 {
53     { "Unset", KEY_UNSET },
54     { "Backspace", KEY_BACKSPACE },
55     { "Tab", KEY_TAB },
56     { "Enter", KEY_ENTER },
57     { "Esc", KEY_ESC },
58     { "Space", ' ' },
59     { "Left", KEY_LEFT },
60     { "Right", KEY_RIGHT },
61     { "Up", KEY_UP },
62     { "Down", KEY_DOWN },
63     { "F1", KEY_F1 },
64     { "F2", KEY_F2 },
65     { "F3", KEY_F3 },
66     { "F4", KEY_F4 },
67     { "F5", KEY_F5 },
68     { "F6", KEY_F6 },
69     { "F7", KEY_F7 },
70     { "F8", KEY_F8 },
71     { "F9", KEY_F9 },
72     { "F10", KEY_F10 },
73     { "F11", KEY_F11 },
74     { "F12", KEY_F12 },
75     { "Home", KEY_HOME },
76     { "End", KEY_END },
77     { "Insert", KEY_INSERT },
78     { "Delete", KEY_DELETE },
79     { "Menu", KEY_MENU },
80     { "Page Up", KEY_PAGEUP },
81     { "Page Down", KEY_PAGEDOWN },
82     { "Browser Back", KEY_BROWSER_BACK },
83     { "Browser Forward", KEY_BROWSER_FORWARD },
84     { "Browser Refresh", KEY_BROWSER_REFRESH },
85     { "Browser Stop", KEY_BROWSER_STOP },
86     { "Browser Search", KEY_BROWSER_SEARCH },
87     { "Browser Favorites", KEY_BROWSER_FAVORITES },
88     { "Browser Home", KEY_BROWSER_HOME },
89     { "Volume Mute", KEY_VOLUME_MUTE },
90     { "Volume Down", KEY_VOLUME_DOWN },
91     { "Volume Up", KEY_VOLUME_UP },
92     { "Media Next Track", KEY_MEDIA_NEXT_TRACK },
93     { "Media Prev Track", KEY_MEDIA_PREV_TRACK },
94     { "Media Stop", KEY_MEDIA_STOP },
95     { "Media Play Pause", KEY_MEDIA_PLAY_PAUSE },
96     { "Mouse Wheel Up", KEY_MOUSEWHEELUP },
97     { "Mouse Wheel Down", KEY_MOUSEWHEELDOWN },
98     { "Mouse Wheel Left", KEY_MOUSEWHEELLEFT },
99     { "Mouse Wheel Right", KEY_MOUSEWHEELRIGHT },
100 };
101 enum { vlc_num_keys=sizeof(vlc_keys)/sizeof(struct key_descriptor_s) };
102
103 static int cmpkey (const void *key, const void *elem)
104 {
105     return ((uintptr_t)key) - ((key_descriptor_t *)elem)->i_key_code;
106 }
107
108 /* Convert Unicode code point to UTF-8 */
109 static char *utf8_cp (uint_fast32_t cp, char *buf)
110 {
111     if (cp < (1 << 7))
112     {
113         buf[1] = 0;
114         buf[0] = cp;
115     }
116     else if (cp < (1 << 11))
117     {
118         buf[2] = 0;
119         buf[1] = 0x80 | (cp & 0x3F);
120         cp >>= 6;
121         buf[0] = 0xC0 | cp;
122     }
123     else if (cp < (1 << 16))
124     {
125         buf[3] = 0;
126         buf[2] = 0x80 | (cp & 0x3F);
127         cp >>= 6;
128         buf[1] = 0x80 | (cp & 0x3F);
129         cp >>= 6;
130         buf[0] = 0xE0 | cp;
131     }
132     else if (cp < (1 << 21))
133     {
134         buf[4] = 0;
135         buf[3] = 0x80 | (cp & 0x3F);
136         cp >>= 6;
137         buf[2] = 0x80 | (cp & 0x3F);
138         cp >>= 6;
139         buf[1] = 0x80 | (cp & 0x3F);
140         cp >>= 6;
141         buf[0] = 0xE0 | cp;
142     }
143     else
144         return NULL;
145     return buf;
146 }
147
148 /**
149  * Parse a human-readable string representation of a VLC key code.
150  * @return a VLC key code, or KEY_UNSET on failure.
151  */
152 static
153 uint_fast32_t vlc_str2keycode (const char *name)
154 {
155     uint_fast32_t mods = 0;
156     uint32_t cp;
157
158     for (;;)
159     {
160         size_t len = strcspn (name, "-+");
161         if (len == 0 || name[len] == '\0')
162             break;
163
164         if (len == 4 && !strncasecmp (name, "Ctrl", 4))
165             mods |= KEY_MODIFIER_CTRL;
166         if (len == 3 && !strncasecmp (name, "Alt", 3))
167             mods |= KEY_MODIFIER_ALT;
168         if (len == 5 && !strncasecmp (name, "Shift", 5))
169             mods |= KEY_MODIFIER_SHIFT;
170         if (len == 4 && !strncasecmp (name, "Meta", 4))
171             mods |= KEY_MODIFIER_META;
172         if (len == 7 && !strncasecmp (name, "Command", 7))
173             mods |= KEY_MODIFIER_COMMAND;
174
175         name += len + 1;
176     }
177
178     for (size_t i = 0; i < vlc_num_keys; i++)
179         if (!strcasecmp( vlc_keys[i].psz_key_string, name))
180             return vlc_keys[i].i_key_code | mods;
181
182     return (vlc_towc (name, &cp) > 0) ? (mods | cp) : KEY_UNSET;
183 }
184
185 /**
186  * Format a human-readable and unique representation of a VLC key code
187  * (including modifiers).
188  * @return a heap-allocated string, or NULL on error.
189  */
190 char *vlc_keycode2str (uint_fast32_t code)
191 {
192     char *str, buf[5];
193     uintptr_t key = code & ~KEY_MODIFIER;
194
195     key_descriptor_t *d = bsearch ((void *)key, vlc_keys, vlc_num_keys,
196                                    sizeof (vlc_keys[0]), cmpkey);
197     if (d == NULL && utf8_cp (key, buf) == NULL)
198         return NULL;
199
200     if (asprintf (&str, "%s%s%s%s%s%s",
201                   (code & KEY_MODIFIER_CTRL) ? "Ctrl+" : "",
202                   (code & KEY_MODIFIER_ALT) ? "Alt+" : "",
203                   (code & KEY_MODIFIER_SHIFT) ? "Shift+" : "",
204                   (code & KEY_MODIFIER_META) ? "Meta+" : "",
205                   (code & KEY_MODIFIER_COMMAND) ? "Command+" : "",
206                   (d != NULL) ? d->psz_key_string : buf) == -1)
207         return NULL;
208
209     return str;
210 }
211
212
213 /*** VLC key map ***/
214
215 struct action
216 {
217     char name[24];
218     int  value;
219 };
220
221 static const struct action actions[] =
222 {
223     /* *MUST* be sorted (ASCII order) */
224     { "key-aspect-ratio", ACTIONID_ASPECT_RATIO, },
225     { "key-audio-track", ACTIONID_AUDIO_TRACK, },
226     { "key-audiodelay-down", ACTIONID_AUDIODELAY_DOWN, },
227     { "key-audiodelay-up", ACTIONID_AUDIODELAY_UP, },
228     { "key-audiodevice-cycle", ACTIONID_AUDIODEVICE_CYCLE, },
229     { "key-chapter-next", ACTIONID_CHAPTER_NEXT, },
230     { "key-chapter-prev", ACTIONID_CHAPTER_PREV, },
231     { "key-crop", ACTIONID_CROP, },
232     { "key-crop-bottom", ACTIONID_CROP_BOTTOM, },
233     { "key-crop-left", ACTIONID_CROP_LEFT, },
234     { "key-crop-right", ACTIONID_CROP_RIGHT, },
235     { "key-crop-top", ACTIONID_CROP_TOP, },
236     { "key-decr-scalefactor", ACTIONID_SCALE_DOWN, },
237     { "key-deinterlace", ACTIONID_DEINTERLACE, },
238     { "key-disc-menu", ACTIONID_DISC_MENU, },
239     { "key-faster", ACTIONID_FASTER, },
240     { "key-frame-next", ACTIONID_FRAME_NEXT, },
241     { "key-incr-scalefactor", ACTIONID_SCALE_UP, },
242     { "key-intf-hide", ACTIONID_INTF_HIDE, },
243     { "key-intf-show", ACTIONID_INTF_SHOW, },
244     { "key-jump+extrashort", ACTIONID_JUMP_FORWARD_EXTRASHORT, },
245     { "key-jump+long", ACTIONID_JUMP_FORWARD_LONG, },
246     { "key-jump+medium", ACTIONID_JUMP_FORWARD_MEDIUM, },
247     { "key-jump+short", ACTIONID_JUMP_FORWARD_SHORT, },
248     { "key-jump-extrashort", ACTIONID_JUMP_BACKWARD_EXTRASHORT, },
249     { "key-jump-long", ACTIONID_JUMP_BACKWARD_LONG, },
250     { "key-jump-medium", ACTIONID_JUMP_BACKWARD_MEDIUM, },
251     { "key-jump-short", ACTIONID_JUMP_BACKWARD_SHORT, },
252     { "key-leave-fullscreen", ACTIONID_LEAVE_FULLSCREEN, },
253     { "key-loop", ACTIONID_LOOP, },
254     { "key-menu-down", ACTIONID_MENU_DOWN, },
255     { "key-menu-left", ACTIONID_MENU_LEFT, },
256     { "key-menu-off", ACTIONID_MENU_OFF, },
257     { "key-menu-on", ACTIONID_MENU_ON, },
258     { "key-menu-right", ACTIONID_MENU_RIGHT, },
259     { "key-menu-select", ACTIONID_MENU_SELECT, },
260     { "key-menu-up", ACTIONID_MENU_UP, },
261     { "key-nav-activate", ACTIONID_NAV_ACTIVATE, },
262     { "key-nav-down", ACTIONID_NAV_DOWN, },
263     { "key-nav-left", ACTIONID_NAV_LEFT, },
264     { "key-nav-right", ACTIONID_NAV_RIGHT, },
265     { "key-nav-up", ACTIONID_NAV_UP, },
266     { "key-next", ACTIONID_NEXT, },
267     { "key-pause", ACTIONID_PAUSE, },
268     { "key-play", ACTIONID_PLAY, },
269     { "key-play-bookmark1", ACTIONID_PLAY_BOOKMARK1, },
270     { "key-play-bookmark10", ACTIONID_PLAY_BOOKMARK10, },
271     { "key-play-bookmark2", ACTIONID_PLAY_BOOKMARK2, },
272     { "key-play-bookmark3", ACTIONID_PLAY_BOOKMARK3, },
273     { "key-play-bookmark4", ACTIONID_PLAY_BOOKMARK4, },
274     { "key-play-bookmark5", ACTIONID_PLAY_BOOKMARK5, },
275     { "key-play-bookmark6", ACTIONID_PLAY_BOOKMARK6, },
276     { "key-play-bookmark7", ACTIONID_PLAY_BOOKMARK7, },
277     { "key-play-bookmark8", ACTIONID_PLAY_BOOKMARK8, },
278     { "key-play-bookmark9", ACTIONID_PLAY_BOOKMARK9, },
279     { "key-play-pause", ACTIONID_PLAY_PAUSE, },
280     { "key-position", ACTIONID_POSITION, },
281     { "key-prev", ACTIONID_PREV, },
282     { "key-quit", ACTIONID_QUIT, },
283     { "key-random", ACTIONID_RANDOM, },
284     { "key-rate-faster-fine", ACTIONID_RATE_FASTER_FINE, },
285     { "key-rate-normal", ACTIONID_RATE_NORMAL, },
286     { "key-rate-slower-fine", ACTIONID_RATE_SLOWER_FINE, },
287     { "key-record", ACTIONID_RECORD, },
288     { "key-set-bookmark1", ACTIONID_SET_BOOKMARK1, },
289     { "key-set-bookmark10", ACTIONID_SET_BOOKMARK10, },
290     { "key-set-bookmark2", ACTIONID_SET_BOOKMARK2, },
291     { "key-set-bookmark3", ACTIONID_SET_BOOKMARK3, },
292     { "key-set-bookmark4", ACTIONID_SET_BOOKMARK4, },
293     { "key-set-bookmark5", ACTIONID_SET_BOOKMARK5, },
294     { "key-set-bookmark6", ACTIONID_SET_BOOKMARK6, },
295     { "key-set-bookmark7", ACTIONID_SET_BOOKMARK7, },
296     { "key-set-bookmark8", ACTIONID_SET_BOOKMARK8, },
297     { "key-set-bookmark9", ACTIONID_SET_BOOKMARK9, },
298     { "key-slower", ACTIONID_SLOWER, },
299     { "key-snapshot", ACTIONID_SNAPSHOT, },
300     { "key-stop", ACTIONID_STOP, },
301     { "key-subdelay-down", ACTIONID_SUBDELAY_DOWN, },
302     { "key-subdelay-up", ACTIONID_SUBDELAY_UP, },
303     { "key-subpos-down", ACTIONID_SUBPOS_DOWN, },
304     { "key-subpos-up", ACTIONID_SUBPOS_UP, },
305     { "key-subtitle-track", ACTIONID_SUBTITLE_TRACK, },
306     { "key-title-next", ACTIONID_TITLE_NEXT, },
307     { "key-title-prev", ACTIONID_TITLE_PREV, },
308     { "key-toggle-autoscale", ACTIONID_TOGGLE_AUTOSCALE, },
309     { "key-toggle-fullscreen", ACTIONID_TOGGLE_FULLSCREEN, },
310     { "key-uncrop-bottom", ACTIONID_UNCROP_BOTTOM, },
311     { "key-uncrop-left", ACTIONID_UNCROP_LEFT, },
312     { "key-uncrop-right", ACTIONID_UNCROP_RIGHT, },
313     { "key-uncrop-top", ACTIONID_UNCROP_TOP, },
314     { "key-unzoom", ACTIONID_UNZOOM, },
315     { "key-vol-down", ACTIONID_VOL_DOWN, },
316     { "key-vol-mute", ACTIONID_VOL_MUTE, },
317     { "key-vol-up", ACTIONID_VOL_UP, },
318     { "key-wallpaper", ACTIONID_WALLPAPER, },
319     { "key-zoom", ACTIONID_ZOOM, },
320     { "key-zoom-double", ACTIONID_ZOOM_DOUBLE, },
321     { "key-zoom-half", ACTIONID_ZOOM_HALF, },
322     { "key-zoom-original", ACTIONID_ZOOM_ORIGINAL, },
323     { "key-zoom-quarter", ACTIONID_ZOOM_QUARTER, },
324 };
325
326 #define ACTIONS_COUNT (sizeof (actions) / sizeof (actions[0]))
327
328 struct mapping
329 {
330     uint32_t  key; ///< Key code
331     vlc_key_t action; ///< Action ID
332 };
333
334 static int keycmp (const void *a, const void *b)
335 {
336     const struct mapping *ka = a, *kb = b;
337
338 #if (INT_MAX >= 0x7fffffff)
339     return ka->key - kb->key;
340 #else
341     return (ka->key < kb->key) ? -1 : (ka->key > kb->key) ? +1 : 0;
342 #endif
343 }
344
345 struct vlc_actions
346 {
347     void *map; /* Key map */
348     struct hotkey keys[0];
349 };
350
351 static int vlc_key_to_action (vlc_object_t *libvlc, const char *varname,
352                               vlc_value_t prevkey, vlc_value_t curkey, void *d)
353 {
354     const struct vlc_actions *as = d;
355     const struct mapping **pent;
356     uint32_t keycode = curkey.i_int;
357
358     pent = tfind (&keycode, &as->map, keycmp);
359     if (pent == NULL)
360         return VLC_SUCCESS;
361
362     (void) varname;
363     (void) prevkey;
364     return var_SetInteger (libvlc, "key-action", (*pent)->action);
365 }
366
367
368 /**
369  * Initializes the key map from configuration.
370  */
371 struct vlc_actions *vlc_InitActions (libvlc_int_t *libvlc)
372 {
373     struct hotkey *keys;
374     struct vlc_actions *as = malloc (sizeof (*as) + (ACTIONS_COUNT + 1) * sizeof (*keys));
375
376     if (unlikely(as == NULL))
377         return NULL;
378     as->map = NULL;
379     keys = as->keys;
380
381     var_Create (libvlc, "key-pressed", VLC_VAR_INTEGER);
382     var_Create (libvlc, "key-action", VLC_VAR_INTEGER);
383
384     /* Initialize from configuration */
385     for (size_t i = 0; i < ACTIONS_COUNT; i++)
386     {
387 #ifndef NDEBUG
388         if (i > 0
389          && strcmp (actions[i-1].name, actions[i].name) >= 0)
390         {
391             msg_Err (libvlc, "%s and %s are not ordered properly",
392                      actions[i-1].name, actions[i].name);
393             abort ();
394         }
395 #endif
396         keys->psz_action = actions[i].name;
397         keys->i_action = actions[i].value;
398         keys++;
399
400         char *str = var_InheritString (libvlc, actions[i].name);
401         uint32_t code = str ? vlc_str2keycode (str) : KEY_UNSET;
402
403         if (code == KEY_UNSET)
404             continue;
405
406         struct mapping *entry = malloc (sizeof (*entry));
407         if (entry == NULL)
408             continue;
409         entry->key = code;
410         entry->action = actions[i].value;
411
412         struct mapping **pent = tsearch (entry, &as->map, keycmp);
413         if (unlikely(pent == NULL))
414             continue;
415         if (*pent != entry)
416         {
417             free (entry);
418             msg_Warn (libvlc, "Key code \"%s\" bound to multiple actions",
419                       str);
420         }
421         free (str);
422     }
423
424     keys->psz_action = NULL;
425     keys->i_action = 0;
426
427     libvlc->p_hotkeys = as->keys;
428     var_AddCallback (libvlc, "key-pressed", vlc_key_to_action, as);
429     return VLC_SUCCESS;
430 }
431
432 /**
433  * Destroys the key map.
434  */
435 void vlc_DeinitActions (libvlc_int_t *libvlc, struct vlc_actions *as)
436 {
437     if (unlikely(as == NULL))
438         return;
439
440     var_DelCallback (libvlc, "key-pressed", vlc_key_to_action, as);
441     tdestroy (as->map, free);
442     free (as);
443     libvlc->p_hotkeys = NULL;
444 }
445
446
447 static int actcmp(const void *key, const void *ent)
448 {
449     const struct action *act = ent;
450     return strcmp(key, act->name);
451 }
452
453 /**
454  * Get the action ID from the action name in the configuration subsystem.
455  * @return the action ID or ACTIONID_NONE on error.
456  */
457 vlc_key_t vlc_GetActionId(const char *name)
458 {
459     const struct action *act;
460
461     act = bsearch(name, actions, ACTIONS_COUNT, sizeof(*act), actcmp);
462     return (act != NULL) ? act->value : ACTIONID_NONE;
463 }