video_filter: erase: use C99 loop declarations
[vlc.git] / modules / video_output / xcb / window.c
blob91a70965d686c26b028ceba320b6ea11f5bac0c3
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", 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", 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 if (cfg->type != VOUT_WINDOW_TYPE_INVALID
206 && cfg->type != VOUT_WINDOW_TYPE_XID)
207 return VLC_EGENERIC;
209 xcb_generic_error_t *err;
210 xcb_void_cookie_t ck;
212 vout_window_sys_t *p_sys = malloc (sizeof (*p_sys));
213 if (p_sys == NULL)
214 return VLC_ENOMEM;
215 p_sys->embedded = false;
217 /* Connect to X */
218 char *display = var_InheritString (wnd, "x11-display");
219 int snum;
221 xcb_connection_t *conn = xcb_connect (display, &snum);
222 if (xcb_connection_has_error (conn) /*== NULL*/)
223 goto error;
225 /* Find configured screen */
226 const xcb_setup_t *setup = xcb_get_setup (conn);
227 const xcb_screen_t *scr = NULL;
228 for (xcb_screen_iterator_t i = xcb_setup_roots_iterator (setup);
229 i.rem > 0; xcb_screen_next (&i))
231 if (snum == 0)
233 scr = i.data;
234 break;
236 snum--;
238 if (scr == NULL)
240 msg_Err (wnd, "bad X11 screen number");
241 goto error;
244 /* Create window */
245 const uint32_t mask = XCB_CW_BACK_PIXEL | XCB_CW_EVENT_MASK;
246 uint32_t values[2] = {
247 /* XCB_CW_BACK_PIXEL */
248 scr->black_pixel,
249 /* XCB_CW_EVENT_MASK */
250 XCB_EVENT_MASK_KEY_PRESS | XCB_EVENT_MASK_STRUCTURE_NOTIFY,
253 xcb_window_t window = xcb_generate_id (conn);
254 ck = xcb_create_window_checked (conn, scr->root_depth, window, scr->root,
255 0, 0, cfg->width, cfg->height, 0,
256 XCB_WINDOW_CLASS_INPUT_OUTPUT,
257 scr->root_visual, mask, values);
258 err = xcb_request_check (conn, ck);
259 if (err)
261 msg_Err (wnd, "creating window: X11 error %d", err->error_code);
262 free (err);
263 goto error;
266 wnd->type = VOUT_WINDOW_TYPE_XID;
267 wnd->handle.xid = window;
268 wnd->display.x11 = display;
269 wnd->control = Control;
270 wnd->sys = p_sys;
272 p_sys->conn = conn;
273 if (var_InheritBool (wnd, "keyboard-events"))
274 p_sys->keys = XCB_keyHandler_Create (VLC_OBJECT(wnd), conn);
275 else
276 p_sys->keys = NULL;
277 p_sys->root = scr->root;
279 /* ICCCM
280 * No cut&paste nor drag&drop, only Window Manager communication. */
281 set_ascii_prop (conn, window, XA_WM_NAME,
282 /* xgettext: This is a plain ASCII spelling of "VLC media player"
283 for the ICCCM window name. This must be pure ASCII.
284 The limitation is partially with ICCCM 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 vlc_pgettext ("ASCII", "VLC media player"));
289 set_ascii_prop (conn, window, XA_WM_ICON_NAME,
290 /* xgettext: This is a plain ASCII spelling of "VLC"
291 for the ICCCM window name. This must be pure ASCII. */
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);
298 /* EWMH */
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_InheritString (wnd, "video-title");
312 if (title)
314 set_string (conn, window, utf8, net_wm_name, title);
315 free (title);
317 else
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 */
327 CacheAtoms (p_sys);
329 /* Make the window visible */
330 xcb_map_window (conn, window);
332 /* Get the initial mapped size (may differ from requested size) */
333 xcb_get_geometry_reply_t *geo =
334 xcb_get_geometry_reply (conn, xcb_get_geometry (conn, window), NULL);
335 if (geo != NULL)
337 vout_window_ReportSize(wnd, geo->width, geo->height);
338 free (geo);
341 /* Create the event thread. It will dequeue all events, so any checked
342 * request from this thread must be completed at this point. */
343 if (vlc_clone (&p_sys->thread, Thread, wnd, VLC_THREAD_PRIORITY_LOW))
345 if (p_sys->keys != NULL)
346 XCB_keyHandler_Destroy (p_sys->keys);
347 goto error;
350 xcb_flush (conn); /* Make sure map_window is sent (should be useless) */
351 return VLC_SUCCESS;
353 error:
354 xcb_disconnect (conn);
355 free (display);
356 free (p_sys);
357 return VLC_EGENERIC;
362 * Destroys the X11 window.
364 static void Close (vout_window_t *wnd)
366 vout_window_sys_t *p_sys = wnd->sys;
367 xcb_connection_t *conn = p_sys->conn;
369 vlc_cancel (p_sys->thread);
370 vlc_join (p_sys->thread, NULL);
371 if (p_sys->keys != NULL)
372 XCB_keyHandler_Destroy (p_sys->keys);
373 xcb_disconnect (conn);
374 free (wnd->display.x11);
375 free (p_sys);
378 static void ProcessEvent (vout_window_t *wnd, xcb_generic_event_t *ev)
380 vout_window_sys_t *sys = wnd->sys;
382 if (sys->keys != NULL && XCB_keyHandler_Process (sys->keys, ev) == 0)
383 return;
385 switch (ev->response_type & 0x7f)
387 case XCB_CONFIGURE_NOTIFY:
389 xcb_configure_notify_event_t *cne = (void *)ev;
390 vout_window_ReportSize (wnd, cne->width, cne->height);
391 break;
393 case XCB_DESTROY_NOTIFY:
394 vout_window_ReportClose (wnd);
395 break;
397 case XCB_MAPPING_NOTIFY:
398 break;
400 default:
401 msg_Dbg (wnd, "unhandled event %"PRIu8, ev->response_type);
404 free (ev);
407 /** Background thread for X11 events handling */
408 static void *Thread (void *data)
410 vout_window_t *wnd = data;
411 vout_window_sys_t *p_sys = wnd->sys;
412 xcb_connection_t *conn = p_sys->conn;
414 int fd = xcb_get_file_descriptor (conn);
415 if (fd == -1)
416 return NULL;
418 for (;;)
420 xcb_generic_event_t *ev;
421 struct pollfd ufd = { .fd = fd, .events = POLLIN, };
423 poll (&ufd, 1, -1);
425 int canc = vlc_savecancel ();
426 while ((ev = xcb_poll_for_event (conn)) != NULL)
427 ProcessEvent(wnd, ev);
428 vlc_restorecancel (canc);
430 if (xcb_connection_has_error (conn))
432 msg_Err (wnd, "X server failure");
433 break;
436 return NULL;
439 /** Changes the EWMH state of the window */
440 static void set_wm_state (vout_window_t *wnd, bool on, xcb_atom_t state)
442 vout_window_sys_t *sys = wnd->sys;
443 /* From EWMH "_WM_STATE" */
444 xcb_client_message_event_t ev = {
445 .response_type = XCB_CLIENT_MESSAGE,
446 .format = 32,
447 .window = wnd->handle.xid,
448 .type = sys->wm_state,
451 ev.data.data32[0] = on ? NET_WM_STATE_ADD : NET_WM_STATE_REMOVE;
452 ev.data.data32[1] = state;
453 ev.data.data32[2] = 0;
454 ev.data.data32[3] = 1;
456 /* From ICCCM "Changing Window State" */
457 xcb_send_event (sys->conn, 0, sys->root,
458 XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY |
459 XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT,
460 (const char *)&ev);
464 static int Control (vout_window_t *wnd, int cmd, va_list ap)
466 vout_window_sys_t *p_sys = wnd->sys;
467 xcb_connection_t *conn = p_sys->conn;
469 switch (cmd)
471 case VOUT_WINDOW_SET_SIZE:
473 if (p_sys->embedded)
474 return VLC_EGENERIC;
476 unsigned width = va_arg (ap, unsigned);
477 unsigned height = va_arg (ap, unsigned);
478 const uint32_t values[] = { width, height, };
480 xcb_configure_window (conn, wnd->handle.xid,
481 XCB_CONFIG_WINDOW_WIDTH |
482 XCB_CONFIG_WINDOW_HEIGHT, values);
483 break;
486 case VOUT_WINDOW_SET_STATE:
488 unsigned state = va_arg (ap, unsigned);
489 bool above = (state & VOUT_WINDOW_STATE_ABOVE) != 0;
490 bool below = (state & VOUT_WINDOW_STATE_BELOW) != 0;
492 set_wm_state (wnd, above, p_sys->wm_state_above);
493 set_wm_state (wnd, below, p_sys->wm_state_below);
494 break;
497 case VOUT_WINDOW_SET_FULLSCREEN:
499 bool fs = va_arg (ap, int);
500 set_wm_state (wnd, fs, p_sys->wm_state_fullscreen);
501 break;
504 default:
505 msg_Err (wnd, "request %d not implemented", cmd);
506 return VLC_EGENERIC;
508 xcb_flush (p_sys->conn);
509 return VLC_SUCCESS;
512 /*** Embedded drawable support ***/
514 static vlc_mutex_t serializer = VLC_STATIC_MUTEX;
516 /** Acquire a drawable */
517 static int AcquireDrawable (vlc_object_t *obj, xcb_window_t window)
519 xcb_window_t *used;
520 size_t n = 0;
522 if (var_Create (obj->p_libvlc, "xid-in-use", VLC_VAR_ADDRESS))
523 return VLC_ENOMEM;
525 /* Keep a list of busy drawables, so we don't overlap videos if there are
526 * more than one video track in the stream. */
527 vlc_mutex_lock (&serializer);
528 used = var_GetAddress (obj->p_libvlc, "xid-in-use");
529 if (used != NULL)
531 while (used[n])
533 if (used[n] == window)
534 goto skip;
535 n++;
539 used = realloc (used, sizeof (*used) * (n + 2));
540 if (used != NULL)
542 used[n] = window;
543 used[n + 1] = 0;
544 var_SetAddress (obj->p_libvlc, "xid-in-use", used);
546 else
548 skip:
549 msg_Warn (obj, "X11 drawable 0x%08"PRIx8" is busy", window);
550 window = 0;
552 vlc_mutex_unlock (&serializer);
554 return (window == 0) ? VLC_EGENERIC : VLC_SUCCESS;
557 /** Remove this drawable from the list of busy ones */
558 static void ReleaseDrawable (vlc_object_t *obj, xcb_window_t window)
560 xcb_window_t *used;
561 size_t n = 0;
563 vlc_mutex_lock (&serializer);
564 used = var_GetAddress (obj->p_libvlc, "xid-in-use");
565 assert (used);
566 while (used[n] != window)
568 assert (used[n]);
569 n++;
572 used[n] = used[n + 1];
573 while (used[++n]);
575 if (n == 0)
576 var_SetAddress (obj->p_libvlc, "xid-in-use", NULL);
577 vlc_mutex_unlock (&serializer);
579 if (n == 0)
580 free (used);
581 /* Variables are reference-counted... */
582 var_Destroy (obj->p_libvlc, "xid-in-use");
586 * Wrap an existing X11 window to embed the video.
588 static int EmOpen (vout_window_t *wnd, const vout_window_cfg_t *cfg)
590 if (cfg->type != VOUT_WINDOW_TYPE_INVALID
591 && cfg->type != VOUT_WINDOW_TYPE_XID)
592 return VLC_EGENERIC;
594 xcb_window_t window = var_InheritInteger (wnd, "drawable-xid");
595 if (window == 0)
596 return VLC_EGENERIC;
598 if (AcquireDrawable (VLC_OBJECT(wnd), window))
599 return VLC_EGENERIC;
601 vout_window_sys_t *p_sys = malloc (sizeof (*p_sys));
602 xcb_connection_t *conn = xcb_connect (NULL, NULL);
603 if (p_sys == NULL || xcb_connection_has_error (conn))
604 goto error;
606 p_sys->embedded = true;
607 wnd->type = VOUT_WINDOW_TYPE_XID;
608 wnd->display.x11 = NULL;
609 wnd->handle.xid = window;
610 wnd->control = Control;
611 wnd->sys = p_sys;
613 p_sys->conn = conn;
615 /* Subscribe to window events (_before_ the geometry is queried) */
616 uint32_t mask = XCB_CW_EVENT_MASK;
617 uint32_t value = XCB_EVENT_MASK_STRUCTURE_NOTIFY;
619 xcb_change_window_attributes (conn, window, mask, &value);
621 xcb_get_geometry_reply_t *geo =
622 xcb_get_geometry_reply (conn, xcb_get_geometry (conn, window), NULL);
623 if (geo == NULL)
625 msg_Err (wnd, "bad X11 window 0x%08"PRIx8, window);
626 goto error;
628 p_sys->root = geo->root;
629 vout_window_ReportSize(wnd, geo->width, geo->height);
630 free (geo);
632 /* Try to subscribe to keyboard events (only one X11 client can
633 * subscribe to input events, so this can fail). */
634 if (var_InheritBool (wnd, "keyboard-events"))
636 p_sys->keys = XCB_keyHandler_Create (VLC_OBJECT(wnd), conn);
637 if (p_sys->keys != NULL)
638 value |= XCB_EVENT_MASK_KEY_PRESS;
640 else
641 p_sys->keys = NULL;
643 if (value & ~XCB_EVENT_MASK_STRUCTURE_NOTIFY)
644 xcb_change_window_attributes (conn, window, mask, &value);
646 CacheAtoms (p_sys);
647 if (vlc_clone (&p_sys->thread, Thread, wnd, VLC_THREAD_PRIORITY_LOW))
649 if (p_sys->keys != NULL)
650 XCB_keyHandler_Destroy (p_sys->keys);
651 goto error;
654 xcb_flush (conn);
655 (void) cfg;
656 return VLC_SUCCESS;
658 error:
659 xcb_disconnect (conn);
660 free (p_sys);
661 ReleaseDrawable (VLC_OBJECT(wnd), window);
662 return VLC_EGENERIC;
665 static void EmClose (vout_window_t *wnd)
667 xcb_window_t window = wnd->handle.xid;
669 Close (wnd);
670 ReleaseDrawable (VLC_OBJECT(wnd), window);