]> git.sesse.net Git - vlc/blob - modules/video_output/xcb/x11.c
XCB: keep the same window through resizing
[vlc] / modules / video_output / xcb / x11.c
1 /**
2  * @file x11.c
3  * @brief X C Bindings video output module for VLC media player
4  */
5 /*****************************************************************************
6  * Copyright © 2009 Rémi Denis-Courmont
7  *
8  * This library is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU General Public License
10  * as published by the Free Software Foundation; either version 2.0
11  * of the License, or (at your option) any later version.
12  *
13  * This library 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 Lesser General Public
19  * License along with this library; if not, write to the Free Software
20  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
21  ****************************************************************************/
22
23 #ifdef HAVE_CONFIG_H
24 # include <config.h>
25 #endif
26
27 #include <stdlib.h>
28 #include <assert.h>
29
30 #include <sys/types.h>
31 #include <sys/shm.h>
32
33 #include <xcb/xcb.h>
34 #include <xcb/shm.h>
35
36 #include <xcb/xcb_image.h>
37
38 #include <vlc_common.h>
39 #include <vlc_plugin.h>
40 #include <vlc_vout.h>
41 #include <vlc_window.h>
42
43 #include "xcb_vlc.h"
44
45 #define DISPLAY_TEXT N_("X11 display")
46 #define DISPLAY_LONGTEXT N_( \
47     "X11 hardware display to use. By default VLC will " \
48     "use the value of the DISPLAY environment variable.")
49
50 #define SHM_TEXT N_("Use shared memory")
51 #define SHM_LONGTEXT N_( \
52     "Use shared memory to communicate between VLC and the X server.")
53
54 static int  Open (vlc_object_t *);
55 static void Close (vlc_object_t *);
56
57 /*
58  * Module descriptor
59  */
60 vlc_module_begin ()
61     set_shortname (N_("XCB"))
62     set_description (N_("(Experimental) XCB video output"))
63     set_category (CAT_VIDEO)
64     set_subcategory (SUBCAT_VIDEO_VOUT)
65     set_capability ("video output", 0)
66     set_callbacks (Open, Close)
67
68     add_string ("x11-display", NULL, NULL,
69                 DISPLAY_TEXT, DISPLAY_LONGTEXT, true)
70     add_bool ("x11-shm", true, NULL, SHM_TEXT, SHM_LONGTEXT, true)
71 vlc_module_end ()
72
73 struct vout_sys_t
74 {
75     xcb_connection_t *conn;
76     vout_window_t *embed; /* VLC window (when windowed) */
77
78     xcb_window_t window; /* drawable X window */
79     xcb_gcontext_t gc; /* context to put images */
80     bool shm; /* whether to use MIT-SHM */
81     uint8_t bpp; /* bits per pixel */
82     uint8_t pad; /* scanline pad */
83     uint8_t depth; /* useful bits per pixel */
84     uint8_t byte_order; /* server byte order */
85 };
86
87 static int Init (vout_thread_t *);
88 static void Deinit (vout_thread_t *);
89 static void Display (vout_thread_t *, picture_t *);
90 static int Manage (vout_thread_t *);
91
92 int CheckError (vout_thread_t *vout, const char *str, xcb_void_cookie_t ck)
93 {
94     xcb_generic_error_t *err;
95
96     err = xcb_request_check (vout->p_sys->conn, ck);
97     if (err)
98     {
99         msg_Err (vout, "%s: X11 error %d", str, err->error_code);
100         return VLC_EGENERIC;
101     }
102     return VLC_SUCCESS;
103 }
104
105 #define p_vout vout
106
107 /**
108  * Probe the X server.
109  */
110 static int Open (vlc_object_t *obj)
111 {
112     vout_thread_t *vout = (vout_thread_t *)obj;
113     vout_sys_t *p_sys = malloc (sizeof (*p_sys));
114     if (p_sys == NULL)
115         return VLC_ENOMEM;
116
117     vout->p_sys = p_sys;
118     p_sys->conn = NULL;
119     p_sys->embed = NULL;
120
121     /* Connect to X */
122     char *display = var_CreateGetNonEmptyString (vout, "x11-display");
123     p_sys->conn = xcb_connect (display, NULL);
124     if (xcb_connection_has_error (p_sys->conn) /*== NULL*/)
125     {
126         msg_Err (vout, "cannot connect to X server %s",
127                  display ? display : "");
128         free (display);
129         goto error;
130     }
131     free (display);
132
133     /* Get window */
134     p_sys->embed = vout_RequestXWindow (vout, &(int){ 0 }, &(int){ 0 },
135                                         &(unsigned){ 0 }, &(unsigned){ 0 });
136     if (p_sys->embed == NULL)
137     {
138         msg_Err (vout, "parent window not available");
139         goto error;
140     }
141     xcb_window_t root;
142     {
143         xcb_get_geometry_reply_t *geo;
144         xcb_get_geometry_cookie_t ck;
145
146         ck = xcb_get_geometry (p_sys->conn, p_sys->embed->handle.xid);
147         geo = xcb_get_geometry_reply (p_sys->conn, ck, NULL);
148         if (geo == NULL)
149         {
150             msg_Err (vout, "parent window not valid");
151             goto error;
152         }
153         root = geo->root;
154         free (geo);
155
156         /* Subscribe to parent window resize events */
157         const uint32_t value = XCB_EVENT_MASK_STRUCTURE_NOTIFY;
158         xcb_change_window_attributes (p_sys->conn, p_sys->embed->handle.xid,
159                                       XCB_CW_EVENT_MASK, &value);
160     }
161
162     /* Find the selected screen */
163     const xcb_setup_t *setup = xcb_get_setup (p_sys->conn);
164     p_sys->byte_order = setup->image_byte_order;
165
166     xcb_screen_t *scr = NULL;
167     for (xcb_screen_iterator_t i = xcb_setup_roots_iterator (setup);
168          i.rem > 0 && scr == NULL; xcb_screen_next (&i))
169     {
170         if (i.data->root == root)
171             scr = i.data;
172     }
173
174     if (scr == NULL)
175     {
176         msg_Err (vout, "parent window screen not found");
177         goto error;
178     }
179     msg_Dbg (vout, "using screen 0x%"PRIx32, scr->root);
180
181     /* Determine our video format. Normally, this is done in pf_init(), but
182      * this plugin always uses the same format for a given X11 screen. */
183     xcb_visualid_t vid = 0;
184     uint8_t depth = 0;
185     bool gray = true;
186     for (const xcb_format_t *fmt = xcb_setup_pixmap_formats (setup),
187              *end = fmt + xcb_setup_pixmap_formats_length (setup);
188          fmt < end; fmt++)
189     {
190         vlc_fourcc_t chroma = 0;
191
192         if (fmt->depth < depth)
193             continue; /* We already found a better format! */
194
195         /* Check that the pixmap format is supported by VLC. */
196         switch (fmt->depth)
197         {
198           case 24:
199             if (fmt->bits_per_pixel == 32)
200                 chroma = VLC_FOURCC ('R', 'V', '3', '2');
201             else if (fmt->bits_per_pixel == 24)
202                 chroma = VLC_FOURCC ('R', 'V', '2', '4');
203             else
204                 continue;
205             break;
206           case 16:
207             if (fmt->bits_per_pixel != 16)
208                 continue;
209             chroma = VLC_FOURCC ('R', 'V', '1', '6');
210             break;
211           case 15:
212             if (fmt->bits_per_pixel != 16)
213                 continue;
214             chroma = VLC_FOURCC ('R', 'V', '1', '5');
215             break;
216           case 8:
217             if (fmt->bits_per_pixel != 8)
218                 continue;
219             chroma = VLC_FOURCC ('R', 'G', 'B', '2');
220             break;
221           default:
222             continue;
223         }
224         if ((fmt->bits_per_pixel << 4) % fmt->scanline_pad)
225             continue; /* VLC pads lines to 16 pixels internally */
226
227         /* Byte sex is a non-issue for 8-bits. It can be worked around with
228          * RGB masks for 24-bits. Too bad for 15-bits and 16-bits. */
229 #ifdef WORDS_BIGENDIAN
230 # define ORDER XCB_IMAGE_ORDER_MSB_FIRST
231 #else
232 # define ORDER XCB_IMAGE_ORDER_LSB_FIRST
233 #endif
234         if (fmt->bits_per_pixel == 16 && setup->image_byte_order != ORDER)
235             continue;
236
237         /* Check that the selected screen supports this depth */
238         xcb_depth_iterator_t it = xcb_screen_allowed_depths_iterator (scr);
239         while (it.rem > 0 && it.data->depth != fmt->depth)
240              xcb_depth_next (&it);
241         if (!it.rem)
242             continue; /* Depth not supported on this screen */
243
244         /* Find a visual type for the selected depth */
245         const xcb_visualtype_t *vt = xcb_depth_visuals (it.data);
246         for (int i = xcb_depth_visuals_length (it.data); i > 0; i--)
247         {
248             if (vt->_class == XCB_VISUAL_CLASS_TRUE_COLOR)
249             {
250                 vid = vt->visual_id;
251                 gray = false;
252                 break;
253             }
254             if (fmt->depth == 8 && vt->_class == XCB_VISUAL_CLASS_STATIC_GRAY)
255             {
256                 if (!gray)
257                     continue; /* Prefer color over gray scale */
258                 vid = vt->visual_id;
259                 chroma = VLC_FOURCC ('G', 'R', 'E', 'Y');
260             }
261         }
262
263         if (!vid)
264             continue; /* The screen does not *really* support this depth */
265
266         vout->fmt_out.i_chroma = vout->output.i_chroma = chroma;
267         if (!gray)
268         {
269             vout->output.i_rmask = vt->red_mask;
270             vout->output.i_gmask = vt->green_mask;
271             vout->output.i_bmask = vt->blue_mask;
272         }
273         p_sys->bpp = fmt->bits_per_pixel;
274         p_sys->pad = fmt->scanline_pad;
275         p_sys->depth = depth = fmt->depth;
276     }
277
278     if (depth == 0)
279     {
280         msg_Err (vout, "no supported pixmap formats or visual types");
281         goto error;
282     }
283
284     msg_Dbg (vout, "using X11 visual ID 0x%"PRIx32, vid);
285     msg_Dbg (vout, " %"PRIu8" bits per pixels, %"PRIu8" bits line pad",
286              p_sys->bpp, p_sys->pad);
287
288     /* Create colormap (needed to select non-default visual) */
289     xcb_colormap_t cmap;
290     if (vid != scr->root_visual)
291     {
292         cmap = xcb_generate_id (p_sys->conn);
293         xcb_create_colormap (p_sys->conn, XCB_COLORMAP_ALLOC_NONE,
294                              cmap, scr->root, vid);
295     }
296     else
297         cmap = scr->default_colormap;
298
299     /* Create window */
300     {
301         const uint32_t mask = XCB_CW_BACK_PIXEL | XCB_CW_EVENT_MASK
302                             | XCB_CW_COLORMAP;
303         const uint32_t values[] = {
304             /* XCB_CW_BACK_PIXEL */
305             scr->black_pixel,
306             /* XCB_CW_EVENT_MASK */
307             XCB_EVENT_MASK_BUTTON_PRESS | XCB_EVENT_MASK_BUTTON_RELEASE |
308             XCB_EVENT_MASK_POINTER_MOTION,
309             /* XCB_CW_COLORMAP */
310             cmap,
311         };
312         xcb_void_cookie_t c;
313         xcb_window_t window = xcb_generate_id (p_sys->conn);
314
315         c = xcb_create_window_checked (p_sys->conn, depth, window,
316                                        p_sys->embed->handle.xid, 0, 0, 1, 1, 0,
317                                        XCB_WINDOW_CLASS_INPUT_OUTPUT,
318                                        vid, mask, values);
319         if (CheckError (vout, "cannot create X11 window", c))
320             goto error;
321         p_sys->window = window;
322         msg_Dbg (vout, "using X11 window %08"PRIx32, p_sys->window);
323         xcb_map_window (p_sys->conn, window);
324     }
325
326     /* Create graphic context (I wonder why the heck do we need this) */
327     p_sys->gc = xcb_generate_id (p_sys->conn);
328     xcb_create_gc (p_sys->conn, p_sys->gc, p_sys->window, 0, NULL);
329     msg_Dbg (vout, "using X11 graphic context %08"PRIx32, p_sys->gc);
330     xcb_flush (p_sys->conn);
331
332     /* Check shared memory support */
333     p_sys->shm = var_CreateGetBool (vout, "x11-shm") > 0;
334     if (p_sys->shm)
335     {
336         xcb_shm_query_version_cookie_t ck;
337         xcb_shm_query_version_reply_t *r;
338
339         ck = xcb_shm_query_version (p_sys->conn);
340         r = xcb_shm_query_version_reply (p_sys->conn, ck, NULL);
341         if (!r)
342         {
343             msg_Err (vout, "shared memory (MIT-SHM) not available");
344             msg_Warn (vout, "display will be slow");
345             p_sys->shm = false;
346         }
347         free (r);
348     }
349
350     vout->pf_init = Init;
351     vout->pf_end = Deinit;
352     vout->pf_display = Display;
353     vout->pf_manage = Manage;
354     return VLC_SUCCESS;
355
356 error:
357     Close (obj);
358     return VLC_EGENERIC;
359 }
360
361
362 /**
363  * Disconnect from the X server.
364  */
365 static void Close (vlc_object_t *obj)
366 {
367     vout_thread_t *vout = (vout_thread_t *)obj;
368     vout_sys_t *p_sys = vout->p_sys;
369
370     if (p_sys->embed)
371         vout_ReleaseWindow (p_sys->embed);
372     /* colormap and window are garbage-collected by X */
373     if (p_sys->conn)
374         xcb_disconnect (p_sys->conn);
375     free (p_sys);
376 }
377
378 struct picture_sys_t
379 {
380     xcb_connection_t *conn; /* Shared connection to X server */
381     xcb_image_t *image;  /* Picture buffer */
382     xcb_shm_seg_t segment; /* Shared memory segment X ID */
383 };
384
385 #define SHM_ERR ((void *)(intptr_t)(-1))
386
387 static int PictureInit (vout_thread_t *vout, picture_t *pic)
388 {
389     vout_sys_t *p_sys = vout->p_sys;
390     picture_sys_t *priv = malloc (sizeof (*p_sys));
391
392     if (priv == NULL)
393         return VLC_ENOMEM;
394
395     assert (pic->i_status == FREE_PICTURE);
396     vout_InitPicture (vout, pic, vout->output.i_chroma,
397                       vout->output.i_width, vout->output.i_height,
398                       vout->output.i_aspect);
399
400     void *shm = SHM_ERR;
401     const size_t size = pic->p->i_pitch * pic->p->i_lines;
402
403     if (p_sys->shm)
404     {   /* Allocate shared memory segment */
405         int id = shmget (IPC_PRIVATE, size, IPC_CREAT | 0700);
406
407         if (id == -1)
408         {
409             msg_Err (vout, "shared memory allocation error: %m");
410             goto error;
411         }
412
413         /* Attach the segment to VLC */
414         shm = shmat (id, NULL, 0 /* read/write */);
415         if (shm == SHM_ERR)
416         {
417             msg_Err (vout, "shared memory attachment error: %m");
418             shmctl (id, IPC_RMID, 0);
419             goto error;
420         }
421
422         /* Attach the segment to X */
423         xcb_void_cookie_t ck;
424
425         priv->segment = xcb_generate_id (p_sys->conn);
426         ck = xcb_shm_attach_checked (p_sys->conn, priv->segment, id, 1);
427         shmctl (id, IPC_RMID, 0);
428
429         if (CheckError (vout, "shared memory server-side error", ck))
430         {
431             msg_Info (vout, "using buggy X (remote) server? SSH?");
432             shmdt (shm);
433             shm = SHM_ERR;
434         }
435     }
436     else
437         priv->segment = 0;
438
439     const unsigned real_width = pic->p->i_pitch / (p_sys->bpp >> 3);
440     /* FIXME: anyway to getthing more intuitive than that?? */
441
442     xcb_image_t *img;
443     img = xcb_image_create (real_width, pic->p->i_lines,
444                             XCB_IMAGE_FORMAT_Z_PIXMAP, p_sys->pad,
445                             p_sys->depth, p_sys->bpp, p_sys->bpp,
446                             p_sys->byte_order, XCB_IMAGE_ORDER_MSB_FIRST,
447                             NULL,
448                             (shm != SHM_ERR) ? size : 0,
449                             (shm != SHM_ERR) ? shm : NULL);
450
451     if (img == NULL)
452     {
453         if (shm != SHM_ERR)
454             xcb_shm_detach (p_sys->conn, priv->segment);
455         goto error;
456     }
457
458     priv->conn = p_sys->conn;
459     priv->image = img;
460     pic->p_sys = priv;
461     pic->p->p_pixels = img->data;
462     pic->i_status = DESTROYED_PICTURE;
463     pic->i_type = DIRECT_PICTURE;
464     return VLC_SUCCESS;
465
466 error:
467     if (shm != SHM_ERR)
468         shmdt (shm);
469     free (priv);
470     return VLC_EGENERIC;
471 }
472
473
474 /**
475  * Release picture private data
476  */
477 static void PictureDeinit (picture_t *pic)
478 {
479     struct picture_sys_t *p_sys = pic->p_sys;
480
481     if (p_sys->segment != 0)
482     {
483         xcb_shm_detach (p_sys->conn, p_sys->segment);
484         shmdt (p_sys->image->data);
485     }
486     xcb_image_destroy (p_sys->image);
487     free (p_sys);
488 }
489
490 static void get_window_size (xcb_connection_t *conn, xcb_window_t win,
491                              unsigned *width, unsigned *height)
492 {
493     xcb_get_geometry_cookie_t ck = xcb_get_geometry (conn, win);
494     xcb_get_geometry_reply_t *geo = xcb_get_geometry_reply (conn, ck, NULL);
495
496     if (geo)
497     {
498         *width = geo->width;
499         *height = geo->height;
500         free (geo);
501     }
502     else
503         *width = *height = 0;
504 }
505
506 /**
507  * Allocate drawable window and picture buffers.
508  */
509 static int Init (vout_thread_t *vout)
510 {
511     vout_sys_t *p_sys = vout->p_sys;
512     unsigned x, y, width, height;
513
514     get_window_size (p_sys->conn, p_sys->embed->handle.xid, &width, &height);
515     vout_PlacePicture (vout, width, height, &x, &y, &width, &height);
516
517     const uint32_t values[] = { x, y, width, height, };
518     xcb_configure_window (p_sys->conn, p_sys->window,
519                           XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y |
520                           XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT,
521                           values);
522
523     /* FIXME: I don't get the subtlety between output and fmt_out here */
524     vout->fmt_out.i_visible_width = width;
525     vout->fmt_out.i_visible_height = height;
526     vout->fmt_out.i_sar_num = vout->fmt_out.i_sar_den = 1;
527
528     vout->output.i_width = vout->fmt_out.i_width =
529         width * vout->fmt_in.i_width / vout->fmt_in.i_visible_width;
530     vout->output.i_height = vout->fmt_out.i_height =
531         height * vout->fmt_in.i_height / vout->fmt_in.i_visible_height;
532     vout->fmt_out.i_x_offset =
533         width * vout->fmt_in.i_x_offset / vout->fmt_in.i_visible_width;
534     p_vout->fmt_out.i_y_offset =
535         height * vout->fmt_in.i_y_offset / vout->fmt_in.i_visible_height;
536
537     assert (height > 0);
538     vout->output.i_aspect = vout->fmt_out.i_aspect =
539         width * VOUT_ASPECT_FACTOR / height;
540
541     /* Allocate picture buffers */
542     I_OUTPUTPICTURES = 0;
543     for (size_t index = 0; I_OUTPUTPICTURES < 2; index++)
544     {
545         picture_t *pic = vout->p_picture + index;
546
547         if (index > sizeof (vout->p_picture) / sizeof (pic))
548             break;
549         if (pic->i_status != FREE_PICTURE)
550             continue;
551         if (PictureInit (vout, pic))
552             break;
553         PP_OUTPUTPICTURE[I_OUTPUTPICTURES++] = pic;
554     }
555
556     return VLC_SUCCESS;
557 }
558
559 /**
560  * Free picture buffers.
561  */
562 static void Deinit (vout_thread_t *vout)
563 {
564     for (int i = 0; i < I_OUTPUTPICTURES; i++)
565         PictureDeinit (PP_OUTPUTPICTURE[i]);
566 }
567
568 /**
569  * Sends an image to the X server.
570  */
571 static void Display (vout_thread_t *vout, picture_t *pic)
572 {
573     vout_sys_t *p_sys = vout->p_sys;
574     picture_sys_t *priv = pic->p_sys;
575     xcb_image_t *img = priv->image;
576
577     if (img->base == NULL)
578     {
579         xcb_shm_segment_info_t info = {
580             .shmseg = priv->segment,
581             .shmid = -1, /* lost track of it, unimportant */
582             .shmaddr = img->data,
583         };
584
585         xcb_image_shm_put (p_sys->conn, p_sys->window, p_sys->gc, img, info,
586                         0, 0, 0, 0, img->width, img->height, 0);
587     }
588     else
589         xcb_image_put (p_sys->conn, p_sys->window, p_sys->gc, img, 0, 0, 0);
590     xcb_flush (p_sys->conn);
591 }
592
593 /**
594  * Process incoming X events.
595  */
596 static int Manage (vout_thread_t *vout)
597 {
598     vout_sys_t *p_sys = vout->p_sys;
599     xcb_generic_event_t *ev;
600
601     while ((ev = xcb_poll_for_event (p_sys->conn)) != NULL)
602         ProcessEvent (vout, p_sys->conn, p_sys->window, ev);
603
604     if (xcb_connection_has_error (p_sys->conn))
605     {
606         msg_Err (vout, "X server failure");
607         return VLC_EGENERIC;
608     }
609     return VLC_SUCCESS;
610 }