]> git.sesse.net Git - vlc/blob - modules/video_output/xcb/window.c
XCB/X11: remove two write-only variables
[vlc] / modules / video_output / xcb / window.c
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
7  *
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.
12  *
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.
17  *
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  ****************************************************************************/
22
23 #ifdef HAVE_CONFIG_H
24 # include <config.h>
25 #endif
26
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 */
32
33 #include <xcb/xcb.h>
34 typedef xcb_atom_t Atom;
35 #include <X11/Xatom.h> /* XA_WM_NAME */
36
37 #include <vlc_common.h>
38 #include <vlc_plugin.h>
39 #include <vlc_vout_window.h>
40
41 #include "xcb_vlc.h"
42
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.")
47
48 #define XID_TEXT N_("X11 window ID")
49 #define XID_LONGTEXT N_( \
50     "Video will be embedded in this pre-existing X11 window. " \
51     "If zero, a new window will be created.")
52
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 *);
57
58 /*
59  * Module descriptor
60  */
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)
68
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")
74
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")
83
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 ()
87
88 vlc_module_end ()
89
90 static int Control (vout_window_t *, int, va_list ap);
91 static void *Thread (void *);
92
93 #define MATCHBOX_HACK 1 /* Matchbox focus hack */
94
95 struct vout_window_sys_t
96 {
97     xcb_connection_t *conn;
98     key_handler_t *keys;
99     vlc_thread_t thread;
100
101     xcb_window_t root;
102     xcb_atom_t wm_state;
103     xcb_atom_t wm_state_above;
104     xcb_atom_t wm_state_below;
105     xcb_atom_t wm_state_fullscreen;
106 #ifdef MATCHBOX_HACK
107     xcb_atom_t mb_current_app_window;
108 #endif
109
110     bool embedded;
111 };
112
113 /** Set an X window property from a nul-terminated string */
114 static inline
115 void set_string (xcb_connection_t *conn, xcb_window_t window,
116                  xcb_atom_t type, xcb_atom_t atom, const char *str)
117 {
118     xcb_change_property (conn, XCB_PROP_MODE_REPLACE, window, atom, type,
119                          /* format */ 8, strlen (str), str);
120 }
121
122 /** Set an X window string property */
123 static inline
124 void set_ascii_prop (xcb_connection_t *conn, xcb_window_t window,
125                      xcb_atom_t atom, const char *value)
126 {
127     set_string (conn, window, XA_STRING, atom, value);
128 }
129
130 static inline
131 void set_wm_hints (xcb_connection_t *conn, xcb_window_t window)
132 {
133     static const uint32_t wm_hints[8] = {
134         3, /* flags: Input, Initial state */
135         1, /* input: True */
136         1, /* initial state: Normal */
137         0, 0, 0, 0, 0, /* Icon */
138     };
139     xcb_change_property (conn, XCB_PROP_MODE_REPLACE, window, XA_WM_HINTS,
140                          XA_WM_HINTS, 32, 8, wm_hints);
141 }
142
143 /** Set the Window ICCCM client machine property */
144 static inline
145 void set_hostname_prop (xcb_connection_t *conn, xcb_window_t window)
146 {
147     char* hostname;
148     long host_name_max = sysconf (_SC_HOST_NAME_MAX);
149     if (host_name_max <= 0) host_name_max = _POSIX_HOST_NAME_MAX;
150     hostname = malloc (host_name_max);
151     if(!hostname) return;
152
153     if (gethostname (hostname, host_name_max) == 0)
154     {
155         hostname[host_name_max - 1] = '\0';
156         set_ascii_prop (conn, window, XA_WM_CLIENT_MACHINE, hostname);
157     }
158     free(hostname);
159 }
160
161 /** Request the X11 server to internalize a string into an atom */
162 static inline
163 xcb_intern_atom_cookie_t intern_string (xcb_connection_t *c, const char *s)
164 {
165     return xcb_intern_atom (c, 0, strlen (s), s);
166 }
167
168 /** Extract the X11 atom from an intern request cookie */
169 static
170 xcb_atom_t get_atom (xcb_connection_t *conn, xcb_intern_atom_cookie_t ck)
171 {
172     xcb_intern_atom_reply_t *reply;
173     xcb_atom_t atom;
174
175     reply = xcb_intern_atom_reply (conn, ck, NULL);
176     if (reply == NULL)
177         return 0;
178
179     atom = reply->atom;
180     free (reply);
181     return atom;
182 }
183
184 #define NET_WM_STATE_REMOVE 0
185 #define NET_WM_STATE_ADD    1
186 #define NET_WM_STATE_TOGGLE 2
187
188 static void CacheAtoms (vout_window_sys_t *p_sys)
189 {
190     xcb_connection_t *conn = p_sys->conn;
191     xcb_intern_atom_cookie_t wm_state_ck, wm_state_above_ck,
192                              wm_state_below_ck, wm_state_fs_ck;
193
194     wm_state_ck = intern_string (conn, "_NET_WM_STATE");
195     wm_state_above_ck = intern_string (conn, "_NET_WM_STATE_ABOVE");
196     wm_state_below_ck = intern_string (conn, "_NET_WM_STATE_BELOW");
197     wm_state_fs_ck = intern_string (conn, "_NET_WM_STATE_FULLSCREEN");
198 #ifdef MATCHBOX_HACK
199     xcb_intern_atom_cookie_t mb_current_app_window;
200     mb_current_app_window = xcb_intern_atom (conn, true,
201                                              strlen ("_MB_CURRENT_APP_WINDOW"),
202                                              "_MB_CURRENT_APP_WINDOW");
203 #endif
204
205     p_sys->wm_state = get_atom (conn, wm_state_ck);
206     p_sys->wm_state_above = get_atom (conn, wm_state_above_ck);
207     p_sys->wm_state_below = get_atom (conn, wm_state_below_ck);
208     p_sys->wm_state_fullscreen = get_atom (conn, wm_state_fs_ck);
209 #ifdef MATCHBOX_HACK
210     p_sys->mb_current_app_window = get_atom (conn, mb_current_app_window);
211 #endif
212 }
213
214 /**
215  * Create an X11 window.
216  */
217 static int Open (vout_window_t *wnd, const vout_window_cfg_t *cfg)
218 {
219     xcb_generic_error_t *err;
220     xcb_void_cookie_t ck;
221
222     vout_window_sys_t *p_sys = malloc (sizeof (*p_sys));
223     if (p_sys == NULL)
224         return VLC_ENOMEM;
225     p_sys->embedded = false;
226
227     /* Connect to X */
228     char *display = var_InheritString (wnd, "x11-display");
229     int snum;
230
231     xcb_connection_t *conn = xcb_connect (display, &snum);
232     if (xcb_connection_has_error (conn) /*== NULL*/)
233         goto error;
234
235     /* Find configured screen */
236     const xcb_setup_t *setup = xcb_get_setup (conn);
237     const xcb_screen_t *scr = NULL;
238     for (xcb_screen_iterator_t i = xcb_setup_roots_iterator (setup);
239          i.rem > 0; xcb_screen_next (&i))
240     {
241         if (snum == 0)
242         {
243             scr = i.data;
244             break;
245         }
246         snum--;
247     }
248     if (scr == NULL)
249     {
250         msg_Err (wnd, "bad X11 screen number");
251         goto error;
252     }
253
254     /* Create window */
255     const uint32_t mask = XCB_CW_BACK_PIXEL | XCB_CW_EVENT_MASK;
256     uint32_t values[2] = {
257         /* XCB_CW_BACK_PIXEL */
258         scr->black_pixel,
259         /* XCB_CW_EVENT_MASK */
260         XCB_EVENT_MASK_KEY_PRESS,
261     };
262
263     xcb_window_t window = xcb_generate_id (conn);
264     ck = xcb_create_window_checked (conn, scr->root_depth, window, scr->root,
265                                     cfg->x, cfg->y, cfg->width, cfg->height, 0,
266                                     XCB_WINDOW_CLASS_INPUT_OUTPUT,
267                                     scr->root_visual, mask, values);
268     err = xcb_request_check (conn, ck);
269     if (err)
270     {
271         msg_Err (wnd, "creating window: X11 error %d", err->error_code);
272         free (err);
273         goto error;
274     }
275
276     wnd->handle.xid = window;
277     wnd->display.x11 = display;
278     wnd->control = Control;
279     wnd->sys = p_sys;
280
281     p_sys->conn = conn;
282     if (var_InheritBool (wnd, "keyboard-events"))
283         p_sys->keys = CreateKeyHandler (VLC_OBJECT(wnd), conn);
284     else
285         p_sys->keys = NULL;
286     p_sys->root = scr->root;
287
288     /* ICCCM
289      * No cut&paste nor drag&drop, only Window Manager communication. */
290     set_ascii_prop (conn, window, XA_WM_NAME,
291     /* xgettext: This is a plain ASCII spelling of "VLC media player"
292        for the ICCCM window name. This must be pure ASCII.
293        The limitation is partially with ICCCM and partially with VLC.
294        For Latin script languages, you may need to strip accents.
295        For other scripts, you will need to transliterate into Latin. */
296                     vlc_pgettext ("ASCII", "VLC media player"));
297
298     set_ascii_prop (conn, window, XA_WM_ICON_NAME,
299     /* xgettext: This is a plain ASCII spelling of "VLC"
300        for the ICCCM window name. This must be pure ASCII. */
301                     vlc_pgettext ("ASCII", "VLC"));
302     set_wm_hints (conn, window);
303     xcb_change_property (conn, XCB_PROP_MODE_REPLACE, window, XA_WM_CLASS,
304                          XA_STRING, 8, 8, "vlc\0Vlc");
305     set_hostname_prop (conn, window);
306
307     /* EWMH */
308     xcb_intern_atom_cookie_t utf8_string_ck
309         = intern_string (conn, "UTF8_STRING");;
310     xcb_intern_atom_cookie_t net_wm_name_ck
311         = intern_string (conn, "_NET_WM_NAME");
312     xcb_intern_atom_cookie_t net_wm_icon_name_ck
313         = intern_string (conn, "_NET_WM_ICON_NAME");
314     xcb_intern_atom_cookie_t wm_window_role_ck
315         = intern_string (conn, "WM_WINDOW_ROLE");
316
317     xcb_atom_t utf8 = get_atom (conn, utf8_string_ck);
318
319     xcb_atom_t net_wm_name = get_atom (conn, net_wm_name_ck);
320     char *title = var_InheritString (wnd, "video-title");
321     if (title)
322     {
323         set_string (conn, window, utf8, net_wm_name, title);
324         free (title);
325     }
326     else
327         set_string (conn, window, utf8, net_wm_name, _("VLC media player"));
328
329     xcb_atom_t net_wm_icon_name = get_atom (conn, net_wm_icon_name_ck);
330     set_string (conn, window, utf8, net_wm_icon_name, _("VLC"));
331
332     xcb_atom_t wm_window_role = get_atom (conn, wm_window_role_ck);
333     set_ascii_prop (conn, window, wm_window_role, "vlc-video");
334
335     /* Cache any EWMH atom we may need later */
336     CacheAtoms (p_sys);
337 #ifdef MATCHBOX_HACK
338     if (p_sys->mb_current_app_window)
339     {
340         uint32_t value = XCB_EVENT_MASK_PROPERTY_CHANGE;
341         xcb_change_window_attributes (conn, scr->root,
342                                       XCB_CW_EVENT_MASK, &value);
343     }
344 #endif
345
346     /* Make the window visible */
347     xcb_map_window (conn, window);
348
349     if (var_InheritBool (wnd, "video-wallpaper"))
350     {
351         vout_window_SetState (wnd, VOUT_WINDOW_STATE_BELOW);
352         vout_window_SetFullScreen (wnd, true);
353     }
354
355     /* Create the event thread. It will dequeue all events, so any checked
356      * request from this thread must be completed at this point. */
357     if ((p_sys->keys != NULL)
358      && vlc_clone (&p_sys->thread, Thread, wnd, VLC_THREAD_PRIORITY_LOW))
359         DestroyKeyHandler (p_sys->keys);
360
361 #ifdef MATCHBOX_HACK
362     if (p_sys->mb_current_app_window)
363         xcb_set_input_focus (p_sys->conn, XCB_INPUT_FOCUS_POINTER_ROOT,
364                              wnd->handle.xid, XCB_CURRENT_TIME);
365 #endif
366     xcb_flush (conn); /* Make sure map_window is sent (should be useless) */
367     return VLC_SUCCESS;
368
369 error:
370     xcb_disconnect (conn);
371     free (display);
372     free (p_sys);
373     return VLC_EGENERIC;
374 }
375
376
377 /**
378  * Destroys the X11 window.
379  */
380 static void Close (vout_window_t *wnd)
381 {
382     vout_window_sys_t *p_sys = wnd->sys;
383     xcb_connection_t *conn = p_sys->conn;
384
385     if (p_sys->keys)
386     {
387         vlc_cancel (p_sys->thread);
388         vlc_join (p_sys->thread, NULL);
389         DestroyKeyHandler (p_sys->keys);
390     }
391     xcb_disconnect (conn);
392     free (wnd->display.x11);
393     free (p_sys);
394 }
395
396
397 /** Background thread for X11 events handling */
398 static void *Thread (void *data)
399 {
400     vout_window_t *wnd = data;
401     vout_window_sys_t *p_sys = wnd->sys;
402     xcb_connection_t *conn = p_sys->conn;
403
404     int fd = xcb_get_file_descriptor (conn);
405     if (fd == -1)
406         return NULL;
407
408     for (;;)
409     {
410         xcb_generic_event_t *ev;
411         struct pollfd ufd = { .fd = fd, .events = POLLIN, };
412
413         poll (&ufd, 1, -1);
414
415         int canc = vlc_savecancel ();
416         while ((ev = xcb_poll_for_event (conn)) != NULL)
417         {
418             if (ProcessKeyEvent (p_sys->keys, ev) == 0)
419                 continue;
420 #ifdef MATCHBOX_HACK
421             if (p_sys->mb_current_app_window
422              && (ev->response_type & 0x7f) == XCB_PROPERTY_NOTIFY)
423             {
424                 const xcb_property_notify_event_t *pne =
425                     (xcb_property_notify_event_t *)ev;
426                 if (pne->atom == p_sys->mb_current_app_window
427                  && pne->state == XCB_PROPERTY_NEW_VALUE)
428                 {
429                     xcb_get_property_reply_t *r =
430                         xcb_get_property_reply (conn,
431                             xcb_get_property (conn, 0, pne->window, pne->atom,
432                                               XA_WINDOW, 0, 4), NULL);
433                     if (r != NULL
434                      && !memcmp (xcb_get_property_value (r), &wnd->handle.xid,
435                                  4))
436                     {
437                         msg_Dbg (wnd, "asking Matchbox for input focus");
438                         xcb_set_input_focus (conn,
439                                              XCB_INPUT_FOCUS_POINTER_ROOT,
440                                              wnd->handle.xid, pne->time);
441                         xcb_flush (conn);
442                     }
443                     free (r);
444                 }
445             }
446             else
447 #endif
448                 msg_Dbg (wnd, "unhandled event: %"PRIu8, ev->response_type);
449             free (ev);
450         }
451         vlc_restorecancel (canc);
452
453         if (xcb_connection_has_error (conn))
454         {
455             msg_Err (wnd, "X server failure");
456             break;
457         }
458     }
459     return NULL;
460 }
461
462 /** Changes the EWMH state of the window */
463 static void set_wm_state (vout_window_t *wnd, bool on, xcb_atom_t state)
464 {
465     vout_window_sys_t *sys = wnd->sys;
466     /* From EWMH "_WM_STATE" */
467     xcb_client_message_event_t ev = {
468          .response_type = XCB_CLIENT_MESSAGE,
469          .format = 32,
470          .window = wnd->handle.xid,
471          .type = sys->wm_state,
472     };
473
474     ev.data.data32[0] = on ? NET_WM_STATE_ADD : NET_WM_STATE_REMOVE;
475     ev.data.data32[1] = state;
476     ev.data.data32[2] = 0;
477     ev.data.data32[3] = 1;
478
479     /* From ICCCM "Changing Window State" */
480     xcb_send_event (sys->conn, 0, sys->root,
481                     XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY |
482                     XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT,
483                     (const char *)&ev);
484 }
485
486
487 static int Control (vout_window_t *wnd, int cmd, va_list ap)
488 {
489     vout_window_sys_t *p_sys = wnd->sys;
490     xcb_connection_t *conn = p_sys->conn;
491
492     switch (cmd)
493     {
494         case VOUT_WINDOW_SET_SIZE:
495         {
496             if (p_sys->embedded)
497                 return VLC_EGENERIC;
498
499             unsigned width = va_arg (ap, unsigned);
500             unsigned height = va_arg (ap, unsigned);
501             const uint32_t values[] = { width, height, };
502
503             xcb_configure_window (conn, wnd->handle.xid,
504                                   XCB_CONFIG_WINDOW_WIDTH |
505                                   XCB_CONFIG_WINDOW_HEIGHT, values);
506             break;
507         }
508
509         case VOUT_WINDOW_SET_STATE:
510         {
511             unsigned state = va_arg (ap, unsigned);
512             bool above = (state & VOUT_WINDOW_STATE_ABOVE) != 0;
513             bool below = (state & VOUT_WINDOW_STATE_BELOW) != 0;
514
515             set_wm_state (wnd, above, p_sys->wm_state_above);
516             set_wm_state (wnd, below, p_sys->wm_state_below);
517             break;
518         }
519
520         case VOUT_WINDOW_SET_FULLSCREEN:
521         {
522             bool fs = va_arg (ap, int);
523             if (!fs && var_GetBool (wnd, "video-wallpaper"))
524                 return VLC_EGENERIC;
525             set_wm_state (wnd, fs, p_sys->wm_state_fullscreen);
526             break;
527         }
528
529         default:
530             msg_Err (wnd, "request %d not implemented", cmd);
531             return VLC_EGENERIC;
532     }
533     xcb_flush (p_sys->conn);
534     return VLC_SUCCESS;
535 }
536
537 /*** Embedded drawable support ***/
538
539 static vlc_mutex_t serializer = VLC_STATIC_MUTEX;
540
541 /** Acquire a drawable */
542 static int AcquireDrawable (vlc_object_t *obj, xcb_window_t window)
543 {
544     xcb_window_t *used;
545     size_t n = 0;
546
547     if (var_Create (obj->p_libvlc, "xid-in-use", VLC_VAR_ADDRESS))
548         return VLC_ENOMEM;
549
550     /* Keep a list of busy drawables, so we don't overlap videos if there are
551      * more than one video track in the stream. */
552     vlc_mutex_lock (&serializer);
553     used = var_GetAddress (obj->p_libvlc, "xid-in-use");
554     if (used != NULL)
555     {
556         while (used[n])
557         {
558             if (used[n] == window)
559                 goto skip;
560             n++;
561         }
562     }
563
564     used = realloc (used, sizeof (*used) * (n + 2));
565     if (used != NULL)
566     {
567         used[n] = window;
568         used[n + 1] = 0;
569         var_SetAddress (obj->p_libvlc, "xid-in-use", used);
570     }
571     else
572     {
573 skip:
574         msg_Warn (obj, "X11 drawable 0x%08"PRIx8" is busy", window);
575         window = 0;
576     }
577     vlc_mutex_unlock (&serializer);
578
579     return (window == 0) ? VLC_EGENERIC : VLC_SUCCESS;
580 }
581
582 /** Remove this drawable from the list of busy ones */
583 static void ReleaseDrawable (vlc_object_t *obj, xcb_window_t window)
584 {
585     xcb_window_t *used;
586     size_t n = 0;
587
588     vlc_mutex_lock (&serializer);
589     used = var_GetAddress (obj->p_libvlc, "xid-in-use");
590     assert (used);
591     while (used[n] != window)
592     {
593         assert (used[n]);
594         n++;
595     }
596     do
597         used[n] = used[n + 1];
598     while (used[++n]);
599
600     if (n == 0)
601          var_SetAddress (obj->p_libvlc, "xid-in-use", NULL);
602     vlc_mutex_unlock (&serializer);
603
604     if (n == 0)
605         free (used);
606     /* Variables are reference-counted... */
607     var_Destroy (obj->p_libvlc, "xid-in-use");
608 }
609
610 /**
611  * Wrap an existing X11 window to embed the video.
612  */
613 static int EmOpen (vout_window_t *wnd, const vout_window_cfg_t *cfg)
614 {
615     xcb_window_t window = var_InheritInteger (wnd, "drawable-xid");
616     if (window == 0)
617         return VLC_EGENERIC;
618
619     if (AcquireDrawable (VLC_OBJECT(wnd), window))
620         return VLC_EGENERIC;
621
622     vout_window_sys_t *p_sys = malloc (sizeof (*p_sys));
623     xcb_connection_t *conn = xcb_connect (NULL, NULL);
624     if (p_sys == NULL || xcb_connection_has_error (conn))
625         goto error;
626
627     p_sys->embedded = true;
628     p_sys->keys = NULL;
629     wnd->handle.xid = window;
630     wnd->control = Control;
631     wnd->sys = p_sys;
632
633     p_sys->conn = conn;
634
635     xcb_get_geometry_reply_t *geo =
636         xcb_get_geometry_reply (conn, xcb_get_geometry (conn, window), NULL);
637     if (geo == NULL)
638     {
639         msg_Err (wnd, "bad X11 window 0x%08"PRIx8, window);
640         goto error;
641     }
642     p_sys->root = geo->root;
643     free (geo);
644
645     if (var_InheritBool (wnd, "keyboard-events"))
646     {
647         p_sys->keys = CreateKeyHandler (VLC_OBJECT(wnd), conn);
648         if (p_sys->keys != NULL)
649         {
650             const uint32_t mask = XCB_CW_EVENT_MASK;
651             const uint32_t values[1] = {
652                 XCB_EVENT_MASK_KEY_PRESS,
653             };
654             xcb_change_window_attributes (conn, window, mask, values);
655         }
656     }
657
658     CacheAtoms (p_sys);
659     if ((p_sys->keys != NULL)
660      && vlc_clone (&p_sys->thread, Thread, wnd, VLC_THREAD_PRIORITY_LOW))
661         DestroyKeyHandler (p_sys->keys);
662
663     xcb_flush (conn);
664     (void) cfg;
665     return VLC_SUCCESS;
666
667 error:
668     xcb_disconnect (conn);
669     free (p_sys);
670     ReleaseDrawable (VLC_OBJECT(wnd), window);
671     return VLC_EGENERIC;
672 }
673
674 static void EmClose (vout_window_t *wnd)
675 {
676     xcb_window_t window = wnd->handle.xid;
677
678     Close (wnd);
679     ReleaseDrawable (VLC_OBJECT(wnd), window);
680 }