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;
105 /** Set an X window property from a nul-terminated string */
107 void set_string (xcb_connection_t *conn, xcb_window_t window,
108 xcb_atom_t type, xcb_atom_t atom, const char *str)
110 xcb_change_property (conn, XCB_PROP_MODE_REPLACE, window, atom, type,
111 /* format */ 8, strlen (str), str);
114 /** Set an X window string property */
116 void set_ascii_prop (xcb_connection_t *conn, xcb_window_t window,
117 xcb_atom_t atom, const char *value)
119 set_string (conn, window, XA_STRING, atom, value);
123 void set_wm_hints (xcb_connection_t *conn, xcb_window_t window)
125 static const uint32_t wm_hints[8] = {
126 3, /* flags: Input, Initial state */
128 1, /* initial state: Normal */
129 0, 0, 0, 0, 0, /* Icon */
131 xcb_change_property (conn, XCB_PROP_MODE_REPLACE, window, XA_WM_HINTS,
132 XA_WM_HINTS, 32, 8, wm_hints);
135 /** Set the Window ICCCM client machine property */
137 void set_hostname_prop (xcb_connection_t *conn, xcb_window_t window)
140 long host_name_max = sysconf (_SC_HOST_NAME_MAX);
141 if (host_name_max <= 0) host_name_max = _POSIX_HOST_NAME_MAX;
142 hostname = malloc (host_name_max);
143 if(!hostname) return;
145 if (gethostname (hostname, host_name_max) == 0)
147 hostname[host_name_max - 1] = '\0';
148 set_ascii_prop (conn, window, XA_WM_CLIENT_MACHINE, hostname);
153 /** Request the X11 server to internalize a string into an atom */
155 xcb_intern_atom_cookie_t intern_string (xcb_connection_t *c, const char *s)
157 return xcb_intern_atom (c, 0, strlen (s), s);
160 /** Extract the X11 atom from an intern request cookie */
162 xcb_atom_t get_atom (xcb_connection_t *conn, xcb_intern_atom_cookie_t ck)
164 xcb_intern_atom_reply_t *reply;
167 reply = xcb_intern_atom_reply (conn, ck, NULL);
176 #define NET_WM_STATE_REMOVE 0
177 #define NET_WM_STATE_ADD 1
178 #define NET_WM_STATE_TOGGLE 2
180 static void CacheAtoms (vout_window_sys_t *p_sys)
182 xcb_connection_t *conn = p_sys->conn;
183 xcb_intern_atom_cookie_t wm_state_ck, wm_state_above_ck,
184 wm_state_below_ck, wm_state_fs_ck;
186 wm_state_ck = intern_string (conn, "_NET_WM_STATE");
187 wm_state_above_ck = intern_string (conn, "_NET_WM_STATE_ABOVE");
188 wm_state_below_ck = intern_string (conn, "_NET_WM_STATE_BELOW");
189 wm_state_fs_ck = intern_string (conn, "_NET_WM_STATE_FULLSCREEN");
191 xcb_intern_atom_cookie_t mb_current_app_window;
192 mb_current_app_window = xcb_intern_atom (conn, true,
193 strlen ("_MB_CURRENT_APP_WINDOW"),
194 "_MB_CURRENT_APP_WINDOW");
197 p_sys->wm_state = get_atom (conn, wm_state_ck);
198 p_sys->wm_state_above = get_atom (conn, wm_state_above_ck);
199 p_sys->wm_state_below = get_atom (conn, wm_state_below_ck);
200 p_sys->wm_state_fullscreen = get_atom (conn, wm_state_fs_ck);
202 p_sys->mb_current_app_window = get_atom (conn, mb_current_app_window);
207 * Create an X11 window.
209 static int Open (vlc_object_t *obj)
211 vout_window_t *wnd = (vout_window_t *)obj;
212 xcb_generic_error_t *err;
213 xcb_void_cookie_t ck;
215 vout_window_sys_t *p_sys = malloc (sizeof (*p_sys));
220 char *display = var_InheritString (wnd, "x11-display");
223 xcb_connection_t *conn = xcb_connect (display, &snum);
224 if (xcb_connection_has_error (conn) /*== NULL*/)
227 /* Find configured screen */
228 const xcb_setup_t *setup = xcb_get_setup (conn);
229 const xcb_screen_t *scr = NULL;
230 for (xcb_screen_iterator_t i = xcb_setup_roots_iterator (setup);
231 i.rem > 0; xcb_screen_next (&i))
242 msg_Err (wnd, "bad X11 screen number");
247 const uint32_t mask = XCB_CW_BACK_PIXEL | XCB_CW_EVENT_MASK;
248 uint32_t values[2] = {
249 /* XCB_CW_BACK_PIXEL */
251 /* XCB_CW_EVENT_MASK */
252 XCB_EVENT_MASK_KEY_PRESS,
255 xcb_window_t window = xcb_generate_id (conn);
256 ck = xcb_create_window_checked (conn, scr->root_depth, window, scr->root,
257 wnd->cfg->x, wnd->cfg->y,
258 wnd->cfg->width, wnd->cfg->height, 0,
259 XCB_WINDOW_CLASS_INPUT_OUTPUT,
260 scr->root_visual, mask, values);
261 err = xcb_request_check (conn, ck);
264 msg_Err (wnd, "creating window: X11 error %d", err->error_code);
269 wnd->handle.xid = window;
270 wnd->display.x11 = display;
271 wnd->control = Control;
275 if (var_InheritBool (obj, "keyboard-events"))
276 p_sys->keys = CreateKeyHandler (obj, conn);
279 p_sys->root = scr->root;
282 * No cut&paste nor drag&drop, only Window Manager communication. */
283 set_ascii_prop (conn, window, XA_WM_NAME,
284 /* xgettext: This is a plain ASCII spelling of "VLC media player"
285 for the ICCCM window name. This must be pure ASCII.
286 The limitation is partially with ICCCM and partially with VLC.
287 For Latin script languages, you may need to strip accents.
288 For other scripts, you will need to transliterate into Latin. */
289 vlc_pgettext ("ASCII", "VLC media player"));
291 set_ascii_prop (conn, window, XA_WM_ICON_NAME,
292 /* xgettext: This is a plain ASCII spelling of "VLC"
293 for the ICCCM window name. This must be pure ASCII. */
294 vlc_pgettext ("ASCII", "VLC"));
295 set_wm_hints (conn, window);
296 xcb_change_property (conn, XCB_PROP_MODE_REPLACE, window, XA_WM_CLASS,
297 XA_STRING, 8, 8, "vlc\0Vlc");
298 set_hostname_prop (conn, window);
301 xcb_intern_atom_cookie_t utf8_string_ck
302 = intern_string (conn, "UTF8_STRING");;
303 xcb_intern_atom_cookie_t net_wm_name_ck
304 = intern_string (conn, "_NET_WM_NAME");
305 xcb_intern_atom_cookie_t net_wm_icon_name_ck
306 = intern_string (conn, "_NET_WM_ICON_NAME");
307 xcb_intern_atom_cookie_t wm_window_role_ck
308 = intern_string (conn, "WM_WINDOW_ROLE");
310 xcb_atom_t utf8 = get_atom (conn, utf8_string_ck);
312 xcb_atom_t net_wm_name = get_atom (conn, net_wm_name_ck);
313 char *title = var_InheritString (wnd, "video-title");
316 set_string (conn, window, utf8, net_wm_name, title);
320 set_string (conn, window, utf8, net_wm_name, _("VLC media player"));
322 xcb_atom_t net_wm_icon_name = get_atom (conn, net_wm_icon_name_ck);
323 set_string (conn, window, utf8, net_wm_icon_name, _("VLC"));
325 xcb_atom_t wm_window_role = get_atom (conn, wm_window_role_ck);
326 set_ascii_prop (conn, window, wm_window_role, "vlc-video");
328 /* Cache any EWMH atom we may need later */
331 if (p_sys->mb_current_app_window)
333 uint32_t value = XCB_EVENT_MASK_PROPERTY_CHANGE;
334 xcb_change_window_attributes (conn, scr->root,
335 XCB_CW_EVENT_MASK, &value);
339 /* Make the window visible */
340 xcb_map_window (conn, window);
342 if (var_InheritBool (obj, "video-wallpaper"))
344 vout_window_SetState (wnd, VOUT_WINDOW_STATE_BELOW);
345 vout_window_SetFullScreen (wnd, true);
348 /* Create the event thread. It will dequeue all events, so any checked
349 * request from this thread must be completed at this point. */
350 if ((p_sys->keys != NULL)
351 && vlc_clone (&p_sys->thread, Thread, wnd, VLC_THREAD_PRIORITY_LOW))
352 DestroyKeyHandler (p_sys->keys);
355 if (p_sys->mb_current_app_window)
356 xcb_set_input_focus (p_sys->conn, XCB_INPUT_FOCUS_POINTER_ROOT,
357 wnd->handle.xid, XCB_CURRENT_TIME);
359 xcb_flush (conn); /* Make sure map_window is sent (should be useless) */
363 xcb_disconnect (conn);
371 * Destroys the X11 window.
373 static void Close (vlc_object_t *obj)
375 vout_window_t *wnd = (vout_window_t *)obj;
376 vout_window_sys_t *p_sys = wnd->sys;
377 xcb_connection_t *conn = p_sys->conn;
381 vlc_cancel (p_sys->thread);
382 vlc_join (p_sys->thread, NULL);
383 DestroyKeyHandler (p_sys->keys);
385 xcb_disconnect (conn);
386 free (wnd->display.x11);
391 /** Background thread for X11 events handling */
392 static void *Thread (void *data)
394 vout_window_t *wnd = data;
395 vout_window_sys_t *p_sys = wnd->sys;
396 xcb_connection_t *conn = p_sys->conn;
398 int fd = xcb_get_file_descriptor (conn);
404 xcb_generic_event_t *ev;
405 struct pollfd ufd = { .fd = fd, .events = POLLIN, };
409 int canc = vlc_savecancel ();
410 while ((ev = xcb_poll_for_event (conn)) != NULL)
412 if (ProcessKeyEvent (p_sys->keys, ev) == 0)
415 if (p_sys->mb_current_app_window
416 && (ev->response_type & 0x7f) == XCB_PROPERTY_NOTIFY)
418 const xcb_property_notify_event_t *pne =
419 (xcb_property_notify_event_t *)ev;
420 if (pne->atom == p_sys->mb_current_app_window
421 && pne->state == XCB_PROPERTY_NEW_VALUE)
423 xcb_get_property_reply_t *r =
424 xcb_get_property_reply (conn,
425 xcb_get_property (conn, 0, pne->window, pne->atom,
426 XA_WINDOW, 0, 4), NULL);
428 && !memcmp (xcb_get_property_value (r), &wnd->handle.xid,
431 msg_Dbg (wnd, "asking Matchbox for input focus");
432 xcb_set_input_focus (conn,
433 XCB_INPUT_FOCUS_POINTER_ROOT,
434 wnd->handle.xid, pne->time);
442 msg_Dbg (wnd, "unhandled event: %"PRIu8, ev->response_type);
445 vlc_restorecancel (canc);
447 if (xcb_connection_has_error (conn))
449 msg_Err (wnd, "X server failure");
456 /** Changes the EWMH state of the window */
457 static void set_wm_state (vout_window_t *wnd, bool on, xcb_atom_t state)
459 vout_window_sys_t *sys = wnd->sys;
460 /* From EWMH "_WM_STATE" */
461 xcb_client_message_event_t ev = {
462 .response_type = XCB_CLIENT_MESSAGE,
464 .window = wnd->handle.xid,
465 .type = sys->wm_state,
468 ev.data.data32[0] = on ? NET_WM_STATE_ADD : NET_WM_STATE_REMOVE;
469 ev.data.data32[1] = state;
470 ev.data.data32[2] = 0;
471 ev.data.data32[3] = 1;
473 /* From ICCCM "Changing Window State" */
474 xcb_send_event (sys->conn, 0, sys->root,
475 XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY |
476 XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT,
481 static int Control (vout_window_t *wnd, int cmd, va_list ap)
483 vout_window_sys_t *p_sys = wnd->sys;
484 xcb_connection_t *conn = p_sys->conn;
488 case VOUT_WINDOW_SET_SIZE:
490 unsigned width = va_arg (ap, unsigned);
491 unsigned height = va_arg (ap, unsigned);
492 const uint32_t values[] = { width, height, };
494 xcb_configure_window (conn, wnd->handle.xid,
495 XCB_CONFIG_WINDOW_WIDTH |
496 XCB_CONFIG_WINDOW_HEIGHT, values);
500 case VOUT_WINDOW_SET_STATE:
502 unsigned state = va_arg (ap, unsigned);
503 bool above = (state & VOUT_WINDOW_STATE_ABOVE) != 0;
504 bool below = (state & VOUT_WINDOW_STATE_BELOW) != 0;
506 set_wm_state (wnd, above, p_sys->wm_state_above);
507 set_wm_state (wnd, below, p_sys->wm_state_below);
511 case VOUT_WINDOW_SET_FULLSCREEN:
513 bool fs = va_arg (ap, int);
514 if (!fs && var_GetBool (wnd, "video-wallpaper"))
516 set_wm_state (wnd, fs, p_sys->wm_state_fullscreen);
521 msg_Err (wnd, "request %d not implemented", cmd);
524 xcb_flush (p_sys->conn);
528 /*** Embedded drawable support ***/
530 static vlc_mutex_t serializer = VLC_STATIC_MUTEX;
532 /** Acquire a drawable */
533 static int AcquireDrawable (vlc_object_t *obj, xcb_window_t window)
538 if (var_Create (obj->p_libvlc, "xid-in-use", VLC_VAR_ADDRESS))
541 /* Keep a list of busy drawables, so we don't overlap videos if there are
542 * more than one video track in the stream. */
543 vlc_mutex_lock (&serializer);
544 used = var_GetAddress (obj->p_libvlc, "xid-in-use");
549 if (used[n] == window)
555 used = realloc (used, sizeof (*used) * (n + 2));
560 var_SetAddress (obj->p_libvlc, "xid-in-use", used);
565 msg_Warn (obj, "X11 drawable 0x%08"PRIx8" is busy", window);
568 vlc_mutex_unlock (&serializer);
570 return (window == 0) ? VLC_EGENERIC : VLC_SUCCESS;
573 /** Remove this drawable from the list of busy ones */
574 static void ReleaseDrawable (vlc_object_t *obj, xcb_window_t window)
579 vlc_mutex_lock (&serializer);
580 used = var_GetAddress (obj->p_libvlc, "xid-in-use");
582 while (used[n] != window)
588 used[n] = used[n + 1];
592 var_SetAddress (obj->p_libvlc, "xid-in-use", NULL);
593 vlc_mutex_unlock (&serializer);
597 /* Variables are reference-counted... */
598 var_Destroy (obj->p_libvlc, "xid-in-use");
602 * Wrap an existing X11 window to embed the video.
604 static int EmOpen (vlc_object_t *obj)
606 vout_window_t *wnd = (vout_window_t *)obj;
608 xcb_window_t window = var_InheritInteger (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_InheritBool (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);