3 * @brief X C Bindings window provider 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 ****************************************************************************/
30 #include <unistd.h> /* gethostname() and sysconf() */
31 #include <limits.h> /* _POSIX_HOST_NAME_MAX */
34 typedef xcb_atom_t Atom;
35 #include <X11/Xatom.h> /* XA_WM_NAME */
37 #include <vlc_common.h>
38 #include <vlc_plugin.h>
39 #include <vlc_vout_window.h>
43 #define DISPLAY_TEXT N_("X11 display")
44 #define DISPLAY_LONGTEXT N_( \
45 "Video will be rendered with this X11 display. " \
46 "If empty, the default display will be used.")
48 #define XID_TEXT N_("X11 window ID")
49 #define XID_LONGTEXT N_( \
50 "Video will be embedded in this pre-existing window. " \
51 "If zero, a new window will be created.")
53 static int Open (vout_window_t *, const vout_window_cfg_t *);
54 static void Close (vout_window_t *);
55 static int EmOpen (vout_window_t *, const vout_window_cfg_t *);
56 static void EmClose (vout_window_t *);
62 set_shortname (N_("X window"))
63 set_description (N_("X11 video window (XCB)"))
64 set_category (CAT_VIDEO)
65 set_subcategory (SUBCAT_VIDEO_VOUT)
66 set_capability ("vout window xid", 10)
67 set_callbacks (Open, Close)
69 /* Obsolete since 1.1.0: */
70 add_obsolete_bool ("x11-altfullscreen")
71 add_obsolete_bool ("xvideo-altfullscreen")
72 add_obsolete_bool ("xvmc-altfullscreen")
73 add_obsolete_bool ("glx-altfullscreen")
76 set_shortname (N_("Drawable"))
77 set_description (N_("Embedded window video"))
78 set_category (CAT_VIDEO)
79 set_subcategory (SUBCAT_VIDEO_VOUT)
80 set_capability ("vout window xid", 70)
81 set_callbacks (EmOpen, EmClose)
82 add_shortcut ("embed-xid")
84 add_string ("x11-display", NULL, DISPLAY_TEXT, DISPLAY_LONGTEXT, true)
85 add_integer ("drawable-xid", 0, XID_TEXT, XID_LONGTEXT, true)
90 static int Control (vout_window_t *, int, va_list ap);
91 static void *Thread (void *);
93 struct vout_window_sys_t
95 xcb_connection_t *conn;
101 xcb_atom_t wm_state_above;
102 xcb_atom_t wm_state_below;
103 xcb_atom_t wm_state_fullscreen;
108 /** Set an X window property from a nul-terminated string */
110 void set_string (xcb_connection_t *conn, xcb_window_t window,
111 xcb_atom_t type, xcb_atom_t atom, const char *str)
113 xcb_change_property (conn, XCB_PROP_MODE_REPLACE, window, atom, type,
114 /* format */ 8, strlen (str), str);
117 /** Set an X window string property */
119 void set_ascii_prop (xcb_connection_t *conn, xcb_window_t window,
120 xcb_atom_t atom, const char *value)
122 set_string (conn, window, XA_STRING, atom, value);
126 void set_wm_hints (xcb_connection_t *conn, xcb_window_t window)
128 static const uint32_t wm_hints[8] = {
129 3, /* flags: Input, Initial state */
131 1, /* initial state: Normal */
132 0, 0, 0, 0, 0, /* Icon */
134 xcb_change_property (conn, XCB_PROP_MODE_REPLACE, window, XA_WM_HINTS,
135 XA_WM_HINTS, 32, 8, wm_hints);
138 /** Set the Window ICCCM client machine property */
140 void set_hostname_prop (xcb_connection_t *conn, xcb_window_t window)
143 long host_name_max = sysconf (_SC_HOST_NAME_MAX);
144 if (host_name_max <= 0) host_name_max = _POSIX_HOST_NAME_MAX;
145 hostname = malloc (host_name_max);
146 if(!hostname) return;
148 if (gethostname (hostname, host_name_max) == 0)
150 hostname[host_name_max - 1] = '\0';
151 set_ascii_prop (conn, window, XA_WM_CLIENT_MACHINE, hostname);
156 /** Request the X11 server to internalize a string into an atom */
158 xcb_intern_atom_cookie_t intern_string (xcb_connection_t *c, const char *s)
160 return xcb_intern_atom (c, 0, strlen (s), s);
163 /** Extract the X11 atom from an intern request cookie */
165 xcb_atom_t get_atom (xcb_connection_t *conn, xcb_intern_atom_cookie_t ck)
167 xcb_intern_atom_reply_t *reply;
170 reply = xcb_intern_atom_reply (conn, ck, NULL);
179 #define NET_WM_STATE_REMOVE 0
180 #define NET_WM_STATE_ADD 1
181 #define NET_WM_STATE_TOGGLE 2
183 static void CacheAtoms (vout_window_sys_t *p_sys)
185 xcb_connection_t *conn = p_sys->conn;
186 xcb_intern_atom_cookie_t wm_state_ck, wm_state_above_ck,
187 wm_state_below_ck, wm_state_fs_ck;
189 wm_state_ck = intern_string (conn, "_NET_WM_STATE");
190 wm_state_above_ck = intern_string (conn, "_NET_WM_STATE_ABOVE");
191 wm_state_below_ck = intern_string (conn, "_NET_WM_STATE_BELOW");
192 wm_state_fs_ck = intern_string (conn, "_NET_WM_STATE_FULLSCREEN");
194 p_sys->wm_state = get_atom (conn, wm_state_ck);
195 p_sys->wm_state_above = get_atom (conn, wm_state_above_ck);
196 p_sys->wm_state_below = get_atom (conn, wm_state_below_ck);
197 p_sys->wm_state_fullscreen = get_atom (conn, wm_state_fs_ck);
201 * Create an X11 window.
203 static int Open (vout_window_t *wnd, const vout_window_cfg_t *cfg)
205 xcb_generic_error_t *err;
206 xcb_void_cookie_t ck;
208 vout_window_sys_t *p_sys = malloc (sizeof (*p_sys));
211 p_sys->embedded = false;
214 char *display = var_InheritString (wnd, "x11-display");
217 xcb_connection_t *conn = xcb_connect (display, &snum);
218 if (xcb_connection_has_error (conn) /*== NULL*/)
221 /* Find configured screen */
222 const xcb_setup_t *setup = xcb_get_setup (conn);
223 const xcb_screen_t *scr = NULL;
224 for (xcb_screen_iterator_t i = xcb_setup_roots_iterator (setup);
225 i.rem > 0; xcb_screen_next (&i))
236 msg_Err (wnd, "bad X11 screen number");
241 const uint32_t mask = XCB_CW_BACK_PIXEL | XCB_CW_EVENT_MASK;
242 uint32_t values[2] = {
243 /* XCB_CW_BACK_PIXEL */
245 /* XCB_CW_EVENT_MASK */
246 XCB_EVENT_MASK_KEY_PRESS,
249 xcb_window_t window = xcb_generate_id (conn);
250 ck = xcb_create_window_checked (conn, scr->root_depth, window, scr->root,
251 cfg->x, cfg->y, cfg->width, cfg->height, 0,
252 XCB_WINDOW_CLASS_INPUT_OUTPUT,
253 scr->root_visual, mask, values);
254 err = xcb_request_check (conn, ck);
257 msg_Err (wnd, "creating window: X11 error %d", err->error_code);
262 wnd->handle.xid = window;
263 wnd->display.x11 = display;
264 wnd->control = Control;
268 if (var_InheritBool (wnd, "keyboard-events"))
269 p_sys->keys = CreateKeyHandler (VLC_OBJECT(wnd), conn);
272 p_sys->root = scr->root;
275 * No cut&paste nor drag&drop, only Window Manager communication. */
276 set_ascii_prop (conn, window, XA_WM_NAME,
277 /* xgettext: This is a plain ASCII spelling of "VLC media player"
278 for the ICCCM window name. This must be pure ASCII.
279 The limitation is partially with ICCCM and partially with VLC.
280 For Latin script languages, you may need to strip accents.
281 For other scripts, you will need to transliterate into Latin. */
282 vlc_pgettext ("ASCII", "VLC media player"));
284 set_ascii_prop (conn, window, XA_WM_ICON_NAME,
285 /* xgettext: This is a plain ASCII spelling of "VLC"
286 for the ICCCM window name. This must be pure ASCII. */
287 vlc_pgettext ("ASCII", "VLC"));
288 set_wm_hints (conn, window);
289 xcb_change_property (conn, XCB_PROP_MODE_REPLACE, window, XA_WM_CLASS,
290 XA_STRING, 8, 8, "vlc\0Vlc");
291 set_hostname_prop (conn, window);
294 xcb_intern_atom_cookie_t utf8_string_ck
295 = intern_string (conn, "UTF8_STRING");;
296 xcb_intern_atom_cookie_t net_wm_name_ck
297 = intern_string (conn, "_NET_WM_NAME");
298 xcb_intern_atom_cookie_t net_wm_icon_name_ck
299 = intern_string (conn, "_NET_WM_ICON_NAME");
300 xcb_intern_atom_cookie_t wm_window_role_ck
301 = intern_string (conn, "WM_WINDOW_ROLE");
303 xcb_atom_t utf8 = get_atom (conn, utf8_string_ck);
305 xcb_atom_t net_wm_name = get_atom (conn, net_wm_name_ck);
306 char *title = var_InheritString (wnd, "video-title");
309 set_string (conn, window, utf8, net_wm_name, title);
313 set_string (conn, window, utf8, net_wm_name, _("VLC media player"));
315 xcb_atom_t net_wm_icon_name = get_atom (conn, net_wm_icon_name_ck);
316 set_string (conn, window, utf8, net_wm_icon_name, _("VLC"));
318 xcb_atom_t wm_window_role = get_atom (conn, wm_window_role_ck);
319 set_ascii_prop (conn, window, wm_window_role, "vlc-video");
321 /* Cache any EWMH atom we may need later */
324 /* Make the window visible */
325 xcb_map_window (conn, window);
327 if (var_InheritBool (wnd, "video-wallpaper"))
329 vout_window_SetState (wnd, VOUT_WINDOW_STATE_BELOW);
330 vout_window_SetFullScreen (wnd, true);
333 /* Create the event thread. It will dequeue all events, so any checked
334 * request from this thread must be completed at this point. */
335 if ((p_sys->keys != NULL)
336 && vlc_clone (&p_sys->thread, Thread, wnd, VLC_THREAD_PRIORITY_LOW))
337 DestroyKeyHandler (p_sys->keys);
339 xcb_flush (conn); /* Make sure map_window is sent (should be useless) */
343 xcb_disconnect (conn);
351 * Destroys the X11 window.
353 static void Close (vout_window_t *wnd)
355 vout_window_sys_t *p_sys = wnd->sys;
356 xcb_connection_t *conn = p_sys->conn;
360 vlc_cancel (p_sys->thread);
361 vlc_join (p_sys->thread, NULL);
362 DestroyKeyHandler (p_sys->keys);
364 xcb_disconnect (conn);
365 free (wnd->display.x11);
370 /** Background thread for X11 events handling */
371 static void *Thread (void *data)
373 vout_window_t *wnd = data;
374 vout_window_sys_t *p_sys = wnd->sys;
375 xcb_connection_t *conn = p_sys->conn;
377 int fd = xcb_get_file_descriptor (conn);
383 xcb_generic_event_t *ev;
384 struct pollfd ufd = { .fd = fd, .events = POLLIN, };
388 int canc = vlc_savecancel ();
389 while ((ev = xcb_poll_for_event (conn)) != NULL)
391 if (ProcessKeyEvent (p_sys->keys, ev) == 0)
393 msg_Dbg (wnd, "unhandled event: %"PRIu8, ev->response_type);
396 vlc_restorecancel (canc);
398 if (xcb_connection_has_error (conn))
400 msg_Err (wnd, "X server failure");
407 /** Changes the EWMH state of the window */
408 static void set_wm_state (vout_window_t *wnd, bool on, xcb_atom_t state)
410 vout_window_sys_t *sys = wnd->sys;
411 /* From EWMH "_WM_STATE" */
412 xcb_client_message_event_t ev = {
413 .response_type = XCB_CLIENT_MESSAGE,
415 .window = wnd->handle.xid,
416 .type = sys->wm_state,
419 ev.data.data32[0] = on ? NET_WM_STATE_ADD : NET_WM_STATE_REMOVE;
420 ev.data.data32[1] = state;
421 ev.data.data32[2] = 0;
422 ev.data.data32[3] = 1;
424 /* From ICCCM "Changing Window State" */
425 xcb_send_event (sys->conn, 0, sys->root,
426 XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY |
427 XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT,
432 static int Control (vout_window_t *wnd, int cmd, va_list ap)
434 vout_window_sys_t *p_sys = wnd->sys;
435 xcb_connection_t *conn = p_sys->conn;
439 case VOUT_WINDOW_SET_SIZE:
444 unsigned width = va_arg (ap, unsigned);
445 unsigned height = va_arg (ap, unsigned);
446 const uint32_t values[] = { width, height, };
448 xcb_configure_window (conn, wnd->handle.xid,
449 XCB_CONFIG_WINDOW_WIDTH |
450 XCB_CONFIG_WINDOW_HEIGHT, values);
454 case VOUT_WINDOW_SET_STATE:
456 unsigned state = va_arg (ap, unsigned);
457 bool above = (state & VOUT_WINDOW_STATE_ABOVE) != 0;
458 bool below = (state & VOUT_WINDOW_STATE_BELOW) != 0;
460 set_wm_state (wnd, above, p_sys->wm_state_above);
461 set_wm_state (wnd, below, p_sys->wm_state_below);
465 case VOUT_WINDOW_SET_FULLSCREEN:
467 bool fs = va_arg (ap, int);
468 if (!fs && var_GetBool (wnd, "video-wallpaper"))
470 set_wm_state (wnd, fs, p_sys->wm_state_fullscreen);
475 msg_Err (wnd, "request %d not implemented", cmd);
478 xcb_flush (p_sys->conn);
482 /*** Embedded drawable support ***/
484 static vlc_mutex_t serializer = VLC_STATIC_MUTEX;
486 /** Acquire a drawable */
487 static int AcquireDrawable (vlc_object_t *obj, xcb_window_t window)
492 if (var_Create (obj->p_libvlc, "xid-in-use", VLC_VAR_ADDRESS))
495 /* Keep a list of busy drawables, so we don't overlap videos if there are
496 * more than one video track in the stream. */
497 vlc_mutex_lock (&serializer);
498 used = var_GetAddress (obj->p_libvlc, "xid-in-use");
503 if (used[n] == window)
509 used = realloc (used, sizeof (*used) * (n + 2));
514 var_SetAddress (obj->p_libvlc, "xid-in-use", used);
519 msg_Warn (obj, "X11 drawable 0x%08"PRIx8" is busy", window);
522 vlc_mutex_unlock (&serializer);
524 return (window == 0) ? VLC_EGENERIC : VLC_SUCCESS;
527 /** Remove this drawable from the list of busy ones */
528 static void ReleaseDrawable (vlc_object_t *obj, xcb_window_t window)
533 vlc_mutex_lock (&serializer);
534 used = var_GetAddress (obj->p_libvlc, "xid-in-use");
536 while (used[n] != window)
542 used[n] = used[n + 1];
546 var_SetAddress (obj->p_libvlc, "xid-in-use", NULL);
547 vlc_mutex_unlock (&serializer);
551 /* Variables are reference-counted... */
552 var_Destroy (obj->p_libvlc, "xid-in-use");
556 * Wrap an existing X11 window to embed the video.
558 static int EmOpen (vout_window_t *wnd, const vout_window_cfg_t *cfg)
560 xcb_window_t window = var_InheritInteger (wnd, "drawable-xid");
564 if (AcquireDrawable (VLC_OBJECT(wnd), window))
567 vout_window_sys_t *p_sys = malloc (sizeof (*p_sys));
568 xcb_connection_t *conn = xcb_connect (NULL, NULL);
569 if (p_sys == NULL || xcb_connection_has_error (conn))
572 p_sys->embedded = true;
574 wnd->handle.xid = window;
575 wnd->control = Control;
580 xcb_get_geometry_reply_t *geo =
581 xcb_get_geometry_reply (conn, xcb_get_geometry (conn, window), NULL);
584 msg_Err (wnd, "bad X11 window 0x%08"PRIx8, window);
587 p_sys->root = geo->root;
590 if (var_InheritBool (wnd, "keyboard-events"))
592 p_sys->keys = CreateKeyHandler (VLC_OBJECT(wnd), conn);
593 if (p_sys->keys != NULL)
595 const uint32_t mask = XCB_CW_EVENT_MASK;
596 const uint32_t values[1] = {
597 XCB_EVENT_MASK_KEY_PRESS,
599 xcb_change_window_attributes (conn, window, mask, values);
604 if ((p_sys->keys != NULL)
605 && vlc_clone (&p_sys->thread, Thread, wnd, VLC_THREAD_PRIORITY_LOW))
606 DestroyKeyHandler (p_sys->keys);
613 xcb_disconnect (conn);
615 ReleaseDrawable (VLC_OBJECT(wnd), window);
619 static void EmClose (vout_window_t *wnd)
621 xcb_window_t window = wnd->handle.xid;
624 ReleaseDrawable (VLC_OBJECT(wnd), window);