]> git.sesse.net Git - vlc/blob - src/config/keys.c
Export vlc_str2keycode()
[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 {   /* Alphabetical order */
53     { "Backspace",         KEY_BACKSPACE         },
54     { "Browser Back",      KEY_BROWSER_BACK      },
55     { "Browser Favorites", KEY_BROWSER_FAVORITES },
56     { "Browser Forward",   KEY_BROWSER_FORWARD   },
57     { "Browser Home",      KEY_BROWSER_HOME      },
58     { "Browser Refresh",   KEY_BROWSER_REFRESH   },
59     { "Browser Search",    KEY_BROWSER_SEARCH    },
60     { "Browser Stop",      KEY_BROWSER_STOP      },
61     { "Delete",            KEY_DELETE            },
62     { "Down",              KEY_DOWN              },
63     { "End",               KEY_END               },
64     { "Enter",             KEY_ENTER             },
65     { "Esc",               KEY_ESC               },
66     { "F1",                KEY_F1                },
67     { "F10",               KEY_F10               },
68     { "F11",               KEY_F11               },
69     { "F12",               KEY_F12               },
70     { "F2",                KEY_F2                },
71     { "F3",                KEY_F3                },
72     { "F4",                KEY_F4                },
73     { "F5",                KEY_F5                },
74     { "F6",                KEY_F6                },
75     { "F7",                KEY_F7                },
76     { "F8",                KEY_F8                },
77     { "F9",                KEY_F9                },
78     { "Home",              KEY_HOME              },
79     { "Insert",            KEY_INSERT            },
80     { "Left",              KEY_LEFT              },
81     { "Media Next Track",  KEY_MEDIA_NEXT_TRACK  },
82     { "Media Play Pause",  KEY_MEDIA_PLAY_PAUSE  },
83     { "Media Prev Track",  KEY_MEDIA_PREV_TRACK  },
84     { "Media Stop",        KEY_MEDIA_STOP        },
85     { "Menu",              KEY_MENU              },
86     { "Mouse Wheel Down",  KEY_MOUSEWHEELDOWN    },
87     { "Mouse Wheel Left",  KEY_MOUSEWHEELLEFT    },
88     { "Mouse Wheel Right", KEY_MOUSEWHEELRIGHT   },
89     { "Mouse Wheel Up",    KEY_MOUSEWHEELUP      },
90     { "Page Down",         KEY_PAGEDOWN          },
91     { "Page Up",           KEY_PAGEUP            },
92     { "Right",             KEY_RIGHT             },
93     { "Space",             ' '                   },
94     { "Tab",               KEY_TAB               },
95     { "Unset",             KEY_UNSET             },
96     { "Up",                KEY_UP                },
97     { "Volume Mute",       KEY_VOLUME_MUTE       },
98     { "Volume Down",       KEY_VOLUME_DOWN       },
99     { "Volume Up",         KEY_VOLUME_UP         },
100 };
101 #define KEYS_COUNT (sizeof(vlc_keys)/sizeof(vlc_keys[0]))
102
103 static int keystrcmp (const void *key, const void *elem)
104 {
105     const char *sa = key, *sb = elem;
106     return strcmp (sa, sb);
107 }
108
109 /* Convert Unicode code point to UTF-8 */
110 static char *utf8_cp (uint_fast32_t cp, char *buf)
111 {
112     if (cp < (1 << 7))
113     {
114         buf[1] = 0;
115         buf[0] = cp;
116     }
117     else if (cp < (1 << 11))
118     {
119         buf[2] = 0;
120         buf[1] = 0x80 | (cp & 0x3F);
121         cp >>= 6;
122         buf[0] = 0xC0 | cp;
123     }
124     else if (cp < (1 << 16))
125     {
126         buf[3] = 0;
127         buf[2] = 0x80 | (cp & 0x3F);
128         cp >>= 6;
129         buf[1] = 0x80 | (cp & 0x3F);
130         cp >>= 6;
131         buf[0] = 0xE0 | cp;
132     }
133     else if (cp < (1 << 21))
134     {
135         buf[4] = 0;
136         buf[3] = 0x80 | (cp & 0x3F);
137         cp >>= 6;
138         buf[2] = 0x80 | (cp & 0x3F);
139         cp >>= 6;
140         buf[1] = 0x80 | (cp & 0x3F);
141         cp >>= 6;
142         buf[0] = 0xE0 | cp;
143     }
144     else
145         return NULL;
146     return buf;
147 }
148
149 /**
150  * Parse a human-readable string representation of a VLC key code.
151  * @return a VLC key code, or KEY_UNSET on failure.
152  */
153 uint_fast32_t vlc_str2keycode (const char *name)
154 {
155     uint_fast32_t mods = 0;
156     uint32_t code;
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     key_descriptor_t *d = bsearch (name, vlc_keys, KEYS_COUNT,
179                                    sizeof (vlc_keys[0]), keystrcmp);
180     if (d != NULL)
181         code = d->i_key_code;
182     else
183     if (vlc_towc (name, &code) <= 0)
184         code = KEY_UNSET;
185
186     if (code != KEY_UNSET)
187         code |= mods;
188     return code;
189 }
190
191 /**
192  * Format a human-readable and unique representation of a VLC key code
193  * (including modifiers).
194  * @return a heap-allocated string, or NULL on error.
195  */
196 char *vlc_keycode2str (uint_fast32_t code)
197 {
198     const char *name;
199     char *str, buf[5];
200     uintptr_t key = code & ~KEY_MODIFIER;
201
202     for (size_t i = 0; i < KEYS_COUNT; i++)
203         if (vlc_keys[i].i_key_code == key)
204         {
205             name = vlc_keys[i].psz_key_string;
206             goto found;
207         }
208
209     if (utf8_cp (key, buf) == NULL)
210         return NULL;
211     name = buf;
212
213 found:
214     if (asprintf (&str, "%s%s%s%s%s%s",
215                   (code & KEY_MODIFIER_CTRL) ? "Ctrl+" : "",
216                   (code & KEY_MODIFIER_ALT) ? "Alt+" : "",
217                   (code & KEY_MODIFIER_SHIFT) ? "Shift+" : "",
218                   (code & KEY_MODIFIER_META) ? "Meta+" : "",
219                   (code & KEY_MODIFIER_COMMAND) ? "Command+" : "", name) == -1)
220         return NULL;
221     return str;
222 }
223
224
225 /*** VLC key map ***/
226
227 #define MAXACTION 20
228 struct action
229 {
230     char name[MAXACTION];
231     vlc_key_t value;
232 };
233
234 static const struct action actions[] =
235 {
236     /* *MUST* be sorted (ASCII order) */
237     { "aspect-ratio", ACTIONID_ASPECT_RATIO, },
238     { "audio-track", ACTIONID_AUDIO_TRACK, },
239     { "audiodelay-down", ACTIONID_AUDIODELAY_DOWN, },
240     { "audiodelay-up", ACTIONID_AUDIODELAY_UP, },
241     { "audiodevice-cycle", ACTIONID_AUDIODEVICE_CYCLE, },
242     { "chapter-next", ACTIONID_CHAPTER_NEXT, },
243     { "chapter-prev", ACTIONID_CHAPTER_PREV, },
244     { "crop", ACTIONID_CROP, },
245     { "crop-bottom", ACTIONID_CROP_BOTTOM, },
246     { "crop-left", ACTIONID_CROP_LEFT, },
247     { "crop-right", ACTIONID_CROP_RIGHT, },
248     { "crop-top", ACTIONID_CROP_TOP, },
249     { "decr-scalefactor", ACTIONID_SCALE_DOWN, },
250     { "deinterlace", ACTIONID_DEINTERLACE, },
251     { "disc-menu", ACTIONID_DISC_MENU, },
252     { "faster", ACTIONID_FASTER, },
253     { "frame-next", ACTIONID_FRAME_NEXT, },
254     { "incr-scalefactor", ACTIONID_SCALE_UP, },
255     { "intf-hide", ACTIONID_INTF_HIDE, },
256     { "intf-show", ACTIONID_INTF_SHOW, },
257     { "jump+extrashort", ACTIONID_JUMP_FORWARD_EXTRASHORT, },
258     { "jump+long", ACTIONID_JUMP_FORWARD_LONG, },
259     { "jump+medium", ACTIONID_JUMP_FORWARD_MEDIUM, },
260     { "jump+short", ACTIONID_JUMP_FORWARD_SHORT, },
261     { "jump-extrashort", ACTIONID_JUMP_BACKWARD_EXTRASHORT, },
262     { "jump-long", ACTIONID_JUMP_BACKWARD_LONG, },
263     { "jump-medium", ACTIONID_JUMP_BACKWARD_MEDIUM, },
264     { "jump-short", ACTIONID_JUMP_BACKWARD_SHORT, },
265     { "leave-fullscreen", ACTIONID_LEAVE_FULLSCREEN, },
266     { "loop", ACTIONID_LOOP, },
267     { "menu-down", ACTIONID_MENU_DOWN, },
268     { "menu-left", ACTIONID_MENU_LEFT, },
269     { "menu-off", ACTIONID_MENU_OFF, },
270     { "menu-on", ACTIONID_MENU_ON, },
271     { "menu-right", ACTIONID_MENU_RIGHT, },
272     { "menu-select", ACTIONID_MENU_SELECT, },
273     { "menu-up", ACTIONID_MENU_UP, },
274     { "nav-activate", ACTIONID_NAV_ACTIVATE, },
275     { "nav-down", ACTIONID_NAV_DOWN, },
276     { "nav-left", ACTIONID_NAV_LEFT, },
277     { "nav-right", ACTIONID_NAV_RIGHT, },
278     { "nav-up", ACTIONID_NAV_UP, },
279     { "next", ACTIONID_NEXT, },
280     { "pause", ACTIONID_PAUSE, },
281     { "play", ACTIONID_PLAY, },
282     { "play-bookmark1", ACTIONID_PLAY_BOOKMARK1, },
283     { "play-bookmark10", ACTIONID_PLAY_BOOKMARK10, },
284     { "play-bookmark2", ACTIONID_PLAY_BOOKMARK2, },
285     { "play-bookmark3", ACTIONID_PLAY_BOOKMARK3, },
286     { "play-bookmark4", ACTIONID_PLAY_BOOKMARK4, },
287     { "play-bookmark5", ACTIONID_PLAY_BOOKMARK5, },
288     { "play-bookmark6", ACTIONID_PLAY_BOOKMARK6, },
289     { "play-bookmark7", ACTIONID_PLAY_BOOKMARK7, },
290     { "play-bookmark8", ACTIONID_PLAY_BOOKMARK8, },
291     { "play-bookmark9", ACTIONID_PLAY_BOOKMARK9, },
292     { "play-pause", ACTIONID_PLAY_PAUSE, },
293     { "position", ACTIONID_POSITION, },
294     { "prev", ACTIONID_PREV, },
295     { "quit", ACTIONID_QUIT, },
296     { "random", ACTIONID_RANDOM, },
297     { "rate-faster-fine", ACTIONID_RATE_FASTER_FINE, },
298     { "rate-normal", ACTIONID_RATE_NORMAL, },
299     { "rate-slower-fine", ACTIONID_RATE_SLOWER_FINE, },
300     { "record", ACTIONID_RECORD, },
301     { "set-bookmark1", ACTIONID_SET_BOOKMARK1, },
302     { "set-bookmark10", ACTIONID_SET_BOOKMARK10, },
303     { "set-bookmark2", ACTIONID_SET_BOOKMARK2, },
304     { "set-bookmark3", ACTIONID_SET_BOOKMARK3, },
305     { "set-bookmark4", ACTIONID_SET_BOOKMARK4, },
306     { "set-bookmark5", ACTIONID_SET_BOOKMARK5, },
307     { "set-bookmark6", ACTIONID_SET_BOOKMARK6, },
308     { "set-bookmark7", ACTIONID_SET_BOOKMARK7, },
309     { "set-bookmark8", ACTIONID_SET_BOOKMARK8, },
310     { "set-bookmark9", ACTIONID_SET_BOOKMARK9, },
311     { "slower", ACTIONID_SLOWER, },
312     { "snapshot", ACTIONID_SNAPSHOT, },
313     { "stop", ACTIONID_STOP, },
314     { "subdelay-down", ACTIONID_SUBDELAY_DOWN, },
315     { "subdelay-up", ACTIONID_SUBDELAY_UP, },
316     { "subpos-down", ACTIONID_SUBPOS_DOWN, },
317     { "subpos-up", ACTIONID_SUBPOS_UP, },
318     { "subtitle-track", ACTIONID_SUBTITLE_TRACK, },
319     { "title-next", ACTIONID_TITLE_NEXT, },
320     { "title-prev", ACTIONID_TITLE_PREV, },
321     { "toggle-autoscale", ACTIONID_TOGGLE_AUTOSCALE, },
322     { "toggle-fullscreen", ACTIONID_TOGGLE_FULLSCREEN, },
323     { "uncrop-bottom", ACTIONID_UNCROP_BOTTOM, },
324     { "uncrop-left", ACTIONID_UNCROP_LEFT, },
325     { "uncrop-right", ACTIONID_UNCROP_RIGHT, },
326     { "uncrop-top", ACTIONID_UNCROP_TOP, },
327     { "unzoom", ACTIONID_UNZOOM, },
328     { "vol-down", ACTIONID_VOL_DOWN, },
329     { "vol-mute", ACTIONID_VOL_MUTE, },
330     { "vol-up", ACTIONID_VOL_UP, },
331     { "wallpaper", ACTIONID_WALLPAPER, },
332     { "zoom", ACTIONID_ZOOM, },
333     { "zoom-double", ACTIONID_ZOOM_DOUBLE, },
334     { "zoom-half", ACTIONID_ZOOM_HALF, },
335     { "zoom-original", ACTIONID_ZOOM_ORIGINAL, },
336     { "zoom-quarter", ACTIONID_ZOOM_QUARTER, },
337 };
338 #define ACTIONS_COUNT (sizeof (actions) / sizeof (actions[0]))
339
340 struct mapping
341 {
342     uint32_t  key; ///< Key code
343     vlc_key_t action; ///< Action ID
344 };
345
346 static int keycmp (const void *a, const void *b)
347 {
348     const struct mapping *ka = a, *kb = b;
349
350 #if (INT_MAX >= 0x7fffffff)
351     return ka->key - kb->key;
352 #else
353     return (ka->key < kb->key) ? -1 : (ka->key > kb->key) ? +1 : 0;
354 #endif
355 }
356
357 struct vlc_actions
358 {
359     void *map; /* Key map */
360     void *global_map; /* Grabbed/global key map */
361     struct hotkey keys[0];
362 };
363
364 static int vlc_key_to_action (vlc_object_t *obj, const char *varname,
365                               vlc_value_t prevkey, vlc_value_t curkey, void *d)
366 {
367     void *const *map = d;
368     const struct mapping **pent;
369     uint32_t keycode = curkey.i_int;
370
371     pent = tfind (&keycode, map, keycmp);
372     if (pent == NULL)
373         return VLC_SUCCESS;
374
375     (void) varname;
376     (void) prevkey;
377     return var_SetInteger (obj, "key-action", (*pent)->action);
378 }
379
380
381 static void vlc_MapAction (vlc_object_t *obj, void **map,
382                            const char *confname, vlc_key_t action)
383 {
384     char *keys = var_InheritString (obj, confname);
385     if (keys == NULL)
386         return;
387
388     for (char *buf, *key = strtok_r (keys, "\t", &buf);
389          key != NULL;
390          key = strtok_r (NULL, "\t", &buf))
391     {
392         uint32_t code = vlc_str2keycode (key);
393         if (code == KEY_UNSET)
394         {
395             msg_Warn (obj, "Key \"%s\" unrecognized", key);
396             continue;
397         }
398
399         struct mapping *entry = malloc (sizeof (*entry));
400         if (entry == NULL)
401             continue;
402         entry->key = code;
403         entry->action = action;
404
405         struct mapping **pent = tsearch (entry, map, keycmp);
406         if (unlikely(pent == NULL))
407             continue;
408         if (*pent != entry)
409         {
410             free (entry);
411             msg_Warn (obj, "Key \"%s\" bound to multiple actions", key);
412         }
413     }
414     free (keys);
415 }
416
417
418 /**
419  * Initializes the key map from configuration.
420  */
421 struct vlc_actions *vlc_InitActions (libvlc_int_t *libvlc)
422 {
423     vlc_object_t *obj = VLC_OBJECT(libvlc);
424     struct hotkey *keys;
425     struct vlc_actions *as = malloc (sizeof (*as) + (ACTIONS_COUNT + 1) * sizeof (*keys));
426
427     if (unlikely(as == NULL))
428         return NULL;
429     as->map = NULL;
430     as->global_map = NULL;
431     keys = as->keys;
432
433     var_Create (obj, "key-pressed", VLC_VAR_INTEGER);
434     var_Create (obj, "global-key-pressed", VLC_VAR_INTEGER);
435     var_Create (obj, "key-action", VLC_VAR_INTEGER);
436
437     /* Initialize from configuration */
438     for (size_t i = 0; i < ACTIONS_COUNT; i++)
439     {
440 #ifndef NDEBUG
441         if (i > 0
442          && strcmp (actions[i-1].name, actions[i].name) >= 0)
443         {
444             msg_Err (libvlc, "key-%s and key-%s are not ordered properly",
445                      actions[i-1].name, actions[i].name);
446             abort ();
447         }
448 #endif
449         keys->psz_action = actions[i].name;
450         keys->i_action = actions[i].value;
451         keys++;
452
453         char name[12 + MAXACTION];
454
455         snprintf (name, sizeof (name), "global-key-%s", actions[i].name);
456         vlc_MapAction (obj, &as->map, name + 7, actions[i].value);
457         vlc_MapAction (obj, &as->global_map, name, actions[i].value);
458     }
459
460     keys->psz_action = NULL;
461     keys->i_action = 0;
462
463     libvlc->p_hotkeys = as->keys;
464     var_AddCallback (obj, "key-pressed", vlc_key_to_action, &as->map);
465     var_AddCallback (obj, "global-key-pressed", vlc_key_to_action,
466                      &as->global_map);
467     return as;
468 }
469
470 /**
471  * Destroys the key map.
472  */
473 void vlc_DeinitActions (libvlc_int_t *libvlc, struct vlc_actions *as)
474 {
475     if (unlikely(as == NULL))
476         return;
477
478     var_DelCallback (libvlc, "global-key-pressed", vlc_key_to_action,
479                      &as->global_map);
480     var_DelCallback (libvlc, "key-pressed", vlc_key_to_action, &as->map);
481
482     tdestroy (as->global_map, free);
483     tdestroy (as->map, free);
484     free (as);
485     libvlc->p_hotkeys = NULL;
486 }
487
488
489 static int actcmp(const void *key, const void *ent)
490 {
491     const struct action *act = ent;
492     return strcmp(key, act->name);
493 }
494
495 /**
496  * Get the action ID from the action name in the configuration subsystem.
497  * @return the action ID or ACTIONID_NONE on error.
498  */
499 vlc_key_t vlc_GetActionId(const char *name)
500 {
501     const struct action *act;
502
503     if (strncmp (name, "key-", 4))
504         return ACTIONID_NONE;
505     name += 4;
506
507     act = bsearch(name, actions, ACTIONS_COUNT, sizeof(*act), actcmp);
508     return (act != NULL) ? act->value : ACTIONID_NONE;
509 }