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 X11 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 #define MATCHBOX_HACK 1 /* Matchbox focus hack */
95 struct vout_window_sys_t
97 xcb_connection_t *conn;
103 xcb_atom_t wm_state_above;
104 xcb_atom_t wm_state_below;
105 xcb_atom_t wm_state_fullscreen;
107 xcb_atom_t mb_current_app_window;
113 /** Set an X window property from a nul-terminated string */
115 void set_string (xcb_connection_t *conn, xcb_window_t window,
116 xcb_atom_t type, xcb_atom_t atom, const char *str)
118 xcb_change_property (conn, XCB_PROP_MODE_REPLACE, window, atom, type,
119 /* format */ 8, strlen (str), str);
122 /** Set an X window string property */
124 void set_ascii_prop (xcb_connection_t *conn, xcb_window_t window,
125 xcb_atom_t atom, const char *value)
127 set_string (conn, window, XA_STRING, atom, value);
131 void set_wm_hints (xcb_connection_t *conn, xcb_window_t window)
133 static const uint32_t wm_hints[8] = {
134 3, /* flags: Input, Initial state */
136 1, /* initial state: Normal */
137 0, 0, 0, 0, 0, /* Icon */
139 xcb_change_property (conn, XCB_PROP_MODE_REPLACE, window, XA_WM_HINTS,
140 XA_WM_HINTS, 32, 8, wm_hints);
143 /** Set the Window ICCCM client machine property */
145 void set_hostname_prop (xcb_connection_t *conn, xcb_window_t window)
148 long host_name_max = sysconf (_SC_HOST_NAME_MAX);
149 if (host_name_max <= 0) host_name_max = _POSIX_HOST_NAME_MAX;
150 hostname = malloc (host_name_max);
151 if(!hostname) return;
153 if (gethostname (hostname, host_name_max) == 0)
155 hostname[host_name_max - 1] = '\0';
156 set_ascii_prop (conn, window, XA_WM_CLIENT_MACHINE, hostname);
161 /** Request the X11 server to internalize a string into an atom */
163 xcb_intern_atom_cookie_t intern_string (xcb_connection_t *c, const char *s)
165 return xcb_intern_atom (c, 0, strlen (s), s);
168 /** Extract the X11 atom from an intern request cookie */
170 xcb_atom_t get_atom (xcb_connection_t *conn, xcb_intern_atom_cookie_t ck)
172 xcb_intern_atom_reply_t *reply;
175 reply = xcb_intern_atom_reply (conn, ck, NULL);
184 #define NET_WM_STATE_REMOVE 0
185 #define NET_WM_STATE_ADD 1
186 #define NET_WM_STATE_TOGGLE 2
188 static void CacheAtoms (vout_window_sys_t *p_sys)
190 xcb_connection_t *conn = p_sys->conn;
191 xcb_intern_atom_cookie_t wm_state_ck, wm_state_above_ck,
192 wm_state_below_ck, wm_state_fs_ck;
194 wm_state_ck = intern_string (conn, "_NET_WM_STATE");
195 wm_state_above_ck = intern_string (conn, "_NET_WM_STATE_ABOVE");
196 wm_state_below_ck = intern_string (conn, "_NET_WM_STATE_BELOW");
197 wm_state_fs_ck = intern_string (conn, "_NET_WM_STATE_FULLSCREEN");
199 xcb_intern_atom_cookie_t mb_current_app_window;
200 mb_current_app_window = xcb_intern_atom (conn, true,
201 strlen ("_MB_CURRENT_APP_WINDOW"),
202 "_MB_CURRENT_APP_WINDOW");
205 p_sys->wm_state = get_atom (conn, wm_state_ck);
206 p_sys->wm_state_above = get_atom (conn, wm_state_above_ck);
207 p_sys->wm_state_below = get_atom (conn, wm_state_below_ck);
208 p_sys->wm_state_fullscreen = get_atom (conn, wm_state_fs_ck);
210 p_sys->mb_current_app_window = get_atom (conn, mb_current_app_window);
215 * Create an X11 window.
217 static int Open (vout_window_t *wnd, const vout_window_cfg_t *cfg)
219 xcb_generic_error_t *err;
220 xcb_void_cookie_t ck;
222 vout_window_sys_t *p_sys = malloc (sizeof (*p_sys));
225 p_sys->embedded = false;
228 char *display = var_InheritString (wnd, "x11-display");
231 xcb_connection_t *conn = xcb_connect (display, &snum);
232 if (xcb_connection_has_error (conn) /*== NULL*/)
235 /* Find configured screen */
236 const xcb_setup_t *setup = xcb_get_setup (conn);
237 const xcb_screen_t *scr = NULL;
238 for (xcb_screen_iterator_t i = xcb_setup_roots_iterator (setup);
239 i.rem > 0; xcb_screen_next (&i))
250 msg_Err (wnd, "bad X11 screen number");
255 const uint32_t mask = XCB_CW_BACK_PIXEL | XCB_CW_EVENT_MASK;
256 uint32_t values[2] = {
257 /* XCB_CW_BACK_PIXEL */
259 /* XCB_CW_EVENT_MASK */
260 XCB_EVENT_MASK_KEY_PRESS,
263 xcb_window_t window = xcb_generate_id (conn);
264 ck = xcb_create_window_checked (conn, scr->root_depth, window, scr->root,
265 cfg->x, cfg->y, cfg->width, cfg->height, 0,
266 XCB_WINDOW_CLASS_INPUT_OUTPUT,
267 scr->root_visual, mask, values);
268 err = xcb_request_check (conn, ck);
271 msg_Err (wnd, "creating window: X11 error %d", err->error_code);
276 wnd->handle.xid = window;
277 wnd->display.x11 = display;
278 wnd->control = Control;
282 if (var_InheritBool (wnd, "keyboard-events"))
283 p_sys->keys = CreateKeyHandler (VLC_OBJECT(wnd), conn);
286 p_sys->root = scr->root;
289 * No cut&paste nor drag&drop, only Window Manager communication. */
290 set_ascii_prop (conn, window, XA_WM_NAME,
291 /* xgettext: This is a plain ASCII spelling of "VLC media player"
292 for the ICCCM window name. This must be pure ASCII.
293 The limitation is partially with ICCCM and partially with VLC.
294 For Latin script languages, you may need to strip accents.
295 For other scripts, you will need to transliterate into Latin. */
296 vlc_pgettext ("ASCII", "VLC media player"));
298 set_ascii_prop (conn, window, XA_WM_ICON_NAME,
299 /* xgettext: This is a plain ASCII spelling of "VLC"
300 for the ICCCM window name. This must be pure ASCII. */
301 vlc_pgettext ("ASCII", "VLC"));
302 set_wm_hints (conn, window);
303 xcb_change_property (conn, XCB_PROP_MODE_REPLACE, window, XA_WM_CLASS,
304 XA_STRING, 8, 8, "vlc\0Vlc");
305 set_hostname_prop (conn, window);
308 xcb_intern_atom_cookie_t utf8_string_ck
309 = intern_string (conn, "UTF8_STRING");;
310 xcb_intern_atom_cookie_t net_wm_name_ck
311 = intern_string (conn, "_NET_WM_NAME");
312 xcb_intern_atom_cookie_t net_wm_icon_name_ck
313 = intern_string (conn, "_NET_WM_ICON_NAME");
314 xcb_intern_atom_cookie_t wm_window_role_ck
315 = intern_string (conn, "WM_WINDOW_ROLE");
317 xcb_atom_t utf8 = get_atom (conn, utf8_string_ck);
319 xcb_atom_t net_wm_name = get_atom (conn, net_wm_name_ck);
320 char *title = var_InheritString (wnd, "video-title");
323 set_string (conn, window, utf8, net_wm_name, title);
327 set_string (conn, window, utf8, net_wm_name, _("VLC media player"));
329 xcb_atom_t net_wm_icon_name = get_atom (conn, net_wm_icon_name_ck);
330 set_string (conn, window, utf8, net_wm_icon_name, _("VLC"));
332 xcb_atom_t wm_window_role = get_atom (conn, wm_window_role_ck);
333 set_ascii_prop (conn, window, wm_window_role, "vlc-video");
335 /* Cache any EWMH atom we may need later */
338 if (p_sys->mb_current_app_window)
340 uint32_t value = XCB_EVENT_MASK_PROPERTY_CHANGE;
341 xcb_change_window_attributes (conn, scr->root,
342 XCB_CW_EVENT_MASK, &value);
346 /* Make the window visible */
347 xcb_map_window (conn, window);
349 if (var_InheritBool (wnd, "video-wallpaper"))
351 vout_window_SetState (wnd, VOUT_WINDOW_STATE_BELOW);
352 vout_window_SetFullScreen (wnd, true);
355 /* Create the event thread. It will dequeue all events, so any checked
356 * request from this thread must be completed at this point. */
357 if ((p_sys->keys != NULL)
358 && vlc_clone (&p_sys->thread, Thread, wnd, VLC_THREAD_PRIORITY_LOW))
359 DestroyKeyHandler (p_sys->keys);
362 if (p_sys->mb_current_app_window)
363 xcb_set_input_focus (p_sys->conn, XCB_INPUT_FOCUS_POINTER_ROOT,
364 wnd->handle.xid, XCB_CURRENT_TIME);
366 xcb_flush (conn); /* Make sure map_window is sent (should be useless) */
370 xcb_disconnect (conn);
378 * Destroys the X11 window.
380 static void Close (vout_window_t *wnd)
382 vout_window_sys_t *p_sys = wnd->sys;
383 xcb_connection_t *conn = p_sys->conn;
387 vlc_cancel (p_sys->thread);
388 vlc_join (p_sys->thread, NULL);
389 DestroyKeyHandler (p_sys->keys);
391 xcb_disconnect (conn);
392 free (wnd->display.x11);
397 /** Background thread for X11 events handling */
398 static void *Thread (void *data)
400 vout_window_t *wnd = data;
401 vout_window_sys_t *p_sys = wnd->sys;
402 xcb_connection_t *conn = p_sys->conn;
404 int fd = xcb_get_file_descriptor (conn);
410 xcb_generic_event_t *ev;
411 struct pollfd ufd = { .fd = fd, .events = POLLIN, };
415 int canc = vlc_savecancel ();
416 while ((ev = xcb_poll_for_event (conn)) != NULL)
418 if (ProcessKeyEvent (p_sys->keys, ev) == 0)
421 if (p_sys->mb_current_app_window
422 && (ev->response_type & 0x7f) == XCB_PROPERTY_NOTIFY)
424 const xcb_property_notify_event_t *pne =
425 (xcb_property_notify_event_t *)ev;
426 if (pne->atom == p_sys->mb_current_app_window
427 && pne->state == XCB_PROPERTY_NEW_VALUE)
429 xcb_get_property_reply_t *r =
430 xcb_get_property_reply (conn,
431 xcb_get_property (conn, 0, pne->window, pne->atom,
432 XA_WINDOW, 0, 4), NULL);
434 && !memcmp (xcb_get_property_value (r), &wnd->handle.xid,
437 msg_Dbg (wnd, "asking Matchbox for input focus");
438 xcb_set_input_focus (conn,
439 XCB_INPUT_FOCUS_POINTER_ROOT,
440 wnd->handle.xid, pne->time);
448 msg_Dbg (wnd, "unhandled event: %"PRIu8, ev->response_type);
451 vlc_restorecancel (canc);
453 if (xcb_connection_has_error (conn))
455 msg_Err (wnd, "X server failure");
462 /** Changes the EWMH state of the window */
463 static void set_wm_state (vout_window_t *wnd, bool on, xcb_atom_t state)
465 vout_window_sys_t *sys = wnd->sys;
466 /* From EWMH "_WM_STATE" */
467 xcb_client_message_event_t ev = {
468 .response_type = XCB_CLIENT_MESSAGE,
470 .window = wnd->handle.xid,
471 .type = sys->wm_state,
474 ev.data.data32[0] = on ? NET_WM_STATE_ADD : NET_WM_STATE_REMOVE;
475 ev.data.data32[1] = state;
476 ev.data.data32[2] = 0;
477 ev.data.data32[3] = 1;
479 /* From ICCCM "Changing Window State" */
480 xcb_send_event (sys->conn, 0, sys->root,
481 XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY |
482 XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT,
487 static int Control (vout_window_t *wnd, int cmd, va_list ap)
489 vout_window_sys_t *p_sys = wnd->sys;
490 xcb_connection_t *conn = p_sys->conn;
494 case VOUT_WINDOW_SET_SIZE:
499 unsigned width = va_arg (ap, unsigned);
500 unsigned height = va_arg (ap, unsigned);
501 const uint32_t values[] = { width, height, };
503 xcb_configure_window (conn, wnd->handle.xid,
504 XCB_CONFIG_WINDOW_WIDTH |
505 XCB_CONFIG_WINDOW_HEIGHT, values);
509 case VOUT_WINDOW_SET_STATE:
511 unsigned state = va_arg (ap, unsigned);
512 bool above = (state & VOUT_WINDOW_STATE_ABOVE) != 0;
513 bool below = (state & VOUT_WINDOW_STATE_BELOW) != 0;
515 set_wm_state (wnd, above, p_sys->wm_state_above);
516 set_wm_state (wnd, below, p_sys->wm_state_below);
520 case VOUT_WINDOW_SET_FULLSCREEN:
522 bool fs = va_arg (ap, int);
523 if (!fs && var_GetBool (wnd, "video-wallpaper"))
525 set_wm_state (wnd, fs, p_sys->wm_state_fullscreen);
530 msg_Err (wnd, "request %d not implemented", cmd);
533 xcb_flush (p_sys->conn);
537 /*** Embedded drawable support ***/
539 static vlc_mutex_t serializer = VLC_STATIC_MUTEX;
541 /** Acquire a drawable */
542 static int AcquireDrawable (vlc_object_t *obj, xcb_window_t window)
547 if (var_Create (obj->p_libvlc, "xid-in-use", VLC_VAR_ADDRESS))
550 /* Keep a list of busy drawables, so we don't overlap videos if there are
551 * more than one video track in the stream. */
552 vlc_mutex_lock (&serializer);
553 used = var_GetAddress (obj->p_libvlc, "xid-in-use");
558 if (used[n] == window)
564 used = realloc (used, sizeof (*used) * (n + 2));
569 var_SetAddress (obj->p_libvlc, "xid-in-use", used);
574 msg_Warn (obj, "X11 drawable 0x%08"PRIx8" is busy", window);
577 vlc_mutex_unlock (&serializer);
579 return (window == 0) ? VLC_EGENERIC : VLC_SUCCESS;
582 /** Remove this drawable from the list of busy ones */
583 static void ReleaseDrawable (vlc_object_t *obj, xcb_window_t window)
588 vlc_mutex_lock (&serializer);
589 used = var_GetAddress (obj->p_libvlc, "xid-in-use");
591 while (used[n] != window)
597 used[n] = used[n + 1];
601 var_SetAddress (obj->p_libvlc, "xid-in-use", NULL);
602 vlc_mutex_unlock (&serializer);
606 /* Variables are reference-counted... */
607 var_Destroy (obj->p_libvlc, "xid-in-use");
611 * Wrap an existing X11 window to embed the video.
613 static int EmOpen (vout_window_t *wnd, const vout_window_cfg_t *cfg)
615 xcb_window_t window = var_InheritInteger (wnd, "drawable-xid");
619 if (AcquireDrawable (VLC_OBJECT(wnd), window))
622 vout_window_sys_t *p_sys = malloc (sizeof (*p_sys));
623 xcb_connection_t *conn = xcb_connect (NULL, NULL);
624 if (p_sys == NULL || xcb_connection_has_error (conn))
627 p_sys->embedded = true;
629 wnd->handle.xid = window;
630 wnd->control = Control;
635 xcb_get_geometry_reply_t *geo =
636 xcb_get_geometry_reply (conn, xcb_get_geometry (conn, window), NULL);
639 msg_Err (wnd, "bad X11 window 0x%08"PRIx8, window);
642 p_sys->root = geo->root;
645 if (var_InheritBool (wnd, "keyboard-events"))
647 p_sys->keys = CreateKeyHandler (VLC_OBJECT(wnd), conn);
648 if (p_sys->keys != NULL)
650 const uint32_t mask = XCB_CW_EVENT_MASK;
651 const uint32_t values[1] = {
652 XCB_EVENT_MASK_KEY_PRESS,
654 xcb_change_window_attributes (conn, window, mask, values);
659 if ((p_sys->keys != NULL)
660 && vlc_clone (&p_sys->thread, Thread, wnd, VLC_THREAD_PRIORITY_LOW))
661 DestroyKeyHandler (p_sys->keys);
668 xcb_disconnect (conn);
670 ReleaseDrawable (VLC_OBJECT(wnd), window);
674 static void EmClose (vout_window_t *wnd)
676 xcb_window_t window = wnd->handle.xid;
679 ReleaseDrawable (VLC_OBJECT(wnd), window);