xcb.c 16 KB
Newer Older
1
2
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
/**
 * @file xcb.c
 * @brief X C Bindings video output module for VLC media player
 */
/*****************************************************************************
 * Copyright © 2009 Rémi Denis-Courmont
 *
 * This library 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.0
 * of the License, or (at your option) any later version.
 *
 * This library 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 Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 ****************************************************************************/

#ifdef HAVE_CONFIG_H
# include <config.h>
#endif

#include <stdlib.h>
#include <assert.h>

30
31
32
#include <sys/types.h>
#include <sys/shm.h>

33
#include <xcb/xcb.h>
34
35
#include <xcb/shm.h>

36
37
38
39
40
41
42
43
#include <xcb/xcb_aux.h>
#include <xcb/xcb_image.h>

#include <vlc_common.h>
#include <vlc_plugin.h>
#include <vlc_vout.h>
#include <vlc_window.h>

44
45
#include "xcb_vlc.h"

46
47
48
49
50
#define DISPLAY_TEXT N_("X11 display")
#define DISPLAY_LONGTEXT N_( \
    "X11 hardware display to use. By default VLC will " \
    "use the value of the DISPLAY environment variable.")

51
52
53
54
#define SHM_TEXT N_("Use shared memory")
#define SHM_LONGTEXT N_( \
    "Use shared memory to communicate between VLC and the X server.")

55
56
57
58
59
60
61
static int  Open (vlc_object_t *);
static void Close (vlc_object_t *);

/*
 * Module descriptor
 */
vlc_module_begin ()
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
62
63
    set_shortname (N_("XCB"))
    set_description (N_("(Experimental) XCB video output"))
64
65
66
67
68
69
70
    set_category (CAT_VIDEO)
    set_subcategory (SUBCAT_VIDEO_VOUT)
    set_capability ("video output", 0)
    set_callbacks (Open, Close)

    add_string ("x11-display", NULL, NULL,
                DISPLAY_TEXT, DISPLAY_LONGTEXT, true)
71
    add_bool ("x11-shm", true, NULL, SHM_TEXT, SHM_LONGTEXT, true)
72
73
74
75
76
77
78
79
vlc_module_end ()

struct vout_sys_t
{
    xcb_connection_t *conn;
    xcb_screen_t *screen;
    vout_window_t *embed; /* VLC window (when windowed) */

80
    xcb_visualid_t vid; /* selected visual */
81
    xcb_window_t parent; /* parent X window */
82
    xcb_colormap_t cmap; /* colormap for selected visual */
83
84
    xcb_window_t window; /* drawable X window */
    xcb_gcontext_t gc; /* context to put images */
85
    bool shm; /* whether to use MIT-SHM */
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
86
    uint8_t bpp; /* bits per pixel */
87
88
89
90
91
92
93
};

static int Init (vout_thread_t *);
static void Deinit (vout_thread_t *);
static void Display (vout_thread_t *, picture_t *);
static int Manage (vout_thread_t *);

94
int CheckError (vout_thread_t *vout, const char *str, xcb_void_cookie_t ck)
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
{
    xcb_generic_error_t *err;

    err = xcb_request_check (vout->p_sys->conn, ck);
    if (err)
    {
        msg_Err (vout, "%s: X11 error %d", str, err->error_code);
        return VLC_EGENERIC;
    }
    return VLC_SUCCESS;
}

#define p_vout vout

/**
 * Probe the X server.
 */
static int Open (vlc_object_t *obj)
{
    vout_thread_t *vout = (vout_thread_t *)obj;
    vout_sys_t *p_sys = malloc (sizeof (*p_sys));
    if (p_sys == NULL)
        return VLC_ENOMEM;

    vout->p_sys = p_sys;
    p_sys->conn = NULL;
#ifndef NDEBUG
    p_sys->embed = NULL;
#endif

    /* Connect to X */
    char *display = var_CreateGetNonEmptyString (vout, "x11-display");
    int snum;
    p_sys->conn = xcb_connect (display, &snum);
    if (xcb_connection_has_error (p_sys->conn) /*== NULL*/)
    {
        msg_Err (vout, "cannot connect to X server %s",
                 display ? display : "");
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
133
        free (display);
134
135
        goto error;
    }
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
136
    free (display);
137
138
139
140
141
142
143
144
145

    /* Get the preferred screen */
    xcb_screen_t *scr = xcb_aux_get_screen (p_sys->conn, snum);
    p_sys->screen = scr;
    assert (p_sys->screen);
    msg_Dbg (vout, "using screen %d (depth %"PRIu8")", snum, scr->root_depth);

    /* Determine the visual (color depth and palette) */
    xcb_visualtype_t *vt = NULL;
146
147
    bool gray = false;

148
149
150
    if ((vt = xcb_aux_find_visual_by_attrs (scr, XCB_VISUAL_CLASS_TRUE_COLOR,
                                            scr->root_depth)) != NULL)
        msg_Dbg (vout, "using TrueColor visual ID %d", (int)vt->visual_id);
151
#if 0
152
    else
153
    if ((vt = xcb_aux_find_visual_by_attrs (scr, XCB_VISUAL_CLASS_STATIC_COLOR,
154
155
                                            scr->root_depth)) != NULL)
        msg_Dbg (vout, "using static color visual ID %d", (int)vt->visual_id);
156
#endif
157
    else
158
159
160
161
162
    if ((scr->root_depth == 8)
     && (vt = xcb_aux_find_visual_by_attrs (scr, XCB_VISUAL_CLASS_STATIC_GRAY,
                                            scr->root_depth)) != NULL)
    {
        msg_Dbg (vout, "using static gray visual ID %d", (int)vt->visual_id);
163
        gray = true;
164
165
    }
    else
166
    {
167
        vt = xcb_aux_find_visual_by_id (scr, scr->root_visual);
168
169
170
171
172
173
        assert (vt);
        msg_Err (vout, "unsupported visual class %"PRIu8, vt->_class);
        goto error;
    }
    p_sys->vid = vt->visual_id;

174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
    /* Determine our input format (normally, done in Init() but X11
     * never changes its format) */
    p_sys->bpp = scr->root_depth;
    switch (scr->root_depth)
    {
        case 24:
            p_sys->bpp = 32;
        case 32: /* FIXME: untested */
            vout->output.i_chroma = VLC_FOURCC ('R', 'V', '3', '2');
            break;

        case 16:
            vout->output.i_chroma = VLC_FOURCC ('R', 'V', '1', '6');
            break;

        case 15:
            p_sys->bpp = 16;
            vout->output.i_chroma = VLC_FOURCC ('R', 'V', '1', '5');
            break;

        case 8: /* FIXME: VLC cannot convert */
            vout->output.i_chroma = gray ? VLC_FOURCC ('G', 'R', 'E', 'Y')
                                         : VLC_FOURCC ('R', 'G', 'B', '2');
            break;

        default:
            msg_Err (vout, "unsupported %"PRIu8"-bits screen depth",
                     scr->root_depth);
            goto error;
    }
    vout->fmt_out.i_chroma = vout->output.i_chroma;
205
206
207
208
209
210
    if (!gray)
    {
        vout->output.i_rmask = vt->red_mask;
        vout->output.i_gmask = vt->green_mask;
        vout->output.i_bmask = vt->blue_mask;
    }
211

212
213
214
215
216
    /* Create colormap (needed to select non-default visual) */
    p_sys->cmap = xcb_generate_id (p_sys->conn);
    xcb_create_colormap (p_sys->conn, XCB_COLORMAP_ALLOC_NONE,
                         p_sys->cmap, scr->root, p_sys->vid);

217
    /* Check shared memory support */
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
    p_sys->shm = var_CreateGetBool (vout, "x11-shm") > 0;
    if (p_sys->shm)
    {
        xcb_shm_query_version_cookie_t ck;
        xcb_shm_query_version_reply_t *r;
        xcb_generic_error_t *err;

        ck = xcb_shm_query_version (p_sys->conn);
        r = xcb_shm_query_version_reply (p_sys->conn, ck, &err);
        if (!r)
        {
            msg_Err (vout, "shared memory (MIT-SHM) not available");
            msg_Warn (vout, "display will be slow");
            p_sys->shm = false;
        }
    }

235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
    vout->pf_init = Init;
    vout->pf_end = Deinit;
    vout->pf_display = Display;
    vout->pf_manage = Manage;
    return VLC_SUCCESS;

error:
    Close (obj);
    return VLC_EGENERIC;
}


/**
 * Disconnect from the X server.
 */
static void Close (vlc_object_t *obj)
{
    vout_thread_t *vout = (vout_thread_t *)obj;
    vout_sys_t *p_sys = vout->p_sys;

    assert (p_sys->embed == NULL);
256
    /* colormap is garbage-ollected by X (?) */
257
258
259
260
261
    if (p_sys->conn)
        xcb_disconnect (p_sys->conn);
    free (p_sys);
}

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
262
263
struct picture_sys_t
{
264
    xcb_connection_t *conn;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
265
    xcb_image_t *image;
266
    xcb_shm_seg_t segment;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
267
268
269
};

static void PictureRelease (picture_t *pic);
270
271
static void PictureShmRelease (picture_t *pic);
#define SHM_ERR ((void *)(intptr_t)(-1))
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
272
273
274
275
276
277
278
279
280
281
282
283
284
285

static int PictureInit (vout_thread_t *vout, picture_t *pic)
{
    vout_sys_t *p_sys = vout->p_sys;
    picture_sys_t *priv = malloc (sizeof (*p_sys));

    if (priv == NULL)
        return VLC_ENOMEM;

    assert (pic->i_status == FREE_PICTURE);
    vout_InitPicture (vout, pic, vout->output.i_chroma,
                      vout->output.i_width, vout->output.i_height,
                      vout->output.i_aspect);

286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
    void *shm = SHM_ERR;
    const size_t size = pic->p->i_pitch * pic->p->i_lines;

    if (p_sys->shm)
    {   /* Allocate shared memory segment */
        int id = shmget (IPC_PRIVATE, size, IPC_CREAT | 0700);

        if (id == -1)
        {
            msg_Err (vout, "shared memory allocation error: %m");
            goto error;
        }

        /* Attach the segment to VLC */
        shm = shmat (id, NULL, 0 /* read/write */);
        if (shm == SHM_ERR)
        {
            msg_Err (vout, "shared memory attachment error: %m");
            shmctl (id, IPC_RMID, 0);
            goto error;
        }

        /* Attach the segment to X */
        xcb_void_cookie_t ck;

        priv->segment = xcb_generate_id (p_sys->conn);
        ck = xcb_shm_attach_checked (p_sys->conn, priv->segment, id, 1);
        shmctl (id, IPC_RMID, 0);

        if (CheckError (vout, "shared memory server-side error", ck))
        {
            msg_Info (vout, "using buggy X (remote) server? SSH?");
            shmdt (shm);
            shm = SHM_ERR;
        }
    }

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
323
324
325
326
327
328
329
330
331
332
333
334
335
336
    const unsigned real_width = pic->p->i_pitch / (p_sys->bpp >> 3);
    /* FIXME: anyway to getthing more intuitive than that?? */

    /* NOTE: 32-bits scanline_pad assumed, FIXME? (see xdpyinfo) */
    xcb_image_t *img;
    img = xcb_image_create (real_width, pic->p->i_lines,
                            XCB_IMAGE_FORMAT_Z_PIXMAP, 32,
                            p_sys->screen->root_depth, p_sys->bpp, p_sys->bpp,
#ifdef WORDS_BIGENDIAN
                            XCB_IMAGE_ORDER_MSB_FIRST,
#else
                            XCB_IMAGE_ORDER_LSB_FIRST,
#endif
                            XCB_IMAGE_ORDER_MSB_FIRST,
337
338
339
                            NULL,
                            (shm != SHM_ERR) ? size : 0,
                            (shm != SHM_ERR) ? shm : NULL);
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
340
341

    if (img == NULL)
342
343
344
345
346
347
348
349
350
    {
        if (shm != SHM_ERR)
            xcb_shm_detach (p_sys->conn, priv->segment);
        goto error;
    }
    if (shm != SHM_ERR && xcb_image_native (p_sys->conn, img, 0) == NULL)
    {
        msg_Err (vout, "incompatible X server image format");
        xcb_image_destroy (img);
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
351
        goto error;
352
    }
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
353

354
    priv->conn = p_sys->conn;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
355
356
357
    priv->image = img;
    pic->p_sys = priv;
    pic->p->p_pixels = img->data;
358
359
    pic->pf_release = (shm != SHM_ERR) ? PictureShmRelease
                                       : PictureRelease;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
360
361
362
363
364
    pic->i_status = DESTROYED_PICTURE;
    pic->i_type = DIRECT_PICTURE;
    return VLC_SUCCESS;

error:
365
366
    if (shm != SHM_ERR)
        shmdt (shm);
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
367
368
369
370
371
372
373
374
    free (p_sys);
    return VLC_EGENERIC;
}


/**
 * Release picture private data
 */
375
376
static void PictureRelease (picture_t *pic)
{
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
377
378
379
380
    struct picture_sys_t *p_sys = pic->p_sys;

    xcb_image_destroy (p_sys->image);
    free (p_sys);
381
382
}

383
384
385
386
387
388
389
390
391
392
393
394
/**
 * Release shared memory picture private data
 */
static void PictureShmRelease (picture_t *pic)
{
    struct picture_sys_t *p_sys = pic->p_sys;

    xcb_shm_detach (p_sys->conn, p_sys->segment);
    shmdt (p_sys->image->data);
    PictureRelease (pic);
}

395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
/**
 * Allocate drawable window and picture buffers.
 */
static int Init (vout_thread_t *vout)
{
    vout_sys_t *p_sys = vout->p_sys;
    const xcb_screen_t *screen = p_sys->screen;
    unsigned x, y, width, height;

    /* Determine parent window */
    if (vout->b_fullscreen)
    {
        p_sys->embed = NULL;
        p_sys->parent = screen->root;
        width = screen->width_in_pixels;
        height = screen->height_in_pixels;
    }
    else
    {
414
        p_sys->embed = vout_RequestXWindow (vout, &(int){ 0 }, &(int){ 0 },
415
416
417
418
419
420
                                            &width, &height);
        if (p_sys->embed == NULL)
        {
            msg_Err (vout, "cannot get window");
            return VLC_EGENERIC;
        }
421
        p_sys->parent = p_sys->embed->handle.xid;
422
423
424
    }

    vout_PlacePicture (vout, width, height, &x, &y, &width, &height);
425

426
    /* FIXME: I don't get the subtlety between output and fmt_out here */
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
    vout->fmt_out.i_visible_width = width;
    vout->fmt_out.i_visible_height = height;
    vout->fmt_out.i_sar_num = vout->fmt_out.i_sar_den = 1;

    vout->output.i_width = vout->fmt_out.i_width =
        width * vout->fmt_in.i_width / vout->fmt_in.i_visible_width;
    vout->output.i_height = vout->fmt_out.i_height =
        height * vout->fmt_in.i_height / vout->fmt_in.i_visible_height;
    vout->fmt_out.i_x_offset =
        width * vout->fmt_in.i_x_offset / vout->fmt_in.i_visible_width;
    p_vout->fmt_out.i_y_offset =
        height * vout->fmt_in.i_y_offset / vout->fmt_in.i_visible_height;

    assert (height > 0);
    vout->output.i_aspect = vout->fmt_out.i_aspect =
        width * VOUT_ASPECT_FACTOR / height;
443
444

    /* Create window */
445
446
447
    const uint32_t mask = XCB_CW_BACK_PIXEL | XCB_CW_EVENT_MASK
                        | XCB_CW_COLORMAP;
    uint32_t values[] = {
448
449
450
451
452
        /* XCB_CW_BACK_PIXEL */
        screen->black_pixel,
        /* XCB_CW_EVENT_MASK */
        XCB_EVENT_MASK_BUTTON_PRESS | XCB_EVENT_MASK_BUTTON_RELEASE |
        XCB_EVENT_MASK_POINTER_MOTION,
453
454
        /* XCB_CW_COLORMAP */
        p_sys->cmap,
455
    };
456
457
458
459
460
461
    xcb_void_cookie_t c;
    xcb_window_t window = xcb_generate_id (p_sys->conn);

    c = xcb_create_window_checked (p_sys->conn, screen->root_depth, window,
                                   p_sys->parent, x, y, width, height, 0,
                                   XCB_WINDOW_CLASS_INPUT_OUTPUT,
462
                                   p_sys->vid, mask, values);
463
464
    if (CheckError (vout, "cannot create X11 window", c))
        goto error;
465
    p_sys->window = window;
466
467
468
469
470
471
472
473
474
475
476
477
478
479
    msg_Dbg (vout, "using X11 window %08"PRIx32, p_sys->window);
    xcb_map_window (p_sys->conn, window);

    /* Create graphic context (I wonder why the heck do we need this) */
    p_sys->gc = xcb_generate_id (p_sys->conn);
    xcb_create_gc (p_sys->conn, p_sys->gc, p_sys->window, 0, NULL);
    msg_Dbg (vout, "using X11 graphic context %08"PRIx32, p_sys->gc);
    xcb_flush (p_sys->conn);

    /* Allocate picture buffers */
    do
    {
        picture_t *pic = vout->p_picture + I_OUTPUTPICTURES;

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
480
481
        if (PictureInit (vout, pic))
            break;
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
        PP_OUTPUTPICTURE[I_OUTPUTPICTURES++] = pic;
    }
    while (I_OUTPUTPICTURES < 2);

    return VLC_SUCCESS;

error:
    Deinit (vout);
    return VLC_EGENERIC;
}

/**
 * Free picture buffers.
 */
static void Deinit (vout_thread_t *vout)
{
    vout_sys_t *p_sys = vout->p_sys;

    while (I_OUTPUTPICTURES > 0)
        picture_Release (PP_OUTPUTPICTURE[--I_OUTPUTPICTURES]);

    xcb_unmap_window (p_sys->conn, p_sys->window);
    xcb_destroy_window (p_sys->conn, p_sys->window);
    vout_ReleaseWindow (p_sys->embed);
    p_sys->embed = NULL;
}

/**
 * Sends an image to the X server.
 */
static void Display (vout_thread_t *vout, picture_t *pic)
{
    vout_sys_t *p_sys = vout->p_sys;
515
516
    picture_sys_t *priv = pic->p_sys;
    xcb_image_t *img = priv->image;
517

518
    if (img->base == NULL)
519
    {
520
521
522
523
524
525
526
527
        xcb_shm_segment_info_t info = {
            .shmseg = priv->segment,
            .shmid = -1, /* lost track of it, unimportant */
            .shmaddr = img->data,
        };

        xcb_image_shm_put (p_sys->conn, p_sys->window, p_sys->gc, img, info,
                        0, 0, 0, 0, img->width, img->height, 0);
528
    }
529
530
531
    else
    {
        xcb_image_t *native = xcb_image_native (p_sys->conn, img, 1);
532

533
534
        if (native == NULL)
            return;
535

536
537
538
        xcb_image_put (p_sys->conn, p_sys->window, p_sys->gc, native, 0, 0, 0);
        if (native != img)
            xcb_image_destroy (native);
539
    }
540
    xcb_flush (p_sys->conn);
541
542
543
544
545
546
547
548
549
550
551
}

/**
 * Process incoming X events.
 */
static int Manage (vout_thread_t *vout)
{
    vout_sys_t *p_sys = vout->p_sys;
    xcb_generic_event_t *ev;

    while ((ev = xcb_poll_for_event (p_sys->conn)) != NULL)
552
        ProcessEvent (vout, ev);
553
554
555
556
557
558
559
560

    if (xcb_connection_has_error (p_sys->conn))
    {
        msg_Err (vout, "X server failure");
        return VLC_EGENERIC;
    }
    return VLC_SUCCESS;
}