]> git.sesse.net Git - vlc/blob - modules/video_output/xcb/x11.c
XCB: factor some code
[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 <vlc_common.h>
37 #include <vlc_plugin.h>
38 #include <vlc_vout.h>
39 #include <vlc_window.h>
40
41 #include "xcb_vlc.h"
42
43 #define DISPLAY_TEXT N_("X11 display")
44 #define DISPLAY_LONGTEXT N_( \
45     "X11 hardware display to use. By default VLC will " \
46     "use the value of the DISPLAY environment variable.")
47
48 #define SHM_TEXT N_("Use shared memory")
49 #define SHM_LONGTEXT N_( \
50     "Use shared memory to communicate between VLC and the X server.")
51
52 static int  Open (vlc_object_t *);
53 static void Close (vlc_object_t *);
54
55 /*
56  * Module descriptor
57  */
58 vlc_module_begin ()
59     set_shortname (N_("XCB"))
60     set_description (N_("(Experimental) XCB video output"))
61     set_category (CAT_VIDEO)
62     set_subcategory (SUBCAT_VIDEO_VOUT)
63     set_capability ("video output", 0)
64     set_callbacks (Open, Close)
65
66     add_string ("x11-display", NULL, NULL,
67                 DISPLAY_TEXT, DISPLAY_LONGTEXT, true)
68     add_bool ("x11-shm", true, NULL, SHM_TEXT, SHM_LONGTEXT, true)
69 vlc_module_end ()
70
71 struct vout_sys_t
72 {
73     xcb_connection_t *conn;
74     vout_window_t *embed; /* VLC window (when windowed) */
75
76     xcb_window_t window; /* drawable X window */
77     xcb_gcontext_t gc; /* context to put images */
78     bool shm; /* whether to use MIT-SHM */
79     uint8_t bpp; /* bits per pixel */
80     uint8_t pad; /* scanline pad */
81     uint8_t depth; /* useful bits per pixel */
82     uint8_t byte_order; /* server byte order */
83 };
84
85 static int Init (vout_thread_t *);
86 static void Deinit (vout_thread_t *);
87 static void Display (vout_thread_t *, picture_t *);
88 static int Manage (vout_thread_t *);
89
90 int CheckError (vout_thread_t *vout, const char *str, xcb_void_cookie_t ck)
91 {
92     xcb_generic_error_t *err;
93
94     err = xcb_request_check (vout->p_sys->conn, ck);
95     if (err)
96     {
97         msg_Err (vout, "%s: X11 error %d", str, err->error_code);
98         return VLC_EGENERIC;
99     }
100     return VLC_SUCCESS;
101 }
102
103 #define p_vout vout
104
105 /**
106  * Probe the X server.
107  */
108 static int Open (vlc_object_t *obj)
109 {
110     vout_thread_t *vout = (vout_thread_t *)obj;
111     vout_sys_t *p_sys = malloc (sizeof (*p_sys));
112     if (p_sys == NULL)
113         return VLC_ENOMEM;
114
115     vout->p_sys = p_sys;
116
117     /* Connect to X */
118     p_sys->conn = Connect (obj);
119     if (p_sys->conn == NULL)
120         return VLC_EGENERIC;
121
122     /* Get window */
123     const xcb_screen_t *scr;
124     p_sys->embed = GetWindow (vout, p_sys->conn, &scr, &p_sys->shm);
125     if (p_sys->embed == NULL)
126     {
127         xcb_disconnect (p_sys->conn);
128         return VLC_EGENERIC;
129     }
130
131     const xcb_setup_t *setup = xcb_get_setup (p_sys->conn);
132     p_sys->byte_order = setup->image_byte_order;
133
134     /* Determine our video format. Normally, this is done in pf_init(), but
135      * this plugin always uses the same format for a given X11 screen. */
136     xcb_visualid_t vid = 0;
137     uint8_t depth = 0;
138     bool gray = true;
139     for (const xcb_format_t *fmt = xcb_setup_pixmap_formats (setup),
140              *end = fmt + xcb_setup_pixmap_formats_length (setup);
141          fmt < end; fmt++)
142     {
143         vlc_fourcc_t chroma = 0;
144
145         if (fmt->depth < depth)
146             continue; /* We already found a better format! */
147
148         /* Check that the pixmap format is supported by VLC. */
149         switch (fmt->depth)
150         {
151           case 24:
152             if (fmt->bits_per_pixel == 32)
153                 chroma = VLC_FOURCC ('R', 'V', '3', '2');
154             else if (fmt->bits_per_pixel == 24)
155                 chroma = VLC_FOURCC ('R', 'V', '2', '4');
156             else
157                 continue;
158             break;
159           case 16:
160             if (fmt->bits_per_pixel != 16)
161                 continue;
162             chroma = VLC_FOURCC ('R', 'V', '1', '6');
163             break;
164           case 15:
165             if (fmt->bits_per_pixel != 16)
166                 continue;
167             chroma = VLC_FOURCC ('R', 'V', '1', '5');
168             break;
169           case 8:
170             if (fmt->bits_per_pixel != 8)
171                 continue;
172             chroma = VLC_FOURCC ('R', 'G', 'B', '2');
173             break;
174           default:
175             continue;
176         }
177         if ((fmt->bits_per_pixel << 4) % fmt->scanline_pad)
178             continue; /* VLC pads lines to 16 pixels internally */
179
180         /* Byte sex is a non-issue for 8-bits. It can be worked around with
181          * RGB masks for 24-bits. Too bad for 15-bits and 16-bits. */
182         if (fmt->bits_per_pixel == 16 && setup->image_byte_order != ORDER)
183             continue;
184
185         /* Check that the selected screen supports this depth */
186         xcb_depth_iterator_t it = xcb_screen_allowed_depths_iterator (scr);
187         while (it.rem > 0 && it.data->depth != fmt->depth)
188              xcb_depth_next (&it);
189         if (!it.rem)
190             continue; /* Depth not supported on this screen */
191
192         /* Find a visual type for the selected depth */
193         const xcb_visualtype_t *vt = xcb_depth_visuals (it.data);
194         for (int i = xcb_depth_visuals_length (it.data); i > 0; i--)
195         {
196             if (vt->_class == XCB_VISUAL_CLASS_TRUE_COLOR)
197             {
198                 vid = vt->visual_id;
199                 gray = false;
200                 break;
201             }
202             if (fmt->depth == 8 && vt->_class == XCB_VISUAL_CLASS_STATIC_GRAY)
203             {
204                 if (!gray)
205                     continue; /* Prefer color over gray scale */
206                 vid = vt->visual_id;
207                 chroma = VLC_FOURCC ('G', 'R', 'E', 'Y');
208             }
209         }
210
211         if (!vid)
212             continue; /* The screen does not *really* support this depth */
213
214         vout->fmt_out.i_chroma = vout->output.i_chroma = chroma;
215         if (!gray)
216         {
217             vout->output.i_rmask = vt->red_mask;
218             vout->output.i_gmask = vt->green_mask;
219             vout->output.i_bmask = vt->blue_mask;
220         }
221         p_sys->bpp = fmt->bits_per_pixel;
222         p_sys->pad = fmt->scanline_pad;
223         p_sys->depth = depth = fmt->depth;
224     }
225
226     if (depth == 0)
227     {
228         msg_Err (vout, "no supported pixmap formats or visual types");
229         goto error;
230     }
231
232     msg_Dbg (vout, "using X11 visual ID 0x%"PRIx32, vid);
233     msg_Dbg (vout, " %"PRIu8" bits per pixels, %"PRIu8" bits line pad",
234              p_sys->bpp, p_sys->pad);
235
236     /* Create colormap (needed to select non-default visual) */
237     xcb_colormap_t cmap;
238     if (vid != scr->root_visual)
239     {
240         cmap = xcb_generate_id (p_sys->conn);
241         xcb_create_colormap (p_sys->conn, XCB_COLORMAP_ALLOC_NONE,
242                              cmap, scr->root, vid);
243     }
244     else
245         cmap = scr->default_colormap;
246
247     /* Create window */
248     {
249         const uint32_t mask = XCB_CW_EVENT_MASK | XCB_CW_COLORMAP;
250         const uint32_t values[] = {
251             /* XCB_CW_EVENT_MASK */
252             XCB_EVENT_MASK_BUTTON_PRESS | XCB_EVENT_MASK_BUTTON_RELEASE |
253             XCB_EVENT_MASK_POINTER_MOTION,
254             /* XCB_CW_COLORMAP */
255             cmap,
256         };
257         xcb_void_cookie_t c;
258         xcb_window_t window = xcb_generate_id (p_sys->conn);
259
260         c = xcb_create_window_checked (p_sys->conn, depth, window,
261                                        p_sys->embed->handle.xid, 0, 0, 1, 1, 0,
262                                        XCB_WINDOW_CLASS_INPUT_OUTPUT,
263                                        vid, mask, values);
264         if (CheckError (vout, "cannot create X11 window", c))
265             goto error;
266         p_sys->window = window;
267         msg_Dbg (vout, "using X11 window %08"PRIx32, p_sys->window);
268         xcb_map_window (p_sys->conn, window);
269     }
270
271     /* Create graphic context (I wonder why the heck do we need this) */
272     p_sys->gc = xcb_generate_id (p_sys->conn);
273     xcb_create_gc (p_sys->conn, p_sys->gc, p_sys->window, 0, NULL);
274     msg_Dbg (vout, "using X11 graphic context %08"PRIx32, p_sys->gc);
275
276     vout->pf_init = Init;
277     vout->pf_end = Deinit;
278     vout->pf_display = Display;
279     vout->pf_manage = Manage;
280     return VLC_SUCCESS;
281
282 error:
283     Close (obj);
284     return VLC_EGENERIC;
285 }
286
287
288 /**
289  * Disconnect from the X server.
290  */
291 static void Close (vlc_object_t *obj)
292 {
293     vout_thread_t *vout = (vout_thread_t *)obj;
294     vout_sys_t *p_sys = vout->p_sys;
295
296     vout_ReleaseWindow (p_sys->embed);
297     /* colormap and window are garbage-collected by X */
298     xcb_disconnect (p_sys->conn);
299     free (p_sys);
300 }
301
302 #define SHM_ERR ((void *)(intptr_t)(-1))
303
304 static int PictureInit (vout_thread_t *vout, picture_t *pic)
305 {
306     vout_sys_t *p_sys = vout->p_sys;
307
308     assert (pic->i_status == FREE_PICTURE);
309     vout_InitPicture (vout, pic, vout->output.i_chroma,
310                       vout->output.i_width, vout->output.i_height,
311                       vout->output.i_aspect);
312
313     void *shm = SHM_ERR;
314     const size_t size = pic->p->i_pitch * pic->p->i_lines;
315
316     /* Allocate shared memory segment */
317     int id = shmget (IPC_PRIVATE, size, IPC_CREAT | 0700);
318     if (id == -1)
319     {
320         msg_Err (vout, "shared memory allocation error: %m");
321         return VLC_EGENERIC;
322     }
323
324     /* Attach the segment to VLC */
325     shm = shmat (id, NULL, 0 /* read/write */);
326     if (shm == SHM_ERR)
327     {
328         msg_Err (vout, "shared memory attachment error: %m");
329         shmctl (id, IPC_RMID, 0);
330         return VLC_EGENERIC;
331     }
332
333     xcb_shm_seg_t segment;
334     if (p_sys->shm)
335     {
336         /* Attach the segment to X */
337         xcb_void_cookie_t ck;
338         segment = xcb_generate_id (p_sys->conn);
339         ck = xcb_shm_attach_checked (p_sys->conn, segment, id, 1);
340
341         if (CheckError (vout, "shared memory server-side error", ck))
342         {
343             msg_Info (vout, "using buggy X11 server - SSH proxying?");
344             segment = 0;
345         }
346     }
347     else
348         segment = 0;
349
350     shmctl (id, IPC_RMID, 0);
351     pic->p_sys = (void *)(uintptr_t)segment;
352     pic->p->p_pixels = shm;
353     pic->i_status = DESTROYED_PICTURE;
354     pic->i_type = DIRECT_PICTURE;
355     return VLC_SUCCESS;
356 }
357
358
359 /**
360  * Release picture private data
361  */
362 static void PictureDeinit (vout_thread_t *vout, picture_t *pic)
363 {
364     xcb_shm_seg_t segment = (uintptr_t)pic->p_sys;
365
366     if (segment != 0)
367         xcb_shm_detach (vout->p_sys->conn, segment);
368     shmdt (pic->p->p_pixels);
369 }
370
371 /**
372  * Allocate drawable window and picture buffers.
373  */
374 static int Init (vout_thread_t *vout)
375 {
376     vout_sys_t *p_sys = vout->p_sys;
377     unsigned x, y, width, height;
378
379     if (GetWindowSize (p_sys->embed, p_sys->conn, &width, &height))
380         return VLC_EGENERIC;
381     vout_PlacePicture (vout, width, height, &x, &y, &width, &height);
382
383     const uint32_t values[] = { x, y, width, height, };
384     xcb_configure_window (p_sys->conn, p_sys->window,
385                           XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y |
386                           XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT,
387                           values);
388
389     /* FIXME: I don't get the subtlety between output and fmt_out here */
390     vout->fmt_out.i_visible_width = width;
391     vout->fmt_out.i_visible_height = height;
392     vout->fmt_out.i_sar_num = vout->fmt_out.i_sar_den = 1;
393
394     vout->output.i_width = vout->fmt_out.i_width =
395         width * vout->fmt_in.i_width / vout->fmt_in.i_visible_width;
396     vout->output.i_height = vout->fmt_out.i_height =
397         height * vout->fmt_in.i_height / vout->fmt_in.i_visible_height;
398     vout->fmt_out.i_x_offset =
399         width * vout->fmt_in.i_x_offset / vout->fmt_in.i_visible_width;
400     p_vout->fmt_out.i_y_offset =
401         height * vout->fmt_in.i_y_offset / vout->fmt_in.i_visible_height;
402
403     assert (height > 0);
404     vout->output.i_aspect = vout->fmt_out.i_aspect =
405         width * VOUT_ASPECT_FACTOR / height;
406
407     /* Allocate picture buffers */
408     I_OUTPUTPICTURES = 0;
409     for (size_t index = 0; I_OUTPUTPICTURES < 2; index++)
410     {
411         picture_t *pic = vout->p_picture + index;
412
413         if (index > sizeof (vout->p_picture) / sizeof (pic))
414             break;
415         if (pic->i_status != FREE_PICTURE)
416             continue;
417         if (PictureInit (vout, pic))
418             break;
419         PP_OUTPUTPICTURE[I_OUTPUTPICTURES++] = pic;
420     }
421     xcb_flush (p_sys->conn);
422     return VLC_SUCCESS;
423 }
424
425 /**
426  * Free picture buffers.
427  */
428 static void Deinit (vout_thread_t *vout)
429 {
430     for (int i = 0; i < I_OUTPUTPICTURES; i++)
431         PictureDeinit (vout, PP_OUTPUTPICTURE[i]);
432 }
433
434 /**
435  * Sends an image to the X server.
436  */
437 static void Display (vout_thread_t *vout, picture_t *pic)
438 {
439     vout_sys_t *p_sys = vout->p_sys;
440     xcb_shm_seg_t segment = (uintptr_t)pic->p_sys;
441
442     if (segment != 0)
443         xcb_shm_put_image (p_sys->conn, p_sys->window, p_sys->gc,
444           /* real width */ pic->p->i_pitch / pic->p->i_pixel_pitch,
445          /* real height */ pic->p->i_lines, /* x */ 0, /* y */ 0,
446                /* width */ pic->p->i_visible_pitch / pic->p->i_pixel_pitch,
447               /* height */ pic->p->i_visible_lines, /* x */ 0, /* y */ 0,
448                            p_sys->depth, XCB_IMAGE_FORMAT_Z_PIXMAP,
449                            0, segment, 0);
450     else
451         xcb_put_image (p_sys->conn, XCB_IMAGE_FORMAT_Z_PIXMAP,
452                        p_sys->window, p_sys->gc,
453                        pic->p->i_pitch / pic->p->i_pixel_pitch,
454                        pic->p->i_lines, 0, 0, 0, p_sys->depth,
455                        pic->p->i_pitch * pic->p->i_lines, pic->p->p_pixels);
456     xcb_flush (p_sys->conn);
457 }
458
459 /**
460  * Process incoming X events.
461  */
462 static int Manage (vout_thread_t *vout)
463 {
464     vout_sys_t *p_sys = vout->p_sys;
465     xcb_generic_event_t *ev;
466
467     while ((ev = xcb_poll_for_event (p_sys->conn)) != NULL)
468         ProcessEvent (vout, p_sys->conn, p_sys->window, ev);
469
470     if (xcb_connection_has_error (p_sys->conn))
471     {
472         msg_Err (vout, "X server failure");
473         return VLC_EGENERIC;
474     }
475     return VLC_SUCCESS;
476 }