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 XID_TEXT N_("ID of the video output X window")
44 #define XID_LONGTEXT N_( \
45 "VLC can embed its video output in an existing X11 window. " \
46 "This is the X identifier of that window (0 means none).")
48 static int Open (vlc_object_t *);
49 static void Close (vlc_object_t *);
50 static int EmOpen (vlc_object_t *);
51 static void EmClose (vlc_object_t *);
57 set_shortname (N_("X window"))
58 set_description (N_("X11 video window (XCB)"))
59 set_category (CAT_VIDEO)
60 set_subcategory (SUBCAT_VIDEO_VOUT)
61 set_capability ("vout window xid", 10)
62 set_callbacks (Open, Close)
64 /* Obsolete since 1.1.0: */
65 add_obsolete_bool ("x11-altfullscreen")
66 add_obsolete_bool ("xvideo-altfullscreen")
67 add_obsolete_bool ("xvmc-altfullscreen")
68 add_obsolete_bool ("glx-altfullscreen")
71 set_shortname (N_("Drawable"))
72 set_description (N_("Embedded window video"))
73 set_category (CAT_VIDEO)
74 set_subcategory (SUBCAT_VIDEO_VOUT)
75 set_capability ("vout window xid", 70)
76 set_callbacks (EmOpen, EmClose)
78 add_integer ("drawable-xid", 0, NULL, XID_TEXT, XID_LONGTEXT, true)
83 static int Control (vout_window_t *, int, va_list ap);
84 static void *Thread (void *);
86 #define MATCHBOX_HACK 1 /* Matchbox focus hack */
88 struct vout_window_sys_t
90 xcb_connection_t *conn;
96 xcb_atom_t wm_state_above;
97 xcb_atom_t wm_state_below;
98 xcb_atom_t wm_state_fullscreen;
100 xcb_atom_t mb_current_app_window;
104 /** Set an X window property from a nul-terminated string */
106 void set_string (xcb_connection_t *conn, xcb_window_t window,
107 xcb_atom_t type, xcb_atom_t atom, const char *str)
109 xcb_change_property (conn, XCB_PROP_MODE_REPLACE, window, atom, type,
110 /* format */ 8, strlen (str), str);
113 /** Set an X window string property */
115 void set_ascii_prop (xcb_connection_t *conn, xcb_window_t window,
116 xcb_atom_t atom, const char *value)
118 set_string (conn, window, XA_STRING, atom, value);
122 void set_wm_hints (xcb_connection_t *conn, xcb_window_t window)
124 static const uint32_t wm_hints[8] = {
125 3, /* flags: Input, Initial state */
127 1, /* initial state: Normal */
128 0, 0, 0, 0, 0, /* Icon */
130 xcb_change_property (conn, XCB_PROP_MODE_REPLACE, window, XA_WM_HINTS,
131 XA_WM_HINTS, 32, 8, wm_hints);
134 /** Set the Window ICCCM client machine property */
136 void set_hostname_prop (xcb_connection_t *conn, xcb_window_t window)
139 long host_name_max = sysconf (_SC_HOST_NAME_MAX);
140 if (host_name_max <= 0) host_name_max = _POSIX_HOST_NAME_MAX;
141 hostname = malloc (host_name_max);
142 if(!hostname) return;
144 if (gethostname (hostname, host_name_max) == 0)
146 hostname[host_name_max - 1] = '\0';
147 set_ascii_prop (conn, window, XA_WM_CLIENT_MACHINE, hostname);
152 /** Request the X11 server to internalize a string into an atom */
154 xcb_intern_atom_cookie_t intern_string (xcb_connection_t *c, const char *s)
156 return xcb_intern_atom (c, 0, strlen (s), s);
159 /** Extract the X11 atom from an intern request cookie */
161 xcb_atom_t get_atom (xcb_connection_t *conn, xcb_intern_atom_cookie_t ck)
163 xcb_intern_atom_reply_t *reply;
166 reply = xcb_intern_atom_reply (conn, ck, NULL);
175 #define NET_WM_STATE_REMOVE 0
176 #define NET_WM_STATE_ADD 1
177 #define NET_WM_STATE_TOGGLE 2
179 static void CacheAtoms (vout_window_sys_t *p_sys)
181 xcb_connection_t *conn = p_sys->conn;
182 xcb_intern_atom_cookie_t wm_state_ck, wm_state_above_ck,
183 wm_state_below_ck, wm_state_fs_ck;
185 wm_state_ck = intern_string (conn, "_NET_WM_STATE");
186 wm_state_above_ck = intern_string (conn, "_NET_WM_STATE_ABOVE");
187 wm_state_below_ck = intern_string (conn, "_NET_WM_STATE_BELOW");
188 wm_state_fs_ck = intern_string (conn, "_NET_WM_STATE_FULLSCREEN");
190 xcb_intern_atom_cookie_t mb_current_app_window;
191 mb_current_app_window = xcb_intern_atom (conn, true,
192 strlen ("_MB_CURRENT_APP_WINDOW"),
193 "_MB_CURRENT_APP_WINDOW");
196 p_sys->wm_state = get_atom (conn, wm_state_ck);
197 p_sys->wm_state_above = get_atom (conn, wm_state_above_ck);
198 p_sys->wm_state_below = get_atom (conn, wm_state_below_ck);
199 p_sys->wm_state_fullscreen = get_atom (conn, wm_state_fs_ck);
201 p_sys->mb_current_app_window = get_atom (conn, mb_current_app_window);
206 * Create an X11 window.
208 static int Open (vlc_object_t *obj)
210 vout_window_t *wnd = (vout_window_t *)obj;
211 xcb_generic_error_t *err;
212 xcb_void_cookie_t ck;
214 vout_window_sys_t *p_sys = malloc (sizeof (*p_sys));
219 char *display = var_CreateGetNonEmptyString (wnd, "x11-display");
222 xcb_connection_t *conn = xcb_connect (display, &snum);
223 if (xcb_connection_has_error (conn) /*== NULL*/)
226 /* Find configured screen */
227 const xcb_setup_t *setup = xcb_get_setup (conn);
228 const xcb_screen_t *scr = NULL;
229 for (xcb_screen_iterator_t i = xcb_setup_roots_iterator (setup);
230 i.rem > 0; xcb_screen_next (&i))
241 msg_Err (wnd, "bad X11 screen number");
246 const uint32_t mask = XCB_CW_BACK_PIXEL | XCB_CW_EVENT_MASK;
247 uint32_t values[2] = {
248 /* XCB_CW_BACK_PIXEL */
250 /* XCB_CW_EVENT_MASK */
251 XCB_EVENT_MASK_KEY_PRESS,
254 xcb_window_t window = xcb_generate_id (conn);
255 ck = xcb_create_window_checked (conn, scr->root_depth, window, scr->root,
256 wnd->cfg->x, wnd->cfg->y,
257 wnd->cfg->width, wnd->cfg->height, 0,
258 XCB_WINDOW_CLASS_INPUT_OUTPUT,
259 scr->root_visual, mask, values);
260 err = xcb_request_check (conn, ck);
263 msg_Err (wnd, "creating window: X11 error %d", err->error_code);
268 wnd->handle.xid = window;
269 wnd->display.x11 = display;
270 wnd->control = Control;
274 if (var_CreateGetBool (obj, "keyboard-events"))
275 p_sys->keys = CreateKeyHandler (obj, conn);
278 p_sys->root = scr->root;
281 * No cut&paste nor drag&drop, only Window Manager communication. */
282 set_ascii_prop (conn, window, XA_WM_NAME,
283 /* xgettext: This is a plain ASCII spelling of "VLC media player"
284 for the ICCCM window name. This must be pure ASCII.
285 The limitation is partially with ICCCM and partially with VLC.
286 For Latin script languages, you may need to strip accents.
287 For other scripts, you will need to transliterate into Latin. */
288 vlc_pgettext ("ASCII", "VLC media player"));
290 set_ascii_prop (conn, window, XA_WM_ICON_NAME,
291 /* xgettext: This is a plain ASCII spelling of "VLC"
292 for the ICCCM window name. This must be pure ASCII. */
293 vlc_pgettext ("ASCII", "VLC"));
294 set_wm_hints (conn, window);
295 xcb_change_property (conn, XCB_PROP_MODE_REPLACE, window, XA_WM_CLASS,
296 XA_STRING, 8, 8, "vlc\0Vlc");
297 set_hostname_prop (conn, window);
300 xcb_intern_atom_cookie_t utf8_string_ck
301 = intern_string (conn, "UTF8_STRING");;
302 xcb_intern_atom_cookie_t net_wm_name_ck
303 = intern_string (conn, "_NET_WM_NAME");
304 xcb_intern_atom_cookie_t net_wm_icon_name_ck
305 = intern_string (conn, "_NET_WM_ICON_NAME");
306 xcb_intern_atom_cookie_t wm_window_role_ck
307 = intern_string (conn, "WM_WINDOW_ROLE");
309 xcb_atom_t utf8 = get_atom (conn, utf8_string_ck);
311 xcb_atom_t net_wm_name = get_atom (conn, net_wm_name_ck);
312 char *title = var_CreateGetNonEmptyString (wnd, "video-title");
315 set_string (conn, window, utf8, net_wm_name, title);
319 set_string (conn, window, utf8, net_wm_name, _("VLC media player"));
321 xcb_atom_t net_wm_icon_name = get_atom (conn, net_wm_icon_name_ck);
322 set_string (conn, window, utf8, net_wm_icon_name, _("VLC"));
324 xcb_atom_t wm_window_role = get_atom (conn, wm_window_role_ck);
325 set_ascii_prop (conn, window, wm_window_role, "vlc-video");
327 /* Cache any EWMH atom we may need later */
330 if (p_sys->mb_current_app_window)
332 uint32_t value = XCB_EVENT_MASK_PROPERTY_CHANGE;
333 xcb_change_window_attributes (conn, scr->root,
334 XCB_CW_EVENT_MASK, &value);
338 /* Make the window visible */
339 xcb_map_window (conn, window);
341 if (var_CreateGetBool (obj, "video-wallpaper"))
343 vout_window_SetState (wnd, VOUT_WINDOW_STATE_BELOW);
344 vout_window_SetFullScreen (wnd, true);
347 /* Create the event thread. It will dequeue all events, so any checked
348 * request from this thread must be completed at this point. */
349 if ((p_sys->keys != NULL)
350 && vlc_clone (&p_sys->thread, Thread, wnd, VLC_THREAD_PRIORITY_LOW))
351 DestroyKeyHandler (p_sys->keys);
354 if (p_sys->mb_current_app_window)
355 xcb_set_input_focus (p_sys->conn, XCB_INPUT_FOCUS_POINTER_ROOT,
356 wnd->handle.xid, XCB_CURRENT_TIME);
358 xcb_flush (conn); /* Make sure map_window is sent (should be useless) */
362 xcb_disconnect (conn);
370 * Destroys the X11 window.
372 static void Close (vlc_object_t *obj)
374 vout_window_t *wnd = (vout_window_t *)obj;
375 vout_window_sys_t *p_sys = wnd->sys;
376 xcb_connection_t *conn = p_sys->conn;
380 vlc_cancel (p_sys->thread);
381 vlc_join (p_sys->thread, NULL);
382 DestroyKeyHandler (p_sys->keys);
384 xcb_disconnect (conn);
385 free (wnd->display.x11);
390 /** Background thread for X11 events handling */
391 static void *Thread (void *data)
393 vout_window_t *wnd = data;
394 vout_window_sys_t *p_sys = wnd->sys;
395 xcb_connection_t *conn = p_sys->conn;
397 int fd = xcb_get_file_descriptor (conn);
403 xcb_generic_event_t *ev;
404 struct pollfd ufd = { .fd = fd, .events = POLLIN, };
408 int canc = vlc_savecancel ();
409 while ((ev = xcb_poll_for_event (conn)) != NULL)
411 if (ProcessKeyEvent (p_sys->keys, ev) == 0)
414 if (p_sys->mb_current_app_window
415 && (ev->response_type & 0x7f) == XCB_PROPERTY_NOTIFY)
417 const xcb_property_notify_event_t *pne =
418 (xcb_property_notify_event_t *)ev;
419 if (pne->atom == p_sys->mb_current_app_window
420 && pne->state == XCB_PROPERTY_NEW_VALUE)
422 xcb_get_property_reply_t *r =
423 xcb_get_property_reply (conn,
424 xcb_get_property (conn, 0, pne->window, pne->atom,
425 XA_WINDOW, 0, 4), NULL);
427 && !memcmp (xcb_get_property_value (r), &wnd->handle.xid,
430 msg_Dbg (wnd, "asking Matchbox for input focus");
431 xcb_set_input_focus (conn,
432 XCB_INPUT_FOCUS_POINTER_ROOT,
433 wnd->handle.xid, pne->time);
441 msg_Dbg (wnd, "unhandled event: %"PRIu8, ev->response_type);
444 vlc_restorecancel (canc);
446 if (xcb_connection_has_error (conn))
448 msg_Err (wnd, "X server failure");
455 /** Changes the EWMH state of the window */
456 static void set_wm_state (vout_window_t *wnd, bool on, xcb_atom_t state)
458 vout_window_sys_t *sys = wnd->sys;
459 /* From EWMH "_WM_STATE" */
460 xcb_client_message_event_t ev = {
461 .response_type = XCB_CLIENT_MESSAGE,
463 .window = wnd->handle.xid,
464 .type = sys->wm_state,
467 ev.data.data32[0] = on ? NET_WM_STATE_ADD : NET_WM_STATE_REMOVE;
468 ev.data.data32[1] = state;
469 ev.data.data32[2] = 0;
470 ev.data.data32[3] = 1;
472 /* From ICCCM "Changing Window State" */
473 xcb_send_event (sys->conn, 0, sys->root,
474 XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY |
475 XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT,
480 static int Control (vout_window_t *wnd, int cmd, va_list ap)
482 vout_window_sys_t *p_sys = wnd->sys;
483 xcb_connection_t *conn = p_sys->conn;
487 case VOUT_WINDOW_SET_SIZE:
489 unsigned width = va_arg (ap, unsigned);
490 unsigned height = va_arg (ap, unsigned);
491 const uint32_t values[] = { width, height, };
493 xcb_configure_window (conn, wnd->handle.xid,
494 XCB_CONFIG_WINDOW_WIDTH |
495 XCB_CONFIG_WINDOW_HEIGHT, values);
499 case VOUT_WINDOW_SET_STATE:
501 unsigned state = va_arg (ap, unsigned);
502 bool above = (state & VOUT_WINDOW_STATE_ABOVE) != 0;
503 bool below = (state & VOUT_WINDOW_STATE_BELOW) != 0;
505 set_wm_state (wnd, above, p_sys->wm_state_above);
506 set_wm_state (wnd, below, p_sys->wm_state_below);
510 case VOUT_WINDOW_SET_FULLSCREEN:
512 bool fs = va_arg (ap, int);
513 if (!fs && var_GetBool (wnd, "video-wallpaper"))
515 set_wm_state (wnd, fs, p_sys->wm_state_fullscreen);
520 msg_Err (wnd, "request %d not implemented", cmd);
523 xcb_flush (p_sys->conn);
527 /*** Embedded drawable support ***/
529 static vlc_mutex_t serializer = VLC_STATIC_MUTEX;
531 /** Acquire a drawable */
532 static int AcquireDrawable (vlc_object_t *obj, xcb_window_t window)
537 if (var_Create (obj->p_libvlc, "xid-in-use", VLC_VAR_ADDRESS))
540 /* Keep a list of busy drawables, so we don't overlap videos if there are
541 * more than one video track in the stream. */
542 vlc_mutex_lock (&serializer);
543 used = var_GetAddress (obj->p_libvlc, "xid-in-use");
548 if (used[n] == window)
554 used = realloc (used, sizeof (*used) * (n + 2));
559 var_SetAddress (obj->p_libvlc, "xid-in-use", used);
564 msg_Warn (obj, "X11 drawable 0x%08"PRIx8" is busy", window);
567 vlc_mutex_unlock (&serializer);
569 return (window == 0) ? VLC_EGENERIC : VLC_SUCCESS;
572 /** Remove this drawable from the list of busy ones */
573 static void ReleaseDrawable (vlc_object_t *obj, xcb_window_t window)
578 vlc_mutex_lock (&serializer);
579 used = var_GetAddress (obj->p_libvlc, "xid-in-use");
581 while (used[n] != window)
587 used[n] = used[n + 1];
591 var_SetAddress (obj->p_libvlc, "xid-in-use", NULL);
592 vlc_mutex_unlock (&serializer);
596 /* Variables are reference-counted... */
597 var_Destroy (obj->p_libvlc, "xid-in-use");
601 * Wrap an existing X11 window to embed the video.
603 static int EmOpen (vlc_object_t *obj)
605 vout_window_t *wnd = (vout_window_t *)obj;
607 xcb_window_t window = var_CreateGetInteger (obj, "drawable-xid");
610 var_Destroy (obj, "drawable-xid");
612 if (AcquireDrawable (obj, window))
615 vout_window_sys_t *p_sys = malloc (sizeof (*p_sys));
616 xcb_connection_t *conn = xcb_connect (NULL, NULL);
617 if (p_sys == NULL || xcb_connection_has_error (conn))
621 wnd->handle.xid = window;
622 wnd->control = Control;
627 xcb_get_geometry_reply_t *geo =
628 xcb_get_geometry_reply (conn, xcb_get_geometry (conn, window), NULL);
631 msg_Err (obj, "bad X11 window 0x%08"PRIx8, window);
634 p_sys->root = geo->root;
637 if (var_CreateGetBool (obj, "keyboard-events"))
639 p_sys->keys = CreateKeyHandler (obj, conn);
640 if (p_sys->keys != NULL)
642 const uint32_t mask = XCB_CW_EVENT_MASK;
643 const uint32_t values[1] = {
644 XCB_EVENT_MASK_KEY_PRESS,
646 xcb_change_window_attributes (conn, window, mask, values);
651 if ((p_sys->keys != NULL)
652 && vlc_clone (&p_sys->thread, Thread, wnd, VLC_THREAD_PRIORITY_LOW))
653 DestroyKeyHandler (p_sys->keys);
660 xcb_disconnect (conn);
662 ReleaseDrawable (obj, window);
666 static void EmClose (vlc_object_t *obj)
668 vout_window_t *wnd = (vout_window_t *)obj;
669 xcb_window_t window = wnd->handle.xid;
672 ReleaseDrawable (obj, window);