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