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 0, 0, wnd->cfg->width, wnd->cfg->height, 0,
257 XCB_WINDOW_CLASS_INPUT_OUTPUT,
258 scr->root_visual, mask, values);
259 err = xcb_request_check (conn, ck);
262 msg_Err (wnd, "creating window: X11 error %d", err->error_code);
267 wnd->handle.xid = window;
268 wnd->display.x11 = display;
269 wnd->control = Control;
273 if (var_CreateGetBool (obj, "keyboard-events"))
274 p_sys->keys = CreateKeyHandler (obj, conn);
277 p_sys->root = scr->root;
280 * No cut&paste nor drag&drop, only Window Manager communication. */
282 Plain ASCII of "VLC media player" for the ICCCM window name.
283 This must be ASCII. The limitation is partially with ICCCM
284 and partially with VLC.
285 For Latin script languages, you may need to strip accents.
286 For other scripts, you will need to transliterate into Latin. */
287 set_ascii_prop (conn, window, XA_WM_NAME,
288 vlc_pgettext ("ASCII", "VLC media player"));
290 Plain ASCII of "VLC" for the ICCCM window name. */
291 set_ascii_prop (conn, window, XA_WM_ICON_NAME,
292 vlc_pgettext ("ASCII", "VLC"));
293 set_wm_hints (conn, window);
294 xcb_change_property (conn, XCB_PROP_MODE_REPLACE, window, XA_WM_CLASS,
295 XA_STRING, 8, 8, "vlc\0Vlc");
296 set_hostname_prop (conn, window);
299 xcb_intern_atom_cookie_t utf8_string_ck
300 = intern_string (conn, "UTF8_STRING");;
301 xcb_intern_atom_cookie_t net_wm_name_ck
302 = intern_string (conn, "_NET_WM_NAME");
303 xcb_intern_atom_cookie_t net_wm_icon_name_ck
304 = intern_string (conn, "_NET_WM_ICON_NAME");
305 xcb_intern_atom_cookie_t wm_window_role_ck
306 = intern_string (conn, "WM_WINDOW_ROLE");
308 xcb_atom_t utf8 = get_atom (conn, utf8_string_ck);
310 xcb_atom_t net_wm_name = get_atom (conn, net_wm_name_ck);
311 char *title = var_CreateGetNonEmptyString (wnd, "video-title");
314 set_string (conn, window, utf8, net_wm_name, title);
318 set_string (conn, window, utf8, net_wm_name, _("VLC media player"));
320 xcb_atom_t net_wm_icon_name = get_atom (conn, net_wm_icon_name_ck);
321 set_string (conn, window, utf8, net_wm_icon_name, _("VLC"));
323 xcb_atom_t wm_window_role = get_atom (conn, wm_window_role_ck);
324 set_ascii_prop (conn, window, wm_window_role, "vlc-video");
326 /* Cache any EWMH atom we may need later */
329 if (p_sys->mb_current_app_window)
331 uint32_t value = XCB_EVENT_MASK_PROPERTY_CHANGE;
332 xcb_change_window_attributes (conn, scr->root,
333 XCB_CW_EVENT_MASK, &value);
337 /* Make the window visible */
338 xcb_map_window (conn, window);
340 if (var_CreateGetBool (obj, "video-wallpaper"))
342 vout_window_SetState (wnd, VOUT_WINDOW_STATE_BELOW);
343 vout_window_SetFullScreen (wnd, true);
346 /* Create the event thread. It will dequeue all events, so any checked
347 * request from this thread must be completed at this point. */
348 if ((p_sys->keys != NULL)
349 && vlc_clone (&p_sys->thread, Thread, wnd, VLC_THREAD_PRIORITY_LOW))
350 DestroyKeyHandler (p_sys->keys);
353 if (p_sys->mb_current_app_window)
354 xcb_set_input_focus (p_sys->conn, XCB_INPUT_FOCUS_POINTER_ROOT,
355 wnd->handle.xid, XCB_CURRENT_TIME);
357 xcb_flush (conn); /* Make sure map_window is sent (should be useless) */
361 xcb_disconnect (conn);
369 * Destroys the X11 window.
371 static void Close (vlc_object_t *obj)
373 vout_window_t *wnd = (vout_window_t *)obj;
374 vout_window_sys_t *p_sys = wnd->sys;
375 xcb_connection_t *conn = p_sys->conn;
379 vlc_cancel (p_sys->thread);
380 vlc_join (p_sys->thread, NULL);
381 DestroyKeyHandler (p_sys->keys);
383 xcb_disconnect (conn);
384 free (wnd->display.x11);
389 /** Background thread for X11 events handling */
390 static void *Thread (void *data)
392 vout_window_t *wnd = data;
393 vout_window_sys_t *p_sys = wnd->sys;
394 xcb_connection_t *conn = p_sys->conn;
396 int fd = xcb_get_file_descriptor (conn);
402 xcb_generic_event_t *ev;
403 struct pollfd ufd = { .fd = fd, .events = POLLIN, };
407 int canc = vlc_savecancel ();
408 while ((ev = xcb_poll_for_event (conn)) != NULL)
410 if (ProcessKeyEvent (p_sys->keys, ev) == 0)
413 if (p_sys->mb_current_app_window
414 && (ev->response_type & 0x7f) == XCB_PROPERTY_NOTIFY)
416 const xcb_property_notify_event_t *pne =
417 (xcb_property_notify_event_t *)ev;
418 if (pne->atom == p_sys->mb_current_app_window
419 && pne->state == XCB_PROPERTY_NEW_VALUE)
421 xcb_get_property_reply_t *r =
422 xcb_get_property_reply (conn,
423 xcb_get_property (conn, 0, pne->window, pne->atom,
424 XA_WINDOW, 0, 4), NULL);
426 && !memcmp (xcb_get_property_value (r), &wnd->handle.xid,
429 msg_Dbg (wnd, "asking Matchbox for input focus");
430 xcb_set_input_focus (conn,
431 XCB_INPUT_FOCUS_POINTER_ROOT,
432 wnd->handle.xid, pne->time);
440 msg_Dbg (wnd, "unhandled event: %"PRIu8, ev->response_type);
443 vlc_restorecancel (canc);
445 if (xcb_connection_has_error (conn))
447 msg_Err (wnd, "X server failure");
454 /** Changes the EWMH state of the window */
455 static void set_wm_state (vout_window_t *wnd, bool on, xcb_atom_t state)
457 vout_window_sys_t *sys = wnd->sys;
458 /* From EWMH "_WM_STATE" */
459 xcb_client_message_event_t ev = {
460 .response_type = XCB_CLIENT_MESSAGE,
462 .window = wnd->handle.xid,
463 .type = sys->wm_state,
466 ev.data.data32[0] = on ? NET_WM_STATE_ADD : NET_WM_STATE_REMOVE;
467 ev.data.data32[1] = state;
468 ev.data.data32[2] = 0;
469 ev.data.data32[3] = 1;
471 /* From ICCCM "Changing Window State" */
472 xcb_send_event (sys->conn, 0, sys->root,
473 XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY |
474 XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT,
479 static int Control (vout_window_t *wnd, int cmd, va_list ap)
481 vout_window_sys_t *p_sys = wnd->sys;
482 xcb_connection_t *conn = p_sys->conn;
486 case VOUT_WINDOW_SET_SIZE:
488 unsigned width = va_arg (ap, unsigned);
489 unsigned height = va_arg (ap, unsigned);
490 const uint32_t values[] = { width, height, };
492 xcb_configure_window (conn, wnd->handle.xid,
493 XCB_CONFIG_WINDOW_WIDTH |
494 XCB_CONFIG_WINDOW_HEIGHT, values);
498 case VOUT_WINDOW_SET_STATE:
500 unsigned state = va_arg (ap, unsigned);
501 bool above = (state & VOUT_WINDOW_STATE_ABOVE) != 0;
502 bool below = (state & VOUT_WINDOW_STATE_BELOW) != 0;
504 set_wm_state (wnd, above, p_sys->wm_state_above);
505 set_wm_state (wnd, below, p_sys->wm_state_below);
509 case VOUT_WINDOW_SET_FULLSCREEN:
511 bool fs = va_arg (ap, int);
512 if (!fs && var_GetBool (wnd, "video-wallpaper"))
514 set_wm_state (wnd, fs, p_sys->wm_state_fullscreen);
519 msg_Err (wnd, "request %d not implemented", cmd);
522 xcb_flush (p_sys->conn);
526 /*** Embedded drawable support ***/
528 static vlc_mutex_t serializer = VLC_STATIC_MUTEX;
530 /** Acquire a drawable */
531 static int AcquireDrawable (vlc_object_t *obj, xcb_window_t window)
536 if (var_Create (obj->p_libvlc, "xid-in-use", VLC_VAR_ADDRESS))
539 /* Keep a list of busy drawables, so we don't overlap videos if there are
540 * more than one video track in the stream. */
541 vlc_mutex_lock (&serializer);
542 used = var_GetAddress (obj->p_libvlc, "xid-in-use");
547 if (used[n] == window)
553 used = realloc (used, sizeof (*used) * (n + 2));
558 var_SetAddress (obj->p_libvlc, "xid-in-use", used);
563 msg_Warn (obj, "X11 drawable 0x%08"PRIx8" is busy", window);
566 vlc_mutex_unlock (&serializer);
568 return (window == 0) ? VLC_EGENERIC : VLC_SUCCESS;
571 /** Remove this drawable from the list of busy ones */
572 static void ReleaseDrawable (vlc_object_t *obj, xcb_window_t window)
577 vlc_mutex_lock (&serializer);
578 used = var_GetAddress (obj->p_libvlc, "xid-in-use");
580 while (used[n] != window)
586 used[n] = used[n + 1];
590 var_SetAddress (obj->p_libvlc, "xid-in-use", NULL);
591 vlc_mutex_unlock (&serializer);
595 /* Variables are reference-counted... */
596 var_Destroy (obj->p_libvlc, "xid-in-use");
600 * Wrap an existing X11 window to embed the video.
602 static int EmOpen (vlc_object_t *obj)
604 vout_window_t *wnd = (vout_window_t *)obj;
606 xcb_window_t window = var_CreateGetInteger (obj, "drawable-xid");
609 var_Destroy (obj, "drawable-xid");
611 if (AcquireDrawable (obj, window))
614 vout_window_sys_t *p_sys = malloc (sizeof (*p_sys));
615 xcb_connection_t *conn = xcb_connect (NULL, NULL);
616 if (p_sys == NULL || xcb_connection_has_error (conn))
619 wnd->handle.xid = window;
620 wnd->control = Control;
625 xcb_get_geometry_reply_t *geo =
626 xcb_get_geometry_reply (conn, xcb_get_geometry (conn, window), NULL);
629 msg_Err (obj, "bad X11 window 0x%08"PRIx8, window);
632 p_sys->root = geo->root;
635 if (var_CreateGetBool (obj, "keyboard-events"))
637 p_sys->keys = CreateKeyHandler (obj, conn);
638 if (p_sys->keys != NULL)
640 const uint32_t mask = XCB_CW_EVENT_MASK;
641 const uint32_t values[1] = {
642 XCB_EVENT_MASK_KEY_PRESS,
644 xcb_change_window_attributes (conn, window, mask, values);
649 if ((p_sys->keys != NULL)
650 && vlc_clone (&p_sys->thread, Thread, wnd, VLC_THREAD_PRIORITY_LOW))
651 DestroyKeyHandler (p_sys->keys);
658 xcb_disconnect (conn);
660 ReleaseDrawable (obj, window);
664 static void EmClose (vlc_object_t *obj)
666 vout_window_t *wnd = (vout_window_t *)obj;
667 xcb_window_t window = wnd->handle.xid;
670 ReleaseDrawable (obj, window);