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