]> git.sesse.net Git - vlc/blob - modules/control/globalhotkeys/xcb.c
c6a43de7baf8ee0a305beb2052163a92b57c9b73
[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 <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     add_shortcut( "globalhotkeys" )
56 vlc_module_end()
57
58 typedef struct
59 {
60     xcb_keycode_t *p_keys;
61     unsigned      i_modifier;
62     uint32_t      i_vlc;
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 bool Mapping( intf_thread_t *p_intf );
78 static void Register( 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     int ret = VLC_EGENERIC;
89
90     p_intf->p_sys = p_sys = calloc( 1, sizeof(*p_sys) );
91     if( !p_sys )
92         return VLC_ENOMEM;
93
94     int i_screen_default;
95     p_sys->p_connection = xcb_connect( NULL, &i_screen_default );
96
97     if( xcb_connection_has_error( p_sys->p_connection ) )
98         goto error;
99
100     /* Get the root windows of the default screen */
101     const xcb_setup_t* xcbsetup = xcb_get_setup( p_sys->p_connection );
102     if( !xcbsetup )
103         goto error;
104     xcb_screen_iterator_t iter = xcb_setup_roots_iterator( xcbsetup );
105     for( int i = 0; i < i_screen_default; i++ )
106     {
107         if( !iter.rem )
108             break;
109         xcb_screen_next( &iter );
110     }
111     if( !iter.rem )
112         goto error;
113     p_sys->root = iter.data->root;
114
115     /* */
116     p_sys->p_symbols = xcb_key_symbols_alloc( p_sys->p_connection ); // FIXME
117     if( !p_sys->p_symbols )
118         goto error;
119
120     if( !Mapping( p_intf ) )
121     {
122         ret = VLC_SUCCESS;
123         p_intf->p_sys = NULL; /* for Close() */
124         goto error;
125     }
126     Register( p_intf );
127
128     if( vlc_clone( &p_sys->thread, Thread, p_intf, VLC_THREAD_PRIORITY_LOW ) )
129     {
130         if( p_sys->p_map )
131         {
132             free( p_sys->p_map->p_keys );
133             free( p_sys->p_map );
134         }
135         goto error;
136     }
137     return VLC_SUCCESS;
138
139 error:
140     if( p_sys->p_symbols )
141         xcb_key_symbols_free( p_sys->p_symbols );
142     xcb_disconnect( p_sys->p_connection );
143     free( p_sys );
144     return ret;
145 }
146
147 /*****************************************************************************
148  * Close:
149  *****************************************************************************/
150 static void Close( vlc_object_t *p_this )
151 {
152     intf_thread_t *p_intf = (intf_thread_t *)p_this;
153     intf_sys_t *p_sys = p_intf->p_sys;
154
155     if( !p_sys )
156         return; /* if we were running disabled */
157
158     vlc_cancel( p_sys->thread );
159     vlc_join( p_sys->thread, NULL );
160
161     if( p_sys->p_map )
162     {
163         free( p_sys->p_map->p_keys );
164         free( p_sys->p_map );
165     }
166     xcb_key_symbols_free( p_sys->p_symbols );
167     xcb_disconnect( p_sys->p_connection );
168     free( p_sys );
169 }
170
171 /*****************************************************************************
172  *
173  *****************************************************************************/
174 static unsigned GetModifier( xcb_connection_t *p_connection, xcb_key_symbols_t *p_symbols, xcb_keysym_t sym )
175 {
176     static const unsigned pi_mask[8] = {
177         XCB_MOD_MASK_SHIFT, XCB_MOD_MASK_LOCK, XCB_MOD_MASK_CONTROL,
178         XCB_MOD_MASK_1, XCB_MOD_MASK_2, XCB_MOD_MASK_3,
179         XCB_MOD_MASK_4, XCB_MOD_MASK_5
180     };
181
182     if( sym == 0 )
183         return 0; /* no modifier */
184
185     xcb_get_modifier_mapping_cookie_t r =
186             xcb_get_modifier_mapping( p_connection );
187     xcb_get_modifier_mapping_reply_t *p_map =
188             xcb_get_modifier_mapping_reply( p_connection, r, NULL );
189     if( !p_map )
190         return 0;
191
192     xcb_keycode_t *p_keys = xcb_key_symbols_get_keycode( p_symbols, sym );
193     if( !p_keys )
194         goto end;
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         goto end;
210
211     xcb_keycode_t *p_keycode = xcb_get_modifier_mapping_keycodes( p_map );
212     if( !p_keycode )
213         goto end;
214
215     for( int i = 0; i < 8; i++ )
216         for( int j = 0; j < p_map->keycodes_per_modifier; j++ )
217             for( int k = 0; p_keys[k] != XCB_NO_SYMBOL; k++ )
218                 if( p_keycode[i*p_map->keycodes_per_modifier + j] == p_keys[k])
219                 {
220                     free( p_keys );
221                     free( p_map );
222                     return pi_mask[i];
223                 }
224
225 end:
226     free( p_keys );
227     free( p_map ); // FIXME to check
228     return 0;
229 }
230
231
232 static unsigned GetX11Modifier( xcb_connection_t *p_connection,
233         xcb_key_symbols_t *p_symbols, unsigned i_vlc )
234 {
235     unsigned i_mask = 0;
236
237     if( i_vlc & KEY_MODIFIER_ALT )
238         i_mask |= GetModifier( p_connection, p_symbols, XK_Alt_L ) |
239                   GetModifier( p_connection, p_symbols, XK_Alt_R );
240     if( i_vlc & KEY_MODIFIER_SHIFT )
241         i_mask |= GetModifier( p_connection, p_symbols, XK_Shift_L ) |
242                   GetModifier( p_connection, p_symbols, XK_Shift_R );
243     if( i_vlc & KEY_MODIFIER_CTRL )
244         i_mask |= GetModifier( p_connection, p_symbols, XK_Control_L ) |
245                   GetModifier( p_connection, p_symbols, XK_Control_R );
246     if( i_vlc & KEY_MODIFIER_META )
247         i_mask |= GetModifier( p_connection, p_symbols, XK_Meta_L ) |
248                   GetModifier( p_connection, p_symbols, XK_Meta_R ) |
249                   GetModifier( p_connection, p_symbols, XK_Super_L ) |
250                   GetModifier( p_connection, p_symbols, XK_Super_R );
251     return i_mask;
252 }
253
254 /* FIXME this table is also used by the vout */
255 static const struct
256 {
257     xcb_keysym_t i_x11;
258     unsigned     i_vlc;
259
260 } x11keys_to_vlckeys[] =
261 {
262 #include "../../video_output/xcb/xcb_keysym.h"
263     { 0, 0 }
264 };
265 static xcb_keysym_t GetX11Key( unsigned i_vlc )
266 {
267     /* X11 and VLC use ASCII for printable ASCII characters */
268     if( i_vlc >= 32 && i_vlc <= 127 )
269         return i_vlc;
270
271     for( int i = 0; x11keys_to_vlckeys[i].i_vlc != 0; i++ )
272     {
273         if( x11keys_to_vlckeys[i].i_vlc == i_vlc )
274             return x11keys_to_vlckeys[i].i_x11;
275     }
276
277     return XK_VoidSymbol;
278 }
279
280 static bool Mapping( intf_thread_t *p_intf )
281 {
282     static const xcb_keysym_t p_x11_modifier_ignored[] = {
283         0,
284         XK_Num_Lock,
285         XK_Scroll_Lock,
286         XK_Caps_Lock,
287     };
288
289     intf_sys_t *p_sys = p_intf->p_sys;
290     bool active = false;
291
292     p_sys->i_map = 0;
293     p_sys->p_map = NULL;
294
295     /* Registering of Hotkeys */
296     for( const struct hotkey *p_hotkey = p_intf->p_libvlc->p_hotkeys;
297             p_hotkey->psz_action != NULL;
298             p_hotkey++ )
299     {
300         char varname[12 + strlen( p_hotkey->psz_action )];
301         sprintf( varname, "global-key-%s", p_hotkey->psz_action );
302
303         char *key = var_InheritString( p_intf, varname );
304         if( key == NULL )
305             continue;
306
307         uint_fast32_t i_vlc_key = vlc_str2keycode( key );
308         free( key );
309         if( i_vlc_key == KEY_UNSET )
310             continue;
311
312         xcb_keycode_t *p_keys = xcb_key_symbols_get_keycode(
313                 p_sys->p_symbols, GetX11Key( i_vlc_key & ~KEY_MODIFIER ) );
314         if( !p_keys )
315             continue;
316
317         const unsigned i_modifier = GetX11Modifier( p_sys->p_connection,
318                 p_sys->p_symbols, i_vlc_key & KEY_MODIFIER );
319
320         const size_t max = sizeof(p_x11_modifier_ignored) /
321                 sizeof(*p_x11_modifier_ignored);
322         for( unsigned int i = 0; i < max; i++ )
323         {
324             const unsigned i_ignored = GetModifier( p_sys->p_connection,
325                     p_sys->p_symbols, p_x11_modifier_ignored[i] );
326             if( i != 0 && i_ignored == 0)
327                 continue;
328
329             hotkey_mapping_t *p_map_old = p_sys->p_map;
330             p_sys->p_map = realloc( p_sys->p_map,
331                     sizeof(*p_sys->p_map) * (p_sys->i_map+1) );
332             if( !p_sys->p_map )
333             {
334                 p_sys->p_map = p_map_old;
335                 break;
336             }
337             hotkey_mapping_t *p_map = &p_sys->p_map[p_sys->i_map++];
338
339             p_map->p_keys = p_keys;
340             p_map->i_modifier = i_modifier|i_ignored;
341             p_map->i_vlc = i_vlc_key;
342             active = true;
343         }
344     }
345     return active;
346 }
347
348 static void Register( intf_thread_t *p_intf )
349 {
350     intf_sys_t *p_sys = p_intf->p_sys;
351
352     for( int i = 0; i < p_sys->i_map; i++ )
353     {
354         const hotkey_mapping_t *p_map = &p_sys->p_map[i];
355         for( int j = 0; p_map->p_keys[j] != XCB_NO_SYMBOL; j++ )
356         {
357             xcb_grab_key( p_sys->p_connection, true, p_sys->root,
358                           p_map->i_modifier, p_map->p_keys[j],
359                           XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC );
360         }
361     }
362 }
363
364 static void *Thread( void *p_data )
365 {
366     intf_thread_t *p_intf = p_data;
367     intf_sys_t *p_sys = p_intf->p_sys;
368     xcb_connection_t *p_connection = p_sys->p_connection;
369
370     int canc = vlc_savecancel();
371
372     /* */
373     xcb_flush( p_connection );
374
375     /* */
376     int fd = xcb_get_file_descriptor( p_connection );
377     for( ;; )
378     {
379         /* Wait for x11 event */
380         vlc_restorecancel( canc );
381         struct pollfd fds = { .fd = fd, .events = POLLIN, };
382         if( poll( &fds, 1, -1 ) < 0 )
383         {
384             if( errno != EINTR )
385                 break;
386             canc = vlc_savecancel();
387             continue;
388         }
389         canc = vlc_savecancel();
390
391         xcb_generic_event_t *p_event;
392         while( ( p_event = xcb_poll_for_event( p_connection ) ) )
393         {
394             if( ( p_event->response_type & 0x7f ) != XCB_KEY_PRESS )
395             {
396                 free( p_event );
397                 continue;
398             }
399
400             xcb_key_press_event_t *e = (xcb_key_press_event_t *)p_event;
401
402             for( int i = 0; i < p_sys->i_map; i++ )
403             {
404                 hotkey_mapping_t *p_map = &p_sys->p_map[i];
405
406                 for( int j = 0; p_map->p_keys[j] != XCB_NO_SYMBOL; j++ )
407                     if( p_map->p_keys[j] == e->detail &&
408                         p_map->i_modifier == e->state )
409                     {
410                         var_SetInteger( p_intf->p_libvlc, "global-key-pressed",
411                                         p_map->i_vlc );
412                         goto done;
413                     }
414             }
415         done:
416             free( p_event );
417         }
418     }
419
420     return NULL;
421 }
422