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