]> git.sesse.net Git - vlc/blob - modules/control/globalhotkeys/xcb.c
f19a6d5b93cd0889ecb455f85d52cd6087fcd636
[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
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     xcb_keycode_t i_x11;
60     unsigned      i_modifier;
61     int           i_action;
62 } hotkey_mapping_t;
63
64 struct intf_sys_t
65 {
66     vlc_thread_t thread;
67
68     xcb_connection_t  *p_connection;
69     xcb_window_t      root;
70     xcb_key_symbols_t *p_symbols;
71
72     int              i_map;
73     hotkey_mapping_t *p_map;
74 };
75
76 static void Mapping( intf_thread_t *p_intf );
77 static void Register( intf_thread_t *p_intf );
78 static void Unregister( intf_thread_t *p_intf );
79 static void *Thread( void *p_data );
80
81 /*****************************************************************************
82  * Open:
83  *****************************************************************************/
84 static int Open( vlc_object_t *p_this )
85 {
86     intf_thread_t *p_intf = (intf_thread_t *)p_this;
87     intf_sys_t *p_sys;
88
89     p_intf->p_sys = p_sys = calloc( 1, sizeof(*p_sys) );
90     if( !p_sys )
91         return VLC_ENOMEM;
92
93     char *psz_display = var_CreateGetNonEmptyString( p_intf, "x11-display" );
94
95     int i_screen_default;
96     p_sys->p_connection = xcb_connect( psz_display, &i_screen_default );
97     free( psz_display );
98
99     if( !p_sys->p_connection )
100         goto error;
101
102     /* Get the root windows of the default screen */
103     const xcb_setup_t* xcbsetup = xcb_get_setup( p_sys->p_connection );
104     if( !xcbsetup )
105         goto error;
106     xcb_screen_iterator_t iter = xcb_setup_roots_iterator( xcbsetup );
107     for( int i = 0; i < i_screen_default; i++ )
108     {
109         if( !iter.rem )
110             break;
111         xcb_screen_next( &iter );
112     }
113     if( !iter.rem )
114         goto error;
115     p_sys->root = iter.data->root;
116
117     /* */
118     p_sys->p_symbols = xcb_key_symbols_alloc( p_sys->p_connection ); // FIXME
119     if( !p_sys->p_symbols )
120         goto error;
121
122     Mapping( p_intf );
123     Register( p_intf );
124
125     if( vlc_clone( &p_sys->thread, Thread, p_intf, VLC_THREAD_PRIORITY_LOW ) )
126     {
127         Unregister( p_intf );
128         free( p_sys->p_map );
129         goto error;
130     }
131     return VLC_SUCCESS;
132
133 error:
134     if( p_sys->p_symbols )
135         xcb_key_symbols_free( p_sys->p_symbols );
136     if( p_sys->p_connection )
137         xcb_disconnect( p_sys->p_connection );
138     free( p_sys );
139     return VLC_EGENERIC;
140 }
141
142 /*****************************************************************************
143  * Close:
144  *****************************************************************************/
145 static void Close( vlc_object_t *p_this )
146 {
147     intf_thread_t *p_intf = (intf_thread_t *)p_this;
148     intf_sys_t *p_sys = p_intf->p_sys;
149
150     vlc_cancel( p_sys->thread );
151     vlc_join( p_sys->thread, NULL );
152
153     Unregister( p_intf );
154     free( p_sys->p_map );
155
156     xcb_key_symbols_free( p_sys->p_symbols );
157     xcb_disconnect( p_sys->p_connection );
158     free( p_sys );
159 }
160
161 /*****************************************************************************
162  *
163  *****************************************************************************/
164 static unsigned GetModifier( xcb_connection_t *p_connection, xcb_key_symbols_t *p_symbols, xcb_keysym_t sym )
165 {
166     static const unsigned pi_mask[8] = {
167         XCB_MOD_MASK_SHIFT, XCB_MOD_MASK_LOCK, XCB_MOD_MASK_CONTROL, XCB_MOD_MASK_1,
168         XCB_MOD_MASK_2, XCB_MOD_MASK_3, XCB_MOD_MASK_4, XCB_MOD_MASK_5
169     };
170
171     const xcb_keycode_t key = xcb_key_symbols_get_keycode( p_symbols, sym );
172     if( key == 0 )
173         return 0;
174
175     xcb_get_modifier_mapping_cookie_t r = xcb_get_modifier_mapping( p_connection );
176     xcb_get_modifier_mapping_reply_t *p_map = xcb_get_modifier_mapping_reply( p_connection, r, NULL );
177     if( !p_map )
178         return 0;
179
180     xcb_keycode_t *p_keycode = xcb_get_modifier_mapping_keycodes( p_map );
181     if( !p_keycode )
182         return 0;
183
184     unsigned i_mask = 0;
185     for( int i = 0; i < 8; i++ )
186     {
187         for( int j = 0; j < p_map->keycodes_per_modifier; j++ )
188         {
189             if( p_keycode[i * p_map->keycodes_per_modifier + j] == key )
190                 i_mask = pi_mask[i];
191         }
192     }
193
194     free( p_map ); // FIXME to check
195     return i_mask;
196 }
197 static unsigned GetX11Modifier( xcb_connection_t *p_connection, xcb_key_symbols_t *p_symbols, unsigned i_vlc )
198 {
199     unsigned i_mask = 0;
200
201     if( i_vlc & KEY_MODIFIER_ALT )
202         i_mask |= GetModifier( p_connection, p_symbols, XK_Alt_L ) |
203                   GetModifier( p_connection, p_symbols, XK_Alt_R );
204     if( i_vlc & KEY_MODIFIER_CTRL )
205         i_mask |= GetModifier( p_connection, p_symbols, XK_Control_L ) |
206                   GetModifier( p_connection, p_symbols, XK_Control_R );
207     if( i_vlc & KEY_MODIFIER_SHIFT )
208         i_mask |= GetModifier( p_connection, p_symbols, XK_Shift_L ) |
209                   GetModifier( p_connection, p_symbols, XK_Shift_R );
210     return i_mask;
211 }
212
213 /* FIXME this table is also used by the vout */
214 static const struct
215 {
216     xcb_keysym_t i_x11;
217     unsigned     i_vlc;
218
219 } x11keys_to_vlckeys[] =
220 {
221     { XK_F1, KEY_F1 }, { XK_F2, KEY_F2 }, { XK_F3, KEY_F3 }, { XK_F4, KEY_F4 },
222     { XK_F5, KEY_F5 }, { XK_F6, KEY_F6 }, { XK_F7, KEY_F7 }, { XK_F8, KEY_F8 },
223     { XK_F9, KEY_F9 }, { XK_F10, KEY_F10 }, { XK_F11, KEY_F11 },
224     { XK_F12, KEY_F12 },
225
226     { XK_Return, KEY_ENTER },
227     { XK_KP_Enter, KEY_ENTER },
228     { XK_space, KEY_SPACE },
229     { XK_Escape, KEY_ESC },
230
231     { XK_Menu, KEY_MENU },
232     { XK_Left, KEY_LEFT },
233     { XK_Right, KEY_RIGHT },
234     { XK_Up, KEY_UP },
235     { XK_Down, KEY_DOWN },
236
237     { XK_Home, KEY_HOME },
238     { XK_End, KEY_END },
239     { XK_Page_Up, KEY_PAGEUP },
240     { XK_Page_Down, KEY_PAGEDOWN },
241
242     { XK_Insert, KEY_INSERT },
243     { XK_Delete, KEY_DELETE },
244     { XF86XK_AudioNext, KEY_MEDIA_NEXT_TRACK},
245     { XF86XK_AudioPrev, KEY_MEDIA_PREV_TRACK},
246     { XF86XK_AudioMute, KEY_VOLUME_MUTE },
247     { XF86XK_AudioLowerVolume, KEY_VOLUME_DOWN },
248     { XF86XK_AudioRaiseVolume, KEY_VOLUME_UP },
249     { XF86XK_AudioPlay, KEY_MEDIA_PLAY_PAUSE },
250     { XF86XK_AudioPause, KEY_MEDIA_PLAY_PAUSE },
251
252     { 0, 0 }
253 };
254 static xcb_keysym_t GetX11Key( unsigned i_vlc )
255 {
256     for( int i = 0; x11keys_to_vlckeys[i].i_vlc != 0; i++ )
257     {
258         if( x11keys_to_vlckeys[i].i_vlc == i_vlc )
259             return x11keys_to_vlckeys[i].i_x11;
260     }
261
262     /* Copied from xcb, it seems that xcb use ascii code for ascii characters */
263     if( isascii( i_vlc ) )
264         return i_vlc;
265
266     return XK_VoidSymbol;
267 }
268
269 static void Mapping( intf_thread_t *p_intf )
270 {
271     static const xcb_keysym_t p_x11_modifier_ignored[] = {
272         0,
273         XK_Num_Lock,
274         XK_Scroll_Lock,
275         XK_Caps_Lock,
276     };
277
278     intf_sys_t *p_sys = p_intf->p_sys;
279
280     p_sys->i_map = 0;
281     p_sys->p_map = NULL;
282
283     /* Registering of Hotkeys */
284     for( struct hotkey *p_hotkey = p_intf->p_libvlc->p_hotkeys;
285             p_hotkey->psz_action != NULL;
286             p_hotkey++ )
287     {
288         char *psz_hotkey;
289         if( asprintf( &psz_hotkey, "global-%s", p_hotkey->psz_action ) < 0 )
290             break;
291
292         const int i_vlc_action = p_hotkey->i_action;
293         const int i_vlc_key = config_GetInt( p_intf, psz_hotkey );
294
295         free( psz_hotkey );
296
297         if( !i_vlc_key )
298             continue;
299
300         const xcb_keycode_t key = xcb_key_symbols_get_keycode( p_sys->p_symbols, GetX11Key( i_vlc_key & ~KEY_MODIFIER ) );
301         const unsigned i_modifier = GetX11Modifier( p_sys->p_connection, p_sys->p_symbols, i_vlc_key & KEY_MODIFIER );
302
303         for( int j = 0; j < sizeof(p_x11_modifier_ignored)/sizeof(*p_x11_modifier_ignored); j++ )
304         {
305             const unsigned i_ignored = GetModifier( p_sys->p_connection, p_sys->p_symbols, p_x11_modifier_ignored[j] );
306             if( j != 0 && i_ignored == 0x00)
307                 continue;
308
309             hotkey_mapping_t *p_map_old = p_sys->p_map;
310             p_sys->p_map = realloc( p_sys->p_map, sizeof(*p_sys->p_map) * (p_sys->i_map+1) );
311             if( !p_sys->p_map )
312             {
313                 p_sys->p_map = p_map_old;
314                 break;
315             }
316             hotkey_mapping_t *p_map = &p_sys->p_map[p_sys->i_map++];
317
318             p_map->i_x11 = key;
319             p_map->i_modifier = i_modifier|i_ignored;
320             p_map->i_action = i_vlc_action;
321         }
322     }
323 }
324
325 static void Register( intf_thread_t *p_intf )
326 {
327     intf_sys_t *p_sys = p_intf->p_sys;
328
329     for( int i = 0; i < p_sys->i_map; i++ )
330     {
331         const hotkey_mapping_t *p_map = &p_sys->p_map[i];
332         xcb_grab_key( p_sys->p_connection, true, p_sys->root,
333                       p_map->i_modifier, p_map->i_x11,
334                       XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC );
335     }
336 }
337 static void Unregister( intf_thread_t *p_intf )
338 {
339     intf_sys_t *p_sys = p_intf->p_sys;
340
341     for( int i = 0; i < p_sys->i_map; i++ )
342     {
343         const hotkey_mapping_t *p_map = &p_sys->p_map[i];
344         xcb_ungrab_key( p_sys->p_connection, p_map->i_x11, p_sys->root, p_map->i_modifier );
345     }
346 }
347
348 static void *Thread( void *p_data )
349 {
350     intf_thread_t *p_intf = p_data;
351     intf_sys_t *p_sys = p_intf->p_sys;
352     xcb_connection_t *p_connection = p_sys->p_connection;
353
354     int canc = vlc_savecancel();
355
356     /* */
357     xcb_flush( p_connection );
358
359     /* */
360     int fd = xcb_get_file_descriptor( p_connection );
361     for( ;; )
362     {
363         /* Wait for x11 event */
364         vlc_restorecancel( canc );
365         struct pollfd fds = { .fd = fd, .events = POLLIN, };
366         if( poll( &fds, 1, -1 ) < 0 )
367         {
368             if( errno != EINTR )
369                 break;
370             canc = vlc_savecancel();
371             continue;
372         }
373         canc = vlc_savecancel();
374
375         xcb_generic_event_t *p_event;
376         while( ( p_event = xcb_poll_for_event( p_connection ) ) )
377         {
378             if( ( p_event->response_type & 0x7f ) != XCB_KEY_PRESS )
379             {
380                 free( p_event );
381                 continue;
382             }
383
384             xcb_key_press_event_t *e = (xcb_key_press_event_t *)p_event;
385
386             for( int i = 0; i < p_sys->i_map; i++ )
387             {
388                 hotkey_mapping_t *p_map = &p_sys->p_map[i];
389
390                 if( p_map->i_x11 == e->detail &&
391                     p_map->i_modifier == e->state )
392                 {
393                     var_SetInteger( p_intf->p_libvlc, "key-action", p_map->i_action );
394                     break;
395                 }
396             }
397             free( p_event );
398         }
399     }
400
401     return NULL;
402 }
403