xcb.c 12 KB
Newer Older
1
/*****************************************************************************
2
 * xcb.c: Global-Hotkey X11 using xcb handling for vlc
3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
 *****************************************************************************
 * Copyright (C) 2009 the VideoLAN team
 *
 * Authors: Laurent Aimar <fenrir _AT_ videolan _DOT_ org>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
 *****************************************************************************/

#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include <vlc_common.h>
#include <vlc_plugin.h>
#include <vlc_interface.h>
#include <vlc_keys.h>
30
#include <errno.h>
31 32 33 34 35 36 37

#include <xcb/xcb.h>
#include <xcb/xcb_keysyms.h>
#include <X11/keysym.h>
#include <X11/XF86keysym.h>

#include <poll.h>
38 39 40 41 42 43 44 45 46 47

/*****************************************************************************
 * Local prototypes
 *****************************************************************************/
static int Open( vlc_object_t *p_this );
static void Close( vlc_object_t *p_this );

/*****************************************************************************
 * Module descriptor
 *****************************************************************************/
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
48
vlc_module_begin()
49
    set_shortname( N_("Global Hotkeys") )
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
50 51
    set_category( CAT_INTERFACE )
    set_subcategory( SUBCAT_INTERFACE_HOTKEYS )
52
    set_description( N_("Global Hotkeys interface") )
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
53 54
    set_capability( "interface", 0 )
    set_callbacks( Open, Close )
55
    add_shortcut( "globalhotkeys" )
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
56
vlc_module_end()
57 58 59

typedef struct
{
60
    xcb_keycode_t *p_keys;
61
    unsigned      i_modifier;
62
    uint32_t      i_vlc;
63 64 65 66 67
} hotkey_mapping_t;

struct intf_sys_t
{
    vlc_thread_t thread;
68 69 70 71

    xcb_connection_t  *p_connection;
    xcb_window_t      root;
    xcb_key_symbols_t *p_symbols;
72 73 74 75 76

    int              i_map;
    hotkey_mapping_t *p_map;
};

77
static bool Mapping( intf_thread_t *p_intf );
78 79 80 81 82 83 84 85 86 87
static void Register( intf_thread_t *p_intf );
static void *Thread( void *p_data );

/*****************************************************************************
 * Open:
 *****************************************************************************/
static int Open( vlc_object_t *p_this )
{
    intf_thread_t *p_intf = (intf_thread_t *)p_this;
    intf_sys_t *p_sys;
88
    int ret = VLC_EGENERIC;
89 90 91 92 93 94

    p_intf->p_sys = p_sys = calloc( 1, sizeof(*p_sys) );
    if( !p_sys )
        return VLC_ENOMEM;

    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 99 100
        goto error;

    /* Get the root windows of the default screen */
101 102 103 104
    const xcb_setup_t* xcbsetup = xcb_get_setup( p_sys->p_connection );
    if( !xcbsetup )
        goto error;
    xcb_screen_iterator_t iter = xcb_setup_roots_iterator( xcbsetup );
105
    for( int i = 0; i < i_screen_default; i++ )
106
    {
107 108 109
        if( !iter.rem )
            break;
        xcb_screen_next( &iter );
110
    }
111 112 113 114 115 116 117 118 119
    if( !iter.rem )
        goto error;
    p_sys->root = iter.data->root;

    /* */
    p_sys->p_symbols = xcb_key_symbols_alloc( p_sys->p_connection ); // FIXME
    if( !p_sys->p_symbols )
        goto error;

120 121 122 123 124 125
    if( !Mapping( p_intf ) )
    {
        ret = VLC_SUCCESS;
        p_intf->p_sys = NULL; /* for Close() */
        goto error;
    }
126 127 128 129
    Register( p_intf );

    if( vlc_clone( &p_sys->thread, Thread, p_intf, VLC_THREAD_PRIORITY_LOW ) )
    {
130
        if( p_sys->p_map )
131
        {
132
            free( p_sys->p_map->p_keys );
133 134
            free( p_sys->p_map );
        }
135
        goto error;
136 137
    }
    return VLC_SUCCESS;
138 139 140 141

error:
    if( p_sys->p_symbols )
        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 149 150 151 152 153 154
}

/*****************************************************************************
 * Close:
 *****************************************************************************/
static void Close( vlc_object_t *p_this )
{
    intf_thread_t *p_intf = (intf_thread_t *)p_this;
    intf_sys_t *p_sys = p_intf->p_sys;

155 156 157
    if( !p_sys )
        return; /* if we were running disabled */

158 159 160
    vlc_cancel( p_sys->thread );
    vlc_join( p_sys->thread, NULL );

161
    if( p_sys->p_map )
162
    {
163
        free( p_sys->p_map->p_keys );
164 165
        free( p_sys->p_map );
    }
166 167
    xcb_key_symbols_free( p_sys->p_symbols );
    xcb_disconnect( p_sys->p_connection );
168 169 170 171
    free( p_sys );
}

/*****************************************************************************
Laurent Aimar's avatar
Laurent Aimar committed
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 178 179
        XCB_MOD_MASK_SHIFT, XCB_MOD_MASK_LOCK, XCB_MOD_MASK_CONTROL,
        XCB_MOD_MASK_1, XCB_MOD_MASK_2, XCB_MOD_MASK_3,
        XCB_MOD_MASK_4, XCB_MOD_MASK_5
180 181
    };

182 183 184
    if( sym == 0 )
        return 0; /* no modifier */

Edward Wang's avatar
Edward Wang committed
185 186 187 188 189
    xcb_get_modifier_mapping_cookie_t r =
            xcb_get_modifier_mapping( p_connection );
    xcb_get_modifier_mapping_reply_t *p_map =
            xcb_get_modifier_mapping_reply( p_connection, r, NULL );
    if( !p_map )
190
        return 0;
191

Edward Wang's avatar
Edward Wang committed
192 193 194 195
    xcb_keycode_t *p_keys = xcb_key_symbols_get_keycode( p_symbols, sym );
    if( !p_keys )
        goto end;

196 197 198 199 200 201 202 203 204 205 206 207 208
    int i = 0;
    bool no_modifier = true;
    while( p_keys[i] != XCB_NO_SYMBOL )
    {
        if( p_keys[i] != 0 )
        {
            no_modifier = false;
            break;
        }
        i++;
    }

    if( no_modifier )
Edward Wang's avatar
Edward Wang committed
209
        goto end;
210

211 212
    xcb_keycode_t *p_keycode = xcb_get_modifier_mapping_keycodes( p_map );
    if( !p_keycode )
Edward Wang's avatar
Edward Wang committed
213
        goto end;
214 215 216

    for( int i = 0; i < 8; i++ )
        for( int j = 0; j < p_map->keycodes_per_modifier; j++ )
217 218 219
            for( int k = 0; p_keys[k] != XCB_NO_SYMBOL; k++ )
                if( p_keycode[i*p_map->keycodes_per_modifier + j] == p_keys[k])
                {
Edward Wang's avatar
Edward Wang committed
220
                    free( p_keys );
221 222 223
                    free( p_map );
                    return pi_mask[i];
                }
224

Edward Wang's avatar
Edward Wang committed
225 226
end:
    free( p_keys );
227
    free( p_map ); // FIXME to check
228
    return 0;
229
}
230 231


232 233
static unsigned GetX11Modifier( xcb_connection_t *p_connection,
        xcb_key_symbols_t *p_symbols, unsigned i_vlc )
234 235 236 237
{
    unsigned i_mask = 0;

    if( i_vlc & KEY_MODIFIER_ALT )
238 239
        i_mask |= GetModifier( p_connection, p_symbols, XK_Alt_L ) |
                  GetModifier( p_connection, p_symbols, XK_Alt_R );
240
    if( i_vlc & KEY_MODIFIER_CTRL )
241 242
        i_mask |= GetModifier( p_connection, p_symbols, XK_Control_L ) |
                  GetModifier( p_connection, p_symbols, XK_Control_R );
243
    if( i_vlc & KEY_MODIFIER_SHIFT )
244 245
        i_mask |= GetModifier( p_connection, p_symbols, XK_Shift_L ) |
                  GetModifier( p_connection, p_symbols, XK_Shift_R );
246 247 248 249 250 251
    return i_mask;
}

/* FIXME this table is also used by the vout */
static const struct
{
252 253
    xcb_keysym_t i_x11;
    unsigned     i_vlc;
254 255 256

} x11keys_to_vlckeys[] =
{
257
#include "../../video_output/xcb/xcb_keysym.h"
258 259
    { 0, 0 }
};
260
static xcb_keysym_t GetX11Key( unsigned i_vlc )
261
{
262 263 264 265
    /* X11 and VLC use ASCII for printable ASCII characters */
    if( i_vlc >= 32 && i_vlc <= 127 )
        return i_vlc;

266 267 268 269 270 271
    for( int i = 0; x11keys_to_vlckeys[i].i_vlc != 0; i++ )
    {
        if( x11keys_to_vlckeys[i].i_vlc == i_vlc )
            return x11keys_to_vlckeys[i].i_x11;
    }

272
    return XK_VoidSymbol;
273 274
}

275
static bool Mapping( intf_thread_t *p_intf )
276
{
277
    static const xcb_keysym_t p_x11_modifier_ignored[] = {
278 279 280 281 282 283 284
        0,
        XK_Num_Lock,
        XK_Scroll_Lock,
        XK_Caps_Lock,
    };

    intf_sys_t *p_sys = p_intf->p_sys;
285
    bool active = false;
286 287 288 289 290

    p_sys->i_map = 0;
    p_sys->p_map = NULL;

    /* Registering of Hotkeys */
291
    for( const struct hotkey *p_hotkey = p_intf->p_libvlc->p_hotkeys;
292 293 294
            p_hotkey->psz_action != NULL;
            p_hotkey++ )
    {
295 296
        char varname[12 + strlen( p_hotkey->psz_action )];
        sprintf( varname, "global-key-%s", p_hotkey->psz_action );
297

298 299
        char *key = var_InheritString( p_intf, varname );
        if( key == NULL )
300
            continue;
301 302 303 304 305 306

        uint_fast32_t i_vlc_key = vlc_str2keycode( key );
        free( key );
        if( i_vlc_key == KEY_UNSET )
            continue;

307 308 309 310
        xcb_keycode_t *p_keys = xcb_key_symbols_get_keycode(
                p_sys->p_symbols, GetX11Key( i_vlc_key & ~KEY_MODIFIER ) );
        if( !p_keys )
            continue;
311

312 313
        const unsigned i_modifier = GetX11Modifier( p_sys->p_connection,
                p_sys->p_symbols, i_vlc_key & KEY_MODIFIER );
314

315 316 317
        const size_t max = sizeof(p_x11_modifier_ignored) /
                sizeof(*p_x11_modifier_ignored);
        for( unsigned int i = 0; i < max; i++ )
318
        {
319 320
            const unsigned i_ignored = GetModifier( p_sys->p_connection,
                    p_sys->p_symbols, p_x11_modifier_ignored[i] );
321
            if( i != 0 && i_ignored == 0)
322 323 324
                continue;

            hotkey_mapping_t *p_map_old = p_sys->p_map;
325 326
            p_sys->p_map = realloc( p_sys->p_map,
                    sizeof(*p_sys->p_map) * (p_sys->i_map+1) );
327 328 329 330 331 332 333
            if( !p_sys->p_map )
            {
                p_sys->p_map = p_map_old;
                break;
            }
            hotkey_mapping_t *p_map = &p_sys->p_map[p_sys->i_map++];

334
            p_map->p_keys = p_keys;
335
            p_map->i_modifier = i_modifier|i_ignored;
336
            p_map->i_vlc = i_vlc_key;
337
            active = true;
338 339
        }
    }
340
    return active;
341 342 343 344 345 346 347 348
}

static void Register( intf_thread_t *p_intf )
{
    intf_sys_t *p_sys = p_intf->p_sys;

    for( int i = 0; i < p_sys->i_map; i++ )
    {
349
        const hotkey_mapping_t *p_map = &p_sys->p_map[i];
350 351 352 353 354 355
        for( int j = 0; p_map->p_keys[j] != XCB_NO_SYMBOL; j++ )
        {
            xcb_grab_key( p_sys->p_connection, true, p_sys->root,
                          p_map->i_modifier, p_map->p_keys[j],
                          XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC );
        }
356 357 358 359 360 361 362
    }
}

static void *Thread( void *p_data )
{
    intf_thread_t *p_intf = p_data;
    intf_sys_t *p_sys = p_intf->p_sys;
363
    xcb_connection_t *p_connection = p_sys->p_connection;
364 365 366 367

    int canc = vlc_savecancel();

    /* */
368
    xcb_flush( p_connection );
369 370

    /* */
371
    int fd = xcb_get_file_descriptor( p_connection );
372 373 374 375 376 377 378 379 380
    for( ;; )
    {
        /* Wait for x11 event */
        vlc_restorecancel( canc );
        struct pollfd fds = { .fd = fd, .events = POLLIN, };
        if( poll( &fds, 1, -1 ) < 0 )
        {
            if( errno != EINTR )
                break;
381
            canc = vlc_savecancel();
382 383 384 385
            continue;
        }
        canc = vlc_savecancel();

386 387
        xcb_generic_event_t *p_event;
        while( ( p_event = xcb_poll_for_event( p_connection ) ) )
388
        {
389 390 391
            if( ( p_event->response_type & 0x7f ) != XCB_KEY_PRESS )
            {
                free( p_event );
392
                continue;
393 394 395
            }

            xcb_key_press_event_t *e = (xcb_key_press_event_t *)p_event;
396 397 398 399 400

            for( int i = 0; i < p_sys->i_map; i++ )
            {
                hotkey_mapping_t *p_map = &p_sys->p_map[i];

401 402 403 404 405 406 407 408
                for( int j = 0; p_map->p_keys[j] != XCB_NO_SYMBOL; j++ )
                    if( p_map->p_keys[j] == e->detail &&
                        p_map->i_modifier == e->state )
                    {
                        var_SetInteger( p_intf->p_libvlc, "global-key-pressed",
                                        p_map->i_vlc );
                        goto done;
                    }
409
            }
410
        done:
411
            free( p_event );
412 413 414 415 416 417
        }
    }

    return NULL;
}