3 * @brief X C Bindings window provider module for VLC media player
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 *****************************************************************************/
30 #include <unistd.h> /* gethostname() and sysconf() */
31 #include <limits.h> /* _POSIX_HOST_NAME_MAX */
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>
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
*);
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
)
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")
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")
84 add_string ("x11-display", NULL
, DISPLAY_TEXT
, DISPLAY_LONGTEXT
, true)
85 add_integer ("drawable-xid", 0, XID_TEXT
, XID_LONGTEXT
, true)
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
;
101 xcb_atom_t wm_state_above
;
102 xcb_atom_t wm_state_below
;
103 xcb_atom_t wm_state_fullscreen
;
108 /** Set an X window property from a nul-terminated string */
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 */
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
);
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 */
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 */
140 void set_hostname_prop (xcb_connection_t
*conn
, xcb_window_t window
)
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
);
156 /** Request the X11 server to internalize a string into an atom */
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 */
165 xcb_atom_t
get_atom (xcb_connection_t
*conn
, xcb_intern_atom_cookie_t ck
)
167 xcb_intern_atom_reply_t
*reply
;
170 reply
= xcb_intern_atom_reply (conn
, ck
, NULL
);
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 xcb_generic_error_t
*err
;
206 xcb_void_cookie_t ck
;
208 vout_window_sys_t
*p_sys
= malloc (sizeof (*p_sys
));
211 p_sys
->embedded
= false;
214 char *display
= var_InheritString (wnd
, "x11-display");
217 xcb_connection_t
*conn
= xcb_connect (display
, &snum
);
218 if (xcb_connection_has_error (conn
) /*== NULL*/)
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
))
236 msg_Err (wnd
, "bad X11 screen number");
241 const uint32_t mask
= XCB_CW_BACK_PIXEL
| XCB_CW_EVENT_MASK
;
242 uint32_t values
[2] = {
243 /* XCB_CW_BACK_PIXEL */
245 /* XCB_CW_EVENT_MASK */
246 XCB_EVENT_MASK_KEY_PRESS
,
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
);
257 msg_Err (wnd
, "creating window: X11 error %d", err
->error_code
);
262 wnd
->handle
.xid
= window
;
263 wnd
->display
.x11
= display
;
264 wnd
->control
= Control
;
268 if (var_InheritBool (wnd
, "keyboard-events"))
269 p_sys
->keys
= XCB_keyHandler_Create (VLC_OBJECT(wnd
), conn
);
272 p_sys
->root
= scr
->root
;
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"));
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
);
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");
303 xcb_atom_t utf8
= get_atom (conn
, utf8_string_ck
);
305 xcb_atom_t net_wm_name
= get_atom (conn
, net_wm_name_ck
);
306 char *title
= var_InheritString (wnd
, "video-title");
309 set_string (conn
, window
, utf8
, net_wm_name
, title
);
313 set_string (conn
, window
, utf8
, net_wm_name
, _("VLC media player"));
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"));
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");
321 /* Cache any EWMH atom we may need later */
324 /* Make the window visible */
325 xcb_map_window (conn
, window
);
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
))
332 XCB_keyHandler_Destroy (p_sys
->keys
);
336 xcb_flush (conn
); /* Make sure map_window is sent (should be useless) */
340 xcb_disconnect (conn
);
348 * Destroys the X11 window.
350 static void Close (vout_window_t
*wnd
)
352 vout_window_sys_t
*p_sys
= wnd
->sys
;
353 xcb_connection_t
*conn
= p_sys
->conn
;
357 vlc_cancel (p_sys
->thread
);
358 vlc_join (p_sys
->thread
, NULL
);
359 XCB_keyHandler_Destroy (p_sys
->keys
);
361 xcb_disconnect (conn
);
362 free (wnd
->display
.x11
);
367 /** Background thread for X11 events handling */
368 static void *Thread (void *data
)
370 vout_window_t
*wnd
= data
;
371 vout_window_sys_t
*p_sys
= wnd
->sys
;
372 xcb_connection_t
*conn
= p_sys
->conn
;
374 int fd
= xcb_get_file_descriptor (conn
);
380 xcb_generic_event_t
*ev
;
381 struct pollfd ufd
= { .fd
= fd
, .events
= POLLIN
, };
385 int canc
= vlc_savecancel ();
386 while ((ev
= xcb_poll_for_event (conn
)) != NULL
)
388 if (XCB_keyHandler_Process (p_sys
->keys
, ev
) == 0)
390 msg_Dbg (wnd
, "unhandled event: %"PRIu8
, ev
->response_type
);
393 vlc_restorecancel (canc
);
395 if (xcb_connection_has_error (conn
))
397 msg_Err (wnd
, "X server failure");
404 /** Changes the EWMH state of the window */
405 static void set_wm_state (vout_window_t
*wnd
, bool on
, xcb_atom_t state
)
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
,
412 .window
= wnd
->handle
.xid
,
413 .type
= sys
->wm_state
,
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;
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
,
429 static int Control (vout_window_t
*wnd
, int cmd
, va_list ap
)
431 vout_window_sys_t
*p_sys
= wnd
->sys
;
432 xcb_connection_t
*conn
= p_sys
->conn
;
436 case VOUT_WINDOW_SET_SIZE
:
441 unsigned width
= va_arg (ap
, unsigned);
442 unsigned height
= va_arg (ap
, unsigned);
443 const uint32_t values
[] = { width
, height
, };
445 xcb_configure_window (conn
, wnd
->handle
.xid
,
446 XCB_CONFIG_WINDOW_WIDTH
|
447 XCB_CONFIG_WINDOW_HEIGHT
, values
);
451 case VOUT_WINDOW_SET_STATE
:
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;
457 set_wm_state (wnd
, above
, p_sys
->wm_state_above
);
458 set_wm_state (wnd
, below
, p_sys
->wm_state_below
);
462 case VOUT_WINDOW_SET_FULLSCREEN
:
464 bool fs
= va_arg (ap
, int);
465 set_wm_state (wnd
, fs
, p_sys
->wm_state_fullscreen
);
470 msg_Err (wnd
, "request %d not implemented", cmd
);
473 xcb_flush (p_sys
->conn
);
477 /*** Embedded drawable support ***/
479 static vlc_mutex_t serializer
= VLC_STATIC_MUTEX
;
481 /** Acquire a drawable */
482 static int AcquireDrawable (vlc_object_t
*obj
, xcb_window_t window
)
487 if (var_Create (obj
->p_libvlc
, "xid-in-use", VLC_VAR_ADDRESS
))
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");
498 if (used
[n
] == window
)
504 used
= realloc (used
, sizeof (*used
) * (n
+ 2));
509 var_SetAddress (obj
->p_libvlc
, "xid-in-use", used
);
514 msg_Warn (obj
, "X11 drawable 0x%08"PRIx8
" is busy", window
);
517 vlc_mutex_unlock (&serializer
);
519 return (window
== 0) ? VLC_EGENERIC
: VLC_SUCCESS
;
522 /** Remove this drawable from the list of busy ones */
523 static void ReleaseDrawable (vlc_object_t
*obj
, xcb_window_t window
)
528 vlc_mutex_lock (&serializer
);
529 used
= var_GetAddress (obj
->p_libvlc
, "xid-in-use");
531 while (used
[n
] != window
)
537 used
[n
] = used
[n
+ 1];
541 var_SetAddress (obj
->p_libvlc
, "xid-in-use", NULL
);
542 vlc_mutex_unlock (&serializer
);
546 /* Variables are reference-counted... */
547 var_Destroy (obj
->p_libvlc
, "xid-in-use");
551 * Wrap an existing X11 window to embed the video.
553 static int EmOpen (vout_window_t
*wnd
, const vout_window_cfg_t
*cfg
)
555 xcb_window_t window
= var_InheritInteger (wnd
, "drawable-xid");
559 if (AcquireDrawable (VLC_OBJECT(wnd
), window
))
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
))
567 p_sys
->embedded
= true;
569 wnd
->handle
.xid
= window
;
570 wnd
->control
= Control
;
575 xcb_get_geometry_reply_t
*geo
=
576 xcb_get_geometry_reply (conn
, xcb_get_geometry (conn
, window
), NULL
);
579 msg_Err (wnd
, "bad X11 window 0x%08"PRIx8
, window
);
582 p_sys
->root
= geo
->root
;
585 if (var_InheritBool (wnd
, "keyboard-events"))
587 p_sys
->keys
= XCB_keyHandler_Create (VLC_OBJECT(wnd
), conn
);
588 if (p_sys
->keys
!= NULL
)
590 const uint32_t mask
= XCB_CW_EVENT_MASK
;
591 const uint32_t values
[1] = {
592 XCB_EVENT_MASK_KEY_PRESS
,
594 xcb_change_window_attributes (conn
, window
, mask
, values
);
599 if ((p_sys
->keys
!= NULL
)
600 && vlc_clone (&p_sys
->thread
, Thread
, wnd
, VLC_THREAD_PRIORITY_LOW
))
602 XCB_keyHandler_Destroy (p_sys
->keys
);
611 xcb_disconnect (conn
);
613 ReleaseDrawable (VLC_OBJECT(wnd
), window
);
617 static void EmClose (vout_window_t
*wnd
)
619 xcb_window_t window
= wnd
->handle
.xid
;
622 ReleaseDrawable (VLC_OBJECT(wnd
), window
);