XCB: refactor
[vlc.git] / modules / video_output / xcb / xcb.c
1 /**
2  * @file xcb.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 <xcb/xcb.h>
31 #include <xcb/xcb_aux.h>
32 #include <xcb/xcb_image.h>
33
34 #include <vlc_common.h>
35 #include <vlc_plugin.h>
36 #include <vlc_vout.h>
37 #include <vlc_window.h>
38
39 #define DISPLAY_TEXT N_("X11 display")
40 #define DISPLAY_LONGTEXT N_( \
41     "X11 hardware display to use. By default VLC will " \
42     "use the value of the DISPLAY environment variable.")
43
44 static int  Open (vlc_object_t *);
45 static void Close (vlc_object_t *);
46
47 /*
48  * Module descriptor
49  */
50 vlc_module_begin ()
51     set_shortname (_("XCB"))
52     set_description (_("(Experimental) XCB video output"))
53     set_category (CAT_VIDEO)
54     set_subcategory (SUBCAT_VIDEO_VOUT)
55     set_capability ("video output", 0)
56     set_callbacks (Open, Close)
57
58     add_string ("x11-display", NULL, NULL,
59                 DISPLAY_TEXT, DISPLAY_LONGTEXT, true)
60 vlc_module_end ()
61
62 struct vout_sys_t
63 {
64     xcb_connection_t *conn;
65     xcb_screen_t *screen;
66     vout_window_t *embed; /* VLC window (when windowed) */
67
68     xcb_visualid_t vid;
69     xcb_window_t parent; /* parent X window */
70     xcb_window_t window; /* drawable X window */
71     xcb_gcontext_t gc; /* context to put images */
72     uint8_t bpp; /* bits per pixel */
73 };
74
75 static int Init (vout_thread_t *);
76 static void Deinit (vout_thread_t *);
77 static void Display (vout_thread_t *, picture_t *);
78 static int Manage (vout_thread_t *);
79
80 static int CheckError (vout_thread_t *vout, const char *str,
81                        xcb_void_cookie_t ck)
82 {
83     xcb_generic_error_t *err;
84
85     err = xcb_request_check (vout->p_sys->conn, ck);
86     if (err)
87     {
88         msg_Err (vout, "%s: X11 error %d", str, err->error_code);
89         return VLC_EGENERIC;
90     }
91     return VLC_SUCCESS;
92 }
93
94 #define p_vout vout
95
96 /**
97  * Probe the X server.
98  */
99 static int Open (vlc_object_t *obj)
100 {
101     vout_thread_t *vout = (vout_thread_t *)obj;
102     vout_sys_t *p_sys = malloc (sizeof (*p_sys));
103     if (p_sys == NULL)
104         return VLC_ENOMEM;
105
106     vout->p_sys = p_sys;
107     p_sys->conn = NULL;
108 #ifndef NDEBUG
109     p_sys->embed = NULL;
110 #endif
111
112     /* Connect to X */
113     char *display = var_CreateGetNonEmptyString (vout, "x11-display");
114     int snum;
115     p_sys->conn = xcb_connect (display, &snum);
116     if (xcb_connection_has_error (p_sys->conn) /*== NULL*/)
117     {
118         msg_Err (vout, "cannot connect to X server %s",
119                  display ? display : "");
120         goto error;
121     }
122
123     /* Get the preferred screen */
124     xcb_screen_t *scr = xcb_aux_get_screen (p_sys->conn, snum);
125     p_sys->screen = scr;
126     assert (p_sys->screen);
127     msg_Dbg (vout, "using screen %d (depth %"PRIu8")", snum, scr->root_depth);
128
129     if (strchr ("\x08\x0f\x10\x18\x20", scr->root_depth) == NULL)
130     {
131         msg_Err (vout, "unsupported %"PRIu8"-bits color depth",
132                  scr->root_depth);
133         goto error;
134     }
135
136     /* Determine the visual (color depth and palette) */
137     xcb_visualtype_t *vt = NULL;
138     if ((vt = xcb_aux_find_visual_by_attrs (scr, XCB_VISUAL_CLASS_TRUE_COLOR,
139                                             scr->root_depth)) != NULL)
140         msg_Dbg (vout, "using TrueColor visual ID %d", (int)vt->visual_id);
141     else
142     if ((vt = xcb_aux_find_visual_by_attrs (scr,XCB_VISUAL_CLASS_STATIC_COLOR,
143                                             scr->root_depth)) != NULL)
144         msg_Dbg (vout, "using static color visual ID %d", (int)vt->visual_id);
145     else
146     {
147         vt = xcb_aux_get_visualtype (p_sys->conn, snum, scr->root_visual);
148         assert (vt);
149         msg_Err (vout, "unsupported visual class %"PRIu8, vt->_class);
150         goto error;
151     }
152     p_sys->vid = vt->visual_id;
153
154     vout->pf_init = Init;
155     vout->pf_end = Deinit;
156     vout->pf_display = Display;
157     vout->pf_manage = Manage;
158     return VLC_SUCCESS;
159
160 error:
161     Close (obj);
162     return VLC_EGENERIC;
163 }
164
165
166 /**
167  * Disconnect from the X server.
168  */
169 static void Close (vlc_object_t *obj)
170 {
171     vout_thread_t *vout = (vout_thread_t *)obj;
172     vout_sys_t *p_sys = vout->p_sys;
173
174     assert (p_sys->embed == NULL);
175     if (p_sys->conn)
176         xcb_disconnect (p_sys->conn);
177     free (p_sys);
178 }
179
180 struct picture_sys_t
181 {
182     xcb_image_t *image;
183 };
184
185 static void PictureRelease (picture_t *pic);
186
187 static int PictureInit (vout_thread_t *vout, picture_t *pic)
188 {
189     vout_sys_t *p_sys = vout->p_sys;
190     picture_sys_t *priv = malloc (sizeof (*p_sys));
191
192     if (priv == NULL)
193         return VLC_ENOMEM;
194
195     assert (pic->i_status == FREE_PICTURE);
196     vout_InitPicture (vout, pic, vout->output.i_chroma,
197                       vout->output.i_width, vout->output.i_height,
198                       vout->output.i_aspect);
199
200     const unsigned real_width = pic->p->i_pitch / (p_sys->bpp >> 3);
201     /* FIXME: anyway to getthing more intuitive than that?? */
202
203     /* NOTE: 32-bits scanline_pad assumed, FIXME? (see xdpyinfo) */
204     xcb_image_t *img;
205     img = xcb_image_create (real_width, pic->p->i_lines,
206                             XCB_IMAGE_FORMAT_Z_PIXMAP, 32,
207                             p_sys->screen->root_depth, p_sys->bpp, p_sys->bpp,
208 #ifdef WORDS_BIGENDIAN
209                             XCB_IMAGE_ORDER_MSB_FIRST,
210 #else
211                             XCB_IMAGE_ORDER_LSB_FIRST,
212 #endif
213                             XCB_IMAGE_ORDER_MSB_FIRST,
214                             NULL, 0, NULL);
215
216     if (img == NULL)
217         goto error;
218
219     priv->image = img;
220     pic->p_sys = priv;
221     pic->p->p_pixels = img->data;
222     pic->pf_release = PictureRelease;
223     pic->i_status = DESTROYED_PICTURE;
224     pic->i_type = DIRECT_PICTURE;
225     return VLC_SUCCESS;
226
227 error:
228     free (p_sys);
229     return VLC_EGENERIC;
230 }
231
232
233 /**
234  * Release picture private data
235  */
236 static void PictureRelease (picture_t *pic)
237 {
238     struct picture_sys_t *p_sys = pic->p_sys;
239
240     xcb_image_destroy (p_sys->image);
241     free (p_sys);
242 }
243
244 /**
245  * Allocate drawable window and picture buffers.
246  */
247 static int Init (vout_thread_t *vout)
248 {
249     vout_sys_t *p_sys = vout->p_sys;
250     const xcb_screen_t *screen = p_sys->screen;
251     unsigned x, y, width, height;
252
253     /* Determine parent window */
254     if (vout->b_fullscreen)
255     {
256         p_sys->embed = NULL;
257         p_sys->parent = screen->root;
258         width = screen->width_in_pixels;
259         height = screen->height_in_pixels;
260     }
261     else
262     {
263         p_sys->embed = vout_RequestWindow (vout, &(int){ 0 }, &(int){ 0 },
264                                             &width, &height);
265         if (p_sys->embed == NULL)
266         {
267             msg_Err (vout, "cannot get window");
268             return VLC_EGENERIC;
269         }
270         p_sys->parent = (intptr_t)p_sys->embed->handle;
271     }
272
273     /* Determine our input format */
274     p_sys->bpp = screen->root_depth;
275     switch (screen->root_depth)
276     {
277         case 24:
278             p_sys->bpp = 32;
279         case 32: /* FIXME: untested */
280             vout->output.i_chroma = VLC_FOURCC ('R', 'V', '3', '2');
281             break;
282
283         case 16:
284             vout->output.i_chroma = VLC_FOURCC ('R', 'V', '1', '6');
285             break;
286
287         case 15:
288             p_sys->bpp = 16;
289             vout->output.i_chroma = VLC_FOURCC ('R', 'V', '1', '5');
290             break;
291
292         case 8: /* FIXME: VLC cannot convert */
293             vout->output.i_chroma = VLC_FOURCC ('R', 'G', 'B', '2');
294             break;
295
296         default:
297             assert (0);
298     }
299
300     vout_PlacePicture (vout, width, height, &x, &y, &width, &height);
301     vout->output.i_width = width;
302     vout->output.i_height = height;
303     assert (height > 0);
304     vout->output.i_aspect = width * VOUT_ASPECT_FACTOR / height;
305     /* FIXME: I don't get the subtlety between output and fmt_out here */
306
307     /* Create window */
308     xcb_void_cookie_t c;
309     xcb_window_t window = xcb_generate_id (p_sys->conn);
310
311     p_sys->window = window;
312     c = xcb_create_window_checked (p_sys->conn, screen->root_depth, window,
313                                    p_sys->parent, x, y, width, height, 0,
314                                    XCB_WINDOW_CLASS_INPUT_OUTPUT,
315                                    screen->root_visual, 0, NULL);
316     if (CheckError (vout, "cannot create X11 window", c))
317         goto error;
318     msg_Dbg (vout, "using X11 window %08"PRIx32, p_sys->window);
319     xcb_map_window (p_sys->conn, window);
320
321     /* Create graphic context (I wonder why the heck do we need this) */
322     p_sys->gc = xcb_generate_id (p_sys->conn);
323     xcb_create_gc (p_sys->conn, p_sys->gc, p_sys->window, 0, NULL);
324     msg_Dbg (vout, "using X11 graphic context %08"PRIx32, p_sys->gc);
325     xcb_flush (p_sys->conn);
326
327     /* Allocate picture buffers */
328     do
329     {
330         picture_t *pic = vout->p_picture + I_OUTPUTPICTURES;
331
332         if (PictureInit (vout, pic))
333             break;
334         PP_OUTPUTPICTURE[I_OUTPUTPICTURES++] = pic;
335     }
336     while (I_OUTPUTPICTURES < 2);
337
338     return VLC_SUCCESS;
339
340 error:
341     Deinit (vout);
342     return VLC_EGENERIC;
343 }
344
345 /**
346  * Free picture buffers.
347  */
348 static void Deinit (vout_thread_t *vout)
349 {
350     vout_sys_t *p_sys = vout->p_sys;
351
352     while (I_OUTPUTPICTURES > 0)
353         picture_Release (PP_OUTPUTPICTURE[--I_OUTPUTPICTURES]);
354
355     xcb_unmap_window (p_sys->conn, p_sys->window);
356     xcb_destroy_window (p_sys->conn, p_sys->window);
357     vout_ReleaseWindow (p_sys->embed);
358     p_sys->embed = NULL;
359 }
360
361 /**
362  * Sends an image to the X server.
363  */
364 static void Display (vout_thread_t *vout, picture_t *pic)
365 {
366     vout_sys_t *p_sys = vout->p_sys;
367     xcb_image_t *img = pic->p_sys->image;
368     xcb_image_t *native = xcb_image_native (p_sys->conn, img, 1);
369
370     if (native == NULL)
371     {
372         msg_Err (vout, "image conversion failure");
373         return;
374     }
375
376     xcb_image_put (p_sys->conn, p_sys->window, p_sys->gc, native, 0, 0, 0);
377     xcb_flush (p_sys->conn);
378
379     if (native != img)
380     {
381         /*msg_Warn (vout, "image not in native X11 pixmap format");*/
382         xcb_image_destroy (native);
383     }
384 }
385
386 /**
387  * Process incoming X events.
388  */
389 static int Manage (vout_thread_t *vout)
390 {
391     vout_sys_t *p_sys = vout->p_sys;
392     xcb_generic_event_t *ev;
393
394     while ((ev = xcb_poll_for_event (p_sys->conn)) != NULL)
395     {
396         switch (ev->response_type & ~0x80)
397         {
398         default:
399             msg_Dbg (vout, "unhandled event %02x", (unsigned)ev->response_type);
400         }
401         free (ev);
402     }
403
404     if (xcb_connection_has_error (p_sys->conn))
405     {
406         msg_Err (vout, "X server failure");
407         return VLC_EGENERIC;
408     }
409     return VLC_SUCCESS;
410 }