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