Use --x11-display whenever calling XOpenDisplay (except Qt4 this far)
[vlc.git] / modules / control / globalhotkeys / x11.c
1 /*****************************************************************************
2  * x11.c: Global-Hotkey X11 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 <X11/Xlib.h>
27 #include <X11/keysym.h>
28 #include <X11/Xutil.h>
29 #include <X11/XF86keysym.h>
30 #include <poll.h>
31
32 #include <vlc_common.h>
33 #include <vlc_plugin.h>
34 #include <vlc_interface.h>
35 #include <vlc_keys.h>
36
37 /*****************************************************************************
38  * Local prototypes
39  *****************************************************************************/
40 static int Open( vlc_object_t *p_this );
41 static void Close( vlc_object_t *p_this );
42
43 /*****************************************************************************
44  * Module descriptor
45  *****************************************************************************/
46 vlc_module_begin()
47     set_shortname( N_("Global Hotkeys") )
48     set_category( CAT_INTERFACE )
49     set_subcategory( SUBCAT_INTERFACE_HOTKEYS )
50     set_description( N_("Global Hotkeys interface") )
51     set_capability( "interface", 0 )
52     set_callbacks( Open, Close )
53 vlc_module_end()
54
55 typedef struct
56 {
57     KeyCode     i_x11;
58     unsigned    i_modifier;
59     int         i_action;
60 } hotkey_mapping_t;
61
62 struct intf_sys_t
63 {
64     vlc_thread_t thread;
65     Display *p_display;
66
67     int              i_map;
68     hotkey_mapping_t *p_map;
69 };
70
71 static void Mapping( intf_thread_t *p_intf );
72 static void Register( intf_thread_t *p_intf );
73 static void Unregister( intf_thread_t *p_intf );
74 static void *Thread( void *p_data );
75 static int X11ErrorHandler( Display *, XErrorEvent * );
76
77 /*****************************************************************************
78  * Open:
79  *****************************************************************************/
80 static int Open( vlc_object_t *p_this )
81 {
82     intf_thread_t *p_intf = (intf_thread_t *)p_this;
83     intf_sys_t *p_sys;
84     char *psz_display = var_CreateGetNonEmptyString( p_intf, "x11-display" );
85
86     Display *p_display = XOpenDisplay( psz_display );
87     free( psz_display );
88     if( !p_display )
89         return VLC_EGENERIC;
90     XSetErrorHandler( X11ErrorHandler );
91
92     p_intf->p_sys = p_sys = malloc( sizeof(*p_sys) );
93     if( !p_sys )
94     {
95         XCloseDisplay( p_display );
96         return VLC_ENOMEM;
97     }
98     p_sys->p_display = p_display;
99     Mapping( p_intf );
100     Register( p_intf );
101
102     if( vlc_clone( &p_sys->thread, Thread, p_intf, VLC_THREAD_PRIORITY_LOW ) )
103     {
104         Unregister( p_intf );
105         XCloseDisplay( p_display );
106         free( p_sys->p_map );
107         free( p_sys );
108         return VLC_ENOMEM;
109     }
110     return VLC_SUCCESS;
111 }
112
113 /*****************************************************************************
114  * Close:
115  *****************************************************************************/
116 static void Close( vlc_object_t *p_this )
117 {
118     intf_thread_t *p_intf = (intf_thread_t *)p_this;
119     intf_sys_t *p_sys = p_intf->p_sys;
120
121     vlc_cancel( p_sys->thread );
122     vlc_join( p_sys->thread, NULL );
123
124     Unregister( p_intf );
125     XCloseDisplay( p_sys->p_display );
126     free( p_sys->p_map );
127
128     free( p_sys );
129 }
130
131 /*****************************************************************************
132  *
133  *****************************************************************************/
134 static int X11ErrorHandler( Display *p_display, XErrorEvent *p_event )
135 {
136 #if 0
137     char psz_txt[1024];
138
139     XGetErrorText( p_display, p_event->error_code, psz_txt, sizeof(psz_txt) );
140     fprintf( stderr,
141              "[????????] globalhotkeys interface error: X11 request %u.%u failed "
142               "with error code %u:\n %s\n",
143              p_event->request_code, p_event->minor_code, p_event->error_code, psz_txt );
144 #else
145     VLC_UNUSED(p_display); VLC_UNUSED(p_event);
146 #endif
147     /* Ignore them */
148     return 0;
149 }
150 static unsigned GetModifier( Display *p_display, KeySym sym )
151 {
152     static const unsigned pi_mask[8] = {
153         ShiftMask, LockMask, ControlMask, Mod1Mask,
154         Mod2Mask, Mod3Mask, Mod4Mask, Mod5Mask
155     };
156
157     const KeyCode key = XKeysymToKeycode( p_display, sym );
158     if( key == 0 )
159         return 0;
160
161     XModifierKeymap *p_map = XGetModifierMapping( p_display );
162     if( !p_map )
163         return 0;
164
165     unsigned i_mask = 0;
166     for( int i = 0; i < 8 * p_map->max_keypermod; i++ )
167     {
168         if( p_map->modifiermap[i] == key )
169         {
170             i_mask = pi_mask[i / p_map->max_keypermod];
171             break;
172         }
173     }
174
175     XFreeModifiermap( p_map );
176     return i_mask;
177 }
178 static unsigned GetX11Modifier( Display *p_display, unsigned i_vlc )
179 {
180     unsigned i_mask = 0;
181
182     if( i_vlc & KEY_MODIFIER_ALT )
183         i_mask |= GetModifier( p_display, XK_Alt_L ) |
184                   GetModifier( p_display, XK_Alt_R );
185     if( i_vlc & KEY_MODIFIER_CTRL )
186         i_mask |= GetModifier( p_display, XK_Control_L ) |
187                   GetModifier( p_display, XK_Control_R );
188     if( i_vlc & KEY_MODIFIER_SHIFT )
189         i_mask |= GetModifier( p_display, XK_Shift_L ) |
190                   GetModifier( p_display, XK_Shift_R );
191     return i_mask;
192 }
193
194 /* FIXME this table is also used by the vout */
195 static const struct
196 {
197     KeySym   i_x11;
198     unsigned i_vlc;
199
200 } x11keys_to_vlckeys[] =
201 {
202     { XK_F1, KEY_F1 }, { XK_F2, KEY_F2 }, { XK_F3, KEY_F3 }, { XK_F4, KEY_F4 },
203     { XK_F5, KEY_F5 }, { XK_F6, KEY_F6 }, { XK_F7, KEY_F7 }, { XK_F8, KEY_F8 },
204     { XK_F9, KEY_F9 }, { XK_F10, KEY_F10 }, { XK_F11, KEY_F11 },
205     { XK_F12, KEY_F12 },
206
207     { XK_Return, KEY_ENTER },
208     { XK_KP_Enter, KEY_ENTER },
209     { XK_space, KEY_SPACE },
210     { XK_Escape, KEY_ESC },
211
212     { XK_Menu, KEY_MENU },
213     { XK_Left, KEY_LEFT },
214     { XK_Right, KEY_RIGHT },
215     { XK_Up, KEY_UP },
216     { XK_Down, KEY_DOWN },
217
218     { XK_Home, KEY_HOME },
219     { XK_End, KEY_END },
220     { XK_Page_Up, KEY_PAGEUP },
221     { XK_Page_Down, KEY_PAGEDOWN },
222
223     { XK_Insert, KEY_INSERT },
224     { XK_Delete, KEY_DELETE },
225     { XF86XK_AudioNext, KEY_MEDIA_NEXT_TRACK},
226     { XF86XK_AudioPrev, KEY_MEDIA_PREV_TRACK},
227     { XF86XK_AudioMute, KEY_VOLUME_MUTE },
228     { XF86XK_AudioLowerVolume, KEY_VOLUME_DOWN },
229     { XF86XK_AudioRaiseVolume, KEY_VOLUME_UP },
230     { XF86XK_AudioPlay, KEY_MEDIA_PLAY_PAUSE },
231     { XF86XK_AudioPause, KEY_MEDIA_PLAY_PAUSE },
232
233     { 0, 0 }
234 };
235 static KeySym GetX11Key( unsigned i_vlc )
236 {
237     for( int i = 0; x11keys_to_vlckeys[i].i_vlc != 0; i++ )
238     {
239         if( x11keys_to_vlckeys[i].i_vlc == i_vlc )
240             return x11keys_to_vlckeys[i].i_x11;
241     }
242
243     char psz_key[2];
244     psz_key[0] = i_vlc;
245     psz_key[1] = '\0';
246     return XStringToKeysym( psz_key );
247 }
248
249 static void Mapping( intf_thread_t *p_intf )
250 {
251     static const KeySym p_x11_modifier_ignored[] = {
252         0,
253         XK_Num_Lock,
254         XK_Scroll_Lock,
255         XK_Caps_Lock,
256     };
257
258     intf_sys_t *p_sys = p_intf->p_sys;
259
260     p_sys->i_map = 0;
261     p_sys->p_map = NULL;
262
263     /* Registering of Hotkeys */
264     for( struct hotkey *p_hotkey = p_intf->p_libvlc->p_hotkeys;
265             p_hotkey->psz_action != NULL;
266             p_hotkey++ )
267     {
268         char *psz_hotkey;
269         if( asprintf( &psz_hotkey, "global-%s", p_hotkey->psz_action ) < 0 )
270             break;
271
272         const int i_vlc_action = p_hotkey->i_action;
273         const int i_vlc_key = config_GetInt( p_intf, psz_hotkey );
274
275         free( psz_hotkey );
276
277         if( !i_vlc_key )
278             continue;
279
280         const KeyCode key = XKeysymToKeycode( p_sys->p_display,
281                                               GetX11Key( i_vlc_key & ~KEY_MODIFIER ) );
282         const unsigned i_modifier = GetX11Modifier( p_sys->p_display, i_vlc_key & KEY_MODIFIER );
283
284         for( int j = 0; j < sizeof(p_x11_modifier_ignored)/sizeof(*p_x11_modifier_ignored); j++ )
285         {
286             const unsigned i_ignored = GetModifier( p_sys->p_display, p_x11_modifier_ignored[j] );
287             if( j != 0 && i_ignored == 0x00)
288                 continue;
289
290             hotkey_mapping_t *p_map_old = p_sys->p_map;
291             p_sys->p_map = realloc( p_sys->p_map, sizeof(*p_sys->p_map) * (p_sys->i_map+1) );
292             if( !p_sys->p_map )
293             {
294                 p_sys->p_map = p_map_old;
295                 break;
296             }
297             hotkey_mapping_t *p_map = &p_sys->p_map[p_sys->i_map++];
298
299             p_map->i_x11 = key;
300             p_map->i_modifier = i_modifier|i_ignored;
301             p_map->i_action = i_vlc_action;
302         }
303     }
304 }
305
306 static void Register( intf_thread_t *p_intf )
307 {
308     intf_sys_t *p_sys = p_intf->p_sys;
309
310     for( int i = 0; i < p_sys->i_map; i++ )
311     {
312         hotkey_mapping_t *p_map = &p_sys->p_map[i];
313         XGrabKey( p_sys->p_display, p_map->i_x11, p_map->i_modifier,
314                   DefaultRootWindow( p_sys->p_display ), True, GrabModeAsync, GrabModeAsync );
315     }
316 }
317 static void Unregister( intf_thread_t *p_intf )
318 {
319     intf_sys_t *p_sys = p_intf->p_sys;
320
321     for( int i = 0; i < p_sys->i_map; i++ )
322     {
323         hotkey_mapping_t *p_map = &p_sys->p_map[i];
324         XUngrabKey( p_sys->p_display, p_map->i_x11, p_map->i_modifier,
325                     DefaultRootWindow( p_sys->p_display ) );
326     }
327 }
328
329 static void *Thread( void *p_data )
330 {
331     intf_thread_t *p_intf = p_data;
332     intf_sys_t *p_sys = p_intf->p_sys;
333     Display *p_display = p_sys->p_display;
334
335     int canc = vlc_savecancel();
336
337     if( !p_display )
338         return NULL;
339
340     /* */
341     XFlush( p_display );
342
343     /* */
344     int fd = ConnectionNumber( p_display );
345     for( ;; )
346     {
347         /* Wait for x11 event */
348         vlc_restorecancel( canc );
349         struct pollfd fds = { .fd = fd, .events = POLLIN, };
350         if( poll( &fds, 1, -1 ) < 0 )
351         {
352             if( errno != EINTR )
353                 break;
354             canc = vlc_savecancel();
355             continue;
356         }
357         canc = vlc_savecancel();
358
359         while( XPending( p_display ) > 0 )
360         {
361             XEvent e;
362
363             XNextEvent( p_display, &e );
364             if( e.type != KeyPress )
365                 continue;
366
367             for( int i = 0; i < p_sys->i_map; i++ )
368             {
369                 hotkey_mapping_t *p_map = &p_sys->p_map[i];
370
371                 if( p_map->i_x11 == e.xkey.keycode &&
372                     p_map->i_modifier == e.xkey.state )
373                 {
374                     var_SetInteger( p_intf->p_libvlc, "key-action", p_map->i_action );
375                     break;
376                 }
377             }
378         }
379     }
380
381     return NULL;
382 }
383