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