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)
77 add_shortcut ("embed-xid")
79 add_integer ("drawable-xid", 0, NULL, XID_TEXT, XID_LONGTEXT, true)
84 static int Control (vout_window_t *, int, va_list ap);
85 static void *Thread (void *);
87 #define MATCHBOX_HACK 1 /* Matchbox focus hack */
89 struct vout_window_sys_t
91 xcb_connection_t *conn;
97 xcb_atom_t wm_state_above;
98 xcb_atom_t wm_state_below;
99 xcb_atom_t wm_state_fullscreen;
101 xcb_atom_t mb_current_app_window;
107 /** Set an X window property from a nul-terminated string */
109 void set_string (xcb_connection_t *conn, xcb_window_t window,
110 xcb_atom_t type, xcb_atom_t atom, const char *str)
112 xcb_change_property (conn, XCB_PROP_MODE_REPLACE, window, atom, type,
113 /* format */ 8, strlen (str), str);
116 /** Set an X window string property */
118 void set_ascii_prop (xcb_connection_t *conn, xcb_window_t window,
119 xcb_atom_t atom, const char *value)
121 set_string (conn, window, XA_STRING, atom, value);
125 void set_wm_hints (xcb_connection_t *conn, xcb_window_t window)
127 static const uint32_t wm_hints[8] = {
128 3, /* flags: Input, Initial state */
130 1, /* initial state: Normal */
131 0, 0, 0, 0, 0, /* Icon */
133 xcb_change_property (conn, XCB_PROP_MODE_REPLACE, window, XA_WM_HINTS,
134 XA_WM_HINTS, 32, 8, wm_hints);
137 /** Set the Window ICCCM client machine property */
139 void set_hostname_prop (xcb_connection_t *conn, xcb_window_t window)
142 long host_name_max = sysconf (_SC_HOST_NAME_MAX);
143 if (host_name_max <= 0) host_name_max = _POSIX_HOST_NAME_MAX;
144 hostname = malloc (host_name_max);
145 if(!hostname) return;
147 if (gethostname (hostname, host_name_max) == 0)
149 hostname[host_name_max - 1] = '\0';
150 set_ascii_prop (conn, window, XA_WM_CLIENT_MACHINE, hostname);
155 /** Request the X11 server to internalize a string into an atom */
157 xcb_intern_atom_cookie_t intern_string (xcb_connection_t *c, const char *s)
159 return xcb_intern_atom (c, 0, strlen (s), s);
162 /** Extract the X11 atom from an intern request cookie */
164 xcb_atom_t get_atom (xcb_connection_t *conn, xcb_intern_atom_cookie_t ck)
166 xcb_intern_atom_reply_t *reply;
169 reply = xcb_intern_atom_reply (conn, ck, NULL);
178 #define NET_WM_STATE_REMOVE 0
179 #define NET_WM_STATE_ADD 1
180 #define NET_WM_STATE_TOGGLE 2
182 static void CacheAtoms (vout_window_sys_t *p_sys)
184 xcb_connection_t *conn = p_sys->conn;
185 xcb_intern_atom_cookie_t wm_state_ck, wm_state_above_ck,
186 wm_state_below_ck, wm_state_fs_ck;
188 wm_state_ck = intern_string (conn, "_NET_WM_STATE");
189 wm_state_above_ck = intern_string (conn, "_NET_WM_STATE_ABOVE");
190 wm_state_below_ck = intern_string (conn, "_NET_WM_STATE_BELOW");
191 wm_state_fs_ck = intern_string (conn, "_NET_WM_STATE_FULLSCREEN");
193 xcb_intern_atom_cookie_t mb_current_app_window;
194 mb_current_app_window = xcb_intern_atom (conn, true,
195 strlen ("_MB_CURRENT_APP_WINDOW"),
196 "_MB_CURRENT_APP_WINDOW");
199 p_sys->wm_state = get_atom (conn, wm_state_ck);
200 p_sys->wm_state_above = get_atom (conn, wm_state_above_ck);
201 p_sys->wm_state_below = get_atom (conn, wm_state_below_ck);
202 p_sys->wm_state_fullscreen = get_atom (conn, wm_state_fs_ck);
204 p_sys->mb_current_app_window = get_atom (conn, mb_current_app_window);
209 * Create an X11 window.
211 static int Open (vlc_object_t *obj)
213 vout_window_t *wnd = (vout_window_t *)obj;
214 xcb_generic_error_t *err;
215 xcb_void_cookie_t ck;
217 vout_window_sys_t *p_sys = malloc (sizeof (*p_sys));
220 p_sys->embedded = false;
223 char *display = var_InheritString (wnd, "x11-display");
226 xcb_connection_t *conn = xcb_connect (display, &snum);
227 if (xcb_connection_has_error (conn) /*== NULL*/)
230 /* Find configured screen */
231 const xcb_setup_t *setup = xcb_get_setup (conn);
232 const xcb_screen_t *scr = NULL;
233 for (xcb_screen_iterator_t i = xcb_setup_roots_iterator (setup);
234 i.rem > 0; xcb_screen_next (&i))
245 msg_Err (wnd, "bad X11 screen number");
250 const uint32_t mask = XCB_CW_BACK_PIXEL | XCB_CW_EVENT_MASK;
251 uint32_t values[2] = {
252 /* XCB_CW_BACK_PIXEL */
254 /* XCB_CW_EVENT_MASK */
255 XCB_EVENT_MASK_KEY_PRESS,
258 xcb_window_t window = xcb_generate_id (conn);
259 ck = xcb_create_window_checked (conn, scr->root_depth, window, scr->root,
260 wnd->cfg->x, wnd->cfg->y,
261 wnd->cfg->width, wnd->cfg->height, 0,
262 XCB_WINDOW_CLASS_INPUT_OUTPUT,
263 scr->root_visual, mask, values);
264 err = xcb_request_check (conn, ck);
267 msg_Err (wnd, "creating window: X11 error %d", err->error_code);
272 wnd->handle.xid = window;
273 wnd->display.x11 = display;
274 wnd->control = Control;
278 if (var_InheritBool (obj, "keyboard-events"))
279 p_sys->keys = CreateKeyHandler (obj, conn);
282 p_sys->root = scr->root;
285 * No cut&paste nor drag&drop, only Window Manager communication. */
286 set_ascii_prop (conn, window, XA_WM_NAME,
287 /* xgettext: This is a plain ASCII spelling of "VLC media player"
288 for the ICCCM window name. This must be pure ASCII.
289 The limitation is partially with ICCCM and partially with VLC.
290 For Latin script languages, you may need to strip accents.
291 For other scripts, you will need to transliterate into Latin. */
292 vlc_pgettext ("ASCII", "VLC media player"));
294 set_ascii_prop (conn, window, XA_WM_ICON_NAME,
295 /* xgettext: This is a plain ASCII spelling of "VLC"
296 for the ICCCM window name. This must be pure ASCII. */
297 vlc_pgettext ("ASCII", "VLC"));
298 set_wm_hints (conn, window);
299 xcb_change_property (conn, XCB_PROP_MODE_REPLACE, window, XA_WM_CLASS,
300 XA_STRING, 8, 8, "vlc\0Vlc");
301 set_hostname_prop (conn, window);
304 xcb_intern_atom_cookie_t utf8_string_ck
305 = intern_string (conn, "UTF8_STRING");;
306 xcb_intern_atom_cookie_t net_wm_name_ck
307 = intern_string (conn, "_NET_WM_NAME");
308 xcb_intern_atom_cookie_t net_wm_icon_name_ck
309 = intern_string (conn, "_NET_WM_ICON_NAME");
310 xcb_intern_atom_cookie_t wm_window_role_ck
311 = intern_string (conn, "WM_WINDOW_ROLE");
313 xcb_atom_t utf8 = get_atom (conn, utf8_string_ck);
315 xcb_atom_t net_wm_name = get_atom (conn, net_wm_name_ck);
316 char *title = var_InheritString (wnd, "video-title");
319 set_string (conn, window, utf8, net_wm_name, title);
323 set_string (conn, window, utf8, net_wm_name, _("VLC media player"));
325 xcb_atom_t net_wm_icon_name = get_atom (conn, net_wm_icon_name_ck);
326 set_string (conn, window, utf8, net_wm_icon_name, _("VLC"));
328 xcb_atom_t wm_window_role = get_atom (conn, wm_window_role_ck);
329 set_ascii_prop (conn, window, wm_window_role, "vlc-video");
331 /* Cache any EWMH atom we may need later */
334 if (p_sys->mb_current_app_window)
336 uint32_t value = XCB_EVENT_MASK_PROPERTY_CHANGE;
337 xcb_change_window_attributes (conn, scr->root,
338 XCB_CW_EVENT_MASK, &value);
342 /* Make the window visible */
343 xcb_map_window (conn, window);
345 if (var_InheritBool (obj, "video-wallpaper"))
347 vout_window_SetState (wnd, VOUT_WINDOW_STATE_BELOW);
348 vout_window_SetFullScreen (wnd, true);
351 /* Create the event thread. It will dequeue all events, so any checked
352 * request from this thread must be completed at this point. */
353 if ((p_sys->keys != NULL)
354 && vlc_clone (&p_sys->thread, Thread, wnd, VLC_THREAD_PRIORITY_LOW))
355 DestroyKeyHandler (p_sys->keys);
358 if (p_sys->mb_current_app_window)
359 xcb_set_input_focus (p_sys->conn, XCB_INPUT_FOCUS_POINTER_ROOT,
360 wnd->handle.xid, XCB_CURRENT_TIME);
362 xcb_flush (conn); /* Make sure map_window is sent (should be useless) */
366 xcb_disconnect (conn);
374 * Destroys the X11 window.
376 static void Close (vlc_object_t *obj)
378 vout_window_t *wnd = (vout_window_t *)obj;
379 vout_window_sys_t *p_sys = wnd->sys;
380 xcb_connection_t *conn = p_sys->conn;
384 vlc_cancel (p_sys->thread);
385 vlc_join (p_sys->thread, NULL);
386 DestroyKeyHandler (p_sys->keys);
388 xcb_disconnect (conn);
389 free (wnd->display.x11);
394 /** Background thread for X11 events handling */
395 static void *Thread (void *data)
397 vout_window_t *wnd = data;
398 vout_window_sys_t *p_sys = wnd->sys;
399 xcb_connection_t *conn = p_sys->conn;
401 int fd = xcb_get_file_descriptor (conn);
407 xcb_generic_event_t *ev;
408 struct pollfd ufd = { .fd = fd, .events = POLLIN, };
412 int canc = vlc_savecancel ();
413 while ((ev = xcb_poll_for_event (conn)) != NULL)
415 if (ProcessKeyEvent (p_sys->keys, ev) == 0)
418 if (p_sys->mb_current_app_window
419 && (ev->response_type & 0x7f) == XCB_PROPERTY_NOTIFY)
421 const xcb_property_notify_event_t *pne =
422 (xcb_property_notify_event_t *)ev;
423 if (pne->atom == p_sys->mb_current_app_window
424 && pne->state == XCB_PROPERTY_NEW_VALUE)
426 xcb_get_property_reply_t *r =
427 xcb_get_property_reply (conn,
428 xcb_get_property (conn, 0, pne->window, pne->atom,
429 XA_WINDOW, 0, 4), NULL);
431 && !memcmp (xcb_get_property_value (r), &wnd->handle.xid,
434 msg_Dbg (wnd, "asking Matchbox for input focus");
435 xcb_set_input_focus (conn,
436 XCB_INPUT_FOCUS_POINTER_ROOT,
437 wnd->handle.xid, pne->time);
445 msg_Dbg (wnd, "unhandled event: %"PRIu8, ev->response_type);
448 vlc_restorecancel (canc);
450 if (xcb_connection_has_error (conn))
452 msg_Err (wnd, "X server failure");
459 /** Changes the EWMH state of the window */
460 static void set_wm_state (vout_window_t *wnd, bool on, xcb_atom_t state)
462 vout_window_sys_t *sys = wnd->sys;
463 /* From EWMH "_WM_STATE" */
464 xcb_client_message_event_t ev = {
465 .response_type = XCB_CLIENT_MESSAGE,
467 .window = wnd->handle.xid,
468 .type = sys->wm_state,
471 ev.data.data32[0] = on ? NET_WM_STATE_ADD : NET_WM_STATE_REMOVE;
472 ev.data.data32[1] = state;
473 ev.data.data32[2] = 0;
474 ev.data.data32[3] = 1;
476 /* From ICCCM "Changing Window State" */
477 xcb_send_event (sys->conn, 0, sys->root,
478 XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY |
479 XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT,
484 static int Control (vout_window_t *wnd, int cmd, va_list ap)
486 vout_window_sys_t *p_sys = wnd->sys;
487 xcb_connection_t *conn = p_sys->conn;
491 case VOUT_WINDOW_SET_SIZE:
496 unsigned width = va_arg (ap, unsigned);
497 unsigned height = va_arg (ap, unsigned);
498 const uint32_t values[] = { width, height, };
500 xcb_configure_window (conn, wnd->handle.xid,
501 XCB_CONFIG_WINDOW_WIDTH |
502 XCB_CONFIG_WINDOW_HEIGHT, values);
506 case VOUT_WINDOW_SET_STATE:
508 unsigned state = va_arg (ap, unsigned);
509 bool above = (state & VOUT_WINDOW_STATE_ABOVE) != 0;
510 bool below = (state & VOUT_WINDOW_STATE_BELOW) != 0;
512 set_wm_state (wnd, above, p_sys->wm_state_above);
513 set_wm_state (wnd, below, p_sys->wm_state_below);
517 case VOUT_WINDOW_SET_FULLSCREEN:
519 bool fs = va_arg (ap, int);
520 if (!fs && var_GetBool (wnd, "video-wallpaper"))
522 set_wm_state (wnd, fs, p_sys->wm_state_fullscreen);
527 msg_Err (wnd, "request %d not implemented", cmd);
530 xcb_flush (p_sys->conn);
534 /*** Embedded drawable support ***/
536 static vlc_mutex_t serializer = VLC_STATIC_MUTEX;
538 /** Acquire a drawable */
539 static int AcquireDrawable (vlc_object_t *obj, xcb_window_t window)
544 if (var_Create (obj->p_libvlc, "xid-in-use", VLC_VAR_ADDRESS))
547 /* Keep a list of busy drawables, so we don't overlap videos if there are
548 * more than one video track in the stream. */
549 vlc_mutex_lock (&serializer);
550 used = var_GetAddress (obj->p_libvlc, "xid-in-use");
555 if (used[n] == window)
561 used = realloc (used, sizeof (*used) * (n + 2));
566 var_SetAddress (obj->p_libvlc, "xid-in-use", used);
571 msg_Warn (obj, "X11 drawable 0x%08"PRIx8" is busy", window);
574 vlc_mutex_unlock (&serializer);
576 return (window == 0) ? VLC_EGENERIC : VLC_SUCCESS;
579 /** Remove this drawable from the list of busy ones */
580 static void ReleaseDrawable (vlc_object_t *obj, xcb_window_t window)
585 vlc_mutex_lock (&serializer);
586 used = var_GetAddress (obj->p_libvlc, "xid-in-use");
588 while (used[n] != window)
594 used[n] = used[n + 1];
598 var_SetAddress (obj->p_libvlc, "xid-in-use", NULL);
599 vlc_mutex_unlock (&serializer);
603 /* Variables are reference-counted... */
604 var_Destroy (obj->p_libvlc, "xid-in-use");
608 * Wrap an existing X11 window to embed the video.
610 static int EmOpen (vlc_object_t *obj)
612 vout_window_t *wnd = (vout_window_t *)obj;
614 xcb_window_t window = var_InheritInteger (obj, "drawable-xid");
618 if (AcquireDrawable (obj, window))
621 vout_window_sys_t *p_sys = malloc (sizeof (*p_sys));
622 xcb_connection_t *conn = xcb_connect (NULL, NULL);
623 if (p_sys == NULL || xcb_connection_has_error (conn))
626 p_sys->embedded = true;
628 wnd->handle.xid = window;
629 wnd->control = Control;
634 xcb_get_geometry_reply_t *geo =
635 xcb_get_geometry_reply (conn, xcb_get_geometry (conn, window), NULL);
638 msg_Err (obj, "bad X11 window 0x%08"PRIx8, window);
641 p_sys->root = geo->root;
644 if (var_InheritBool (obj, "keyboard-events"))
646 p_sys->keys = CreateKeyHandler (obj, conn);
647 if (p_sys->keys != NULL)
649 const uint32_t mask = XCB_CW_EVENT_MASK;
650 const uint32_t values[1] = {
651 XCB_EVENT_MASK_KEY_PRESS,
653 xcb_change_window_attributes (conn, window, mask, values);
658 if ((p_sys->keys != NULL)
659 && vlc_clone (&p_sys->thread, Thread, wnd, VLC_THREAD_PRIORITY_LOW))
660 DestroyKeyHandler (p_sys->keys);
667 xcb_disconnect (conn);
669 ReleaseDrawable (obj, window);
673 static void EmClose (vlc_object_t *obj)
675 vout_window_t *wnd = (vout_window_t *)obj;
676 xcb_window_t window = wnd->handle.xid;
679 ReleaseDrawable (obj, window);