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