contrib: theora: do not run autoreconf
[vlc/gmpfix.git] / modules / video_output / xcb / window.c
blob246429dbab9054a410aa8c9b1019d41c8f92203e
1 /**
2 * @file window.c
3 * @brief X C Bindings window provider module for VLC media player
4 */
5 /*****************************************************************************
6 * Copyright © 2009 Rémi Denis-Courmont
8 * This program is free software; you can redistribute it and/or modify it
9 * under the terms of the GNU Lesser General Public License as published by
10 * the Free Software Foundation; either version 2.1 of the License, or
11 * (at your option) any later version.
13 * This program 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 Lesser General Public License for more details.
18 * You should have received a copy of the GNU Lesser General Public License
19 * along with this program; if not, write to the Free Software Foundation,
20 * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
21 *****************************************************************************/
23 #ifdef HAVE_CONFIG_H
24 # include <config.h>
25 #endif
27 #include <stdarg.h>
28 #include <assert.h>
29 #include <poll.h>
30 #include <unistd.h> /* gethostname() and sysconf() */
31 #include <limits.h> /* _POSIX_HOST_NAME_MAX */
33 #include <xcb/xcb.h>
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>
41 #include "events.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 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 *);
59 * Module descriptor
61 vlc_module_begin ()
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")
75 add_submodule ()
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)
86 change_volatile ()
88 vlc_module_end ()
90 static int Control (vout_window_t *, int, va_list ap);
91 static void *Thread (void *);
93 struct vout_window_sys_t
95 xcb_connection_t *conn;
96 key_handler_t *keys;
97 vlc_thread_t thread;
99 xcb_window_t root;
100 xcb_atom_t wm_state;
101 xcb_atom_t wm_state_above;
102 xcb_atom_t wm_state_below;
103 xcb_atom_t wm_state_fullscreen;
105 bool embedded;
108 /** Set an X window property from a nul-terminated string */
109 static inline
110 void set_string (xcb_connection_t *conn, xcb_window_t window,
111 xcb_atom_t type, xcb_atom_t atom, const char *str)
113 xcb_change_property (conn, XCB_PROP_MODE_REPLACE, window, atom, type,
114 /* format */ 8, strlen (str), str);
117 /** Set an X window string property */
118 static inline
119 void set_ascii_prop (xcb_connection_t *conn, xcb_window_t window,
120 xcb_atom_t atom, const char *value)
122 set_string (conn, window, XA_STRING, atom, value);
125 static inline
126 void set_wm_hints (xcb_connection_t *conn, xcb_window_t window)
128 static const uint32_t wm_hints[8] = {
129 3, /* flags: Input, Initial state */
130 1, /* input: True */
131 1, /* initial state: Normal */
132 0, 0, 0, 0, 0, /* Icon */
134 xcb_change_property (conn, XCB_PROP_MODE_REPLACE, window, XA_WM_HINTS,
135 XA_WM_HINTS, 32, 8, wm_hints);
138 /** Set the Window ICCCM client machine property */
139 static inline
140 void set_hostname_prop (xcb_connection_t *conn, xcb_window_t window)
142 char* hostname;
143 long host_name_max = sysconf (_SC_HOST_NAME_MAX);
144 if (host_name_max <= 0) host_name_max = _POSIX_HOST_NAME_MAX;
145 hostname = malloc (host_name_max);
146 if(!hostname) return;
148 if (gethostname (hostname, host_name_max) == 0)
150 hostname[host_name_max - 1] = '\0';
151 set_ascii_prop (conn, window, XA_WM_CLIENT_MACHINE, hostname);
153 free(hostname);
156 /** Request the X11 server to internalize a string into an atom */
157 static inline
158 xcb_intern_atom_cookie_t intern_string (xcb_connection_t *c, const char *s)
160 return xcb_intern_atom (c, 0, strlen (s), s);
163 /** Extract the X11 atom from an intern request cookie */
164 static
165 xcb_atom_t get_atom (xcb_connection_t *conn, xcb_intern_atom_cookie_t ck)
167 xcb_intern_atom_reply_t *reply;
168 xcb_atom_t atom;
170 reply = xcb_intern_atom_reply (conn, ck, NULL);
171 if (reply == NULL)
172 return 0;
174 atom = reply->atom;
175 free (reply);
176 return atom;
179 #define NET_WM_STATE_REMOVE 0
180 #define NET_WM_STATE_ADD 1
181 #define NET_WM_STATE_TOGGLE 2
183 static void CacheAtoms (vout_window_sys_t *p_sys)
185 xcb_connection_t *conn = p_sys->conn;
186 xcb_intern_atom_cookie_t wm_state_ck, wm_state_above_ck,
187 wm_state_below_ck, wm_state_fs_ck;
189 wm_state_ck = intern_string (conn, "_NET_WM_STATE");
190 wm_state_above_ck = intern_string (conn, "_NET_WM_STATE_ABOVE");
191 wm_state_below_ck = intern_string (conn, "_NET_WM_STATE_BELOW");
192 wm_state_fs_ck = intern_string (conn, "_NET_WM_STATE_FULLSCREEN");
194 p_sys->wm_state = get_atom (conn, wm_state_ck);
195 p_sys->wm_state_above = get_atom (conn, wm_state_above_ck);
196 p_sys->wm_state_below = get_atom (conn, wm_state_below_ck);
197 p_sys->wm_state_fullscreen = get_atom (conn, wm_state_fs_ck);
201 * Create an X11 window.
203 static int Open (vout_window_t *wnd, const vout_window_cfg_t *cfg)
205 xcb_generic_error_t *err;
206 xcb_void_cookie_t ck;
208 vout_window_sys_t *p_sys = malloc (sizeof (*p_sys));
209 if (p_sys == NULL)
210 return VLC_ENOMEM;
211 p_sys->embedded = false;
213 /* Connect to X */
214 char *display = var_InheritString (wnd, "x11-display");
215 int snum;
217 xcb_connection_t *conn = xcb_connect (display, &snum);
218 if (xcb_connection_has_error (conn) /*== NULL*/)
219 goto error;
221 /* Find configured screen */
222 const xcb_setup_t *setup = xcb_get_setup (conn);
223 const xcb_screen_t *scr = NULL;
224 for (xcb_screen_iterator_t i = xcb_setup_roots_iterator (setup);
225 i.rem > 0; xcb_screen_next (&i))
227 if (snum == 0)
229 scr = i.data;
230 break;
232 snum--;
234 if (scr == NULL)
236 msg_Err (wnd, "bad X11 screen number");
237 goto error;
240 /* Create window */
241 const uint32_t mask = XCB_CW_BACK_PIXEL | XCB_CW_EVENT_MASK;
242 uint32_t values[2] = {
243 /* XCB_CW_BACK_PIXEL */
244 scr->black_pixel,
245 /* XCB_CW_EVENT_MASK */
246 XCB_EVENT_MASK_KEY_PRESS,
249 xcb_window_t window = xcb_generate_id (conn);
250 ck = xcb_create_window_checked (conn, scr->root_depth, window, scr->root,
251 cfg->x, cfg->y, cfg->width, cfg->height, 0,
252 XCB_WINDOW_CLASS_INPUT_OUTPUT,
253 scr->root_visual, mask, values);
254 err = xcb_request_check (conn, ck);
255 if (err)
257 msg_Err (wnd, "creating window: X11 error %d", err->error_code);
258 free (err);
259 goto error;
262 wnd->handle.xid = window;
263 wnd->display.x11 = display;
264 wnd->control = Control;
265 wnd->sys = p_sys;
267 p_sys->conn = conn;
268 if (var_InheritBool (wnd, "keyboard-events"))
269 p_sys->keys = XCB_keyHandler_Create (VLC_OBJECT(wnd), conn);
270 else
271 p_sys->keys = NULL;
272 p_sys->root = scr->root;
274 /* ICCCM
275 * No cut&paste nor drag&drop, only Window Manager communication. */
276 set_ascii_prop (conn, window, XA_WM_NAME,
277 /* xgettext: This is a plain ASCII spelling of "VLC media player"
278 for the ICCCM window name. This must be pure ASCII.
279 The limitation is partially with ICCCM and partially with VLC.
280 For Latin script languages, you may need to strip accents.
281 For other scripts, you will need to transliterate into Latin. */
282 vlc_pgettext ("ASCII", "VLC media player"));
284 set_ascii_prop (conn, window, XA_WM_ICON_NAME,
285 /* xgettext: This is a plain ASCII spelling of "VLC"
286 for the ICCCM window name. This must be pure ASCII. */
287 vlc_pgettext ("ASCII", "VLC"));
288 set_wm_hints (conn, window);
289 xcb_change_property (conn, XCB_PROP_MODE_REPLACE, window, XA_WM_CLASS,
290 XA_STRING, 8, 8, "vlc\0Vlc");
291 set_hostname_prop (conn, window);
293 /* EWMH */
294 xcb_intern_atom_cookie_t utf8_string_ck
295 = intern_string (conn, "UTF8_STRING");;
296 xcb_intern_atom_cookie_t net_wm_name_ck
297 = intern_string (conn, "_NET_WM_NAME");
298 xcb_intern_atom_cookie_t net_wm_icon_name_ck
299 = intern_string (conn, "_NET_WM_ICON_NAME");
300 xcb_intern_atom_cookie_t wm_window_role_ck
301 = intern_string (conn, "WM_WINDOW_ROLE");
303 xcb_atom_t utf8 = get_atom (conn, utf8_string_ck);
305 xcb_atom_t net_wm_name = get_atom (conn, net_wm_name_ck);
306 char *title = var_InheritString (wnd, "video-title");
307 if (title)
309 set_string (conn, window, utf8, net_wm_name, title);
310 free (title);
312 else
313 set_string (conn, window, utf8, net_wm_name, _("VLC media player"));
315 xcb_atom_t net_wm_icon_name = get_atom (conn, net_wm_icon_name_ck);
316 set_string (conn, window, utf8, net_wm_icon_name, _("VLC"));
318 xcb_atom_t wm_window_role = get_atom (conn, wm_window_role_ck);
319 set_ascii_prop (conn, window, wm_window_role, "vlc-video");
321 /* Cache any EWMH atom we may need later */
322 CacheAtoms (p_sys);
324 /* Make the window visible */
325 xcb_map_window (conn, window);
327 /* Create the event thread. It will dequeue all events, so any checked
328 * request from this thread must be completed at this point. */
329 if ((p_sys->keys != NULL)
330 && vlc_clone (&p_sys->thread, Thread, wnd, VLC_THREAD_PRIORITY_LOW))
332 XCB_keyHandler_Destroy (p_sys->keys);
333 p_sys->keys = NULL;
336 xcb_flush (conn); /* Make sure map_window is sent (should be useless) */
337 return VLC_SUCCESS;
339 error:
340 xcb_disconnect (conn);
341 free (display);
342 free (p_sys);
343 return VLC_EGENERIC;
348 * Destroys the X11 window.
350 static void Close (vout_window_t *wnd)
352 vout_window_sys_t *p_sys = wnd->sys;
353 xcb_connection_t *conn = p_sys->conn;
355 if (p_sys->keys)
357 vlc_cancel (p_sys->thread);
358 vlc_join (p_sys->thread, NULL);
359 XCB_keyHandler_Destroy (p_sys->keys);
361 xcb_disconnect (conn);
362 free (wnd->display.x11);
363 free (p_sys);
367 /** Background thread for X11 events handling */
368 static void *Thread (void *data)
370 vout_window_t *wnd = data;
371 vout_window_sys_t *p_sys = wnd->sys;
372 xcb_connection_t *conn = p_sys->conn;
374 int fd = xcb_get_file_descriptor (conn);
375 if (fd == -1)
376 return NULL;
378 for (;;)
380 xcb_generic_event_t *ev;
381 struct pollfd ufd = { .fd = fd, .events = POLLIN, };
383 poll (&ufd, 1, -1);
385 int canc = vlc_savecancel ();
386 while ((ev = xcb_poll_for_event (conn)) != NULL)
388 if (XCB_keyHandler_Process (p_sys->keys, ev) == 0)
389 continue;
390 msg_Dbg (wnd, "unhandled event: %"PRIu8, ev->response_type);
391 free (ev);
393 vlc_restorecancel (canc);
395 if (xcb_connection_has_error (conn))
397 msg_Err (wnd, "X server failure");
398 break;
401 return NULL;
404 /** Changes the EWMH state of the window */
405 static void set_wm_state (vout_window_t *wnd, bool on, xcb_atom_t state)
407 vout_window_sys_t *sys = wnd->sys;
408 /* From EWMH "_WM_STATE" */
409 xcb_client_message_event_t ev = {
410 .response_type = XCB_CLIENT_MESSAGE,
411 .format = 32,
412 .window = wnd->handle.xid,
413 .type = sys->wm_state,
416 ev.data.data32[0] = on ? NET_WM_STATE_ADD : NET_WM_STATE_REMOVE;
417 ev.data.data32[1] = state;
418 ev.data.data32[2] = 0;
419 ev.data.data32[3] = 1;
421 /* From ICCCM "Changing Window State" */
422 xcb_send_event (sys->conn, 0, sys->root,
423 XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY |
424 XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT,
425 (const char *)&ev);
429 static int Control (vout_window_t *wnd, int cmd, va_list ap)
431 vout_window_sys_t *p_sys = wnd->sys;
432 xcb_connection_t *conn = p_sys->conn;
434 switch (cmd)
436 case VOUT_WINDOW_SET_SIZE:
438 if (p_sys->embedded)
439 return VLC_EGENERIC;
441 unsigned width = va_arg (ap, unsigned);
442 unsigned height = va_arg (ap, unsigned);
443 const uint32_t values[] = { width, height, };
445 xcb_configure_window (conn, wnd->handle.xid,
446 XCB_CONFIG_WINDOW_WIDTH |
447 XCB_CONFIG_WINDOW_HEIGHT, values);
448 break;
451 case VOUT_WINDOW_SET_STATE:
453 unsigned state = va_arg (ap, unsigned);
454 bool above = (state & VOUT_WINDOW_STATE_ABOVE) != 0;
455 bool below = (state & VOUT_WINDOW_STATE_BELOW) != 0;
457 set_wm_state (wnd, above, p_sys->wm_state_above);
458 set_wm_state (wnd, below, p_sys->wm_state_below);
459 break;
462 case VOUT_WINDOW_SET_FULLSCREEN:
464 bool fs = va_arg (ap, int);
465 set_wm_state (wnd, fs, p_sys->wm_state_fullscreen);
466 break;
469 default:
470 msg_Err (wnd, "request %d not implemented", cmd);
471 return VLC_EGENERIC;
473 xcb_flush (p_sys->conn);
474 return VLC_SUCCESS;
477 /*** Embedded drawable support ***/
479 static vlc_mutex_t serializer = VLC_STATIC_MUTEX;
481 /** Acquire a drawable */
482 static int AcquireDrawable (vlc_object_t *obj, xcb_window_t window)
484 xcb_window_t *used;
485 size_t n = 0;
487 if (var_Create (obj->p_libvlc, "xid-in-use", VLC_VAR_ADDRESS))
488 return VLC_ENOMEM;
490 /* Keep a list of busy drawables, so we don't overlap videos if there are
491 * more than one video track in the stream. */
492 vlc_mutex_lock (&serializer);
493 used = var_GetAddress (obj->p_libvlc, "xid-in-use");
494 if (used != NULL)
496 while (used[n])
498 if (used[n] == window)
499 goto skip;
500 n++;
504 used = realloc (used, sizeof (*used) * (n + 2));
505 if (used != NULL)
507 used[n] = window;
508 used[n + 1] = 0;
509 var_SetAddress (obj->p_libvlc, "xid-in-use", used);
511 else
513 skip:
514 msg_Warn (obj, "X11 drawable 0x%08"PRIx8" is busy", window);
515 window = 0;
517 vlc_mutex_unlock (&serializer);
519 return (window == 0) ? VLC_EGENERIC : VLC_SUCCESS;
522 /** Remove this drawable from the list of busy ones */
523 static void ReleaseDrawable (vlc_object_t *obj, xcb_window_t window)
525 xcb_window_t *used;
526 size_t n = 0;
528 vlc_mutex_lock (&serializer);
529 used = var_GetAddress (obj->p_libvlc, "xid-in-use");
530 assert (used);
531 while (used[n] != window)
533 assert (used[n]);
534 n++;
537 used[n] = used[n + 1];
538 while (used[++n]);
540 if (n == 0)
541 var_SetAddress (obj->p_libvlc, "xid-in-use", NULL);
542 vlc_mutex_unlock (&serializer);
544 if (n == 0)
545 free (used);
546 /* Variables are reference-counted... */
547 var_Destroy (obj->p_libvlc, "xid-in-use");
551 * Wrap an existing X11 window to embed the video.
553 static int EmOpen (vout_window_t *wnd, const vout_window_cfg_t *cfg)
555 xcb_window_t window = var_InheritInteger (wnd, "drawable-xid");
556 if (window == 0)
557 return VLC_EGENERIC;
559 if (AcquireDrawable (VLC_OBJECT(wnd), window))
560 return VLC_EGENERIC;
562 vout_window_sys_t *p_sys = malloc (sizeof (*p_sys));
563 xcb_connection_t *conn = xcb_connect (NULL, NULL);
564 if (p_sys == NULL || xcb_connection_has_error (conn))
565 goto error;
567 p_sys->embedded = true;
568 p_sys->keys = NULL;
569 wnd->handle.xid = window;
570 wnd->control = Control;
571 wnd->sys = p_sys;
573 p_sys->conn = conn;
575 xcb_get_geometry_reply_t *geo =
576 xcb_get_geometry_reply (conn, xcb_get_geometry (conn, window), NULL);
577 if (geo == NULL)
579 msg_Err (wnd, "bad X11 window 0x%08"PRIx8, window);
580 goto error;
582 p_sys->root = geo->root;
583 free (geo);
585 if (var_InheritBool (wnd, "keyboard-events"))
587 p_sys->keys = XCB_keyHandler_Create (VLC_OBJECT(wnd), conn);
588 if (p_sys->keys != NULL)
590 const uint32_t mask = XCB_CW_EVENT_MASK;
591 const uint32_t values[1] = {
592 XCB_EVENT_MASK_KEY_PRESS,
594 xcb_change_window_attributes (conn, window, mask, values);
598 CacheAtoms (p_sys);
599 if ((p_sys->keys != NULL)
600 && vlc_clone (&p_sys->thread, Thread, wnd, VLC_THREAD_PRIORITY_LOW))
602 XCB_keyHandler_Destroy (p_sys->keys);
603 p_sys->keys = NULL;
606 xcb_flush (conn);
607 (void) cfg;
608 return VLC_SUCCESS;
610 error:
611 xcb_disconnect (conn);
612 free (p_sys);
613 ReleaseDrawable (VLC_OBJECT(wnd), window);
614 return VLC_EGENERIC;
617 static void EmClose (vout_window_t *wnd)
619 xcb_window_t window = wnd->handle.xid;
621 Close (wnd);
622 ReleaseDrawable (VLC_OBJECT(wnd), window);