]> git.sesse.net Git - vlc/blob - modules/control/globalhotkeys/xcb.c
8fe4167f8141a470703aaa2d29c33b0f6dd6e3dd
[vlc] / modules / control / globalhotkeys / xcb.c
1 /*****************************************************************************
2  * xcb.c: Global-Hotkey X11 using xcb handling for vlc
3  *****************************************************************************
4  * Copyright (C) 2009 the VideoLAN team
5  *
6  * Authors: Laurent Aimar <fenrir _AT_ videolan _DOT_ 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 #include <vlc_common.h>
27 #include <vlc_plugin.h>
28 #include <vlc_interface.h>
29 #include <vlc_keys.h>
30 #include <ctype.h>
31 #include <errno.h>
32
33 #include <xcb/xcb.h>
34 #include <xcb/xcb_keysyms.h>
35 #include <X11/keysym.h>
36 #include <X11/XF86keysym.h>
37
38 #include <poll.h>
39
40 /*****************************************************************************
41  * Local prototypes
42  *****************************************************************************/
43 static int Open( vlc_object_t *p_this );
44 static void Close( vlc_object_t *p_this );
45
46 /*****************************************************************************
47  * Module descriptor
48  *****************************************************************************/
49 vlc_module_begin()
50     set_shortname( N_("Global Hotkeys") )
51     set_category( CAT_INTERFACE )
52     set_subcategory( SUBCAT_INTERFACE_HOTKEYS )
53     set_description( N_("Global Hotkeys interface") )
54     set_capability( "interface", 0 )
55     set_callbacks( Open, Close )
56 vlc_module_end()
57
58 typedef struct
59 {
60 #ifdef XCB_KEYSYM_OLD_API /* as seen in Debian Lenny */
61     xcb_keycode_t i_x11;
62 #else
63     xcb_keycode_t *p_keys;
64 #endif
65     unsigned      i_modifier;
66     int           i_action;
67 } hotkey_mapping_t;
68
69 struct intf_sys_t
70 {
71     vlc_thread_t thread;
72
73     xcb_connection_t  *p_connection;
74     xcb_window_t      root;
75     xcb_key_symbols_t *p_symbols;
76
77     int              i_map;
78     hotkey_mapping_t *p_map;
79 };
80
81 static bool Mapping( intf_thread_t *p_intf );
82 static void Register( intf_thread_t *p_intf );
83 static void *Thread( void *p_data );
84
85 /*****************************************************************************
86  * Open:
87  *****************************************************************************/
88 static int Open( vlc_object_t *p_this )
89 {
90     intf_thread_t *p_intf = (intf_thread_t *)p_this;
91     intf_sys_t *p_sys;
92     int ret = VLC_EGENERIC;
93
94     p_intf->p_sys = p_sys = calloc( 1, sizeof(*p_sys) );
95     if( !p_sys )
96         return VLC_ENOMEM;
97
98     char *psz_display = var_CreateGetNonEmptyString( p_intf, "x11-display" );
99
100     int i_screen_default;
101     p_sys->p_connection = xcb_connect( psz_display, &i_screen_default );
102     free( psz_display );
103
104     if( xcb_connection_has_error( p_sys->p_connection ) )
105         goto error;
106
107     /* Get the root windows of the default screen */
108     const xcb_setup_t* xcbsetup = xcb_get_setup( p_sys->p_connection );
109     if( !xcbsetup )
110         goto error;
111     xcb_screen_iterator_t iter = xcb_setup_roots_iterator( xcbsetup );
112     for( int i = 0; i < i_screen_default; i++ )
113     {
114         if( !iter.rem )
115             break;
116         xcb_screen_next( &iter );
117     }
118     if( !iter.rem )
119         goto error;
120     p_sys->root = iter.data->root;
121
122     /* */
123     p_sys->p_symbols = xcb_key_symbols_alloc( p_sys->p_connection ); // FIXME
124     if( !p_sys->p_symbols )
125         goto error;
126
127     if( !Mapping( p_intf ) )
128     {
129         ret = VLC_SUCCESS;
130         p_intf->p_sys = NULL; /* for Close() */
131         goto error;
132     }
133     Register( p_intf );
134
135     if( vlc_clone( &p_sys->thread, Thread, p_intf, VLC_THREAD_PRIORITY_LOW ) )
136     {
137 #ifndef XCB_KEYSYM_OLD_API /* as seen in Debian Lenny */
138         if( p_sys->p_map )
139             free( p_sys->p_map->p_keys );
140 #endif
141         free( p_sys->p_map );
142         goto error;
143     }
144     return VLC_SUCCESS;
145
146 error:
147     if( p_sys->p_symbols )
148         xcb_key_symbols_free( p_sys->p_symbols );
149     xcb_disconnect( p_sys->p_connection );
150     free( p_sys );
151     return ret;
152 }
153
154 /*****************************************************************************
155  * Close:
156  *****************************************************************************/
157 static void Close( vlc_object_t *p_this )
158 {
159     intf_thread_t *p_intf = (intf_thread_t *)p_this;
160     intf_sys_t *p_sys = p_intf->p_sys;
161
162     if( !p_sys )
163         return; /* if we were running disabled */
164
165     vlc_cancel( p_sys->thread );
166     vlc_join( p_sys->thread, NULL );
167
168 #ifndef XCB_KEYSYM_OLD_API /* as seen in Debian Lenny */
169     if( p_sys->p_map )
170         free( p_sys->p_map->p_keys );
171 #endif
172     free( p_sys->p_map );
173
174     xcb_key_symbols_free( p_sys->p_symbols );
175     xcb_disconnect( p_sys->p_connection );
176     free( p_sys );
177 }
178
179 /*****************************************************************************
180  *
181  *****************************************************************************/
182 static unsigned GetModifier( xcb_connection_t *p_connection, xcb_key_symbols_t *p_symbols, xcb_keysym_t sym )
183 {
184     static const unsigned pi_mask[8] = {
185         XCB_MOD_MASK_SHIFT, XCB_MOD_MASK_LOCK, XCB_MOD_MASK_CONTROL,
186         XCB_MOD_MASK_1, XCB_MOD_MASK_2, XCB_MOD_MASK_3,
187         XCB_MOD_MASK_4, XCB_MOD_MASK_5
188     };
189
190     if( sym == 0 )
191         return 0; /* no modifier */
192
193 #ifdef XCB_KEYSYM_OLD_API /* as seen in Debian Lenny */
194     const xcb_keycode_t key = xcb_key_symbols_get_keycode( p_symbols, sym );
195     if( key == 0 )
196         return 0;
197 #else
198     const xcb_keycode_t *p_keys = xcb_key_symbols_get_keycode( p_symbols, sym );
199     if( !p_keys )
200         return 0;
201
202     int i = 0;
203     bool no_modifier = true;
204     while( p_keys[i] != XCB_NO_SYMBOL )
205     {
206         if( p_keys[i] != 0 )
207         {
208             no_modifier = false;
209             break;
210         }
211         i++;
212     }
213
214     if( no_modifier )
215         return 0;
216 #endif
217
218     xcb_get_modifier_mapping_cookie_t r =
219             xcb_get_modifier_mapping( p_connection );
220     xcb_get_modifier_mapping_reply_t *p_map =
221             xcb_get_modifier_mapping_reply( p_connection, r, NULL );
222     if( !p_map )
223         return 0;
224
225     xcb_keycode_t *p_keycode = xcb_get_modifier_mapping_keycodes( p_map );
226     if( !p_keycode )
227         return 0;
228
229     for( int i = 0; i < 8; i++ )
230         for( int j = 0; j < p_map->keycodes_per_modifier; j++ )
231 #ifdef XCB_KEYSYM_OLD_API /* as seen in Debian Lenny */
232             if( p_keycode[i * p_map->keycodes_per_modifier + j] == key )
233             {
234                 free( p_map );
235                 return pi_mask[i];
236             }
237 #else
238             for( int k = 0; p_keys[k] != XCB_NO_SYMBOL; k++ )
239                 if( p_keycode[i*p_map->keycodes_per_modifier + j] == p_keys[k])
240                 {
241                     free( p_map );
242                     return pi_mask[i];
243                 }
244 #endif
245
246     free( p_map ); // FIXME to check
247     return 0;
248 }
249
250
251 static unsigned GetX11Modifier( xcb_connection_t *p_connection,
252         xcb_key_symbols_t *p_symbols, unsigned i_vlc )
253 {
254     unsigned i_mask = 0;
255
256     if( i_vlc & KEY_MODIFIER_ALT )
257         i_mask |= GetModifier( p_connection, p_symbols, XK_Alt_L ) |
258                   GetModifier( p_connection, p_symbols, XK_Alt_R );
259     if( i_vlc & KEY_MODIFIER_CTRL )
260         i_mask |= GetModifier( p_connection, p_symbols, XK_Control_L ) |
261                   GetModifier( p_connection, p_symbols, XK_Control_R );
262     if( i_vlc & KEY_MODIFIER_SHIFT )
263         i_mask |= GetModifier( p_connection, p_symbols, XK_Shift_L ) |
264                   GetModifier( p_connection, p_symbols, XK_Shift_R );
265     return i_mask;
266 }
267
268 /* FIXME this table is also used by the vout */
269 static const struct
270 {
271     xcb_keysym_t i_x11;
272     unsigned     i_vlc;
273
274 } x11keys_to_vlckeys[] =
275 {
276     { XK_F1, KEY_F1 }, { XK_F2, KEY_F2 }, { XK_F3, KEY_F3 }, { XK_F4, KEY_F4 },
277     { XK_F5, KEY_F5 }, { XK_F6, KEY_F6 }, { XK_F7, KEY_F7 }, { XK_F8, KEY_F8 },
278     { XK_F9, KEY_F9 }, { XK_F10, KEY_F10 }, { XK_F11, KEY_F11 },
279     { XK_F12, KEY_F12 },
280
281     { XK_Return, KEY_ENTER },
282     { XK_KP_Enter, KEY_ENTER },
283     { XK_Escape, KEY_ESC },
284
285     { XK_Menu, KEY_MENU },
286     { XK_Left, KEY_LEFT },
287     { XK_Right, KEY_RIGHT },
288     { XK_Up, KEY_UP },
289     { XK_Down, KEY_DOWN },
290
291     { XK_Home, KEY_HOME },
292     { XK_End, KEY_END },
293     { XK_Page_Up, KEY_PAGEUP },
294     { XK_Page_Down, KEY_PAGEDOWN },
295
296     { XK_Insert, KEY_INSERT },
297     { XK_Delete, KEY_DELETE },
298     { XF86XK_AudioNext, KEY_MEDIA_NEXT_TRACK},
299     { XF86XK_AudioPrev, KEY_MEDIA_PREV_TRACK},
300     { XF86XK_AudioMute, KEY_VOLUME_MUTE },
301     { XF86XK_AudioLowerVolume, KEY_VOLUME_DOWN },
302     { XF86XK_AudioRaiseVolume, KEY_VOLUME_UP },
303     { XF86XK_AudioPlay, KEY_MEDIA_PLAY_PAUSE },
304     { XF86XK_AudioPause, KEY_MEDIA_PLAY_PAUSE },
305
306     { 0, 0 }
307 };
308 static xcb_keysym_t GetX11Key( unsigned i_vlc )
309 {
310     /* X11 and VLC use ASCII for printable ASCII characters */
311     if( i_vlc >= 32 && i_vlc <= 127 )
312         return i_vlc;
313
314     for( int i = 0; x11keys_to_vlckeys[i].i_vlc != 0; i++ )
315     {
316         if( x11keys_to_vlckeys[i].i_vlc == i_vlc )
317             return x11keys_to_vlckeys[i].i_x11;
318     }
319
320     return XK_VoidSymbol;
321 }
322
323 static bool Mapping( intf_thread_t *p_intf )
324 {
325     static const xcb_keysym_t p_x11_modifier_ignored[] = {
326         0,
327         XK_Num_Lock,
328         XK_Scroll_Lock,
329         XK_Caps_Lock,
330     };
331
332     intf_sys_t *p_sys = p_intf->p_sys;
333     bool active = false;
334
335     p_sys->i_map = 0;
336     p_sys->p_map = NULL;
337
338     /* Registering of Hotkeys */
339     for( const struct hotkey *p_hotkey = p_intf->p_libvlc->p_hotkeys;
340             p_hotkey->psz_action != NULL;
341             p_hotkey++ )
342     {
343         char *psz_hotkey;
344         if( asprintf( &psz_hotkey, "global-%s", p_hotkey->psz_action ) < 0 )
345             break;
346
347         const int i_vlc_action = p_hotkey->i_action;
348         const int i_vlc_key = config_GetInt( p_intf, psz_hotkey );
349         free( psz_hotkey );
350
351         if( !i_vlc_key )
352             continue;
353 #ifdef XCB_KEYSYM_OLD_API /* as seen in Debian Lenny */
354         const xcb_keycode_t key = xcb_key_symbols_get_keycode(
355                 p_sys->p_symbols, GetX11Key( i_vlc_key & ~KEY_MODIFIER ) );
356 #else
357         xcb_keycode_t *p_keys = xcb_key_symbols_get_keycode(
358                 p_sys->p_symbols, GetX11Key( i_vlc_key & ~KEY_MODIFIER ) );
359         if( !p_keys )
360             continue;
361 #endif
362         const unsigned i_modifier = GetX11Modifier( p_sys->p_connection,
363                 p_sys->p_symbols, i_vlc_key & KEY_MODIFIER );
364
365         const size_t max = sizeof(p_x11_modifier_ignored) /
366                 sizeof(*p_x11_modifier_ignored);
367         for( unsigned int i = 0; i < max; i++ )
368         {
369             const unsigned i_ignored = GetModifier( p_sys->p_connection,
370                     p_sys->p_symbols, p_x11_modifier_ignored[i] );
371             if( i != 0 && i_ignored == 0)
372                 continue;
373
374             hotkey_mapping_t *p_map_old = p_sys->p_map;
375             p_sys->p_map = realloc( p_sys->p_map,
376                     sizeof(*p_sys->p_map) * (p_sys->i_map+1) );
377             if( !p_sys->p_map )
378             {
379                 p_sys->p_map = p_map_old;
380                 break;
381             }
382             hotkey_mapping_t *p_map = &p_sys->p_map[p_sys->i_map++];
383
384 #ifdef XCB_KEYSYM_OLD_API /* as seen in Debian Lenny */
385             p_map->i_x11 = key;
386 #else
387             p_map->p_keys = p_keys;
388 #endif
389             p_map->i_modifier = i_modifier|i_ignored;
390             p_map->i_action = i_vlc_action;
391             active = true;
392         }
393     }
394     return active;
395 }
396
397 static void Register( intf_thread_t *p_intf )
398 {
399     intf_sys_t *p_sys = p_intf->p_sys;
400
401     for( int i = 0; i < p_sys->i_map; i++ )
402     {
403         const hotkey_mapping_t *p_map = &p_sys->p_map[i];
404 #ifdef XCB_KEYSYM_OLD_API /* as seen in Debian Lenny */
405         xcb_grab_key( p_sys->p_connection, true, p_sys->root,
406                       p_map->i_modifier, p_map->i_x11,
407                       XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC );
408 #else
409         for( int j = 0; p_map->p_keys[j] != XCB_NO_SYMBOL; j++ )
410         {
411             xcb_grab_key( p_sys->p_connection, true, p_sys->root,
412                           p_map->i_modifier, p_map->p_keys[j],
413                           XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC );
414         }
415 #endif
416     }
417 }
418
419 static void *Thread( void *p_data )
420 {
421     intf_thread_t *p_intf = p_data;
422     intf_sys_t *p_sys = p_intf->p_sys;
423     xcb_connection_t *p_connection = p_sys->p_connection;
424
425     int canc = vlc_savecancel();
426
427     /* */
428     xcb_flush( p_connection );
429
430     /* */
431     int fd = xcb_get_file_descriptor( p_connection );
432     for( ;; )
433     {
434         /* Wait for x11 event */
435         vlc_restorecancel( canc );
436         struct pollfd fds = { .fd = fd, .events = POLLIN, };
437         if( poll( &fds, 1, -1 ) < 0 )
438         {
439             if( errno != EINTR )
440                 break;
441             canc = vlc_savecancel();
442             continue;
443         }
444         canc = vlc_savecancel();
445
446         xcb_generic_event_t *p_event;
447         while( ( p_event = xcb_poll_for_event( p_connection ) ) )
448         {
449             if( ( p_event->response_type & 0x7f ) != XCB_KEY_PRESS )
450             {
451                 free( p_event );
452                 continue;
453             }
454
455             xcb_key_press_event_t *e = (xcb_key_press_event_t *)p_event;
456
457             for( int i = 0; i < p_sys->i_map; i++ )
458             {
459                 hotkey_mapping_t *p_map = &p_sys->p_map[i];
460
461 #ifdef XCB_KEYSYM_OLD_API /* as seen in Debian Lenny */
462                 if( p_map->i_x11 == e->detail &&
463                     p_map->i_modifier == e->state )
464                 {
465                     var_SetInteger( p_intf->p_libvlc, "key-action",
466                             p_map->i_action );
467                     break;
468                 }
469 #else
470             bool loop_break = false;
471             for( int j = 0; p_map->p_keys[j] != XCB_NO_SYMBOL; j++ )
472                 if( p_map->p_keys[j] == e->detail &&
473                     p_map->i_modifier == e->state )
474                 {
475                     var_SetInteger( p_intf->p_libvlc, "key-action",
476                             p_map->i_action );
477                     loop_break = true;
478                     break;
479                 }
480             if( loop_break )
481                 break;
482 #endif
483             }
484             free( p_event );
485         }
486     }
487
488     return NULL;
489 }
490