3 * @brief X C Bindings video output module for VLC media player
5 /*****************************************************************************
6 * Copyright © 2009 Rémi Denis-Courmont
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
11 * of the License, or (at your option) any later version.
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.
18 * You should have received a copy of the GNU 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 ****************************************************************************/
33 #include <vlc_common.h>
34 #include <vlc_plugin.h>
35 #include <vlc_vout_display.h>
36 #include <vlc_picture_pool.h>
40 #define SHM_TEXT N_("Use shared memory")
41 #define SHM_LONGTEXT N_( \
42 "Use shared memory to communicate between VLC and the X server.")
44 static int Open (vlc_object_t
*);
45 static void Close (vlc_object_t
*);
51 set_shortname (N_("X11"))
52 set_description (N_("X11 video output (XCB)"))
53 set_category (CAT_VIDEO
)
54 set_subcategory (SUBCAT_VIDEO_VOUT
)
55 set_capability ("vout display", 75)
56 set_callbacks (Open
, Close
)
57 add_shortcut ("xcb-x11", "x11", "xid")
59 add_bool ("x11-shm", true, NULL
, SHM_TEXT
, SHM_LONGTEXT
, true)
62 /* It must be large enough to absorb the server display jitter but it is
63 * useless to used a too large value, direct rendering cannot be used with
66 #define MAX_PICTURES (3)
68 struct vout_display_sys_t
70 xcb_connection_t
*conn
;
71 vout_window_t
*embed
; /* VLC window (when windowed) */
73 xcb_cursor_t cursor
; /* blank cursor */
74 xcb_window_t window
; /* drawable X window */
75 xcb_gcontext_t gc
; /* context to put images */
76 bool shm
; /* whether to use MIT-SHM */
77 bool visible
; /* whether to draw */
78 uint8_t bpp
; /* bits per pixel */
79 uint8_t pad
; /* scanline pad */
80 uint8_t depth
; /* useful bits per pixel */
81 uint8_t byte_order
; /* server byte order */
83 picture_pool_t
*pool
; /* picture pool */
84 picture_resource_t resource
[MAX_PICTURES
];
87 static picture_pool_t
*Pool (vout_display_t
*, unsigned);
88 static void Display (vout_display_t
*, picture_t
*);
89 static int Control (vout_display_t
*, int, va_list);
90 static void Manage (vout_display_t
*);
92 static void ResetPictures (vout_display_t
*);
94 static const xcb_depth_t
*FindDepth (const xcb_screen_t
*scr
,
97 xcb_depth_t
*d
= NULL
;
98 for (xcb_depth_iterator_t it
= xcb_screen_allowed_depths_iterator (scr
);
99 it
.rem
> 0 && d
== NULL
;
100 xcb_depth_next (&it
))
102 if (it
.data
->depth
== depth
)
111 * Probe the X server.
113 static int Open (vlc_object_t
*obj
)
115 vout_display_t
*vd
= (vout_display_t
*)obj
;
116 vout_display_sys_t
*p_sys
= malloc (sizeof (*p_sys
));
123 /* Get window, connect to X server */
124 const xcb_screen_t
*scr
;
125 p_sys
->embed
= GetWindow (vd
, &p_sys
->conn
, &scr
, &(uint8_t){ 0 });
126 if (p_sys
->embed
== NULL
)
132 const xcb_setup_t
*setup
= xcb_get_setup (p_sys
->conn
);
133 p_sys
->byte_order
= setup
->image_byte_order
;
135 /* Determine our pixel format */
136 xcb_visualid_t vid
= 0;
139 for (const xcb_format_t
*fmt
= xcb_setup_pixmap_formats (setup
),
140 *end
= fmt
+ xcb_setup_pixmap_formats_length (setup
);
144 if (fmt
->depth
<= p_sys
->depth
)
145 continue; /* no better than earlier format */
147 video_format_t fmt_pic
= vd
->fmt
;
149 /* Check that the pixmap format is supported by VLC. */
153 if (fmt
->bits_per_pixel
!= 32)
155 #ifdef FIXED_VLC_RGBA_MASK
156 fmt_pic
.i_chroma
= VLC_CODEC_RGBA
;
159 msg_Dbg (vd
, "X11 visual with alpha-channel not supported");
163 if (fmt
->bits_per_pixel
== 32)
164 fmt_pic
.i_chroma
= VLC_CODEC_RGB32
;
165 else if (fmt
->bits_per_pixel
== 24)
166 fmt_pic
.i_chroma
= VLC_CODEC_RGB24
;
171 if (fmt
->bits_per_pixel
!= 16)
173 fmt_pic
.i_chroma
= VLC_CODEC_RGB16
;
176 if (fmt
->bits_per_pixel
!= 16)
178 fmt_pic
.i_chroma
= VLC_CODEC_RGB15
;
181 if (fmt
->bits_per_pixel
!= 8)
183 fmt_pic
.i_chroma
= VLC_CODEC_RGB8
;
189 /* VLC pads lines to 16 pixels internally */
190 if ((fmt
->bits_per_pixel
<< 4) % fmt
->scanline_pad
)
193 /* Byte sex is a non-issue for 8-bits. It can be worked around with
194 * RGB masks for 24-bits. Too bad for 15-bits and 16-bits. */
195 if (fmt
->bits_per_pixel
== 16 && setup
->image_byte_order
!= ORDER
)
198 /* Check that the selected screen supports this depth */
199 const xcb_depth_t
*d
= FindDepth (scr
, fmt
->depth
);
203 /* Find a visual type for the selected depth */
204 const xcb_visualtype_t
*vt
= xcb_depth_visuals (d
);
206 /* First try True Color class */
207 for (int i
= xcb_depth_visuals_length (d
); i
> 0; i
--)
209 if (vt
->_class
== XCB_VISUAL_CLASS_TRUE_COLOR
)
211 fmt_pic
.i_rmask
= vt
->red_mask
;
212 fmt_pic
.i_gmask
= vt
->green_mask
;
213 fmt_pic
.i_bmask
= vt
->blue_mask
;
218 /* Then try Static Gray class */
220 for (int i
= xcb_depth_visuals_length (d
); i
> 0 && !vid
; i
--)
222 if (vt
->_class
== XCB_VISUAL_CLASS_STATIC_GRAY
)
227 continue; /* Fail: unusable pixel format */
230 fmt_pic
.i_chroma
= VLC_CODEC_GREY
;
232 p_sys
->bpp
= fmt
->bits_per_pixel
;
233 p_sys
->pad
= fmt
->scanline_pad
;
234 p_sys
->depth
= fmt
->depth
;
241 msg_Err (obj
, "no supported pixel format & visual");
245 msg_Dbg (vd
, "using X11 visual ID 0x%"PRIx32
, vid
);
246 msg_Dbg (vd
, " %"PRIu8
" bits depth", p_sys
->depth
);
247 msg_Dbg (vd
, " %"PRIu8
" bits per pixel", p_sys
->bpp
);
248 msg_Dbg (vd
, " %"PRIu8
" bits line pad", p_sys
->pad
);
250 /* Create colormap (needed to select non-default visual) */
252 if (vid
!= scr
->root_visual
)
254 cmap
= xcb_generate_id (p_sys
->conn
);
255 xcb_create_colormap (p_sys
->conn
, XCB_COLORMAP_ALLOC_NONE
,
256 cmap
, scr
->root
, vid
);
259 cmap
= scr
->default_colormap
;
262 unsigned width
, height
;
263 if (GetWindowSize (p_sys
->embed
, p_sys
->conn
, &width
, &height
))
266 p_sys
->window
= xcb_generate_id (p_sys
->conn
);
267 p_sys
->gc
= xcb_generate_id (p_sys
->conn
);
268 xcb_pixmap_t pixmap
= xcb_generate_id (p_sys
->conn
);
270 const uint32_t mask
=
273 XCB_CW_BORDER_PIXMAP
|
274 XCB_CW_BORDER_PIXEL
|
277 const uint32_t values
[] = {
278 /* XCB_CW_BACK_PIXMAP */
280 /* XCB_CW_BACK_PIXEL */
282 /* XCB_CW_BORDER_PIXMAP */
284 /* XCB_CW_BORDER_PIXEL */
286 /* XCB_CW_EVENT_MASK */
287 XCB_EVENT_MASK_VISIBILITY_CHANGE
,
288 /* XCB_CW_COLORMAP */
293 xcb_create_pixmap (p_sys
->conn
, p_sys
->depth
, pixmap
, scr
->root
, 1, 1);
294 c
= xcb_create_window_checked (p_sys
->conn
, p_sys
->depth
,
296 p_sys
->embed
->handle
.xid
, 0, 0,
298 XCB_WINDOW_CLASS_INPUT_OUTPUT
,
300 xcb_map_window (p_sys
->conn
, p_sys
->window
);
301 /* Create graphic context (I wonder why the heck do we need this) */
302 xcb_create_gc (p_sys
->conn
, p_sys
->gc
, p_sys
->window
, 0, NULL
);
304 if (CheckError (vd
, p_sys
->conn
, "cannot create X11 window", c
))
307 msg_Dbg (vd
, "using X11 window %08"PRIx32
, p_sys
->window
);
308 msg_Dbg (vd
, "using X11 graphic context %08"PRIx32
, p_sys
->gc
);
309 p_sys
->cursor
= CreateBlankCursor (p_sys
->conn
, scr
);
311 p_sys
->visible
= false;
313 CheckSHM (obj
, p_sys
->conn
, &p_sys
->shm
);
316 vout_display_info_t info
= vd
->info
;
317 info
.has_pictures_invalid
= true;
318 info
.has_event_thread
= true;
320 /* Setup vout_display_t once everything is fine */
325 vd
->display
= Display
;
326 vd
->control
= Control
;
330 bool is_fullscreen
= vd
->cfg
->is_fullscreen
;
331 if (is_fullscreen
&& vout_window_SetFullScreen (p_sys
->embed
, true))
332 is_fullscreen
= false;
333 vout_display_SendEventFullscreen (vd
, is_fullscreen
);
334 vout_display_SendEventDisplaySize (vd
, width
, height
, is_fullscreen
);
345 * Disconnect from the X server.
347 static void Close (vlc_object_t
*obj
)
349 vout_display_t
*vd
= (vout_display_t
*)obj
;
350 vout_display_sys_t
*p_sys
= vd
->sys
;
354 /* show the default cursor */
355 xcb_change_window_attributes (p_sys
->conn
, p_sys
->embed
->handle
.xid
, XCB_CW_CURSOR
,
356 &(uint32_t) { XCB_CURSOR_NONE
});
357 xcb_flush (p_sys
->conn
);
359 /* colormap, window and context are garbage-collected by X */
360 xcb_disconnect (p_sys
->conn
);
361 vout_display_DeleteWindow (vd
, p_sys
->embed
);
366 * Return a direct buffer
368 static picture_pool_t
*Pool (vout_display_t
*vd
, unsigned requested_count
)
370 vout_display_sys_t
*p_sys
= vd
->sys
;
371 (void)requested_count
;
375 vout_display_place_t place
;
377 vout_display_PlacePicture (&place
, &vd
->source
, vd
->cfg
, false);
380 const uint32_t values
[] = { place
.x
, place
.y
, place
.width
, place
.height
};
381 xcb_configure_window (p_sys
->conn
, p_sys
->window
,
382 XCB_CONFIG_WINDOW_X
| XCB_CONFIG_WINDOW_Y
|
383 XCB_CONFIG_WINDOW_WIDTH
| XCB_CONFIG_WINDOW_HEIGHT
,
386 picture_t
*pic
= picture_NewFromFormat (&vd
->fmt
);
390 assert (pic
->i_planes
== 1);
391 memset (p_sys
->resource
, 0, sizeof(p_sys
->resource
));
394 picture_t
*pic_array
[MAX_PICTURES
];
395 for (count
= 0; count
< MAX_PICTURES
; count
++)
397 picture_resource_t
*res
= &p_sys
->resource
[count
];
399 res
->p
->i_lines
= pic
->p
->i_lines
;
400 res
->p
->i_pitch
= pic
->p
->i_pitch
;
401 if (PictureResourceAlloc (vd
, res
, res
->p
->i_pitch
* res
->p
->i_lines
,
402 p_sys
->conn
, p_sys
->shm
))
404 pic_array
[count
] = picture_NewFromResource (&vd
->fmt
, res
);
405 if (!pic_array
[count
])
407 PictureResourceFree (res
, p_sys
->conn
);
408 memset (res
, 0, sizeof(*res
));
412 picture_Release (pic
);
417 p_sys
->pool
= picture_pool_New (count
, pic_array
);
418 /* TODO release picture resources if NULL */
419 xcb_flush (p_sys
->conn
);
426 * Sends an image to the X server.
428 static void Display (vout_display_t
*vd
, picture_t
*pic
)
430 vout_display_sys_t
*p_sys
= vd
->sys
;
431 xcb_shm_seg_t segment
= pic
->p_sys
->segment
;
432 xcb_void_cookie_t ck
;
437 ck
= xcb_shm_put_image_checked (p_sys
->conn
, p_sys
->window
, p_sys
->gc
,
438 /* real width */ pic
->p
->i_pitch
/ pic
->p
->i_pixel_pitch
,
439 /* real height */ pic
->p
->i_lines
,
440 /* x */ vd
->fmt
.i_x_offset
,
441 /* y */ vd
->fmt
.i_y_offset
,
442 /* width */ vd
->fmt
.i_visible_width
,
443 /* height */ vd
->fmt
.i_visible_height
,
444 0, 0, p_sys
->depth
, XCB_IMAGE_FORMAT_Z_PIXMAP
,
448 const size_t offset
= vd
->fmt
.i_y_offset
* pic
->p
->i_pitch
;
449 const unsigned lines
= pic
->p
->i_lines
- vd
->fmt
.i_y_offset
;
451 ck
= xcb_put_image_checked (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 lines
, -vd
->fmt
.i_x_offset
, 0, 0, p_sys
->depth
,
455 pic
->p
->i_pitch
* lines
, pic
->p
->p_pixels
+ offset
);
458 /* Wait for reply. This makes sure that the X server gets CPU time to
459 * display the picture. xcb_flush() is *not* sufficient: especially with
460 * shared memory the PUT requests are so short that many of them can fit in
461 * X11 socket output buffer before the kernel preempts VLC. */
462 xcb_generic_error_t
*e
= xcb_request_check (p_sys
->conn
, ck
);
465 msg_Dbg (vd
, "%s: X11 error %d", "cannot put image", e
->error_code
);
469 /* FIXME might be WAY better to wait in some case (be carefull with
470 * VOUT_DISPLAY_RESET_PICTURES if done) + does not work with
471 * vout_display wrapper. */
473 picture_Release (pic
);
476 static int Control (vout_display_t
*vd
, int query
, va_list ap
)
478 vout_display_sys_t
*p_sys
= vd
->sys
;
482 case VOUT_DISPLAY_CHANGE_FULLSCREEN
:
484 const vout_display_cfg_t
*c
= va_arg (ap
, const vout_display_cfg_t
*);
485 return vout_window_SetFullScreen (p_sys
->embed
, c
->is_fullscreen
);
488 case VOUT_DISPLAY_CHANGE_DISPLAY_SIZE
:
490 const vout_display_cfg_t
*p_cfg
=
491 (const vout_display_cfg_t
*)va_arg (ap
, const vout_display_cfg_t
*);
492 const bool is_forced
= (bool)va_arg (ap
, int);
495 && vout_window_SetSize (p_sys
->embed
,
496 p_cfg
->display
.width
,
497 p_cfg
->display
.height
))
500 vout_display_place_t place
;
501 vout_display_PlacePicture (&place
, &vd
->source
, p_cfg
, false);
503 if (place
.width
!= vd
->fmt
.i_visible_width
||
504 place
.height
!= vd
->fmt
.i_visible_height
)
506 vout_display_SendEventPicturesInvalid (vd
);
510 /* Move the picture within the window */
511 const uint32_t values
[] = { place
.x
, place
.y
};
512 xcb_configure_window (p_sys
->conn
, p_sys
->window
,
513 XCB_CONFIG_WINDOW_X
| XCB_CONFIG_WINDOW_Y
,
517 case VOUT_DISPLAY_CHANGE_WINDOW_STATE
:
519 unsigned state
= va_arg (ap
, unsigned);
520 return vout_window_SetState (p_sys
->embed
, state
);
523 case VOUT_DISPLAY_CHANGE_ZOOM
:
524 case VOUT_DISPLAY_CHANGE_DISPLAY_FILLED
:
525 case VOUT_DISPLAY_CHANGE_SOURCE_ASPECT
:
526 case VOUT_DISPLAY_CHANGE_SOURCE_CROP
:
527 /* I am not sure it is always necessary, but it is way simpler ... */
528 vout_display_SendEventPicturesInvalid (vd
);
531 case VOUT_DISPLAY_RESET_PICTURES
:
535 vout_display_place_t place
;
536 vout_display_PlacePicture (&place
, &vd
->source
, vd
->cfg
, false);
538 vd
->fmt
.i_width
= vd
->source
.i_width
* place
.width
/ vd
->source
.i_visible_width
;
539 vd
->fmt
.i_height
= vd
->source
.i_height
* place
.height
/ vd
->source
.i_visible_height
;
541 vd
->fmt
.i_visible_width
= place
.width
;
542 vd
->fmt
.i_visible_height
= place
.height
;
543 vd
->fmt
.i_x_offset
= vd
->source
.i_x_offset
* place
.width
/ vd
->source
.i_visible_width
;
544 vd
->fmt
.i_y_offset
= vd
->source
.i_y_offset
* place
.height
/ vd
->source
.i_visible_height
;
548 /* Hide the mouse. It will be send when
549 * vout_display_t::info.b_hide_mouse is false */
550 case VOUT_DISPLAY_HIDE_MOUSE
:
551 xcb_change_window_attributes (p_sys
->conn
, p_sys
->embed
->handle
.xid
,
552 XCB_CW_CURSOR
, &(uint32_t){ p_sys
->cursor
});
556 msg_Err (vd
, "Unknown request in XCB vout display");
561 static void Manage (vout_display_t
*vd
)
563 vout_display_sys_t
*p_sys
= vd
->sys
;
565 ManageEvent (vd
, p_sys
->conn
, &p_sys
->visible
);
568 static void ResetPictures (vout_display_t
*vd
)
570 vout_display_sys_t
*p_sys
= vd
->sys
;
575 for (unsigned i
= 0; i
< MAX_PICTURES
; i
++)
577 picture_resource_t
*res
= &p_sys
->resource
[i
];
579 if (!res
->p
->p_pixels
)
581 PictureResourceFree (res
, p_sys
->conn
);
583 picture_pool_Delete (p_sys
->pool
);