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