]> git.sesse.net Git - vlc/blob - modules/control/globalhotkeys/xcb.c
356f233dd90d291f4e8efa40e1a5f5def454ea00
[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 void Mapping( intf_thread_t *p_intf );
82 static void Register( intf_thread_t *p_intf );
83 static void Unregister( intf_thread_t *p_intf );
84 static void *Thread( void *p_data );
85
86 /*****************************************************************************
87  * Open:
88  *****************************************************************************/
89 static int Open( vlc_object_t *p_this )
90 {
91     intf_thread_t *p_intf = (intf_thread_t *)p_this;
92     intf_sys_t *p_sys;
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     Mapping( p_intf );
128     Register( p_intf );
129
130     if( vlc_clone( &p_sys->thread, Thread, p_intf, VLC_THREAD_PRIORITY_LOW ) )
131     {
132         Unregister( p_intf );
133 #ifndef XCB_KEYSYM_OLD_API /* as seen in Debian Lenny */
134         if( p_sys->p_map )
135             free( p_sys->p_map->p_keys );
136 #endif
137         free( p_sys->p_map );
138         goto error;
139     }
140     return VLC_SUCCESS;
141
142 error:
143     if( p_sys->p_symbols )
144         xcb_key_symbols_free( p_sys->p_symbols );
145     xcb_disconnect( p_sys->p_connection );
146     free( p_sys );
147     return VLC_EGENERIC;
148 }
149
150 /*****************************************************************************
151  * Close:
152  *****************************************************************************/
153 static void Close( vlc_object_t *p_this )
154 {
155     intf_thread_t *p_intf = (intf_thread_t *)p_this;
156     intf_sys_t *p_sys = p_intf->p_sys;
157
158     vlc_cancel( p_sys->thread );
159     vlc_join( p_sys->thread, NULL );
160
161     Unregister( p_intf );
162 #ifndef XCB_KEYSYM_OLD_API /* as seen in Debian Lenny */
163     if( p_sys->p_map )
164         free( p_sys->p_map->p_keys );
165 #endif
166     free( p_sys->p_map );
167
168     xcb_key_symbols_free( p_sys->p_symbols );
169     xcb_disconnect( p_sys->p_connection );
170     free( p_sys );
171 }
172
173 /*****************************************************************************
174  *
175  *****************************************************************************/
176 static unsigned GetModifier( xcb_connection_t *p_connection, xcb_key_symbols_t *p_symbols, xcb_keysym_t sym )
177 {
178     static const unsigned pi_mask[8] = {
179         XCB_MOD_MASK_SHIFT, XCB_MOD_MASK_LOCK, XCB_MOD_MASK_CONTROL,
180         XCB_MOD_MASK_1, XCB_MOD_MASK_2, XCB_MOD_MASK_3,
181         XCB_MOD_MASK_4, XCB_MOD_MASK_5
182     };
183
184     if( sym == 0 )
185         return 0; /* no modifier */
186
187 #ifdef XCB_KEYSYM_OLD_API /* as seen in Debian Lenny */
188     const xcb_keycode_t key = xcb_key_symbols_get_keycode( p_symbols, sym );
189     if( key == 0 )
190         return 0;
191 #else
192     const xcb_keycode_t *p_keys = xcb_key_symbols_get_keycode( p_symbols, sym );
193     if( !p_keys )
194         return 0;
195
196     int i = 0;
197     bool no_modifier = true;
198     while( p_keys[i] != XCB_NO_SYMBOL )
199     {
200         if( p_keys[i] != 0 )
201         {
202             no_modifier = false;
203             break;
204         }
205         i++;
206     }
207
208     if( no_modifier )
209         return 0;
210 #endif
211
212     xcb_get_modifier_mapping_cookie_t r =
213             xcb_get_modifier_mapping( p_connection );
214     xcb_get_modifier_mapping_reply_t *p_map =
215             xcb_get_modifier_mapping_reply( p_connection, r, NULL );
216     if( !p_map )
217         return 0;
218
219     xcb_keycode_t *p_keycode = xcb_get_modifier_mapping_keycodes( p_map );
220     if( !p_keycode )
221         return 0;
222
223     for( int i = 0; i < 8; i++ )
224         for( int j = 0; j < p_map->keycodes_per_modifier; j++ )
225 #ifdef XCB_KEYSYM_OLD_API /* as seen in Debian Lenny */
226             if( p_keycode[i * p_map->keycodes_per_modifier + j] == key )
227             {
228                 free( p_map );
229                 return pi_mask[i];
230             }
231 #else
232             for( int k = 0; p_keys[k] != XCB_NO_SYMBOL; k++ )
233                 if( p_keycode[i*p_map->keycodes_per_modifier + j] == p_keys[k])
234                 {
235                     free( p_map );
236                     return pi_mask[i];
237                 }
238 #endif
239
240     free( p_map ); // FIXME to check
241     return 0;
242 }
243
244
245 static unsigned GetX11Modifier( xcb_connection_t *p_connection,
246         xcb_key_symbols_t *p_symbols, unsigned i_vlc )
247 {
248     unsigned i_mask = 0;
249
250     if( i_vlc & KEY_MODIFIER_ALT )
251         i_mask |= GetModifier( p_connection, p_symbols, XK_Alt_L ) |
252                   GetModifier( p_connection, p_symbols, XK_Alt_R );
253     if( i_vlc & KEY_MODIFIER_CTRL )
254         i_mask |= GetModifier( p_connection, p_symbols, XK_Control_L ) |
255                   GetModifier( p_connection, p_symbols, XK_Control_R );
256     if( i_vlc & KEY_MODIFIER_SHIFT )
257         i_mask |= GetModifier( p_connection, p_symbols, XK_Shift_L ) |
258                   GetModifier( p_connection, p_symbols, XK_Shift_R );
259     return i_mask;
260 }
261
262 /* FIXME this table is also used by the vout */
263 static const struct
264 {
265     xcb_keysym_t i_x11;
266     unsigned     i_vlc;
267
268 } x11keys_to_vlckeys[] =
269 {
270     { XK_F1, KEY_F1 }, { XK_F2, KEY_F2 }, { XK_F3, KEY_F3 }, { XK_F4, KEY_F4 },
271     { XK_F5, KEY_F5 }, { XK_F6, KEY_F6 }, { XK_F7, KEY_F7 }, { XK_F8, KEY_F8 },
272     { XK_F9, KEY_F9 }, { XK_F10, KEY_F10 }, { XK_F11, KEY_F11 },
273     { XK_F12, KEY_F12 },
274
275     { XK_Return, KEY_ENTER },
276     { XK_KP_Enter, KEY_ENTER },
277     { XK_Escape, KEY_ESC },
278
279     { XK_Menu, KEY_MENU },
280     { XK_Left, KEY_LEFT },
281     { XK_Right, KEY_RIGHT },
282     { XK_Up, KEY_UP },
283     { XK_Down, KEY_DOWN },
284
285     { XK_Home, KEY_HOME },
286     { XK_End, KEY_END },
287     { XK_Page_Up, KEY_PAGEUP },
288     { XK_Page_Down, KEY_PAGEDOWN },
289
290     { XK_Insert, KEY_INSERT },
291     { XK_Delete, KEY_DELETE },
292     { XF86XK_AudioNext, KEY_MEDIA_NEXT_TRACK},
293     { XF86XK_AudioPrev, KEY_MEDIA_PREV_TRACK},
294     { XF86XK_AudioMute, KEY_VOLUME_MUTE },
295     { XF86XK_AudioLowerVolume, KEY_VOLUME_DOWN },
296     { XF86XK_AudioRaiseVolume, KEY_VOLUME_UP },
297     { XF86XK_AudioPlay, KEY_MEDIA_PLAY_PAUSE },
298     { XF86XK_AudioPause, KEY_MEDIA_PLAY_PAUSE },
299
300     { 0, 0 }
301 };
302 static xcb_keysym_t GetX11Key( unsigned i_vlc )
303 {
304     /* X11 and VLC use ASCII for printable ASCII characters */
305     if( i_vlc >= 32 && i_vlc <= 127 )
306         return i_vlc;
307
308     for( int i = 0; x11keys_to_vlckeys[i].i_vlc != 0; i++ )
309     {
310         if( x11keys_to_vlckeys[i].i_vlc == i_vlc )
311             return x11keys_to_vlckeys[i].i_x11;
312     }
313
314     return XK_VoidSymbol;
315 }
316
317 static void Mapping( intf_thread_t *p_intf )
318 {
319     static const xcb_keysym_t p_x11_modifier_ignored[] = {
320         0,
321         XK_Num_Lock,
322         XK_Scroll_Lock,
323         XK_Caps_Lock,
324     };
325
326     intf_sys_t *p_sys = p_intf->p_sys;
327
328     p_sys->i_map = 0;
329     p_sys->p_map = NULL;
330
331     /* Registering of Hotkeys */
332     for( const struct hotkey *p_hotkey = p_intf->p_libvlc->p_hotkeys;
333             p_hotkey->psz_action != NULL;
334             p_hotkey++ )
335     {
336         char *psz_hotkey;
337         if( asprintf( &psz_hotkey, "global-%s", p_hotkey->psz_action ) < 0 )
338             break;
339
340         const int i_vlc_action = p_hotkey->i_action;
341         const int i_vlc_key = config_GetInt( p_intf, psz_hotkey );
342         free( psz_hotkey );
343
344         if( !i_vlc_key )
345             continue;
346 #ifdef XCB_KEYSYM_OLD_API /* as seen in Debian Lenny */
347         const xcb_keycode_t key = xcb_key_symbols_get_keycode(
348                 p_sys->p_symbols, GetX11Key( i_vlc_key & ~KEY_MODIFIER ) );
349 #else
350         xcb_keycode_t *p_keys = xcb_key_symbols_get_keycode(
351                 p_sys->p_symbols, GetX11Key( i_vlc_key & ~KEY_MODIFIER ) );
352         if( !p_keys )
353             continue;
354 #endif
355         const unsigned i_modifier = GetX11Modifier( p_sys->p_connection,
356                 p_sys->p_symbols, i_vlc_key & KEY_MODIFIER );
357
358         const size_t max = sizeof(p_x11_modifier_ignored) /
359                 sizeof(*p_x11_modifier_ignored);
360         for( unsigned int i = 0; i < max; i++ )
361         {
362             const unsigned i_ignored = GetModifier( p_sys->p_connection,
363                     p_sys->p_symbols, p_x11_modifier_ignored[i] );
364             if( i != 0 && i_ignored == 0)
365                 continue;
366
367             hotkey_mapping_t *p_map_old = p_sys->p_map;
368             p_sys->p_map = realloc( p_sys->p_map,
369                     sizeof(*p_sys->p_map) * (p_sys->i_map+1) );
370             if( !p_sys->p_map )
371             {
372                 p_sys->p_map = p_map_old;
373                 break;
374             }
375             hotkey_mapping_t *p_map = &p_sys->p_map[p_sys->i_map++];
376
377 #ifdef XCB_KEYSYM_OLD_API /* as seen in Debian Lenny */
378             p_map->i_x11 = key;
379 #else
380             p_map->p_keys = p_keys;
381 #endif
382             p_map->i_modifier = i_modifier|i_ignored;
383             p_map->i_action = i_vlc_action;
384         }
385     }
386 }
387
388 static void Register( intf_thread_t *p_intf )
389 {
390     intf_sys_t *p_sys = p_intf->p_sys;
391
392     for( int i = 0; i < p_sys->i_map; i++ )
393     {
394         const hotkey_mapping_t *p_map = &p_sys->p_map[i];
395 #ifdef XCB_KEYSYM_OLD_API /* as seen in Debian Lenny */
396         xcb_grab_key( p_sys->p_connection, true, p_sys->root,
397                       p_map->i_modifier, p_map->i_x11,
398                       XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC );
399 #else
400         for( int j = 0; p_map->p_keys[j] != XCB_NO_SYMBOL; j++ )
401         {
402             xcb_grab_key( p_sys->p_connection, true, p_sys->root,
403                           p_map->i_modifier, p_map->p_keys[j],
404                           XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC );
405         }
406 #endif
407     }
408 }
409 static void Unregister( intf_thread_t *p_intf )
410 {
411     intf_sys_t *p_sys = p_intf->p_sys;
412
413     for( int i = 0; i < p_sys->i_map; i++ )
414     {
415         const hotkey_mapping_t *p_map = &p_sys->p_map[i];
416 #ifdef XCB_KEYSYM_OLD_API /* as seen in Debian Lenny */
417         xcb_ungrab_key( p_sys->p_connection, p_map->i_x11, p_sys->root,
418                 p_map->i_modifier );
419 #else
420         for( int j = 0; p_map->p_keys[j] != XCB_NO_SYMBOL; j++ )
421             xcb_ungrab_key( p_sys->p_connection, p_map->p_keys[j], p_sys->root,
422                     p_map->i_modifier );
423 #endif
424     }
425 }
426
427 static void *Thread( void *p_data )
428 {
429     intf_thread_t *p_intf = p_data;
430     intf_sys_t *p_sys = p_intf->p_sys;
431     xcb_connection_t *p_connection = p_sys->p_connection;
432
433     int canc = vlc_savecancel();
434
435     /* */
436     xcb_flush( p_connection );
437
438     /* */
439     int fd = xcb_get_file_descriptor( p_connection );
440     for( ;; )
441     {
442         /* Wait for x11 event */
443         vlc_restorecancel( canc );
444         struct pollfd fds = { .fd = fd, .events = POLLIN, };
445         if( poll( &fds, 1, -1 ) < 0 )
446         {
447             if( errno != EINTR )
448                 break;
449             canc = vlc_savecancel();
450             continue;
451         }
452         canc = vlc_savecancel();
453
454         xcb_generic_event_t *p_event;
455         while( ( p_event = xcb_poll_for_event( p_connection ) ) )
456         {
457             if( ( p_event->response_type & 0x7f ) != XCB_KEY_PRESS )
458             {
459                 free( p_event );
460                 continue;
461             }
462
463             xcb_key_press_event_t *e = (xcb_key_press_event_t *)p_event;
464
465             for( int i = 0; i < p_sys->i_map; i++ )
466             {
467                 hotkey_mapping_t *p_map = &p_sys->p_map[i];
468
469 #ifdef XCB_KEYSYM_OLD_API /* as seen in Debian Lenny */
470                 if( p_map->i_x11 == e->detail &&
471                     p_map->i_modifier == e->state )
472                 {
473                     var_SetInteger( p_intf->p_libvlc, "key-action",
474                             p_map->i_action );
475                     break;
476                 }
477 #else
478             bool loop_break = false;
479             for( int j = 0; p_map->p_keys[j] != XCB_NO_SYMBOL; j++ )
480                 if( p_map->p_keys[j] == e->detail &&
481                     p_map->i_modifier == e->state )
482                 {
483                     var_SetInteger( p_intf->p_libvlc, "key-action",
484                             p_map->i_action );
485                     loop_break = true;
486                     break;
487                 }
488             if( loop_break )
489                 break;
490 #endif
491             }
492             free( p_event );
493         }
494     }
495
496     return NULL;
497 }
498