]> git.sesse.net Git - vlc/blob - modules/video_output/xcb/window.c
Revert "OpenGL: fix compilation / missing glXGetProcAddressARB"
[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 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 = CreateKeyHandler (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     if (var_InheritBool (wnd, "video-wallpaper"))
328     {
329         vout_window_SetState (wnd, VOUT_WINDOW_STATE_BELOW);
330         vout_window_SetFullScreen (wnd, true);
331     }
332
333     /* Create the event thread. It will dequeue all events, so any checked
334      * request from this thread must be completed at this point. */
335     if ((p_sys->keys != NULL)
336      && vlc_clone (&p_sys->thread, Thread, wnd, VLC_THREAD_PRIORITY_LOW))
337         DestroyKeyHandler (p_sys->keys);
338
339     xcb_flush (conn); /* Make sure map_window is sent (should be useless) */
340     return VLC_SUCCESS;
341
342 error:
343     xcb_disconnect (conn);
344     free (display);
345     free (p_sys);
346     return VLC_EGENERIC;
347 }
348
349
350 /**
351  * Destroys the X11 window.
352  */
353 static void Close (vout_window_t *wnd)
354 {
355     vout_window_sys_t *p_sys = wnd->sys;
356     xcb_connection_t *conn = p_sys->conn;
357
358     if (p_sys->keys)
359     {
360         vlc_cancel (p_sys->thread);
361         vlc_join (p_sys->thread, NULL);
362         DestroyKeyHandler (p_sys->keys);
363     }
364     xcb_disconnect (conn);
365     free (wnd->display.x11);
366     free (p_sys);
367 }
368
369
370 /** Background thread for X11 events handling */
371 static void *Thread (void *data)
372 {
373     vout_window_t *wnd = data;
374     vout_window_sys_t *p_sys = wnd->sys;
375     xcb_connection_t *conn = p_sys->conn;
376
377     int fd = xcb_get_file_descriptor (conn);
378     if (fd == -1)
379         return NULL;
380
381     for (;;)
382     {
383         xcb_generic_event_t *ev;
384         struct pollfd ufd = { .fd = fd, .events = POLLIN, };
385
386         poll (&ufd, 1, -1);
387
388         int canc = vlc_savecancel ();
389         while ((ev = xcb_poll_for_event (conn)) != NULL)
390         {
391             if (ProcessKeyEvent (p_sys->keys, ev) == 0)
392                 continue;
393             msg_Dbg (wnd, "unhandled event: %"PRIu8, ev->response_type);
394             free (ev);
395         }
396         vlc_restorecancel (canc);
397
398         if (xcb_connection_has_error (conn))
399         {
400             msg_Err (wnd, "X server failure");
401             break;
402         }
403     }
404     return NULL;
405 }
406
407 /** Changes the EWMH state of the window */
408 static void set_wm_state (vout_window_t *wnd, bool on, xcb_atom_t state)
409 {
410     vout_window_sys_t *sys = wnd->sys;
411     /* From EWMH "_WM_STATE" */
412     xcb_client_message_event_t ev = {
413          .response_type = XCB_CLIENT_MESSAGE,
414          .format = 32,
415          .window = wnd->handle.xid,
416          .type = sys->wm_state,
417     };
418
419     ev.data.data32[0] = on ? NET_WM_STATE_ADD : NET_WM_STATE_REMOVE;
420     ev.data.data32[1] = state;
421     ev.data.data32[2] = 0;
422     ev.data.data32[3] = 1;
423
424     /* From ICCCM "Changing Window State" */
425     xcb_send_event (sys->conn, 0, sys->root,
426                     XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY |
427                     XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT,
428                     (const char *)&ev);
429 }
430
431
432 static int Control (vout_window_t *wnd, int cmd, va_list ap)
433 {
434     vout_window_sys_t *p_sys = wnd->sys;
435     xcb_connection_t *conn = p_sys->conn;
436
437     switch (cmd)
438     {
439         case VOUT_WINDOW_SET_SIZE:
440         {
441             if (p_sys->embedded)
442                 return VLC_EGENERIC;
443
444             unsigned width = va_arg (ap, unsigned);
445             unsigned height = va_arg (ap, unsigned);
446             const uint32_t values[] = { width, height, };
447
448             xcb_configure_window (conn, wnd->handle.xid,
449                                   XCB_CONFIG_WINDOW_WIDTH |
450                                   XCB_CONFIG_WINDOW_HEIGHT, values);
451             break;
452         }
453
454         case VOUT_WINDOW_SET_STATE:
455         {
456             unsigned state = va_arg (ap, unsigned);
457             bool above = (state & VOUT_WINDOW_STATE_ABOVE) != 0;
458             bool below = (state & VOUT_WINDOW_STATE_BELOW) != 0;
459
460             set_wm_state (wnd, above, p_sys->wm_state_above);
461             set_wm_state (wnd, below, p_sys->wm_state_below);
462             break;
463         }
464
465         case VOUT_WINDOW_SET_FULLSCREEN:
466         {
467             bool fs = va_arg (ap, int);
468             if (!fs && var_GetBool (wnd, "video-wallpaper"))
469                 return VLC_EGENERIC;
470             set_wm_state (wnd, fs, p_sys->wm_state_fullscreen);
471             break;
472         }
473
474         default:
475             msg_Err (wnd, "request %d not implemented", cmd);
476             return VLC_EGENERIC;
477     }
478     xcb_flush (p_sys->conn);
479     return VLC_SUCCESS;
480 }
481
482 /*** Embedded drawable support ***/
483
484 static vlc_mutex_t serializer = VLC_STATIC_MUTEX;
485
486 /** Acquire a drawable */
487 static int AcquireDrawable (vlc_object_t *obj, xcb_window_t window)
488 {
489     xcb_window_t *used;
490     size_t n = 0;
491
492     if (var_Create (obj->p_libvlc, "xid-in-use", VLC_VAR_ADDRESS))
493         return VLC_ENOMEM;
494
495     /* Keep a list of busy drawables, so we don't overlap videos if there are
496      * more than one video track in the stream. */
497     vlc_mutex_lock (&serializer);
498     used = var_GetAddress (obj->p_libvlc, "xid-in-use");
499     if (used != NULL)
500     {
501         while (used[n])
502         {
503             if (used[n] == window)
504                 goto skip;
505             n++;
506         }
507     }
508
509     used = realloc (used, sizeof (*used) * (n + 2));
510     if (used != NULL)
511     {
512         used[n] = window;
513         used[n + 1] = 0;
514         var_SetAddress (obj->p_libvlc, "xid-in-use", used);
515     }
516     else
517     {
518 skip:
519         msg_Warn (obj, "X11 drawable 0x%08"PRIx8" is busy", window);
520         window = 0;
521     }
522     vlc_mutex_unlock (&serializer);
523
524     return (window == 0) ? VLC_EGENERIC : VLC_SUCCESS;
525 }
526
527 /** Remove this drawable from the list of busy ones */
528 static void ReleaseDrawable (vlc_object_t *obj, xcb_window_t window)
529 {
530     xcb_window_t *used;
531     size_t n = 0;
532
533     vlc_mutex_lock (&serializer);
534     used = var_GetAddress (obj->p_libvlc, "xid-in-use");
535     assert (used);
536     while (used[n] != window)
537     {
538         assert (used[n]);
539         n++;
540     }
541     do
542         used[n] = used[n + 1];
543     while (used[++n]);
544
545     if (n == 0)
546          var_SetAddress (obj->p_libvlc, "xid-in-use", NULL);
547     vlc_mutex_unlock (&serializer);
548
549     if (n == 0)
550         free (used);
551     /* Variables are reference-counted... */
552     var_Destroy (obj->p_libvlc, "xid-in-use");
553 }
554
555 /**
556  * Wrap an existing X11 window to embed the video.
557  */
558 static int EmOpen (vout_window_t *wnd, const vout_window_cfg_t *cfg)
559 {
560     xcb_window_t window = var_InheritInteger (wnd, "drawable-xid");
561     if (window == 0)
562         return VLC_EGENERIC;
563
564     if (AcquireDrawable (VLC_OBJECT(wnd), window))
565         return VLC_EGENERIC;
566
567     vout_window_sys_t *p_sys = malloc (sizeof (*p_sys));
568     xcb_connection_t *conn = xcb_connect (NULL, NULL);
569     if (p_sys == NULL || xcb_connection_has_error (conn))
570         goto error;
571
572     p_sys->embedded = true;
573     p_sys->keys = NULL;
574     wnd->handle.xid = window;
575     wnd->control = Control;
576     wnd->sys = p_sys;
577
578     p_sys->conn = conn;
579
580     xcb_get_geometry_reply_t *geo =
581         xcb_get_geometry_reply (conn, xcb_get_geometry (conn, window), NULL);
582     if (geo == NULL)
583     {
584         msg_Err (wnd, "bad X11 window 0x%08"PRIx8, window);
585         goto error;
586     }
587     p_sys->root = geo->root;
588     free (geo);
589
590     if (var_InheritBool (wnd, "keyboard-events"))
591     {
592         p_sys->keys = CreateKeyHandler (VLC_OBJECT(wnd), conn);
593         if (p_sys->keys != NULL)
594         {
595             const uint32_t mask = XCB_CW_EVENT_MASK;
596             const uint32_t values[1] = {
597                 XCB_EVENT_MASK_KEY_PRESS,
598             };
599             xcb_change_window_attributes (conn, window, mask, values);
600         }
601     }
602
603     CacheAtoms (p_sys);
604     if ((p_sys->keys != NULL)
605      && vlc_clone (&p_sys->thread, Thread, wnd, VLC_THREAD_PRIORITY_LOW))
606         DestroyKeyHandler (p_sys->keys);
607
608     xcb_flush (conn);
609     (void) cfg;
610     return VLC_SUCCESS;
611
612 error:
613     xcb_disconnect (conn);
614     free (p_sys);
615     ReleaseDrawable (VLC_OBJECT(wnd), window);
616     return VLC_EGENERIC;
617 }
618
619 static void EmClose (vout_window_t *wnd)
620 {
621     xcb_window_t window = wnd->handle.xid;
622
623     Close (wnd);
624     ReleaseDrawable (VLC_OBJECT(wnd), window);
625 }