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