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 #define VLC_MODULE_LICENSE VLC_LICENSE_GPL_2_PLUS
27 #include <vlc_common.h>
28 #include <vlc_plugin.h>
29 #include <vlc_interface.h>
30 #include <vlc_actions.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     add_shortcut( "globalhotkeys" )
57 vlc_module_end()
58 
59 typedef struct
60 {
61     xcb_keycode_t *p_keys;
62     unsigned      i_modifier;
63     uint32_t      i_vlc;
64 } hotkey_mapping_t;
65 
66 struct intf_sys_t
67 {
68     vlc_thread_t thread;
69 
70     xcb_connection_t  *p_connection;
71     xcb_window_t      root;
72     xcb_key_symbols_t *p_symbols;
73 
74     int              i_map;
75     hotkey_mapping_t *p_map;
76 };
77 
78 static bool Mapping( intf_thread_t *p_intf );
79 static void Register( intf_thread_t *p_intf );
80 static void *Thread( void *p_data );
81 
82 /*****************************************************************************
83  * Open:
84  *****************************************************************************/
Open(vlc_object_t * p_this)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     int ret = VLC_EGENERIC;
90 
91     p_intf->p_sys = p_sys = calloc( 1, sizeof(*p_sys) );
92     if( !p_sys )
93         return VLC_ENOMEM;
94 
95     int i_screen_default;
96     p_sys->p_connection = xcb_connect( NULL, &i_screen_default );
97 
98     if( xcb_connection_has_error( p_sys->p_connection ) )
99         goto error;
100 
101     /* Get the root windows of the default screen */
102     const xcb_setup_t* xcbsetup = xcb_get_setup( p_sys->p_connection );
103     if( !xcbsetup )
104         goto error;
105     xcb_screen_iterator_t iter = xcb_setup_roots_iterator( xcbsetup );
106     for( int i = 0; i < i_screen_default; i++ )
107     {
108         if( !iter.rem )
109             break;
110         xcb_screen_next( &iter );
111     }
112     if( !iter.rem )
113         goto error;
114     p_sys->root = iter.data->root;
115 
116     /* */
117     p_sys->p_symbols = xcb_key_symbols_alloc( p_sys->p_connection ); // FIXME
118     if( !p_sys->p_symbols )
119         goto error;
120 
121     if( !Mapping( p_intf ) )
122     {
123         ret = VLC_SUCCESS;
124         p_intf->p_sys = NULL; /* for Close() */
125         goto error;
126     }
127     Register( p_intf );
128 
129     if( vlc_clone( &p_sys->thread, Thread, p_intf, VLC_THREAD_PRIORITY_LOW ) )
130     {
131         if( p_sys->p_map )
132         {
133             free( p_sys->p_map->p_keys );
134             free( p_sys->p_map );
135         }
136         goto error;
137     }
138     return VLC_SUCCESS;
139 
140 error:
141     if( p_sys->p_symbols )
142         xcb_key_symbols_free( p_sys->p_symbols );
143     xcb_disconnect( p_sys->p_connection );
144     free( p_sys );
145     return ret;
146 }
147 
148 /*****************************************************************************
149  * Close:
150  *****************************************************************************/
Close(vlc_object_t * p_this)151 static void Close( vlc_object_t *p_this )
152 {
153     intf_thread_t *p_intf = (intf_thread_t *)p_this;
154     intf_sys_t *p_sys = p_intf->p_sys;
155 
156     if( !p_sys )
157         return; /* if we were running disabled */
158 
159     vlc_cancel( p_sys->thread );
160     vlc_join( p_sys->thread, NULL );
161 
162     for( int i = 0; i < p_sys->i_map; i++ )
163         free( p_sys->p_map[i].p_keys );
164     free( p_sys->p_map );
165     xcb_key_symbols_free( p_sys->p_symbols );
166     xcb_disconnect( p_sys->p_connection );
167     free( p_sys );
168 }
169 
170 /*****************************************************************************
171  *
172  *****************************************************************************/
GetModifier(xcb_connection_t * p_connection,xcb_key_symbols_t * p_symbols,xcb_keysym_t sym)173 static unsigned GetModifier( xcb_connection_t *p_connection, xcb_key_symbols_t *p_symbols, xcb_keysym_t sym )
174 {
175     static const unsigned pi_mask[8] = {
176         XCB_MOD_MASK_SHIFT, XCB_MOD_MASK_LOCK, XCB_MOD_MASK_CONTROL,
177         XCB_MOD_MASK_1, XCB_MOD_MASK_2, XCB_MOD_MASK_3,
178         XCB_MOD_MASK_4, XCB_MOD_MASK_5
179     };
180 
181     if( sym == 0 )
182         return 0; /* no modifier */
183 
184     xcb_get_modifier_mapping_cookie_t r =
185             xcb_get_modifier_mapping( p_connection );
186     xcb_get_modifier_mapping_reply_t *p_map =
187             xcb_get_modifier_mapping_reply( p_connection, r, NULL );
188     if( !p_map )
189         return 0;
190 
191     xcb_keycode_t *p_keys = xcb_key_symbols_get_keycode( p_symbols, sym );
192     if( !p_keys )
193         goto end;
194 
195     bool no_modifier = true;
196     for( int i = 0; p_keys[i] != XCB_NO_SYMBOL; i++ )
197     {
198         if( p_keys[i] != 0 )
199         {
200             no_modifier = false;
201             break;
202         }
203     }
204 
205     if( no_modifier )
206         goto end;
207 
208     xcb_keycode_t *p_keycode = xcb_get_modifier_mapping_keycodes( p_map );
209     if( !p_keycode )
210         goto end;
211 
212     for( int i = 0; i < 8; i++ )
213         for( int j = 0; j < p_map->keycodes_per_modifier; j++ )
214             for( int k = 0; p_keys[k] != XCB_NO_SYMBOL; k++ )
215                 if( p_keycode[i*p_map->keycodes_per_modifier + j] == p_keys[k])
216                 {
217                     free( p_keys );
218                     free( p_map );
219                     return pi_mask[i];
220                 }
221 
222 end:
223     free( p_keys );
224     free( p_map ); // FIXME to check
225     return 0;
226 }
227 
228 
GetX11Modifier(xcb_connection_t * p_connection,xcb_key_symbols_t * p_symbols,unsigned i_vlc)229 static unsigned GetX11Modifier( xcb_connection_t *p_connection,
230         xcb_key_symbols_t *p_symbols, unsigned i_vlc )
231 {
232     unsigned i_mask = 0;
233 
234     if( i_vlc & KEY_MODIFIER_ALT )
235         i_mask |= GetModifier( p_connection, p_symbols, XK_Alt_L ) |
236                   GetModifier( p_connection, p_symbols, XK_Alt_R );
237     if( i_vlc & KEY_MODIFIER_SHIFT )
238         i_mask |= GetModifier( p_connection, p_symbols, XK_Shift_L ) |
239                   GetModifier( p_connection, p_symbols, XK_Shift_R );
240     if( i_vlc & KEY_MODIFIER_CTRL )
241         i_mask |= GetModifier( p_connection, p_symbols, XK_Control_L ) |
242                   GetModifier( p_connection, p_symbols, XK_Control_R );
243     if( i_vlc & KEY_MODIFIER_META )
244         i_mask |= GetModifier( p_connection, p_symbols, XK_Meta_L ) |
245                   GetModifier( p_connection, p_symbols, XK_Meta_R ) |
246                   GetModifier( p_connection, p_symbols, XK_Super_L ) |
247                   GetModifier( p_connection, p_symbols, XK_Super_R );
248     return i_mask;
249 }
250 
251 /* FIXME this table is also used by the vout */
252 static const struct
253 {
254     xcb_keysym_t i_x11;
255     unsigned     i_vlc;
256 
257 } x11keys_to_vlckeys[] =
258 {
259 #include "../../video_output/xcb/xcb_keysym.h"
260     { 0, 0 }
261 };
GetX11Key(unsigned i_vlc)262 static xcb_keysym_t GetX11Key( unsigned i_vlc )
263 {
264     /* X11 and VLC use ASCII for printable ASCII characters */
265     if( i_vlc >= 32 && i_vlc <= 127 )
266         return i_vlc;
267 
268     for( int i = 0; x11keys_to_vlckeys[i].i_vlc != 0; i++ )
269     {
270         if( x11keys_to_vlckeys[i].i_vlc == i_vlc )
271             return x11keys_to_vlckeys[i].i_x11;
272     }
273 
274     return XK_VoidSymbol;
275 }
276 
Mapping(intf_thread_t * p_intf)277 static bool Mapping( intf_thread_t *p_intf )
278 {
279     static const xcb_keysym_t p_x11_modifier_ignored[] = {
280         0,
281         XK_Num_Lock,
282         XK_Scroll_Lock,
283         XK_Caps_Lock,
284     };
285 
286     intf_sys_t *p_sys = p_intf->p_sys;
287     bool active = false;
288 
289     p_sys->i_map = 0;
290     p_sys->p_map = NULL;
291 
292     /* Registering of Hotkeys */
293     for( const char* const* ppsz_keys = vlc_actions_get_key_names( p_intf );
294          *ppsz_keys != NULL; ppsz_keys++ )
295     {
296         uint_fast32_t *p_keys;
297         size_t i_nb_keys = vlc_actions_get_keycodes( p_intf, *ppsz_keys, true,
298                                                      &p_keys );
299 
300         for( size_t i = 0; i < i_nb_keys; ++i )
301         {
302             uint_fast32_t i_vlc_key = p_keys[i];
303             const unsigned i_modifier = GetX11Modifier( p_sys->p_connection,
304                     p_sys->p_symbols, i_vlc_key & KEY_MODIFIER );
305 
306             const size_t max = sizeof(p_x11_modifier_ignored) /
307                     sizeof(*p_x11_modifier_ignored);
308             for( unsigned int j = 0; j < max; j++ )
309             {
310                 const unsigned i_ignored = GetModifier( p_sys->p_connection,
311                         p_sys->p_symbols, p_x11_modifier_ignored[j] );
312                 if( j != 0 && i_ignored == 0)
313                     continue;
314 
315                 xcb_keycode_t *keycodes = xcb_key_symbols_get_keycode(
316                     p_sys->p_symbols, GetX11Key( i_vlc_key & ~KEY_MODIFIER ) );
317 
318                 if( keycodes == NULL )
319                     break;
320 
321                 hotkey_mapping_t *p_map = realloc( p_sys->p_map,
322                                   sizeof(*p_sys->p_map) * (p_sys->i_map+1) );
323                 if( !p_map )
324                 {
325                     free( keycodes );
326                     break;
327                 }
328                 p_sys->p_map = p_map;
329                 p_map += p_sys->i_map;
330                 p_sys->i_map++;
331 
332                 p_map->p_keys = keycodes;
333                 p_map->i_modifier = i_modifier|i_ignored;
334                 p_map->i_vlc = i_vlc_key;
335                 active = true;
336             }
337         }
338         free( p_keys );
339     }
340     return active;
341 }
342 
Register(intf_thread_t * p_intf)343 static void Register( intf_thread_t *p_intf )
344 {
345     intf_sys_t *p_sys = p_intf->p_sys;
346 
347     for( int i = 0; i < p_sys->i_map; i++ )
348     {
349         const hotkey_mapping_t *p_map = &p_sys->p_map[i];
350         for( int j = 0; p_map->p_keys[j] != XCB_NO_SYMBOL; j++ )
351         {
352             xcb_grab_key( p_sys->p_connection, true, p_sys->root,
353                           p_map->i_modifier, p_map->p_keys[j],
354                           XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC );
355         }
356     }
357 }
358 
Thread(void * p_data)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                 for( int j = 0; p_map->p_keys[j] != XCB_NO_SYMBOL; j++ )
402                     if( p_map->p_keys[j] == e->detail &&
403                         p_map->i_modifier == e->state )
404                     {
405                         var_SetInteger( p_intf->obj.libvlc,
406                                         "global-key-pressed", p_map->i_vlc );
407                         goto done;
408                     }
409             }
410         done:
411             free( p_event );
412         }
413     }
414 
415     return NULL;
416 }
417 
418