XCB/window: fix potential use-after-free
[vlc.git] / 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 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.
12  *
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.
17  *
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  *****************************************************************************/
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 "events.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 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 struct vout_window_sys_t
94 {
95     xcb_connection_t *conn;
96     key_handler_t *keys;
97     vlc_thread_t thread;
98
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;
104
105     bool embedded;
106 };
107
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)
112 {
113     xcb_change_property (conn, XCB_PROP_MODE_REPLACE, window, atom, type,
114                          /* format */ 8, strlen (str), str);
115 }
116
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)
121 {
122     set_string (conn, window, XA_STRING, atom, value);
123 }
124
125 static inline
126 void set_wm_hints (xcb_connection_t *conn, xcb_window_t window)
127 {
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 */
133     };
134     xcb_change_property (conn, XCB_PROP_MODE_REPLACE, window, XA_WM_HINTS,
135                          XA_WM_HINTS, 32, 8, wm_hints);
136 }
137
138 /** Set the Window ICCCM client machine property */
139 static inline
140 void set_hostname_prop (xcb_connection_t *conn, xcb_window_t window)
141 {
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;
147
148     if (gethostname (hostname, host_name_max) == 0)
149     {
150         hostname[host_name_max - 1] = '\0';
151         set_ascii_prop (conn, window, XA_WM_CLIENT_MACHINE, hostname);
152     }
153     free(hostname);
154 }
155
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)
159 {
160     return xcb_intern_atom (c, 0, strlen (s), s);
161 }
162
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)
166 {
167     xcb_intern_atom_reply_t *reply;
168     xcb_atom_t atom;
169
170     reply = xcb_intern_atom_reply (conn, ck, NULL);
171     if (reply == NULL)
172         return 0;
173
174     atom = reply->atom;
175     free (reply);
176     return atom;
177 }
178
179 #define NET_WM_STATE_REMOVE 0
180 #define NET_WM_STATE_ADD    1
181 #define NET_WM_STATE_TOGGLE 2
182
183 static void CacheAtoms (vout_window_sys_t *p_sys)
184 {
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;
188
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");
193
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);
198 }
199
200 /**
201  * Create an X11 window.
202  */
203 static int Open (vout_window_t *wnd, const vout_window_cfg_t *cfg)
204 {
205     xcb_generic_error_t *err;
206     xcb_void_cookie_t ck;
207
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;
212
213     /* Connect to X */
214     char *display = var_InheritString (wnd, "x11-display");
215     int snum;
216
217     xcb_connection_t *conn = xcb_connect (display, &snum);
218     if (xcb_connection_has_error (conn) /*== NULL*/)
219         goto error;
220
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))
226     {
227         if (snum == 0)
228         {
229             scr = i.data;
230             break;
231         }
232         snum--;
233     }
234     if (scr == NULL)
235     {
236         msg_Err (wnd, "bad X11 screen number");
237         goto error;
238     }
239
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,
247     };
248
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)
256     {
257         msg_Err (wnd, "creating window: X11 error %d", err->error_code);
258         free (err);
259         goto error;
260     }
261
262     wnd->handle.xid = window;
263     wnd->display.x11 = display;
264     wnd->control = Control;
265     wnd->sys = p_sys;
266
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;
273
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"));
283
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);
292
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");
302
303     xcb_atom_t utf8 = get_atom (conn, utf8_string_ck);
304
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)
308     {
309         set_string (conn, window, utf8, net_wm_name, title);
310         free (title);
311     }
312     else
313         set_string (conn, window, utf8, net_wm_name, _("VLC media player"));
314
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"));
317
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");
320
321     /* Cache any EWMH atom we may need later */
322     CacheAtoms (p_sys);
323
324     /* Make the window visible */
325     xcb_map_window (conn, window);
326
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))
331     {
332         XCB_keyHandler_Destroy (p_sys->keys);
333         p_sys->keys = NULL;
334     }
335
336     xcb_flush (conn); /* Make sure map_window is sent (should be useless) */
337     return VLC_SUCCESS;
338
339 error:
340     xcb_disconnect (conn);
341     free (display);
342     free (p_sys);
343     return VLC_EGENERIC;
344 }
345
346
347 /**
348  * Destroys the X11 window.
349  */
350 static void Close (vout_window_t *wnd)
351 {
352     vout_window_sys_t *p_sys = wnd->sys;
353     xcb_connection_t *conn = p_sys->conn;
354
355     if (p_sys->keys)
356     {
357         vlc_cancel (p_sys->thread);
358         vlc_join (p_sys->thread, NULL);
359         XCB_keyHandler_Destroy (p_sys->keys);
360     }
361     xcb_disconnect (conn);
362     free (wnd->display.x11);
363     free (p_sys);
364 }
365
366
367 /** Background thread for X11 events handling */
368 static void *Thread (void *data)
369 {
370     vout_window_t *wnd = data;
371     vout_window_sys_t *p_sys = wnd->sys;
372     xcb_connection_t *conn = p_sys->conn;
373
374     int fd = xcb_get_file_descriptor (conn);
375     if (fd == -1)
376         return NULL;
377
378     for (;;)
379     {
380         xcb_generic_event_t *ev;
381         struct pollfd ufd = { .fd = fd, .events = POLLIN, };
382
383         poll (&ufd, 1, -1);
384
385         int canc = vlc_savecancel ();
386         while ((ev = xcb_poll_for_event (conn)) != NULL)
387         {
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);
392         }
393         vlc_restorecancel (canc);
394
395         if (xcb_connection_has_error (conn))
396         {
397             msg_Err (wnd, "X server failure");
398             break;
399         }
400     }
401     return NULL;
402 }
403
404 /** Changes the EWMH state of the window */
405 static void set_wm_state (vout_window_t *wnd, bool on, xcb_atom_t state)
406 {
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,
414     };
415
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;
420
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);
426 }
427
428
429 static int Control (vout_window_t *wnd, int cmd, va_list ap)
430 {
431     vout_window_sys_t *p_sys = wnd->sys;
432     xcb_connection_t *conn = p_sys->conn;
433
434     switch (cmd)
435     {
436         case VOUT_WINDOW_SET_SIZE:
437         {
438             if (p_sys->embedded)
439                 return VLC_EGENERIC;
440
441             unsigned width = va_arg (ap, unsigned);
442             unsigned height = va_arg (ap, unsigned);
443             const uint32_t values[] = { width, height, };
444
445             xcb_configure_window (conn, wnd->handle.xid,
446                                   XCB_CONFIG_WINDOW_WIDTH |
447                                   XCB_CONFIG_WINDOW_HEIGHT, values);
448             break;
449         }
450
451         case VOUT_WINDOW_SET_STATE:
452         {
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;
456
457             set_wm_state (wnd, above, p_sys->wm_state_above);
458             set_wm_state (wnd, below, p_sys->wm_state_below);
459             break;
460         }
461
462         case VOUT_WINDOW_SET_FULLSCREEN:
463         {
464             bool fs = va_arg (ap, int);
465             set_wm_state (wnd, fs, p_sys->wm_state_fullscreen);
466             break;
467         }
468
469         default:
470             msg_Err (wnd, "request %d not implemented", cmd);
471             return VLC_EGENERIC;
472     }
473     xcb_flush (p_sys->conn);
474     return VLC_SUCCESS;
475 }
476
477 /*** Embedded drawable support ***/
478
479 static vlc_mutex_t serializer = VLC_STATIC_MUTEX;
480
481 /** Acquire a drawable */
482 static int AcquireDrawable (vlc_object_t *obj, xcb_window_t window)
483 {
484     xcb_window_t *used;
485     size_t n = 0;
486
487     if (var_Create (obj->p_libvlc, "xid-in-use", VLC_VAR_ADDRESS))
488         return VLC_ENOMEM;
489
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)
495     {
496         while (used[n])
497         {
498             if (used[n] == window)
499                 goto skip;
500             n++;
501         }
502     }
503
504     used = realloc (used, sizeof (*used) * (n + 2));
505     if (used != NULL)
506     {
507         used[n] = window;
508         used[n + 1] = 0;
509         var_SetAddress (obj->p_libvlc, "xid-in-use", used);
510     }
511     else
512     {
513 skip:
514         msg_Warn (obj, "X11 drawable 0x%08"PRIx8" is busy", window);
515         window = 0;
516     }
517     vlc_mutex_unlock (&serializer);
518
519     return (window == 0) ? VLC_EGENERIC : VLC_SUCCESS;
520 }
521
522 /** Remove this drawable from the list of busy ones */
523 static void ReleaseDrawable (vlc_object_t *obj, xcb_window_t window)
524 {
525     xcb_window_t *used;
526     size_t n = 0;
527
528     vlc_mutex_lock (&serializer);
529     used = var_GetAddress (obj->p_libvlc, "xid-in-use");
530     assert (used);
531     while (used[n] != window)
532     {
533         assert (used[n]);
534         n++;
535     }
536     do
537         used[n] = used[n + 1];
538     while (used[++n]);
539
540     if (n == 0)
541          var_SetAddress (obj->p_libvlc, "xid-in-use", NULL);
542     vlc_mutex_unlock (&serializer);
543
544     if (n == 0)
545         free (used);
546     /* Variables are reference-counted... */
547     var_Destroy (obj->p_libvlc, "xid-in-use");
548 }
549
550 /**
551  * Wrap an existing X11 window to embed the video.
552  */
553 static int EmOpen (vout_window_t *wnd, const vout_window_cfg_t *cfg)
554 {
555     xcb_window_t window = var_InheritInteger (wnd, "drawable-xid");
556     if (window == 0)
557         return VLC_EGENERIC;
558
559     if (AcquireDrawable (VLC_OBJECT(wnd), window))
560         return VLC_EGENERIC;
561
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;
566
567     p_sys->embedded = true;
568     p_sys->keys = NULL;
569     wnd->handle.xid = window;
570     wnd->control = Control;
571     wnd->sys = p_sys;
572
573     p_sys->conn = conn;
574
575     xcb_get_geometry_reply_t *geo =
576         xcb_get_geometry_reply (conn, xcb_get_geometry (conn, window), NULL);
577     if (geo == NULL)
578     {
579         msg_Err (wnd, "bad X11 window 0x%08"PRIx8, window);
580         goto error;
581     }
582     p_sys->root = geo->root;
583     free (geo);
584
585     if (var_InheritBool (wnd, "keyboard-events"))
586     {
587         p_sys->keys = XCB_keyHandler_Create (VLC_OBJECT(wnd), conn);
588         if (p_sys->keys != NULL)
589         {
590             const uint32_t mask = XCB_CW_EVENT_MASK;
591             const uint32_t values[1] = {
592                 XCB_EVENT_MASK_KEY_PRESS,
593             };
594             xcb_change_window_attributes (conn, window, mask, values);
595         }
596     }
597
598     CacheAtoms (p_sys);
599     if ((p_sys->keys != NULL)
600      && vlc_clone (&p_sys->thread, Thread, wnd, VLC_THREAD_PRIORITY_LOW))
601     {
602         XCB_keyHandler_Destroy (p_sys->keys);
603         p_sys->keys = NULL;
604     }
605
606     xcb_flush (conn);
607     (void) cfg;
608     return VLC_SUCCESS;
609
610 error:
611     xcb_disconnect (conn);
612     free (p_sys);
613     ReleaseDrawable (VLC_OBJECT(wnd), window);
614     return VLC_EGENERIC;
615 }
616
617 static void EmClose (vout_window_t *wnd)
618 {
619     xcb_window_t window = wnd->handle.xid;
620
621     Close (wnd);
622     ReleaseDrawable (VLC_OBJECT(wnd), window);
623 }