1 /**
2  * @file xdg-shell.c
3  * @brief XDG shell surface provider module for VLC media player
4  */
5 /*****************************************************************************
6  * Copyright © 2014, 2017 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 <assert.h>
28 #include <stdarg.h>
29 #include <stdint.h>
30 #include <string.h>
31 #include <poll.h>
32 
33 #include <wayland-client.h>
34 #include "xdg-shell-client-protocol.h"
35 #include "server-decoration-client-protocol.h"
36 
37 #include <vlc_common.h>
38 #include <vlc_plugin.h>
39 #include <vlc_vout_window.h>
40 
41 static_assert (XDG_SHELL_VERSION_CURRENT == 5, "XDG shell version mismatch");
42 
43 struct vout_window_sys_t
44 {
45     struct wl_compositor *compositor;
46     struct xdg_shell *shell;
47     struct xdg_surface *surface;
48     struct org_kde_kwin_server_decoration_manager *deco_manager;
49     struct org_kde_kwin_server_decoration *deco;
50 
51     vlc_thread_t thread;
52 };
53 
cleanup_wl_display_read(void * data)54 static void cleanup_wl_display_read(void *data)
55 {
56     struct wl_display *display = data;
57 
58     wl_display_cancel_read(display);
59 }
60 
61 /** Background thread for Wayland shell events handling */
Thread(void * data)62 static void *Thread(void *data)
63 {
64     vout_window_t *wnd = data;
65     struct wl_display *display = wnd->display.wl;
66     struct pollfd ufd[1];
67 
68     int canc = vlc_savecancel();
69     vlc_cleanup_push(cleanup_wl_display_read, display);
70 
71     ufd[0].fd = wl_display_get_fd(display);
72     ufd[0].events = POLLIN;
73 
74     for (;;)
75     {
76         while (wl_display_prepare_read(display) != 0)
77             wl_display_dispatch_pending(display);
78 
79         wl_display_flush(display);
80         vlc_restorecancel(canc);
81 
82         while (poll(ufd, 1, -1) < 0);
83 
84         canc = vlc_savecancel();
85         wl_display_read_events(display);
86         wl_display_dispatch_pending(display);
87     }
88     vlc_assert_unreachable();
89     vlc_cleanup_pop();
90     //vlc_restorecancel(canc);
91     //return NULL;
92 }
93 
Control(vout_window_t * wnd,int cmd,va_list ap)94 static int Control(vout_window_t *wnd, int cmd, va_list ap)
95 {
96     vout_window_sys_t *sys = wnd->sys;
97     struct wl_display *display = wnd->display.wl;
98 
99     switch (cmd)
100     {
101         case VOUT_WINDOW_SET_STATE:
102             return VLC_EGENERIC;
103 
104         case VOUT_WINDOW_SET_SIZE:
105         {
106             unsigned width = va_arg(ap, unsigned);
107             unsigned height = va_arg(ap, unsigned);
108 
109             /* Unlike X11, the client basically gets to choose its size, which
110              * is the size of the buffer attached to the surface.
111              * Note that it is unspecified who "wins" in case of a race
112              * (e.g. if trying to resize the window, and changing the zoom
113              * at the same time). With X11, the race is arbitrated by the X11
114              * server. With Wayland, it is arbitrated in the client windowing
115              * code. In this case, it is arbitrated by the window core code.
116              */
117             vout_window_ReportSize(wnd, width, height);
118             xdg_surface_set_window_geometry(sys->surface, 0, 0, width, height);
119             break;
120         }
121 
122         case VOUT_WINDOW_SET_FULLSCREEN:
123         {
124             bool fs = va_arg(ap, int);
125 
126             if (fs)
127                 xdg_surface_set_fullscreen(sys->surface, NULL);
128             else
129                 xdg_surface_unset_fullscreen(sys->surface);
130             break;
131         }
132 
133         default:
134             msg_Err(wnd, "request %d not implemented", cmd);
135             return VLC_EGENERIC;
136     }
137 
138     wl_display_flush(display);
139     return VLC_SUCCESS;
140 }
141 
xdg_surface_configure_cb(void * data,struct xdg_surface * surface,int32_t width,int32_t height,struct wl_array * states,uint32_t serial)142 static void xdg_surface_configure_cb(void *data, struct xdg_surface *surface,
143                                      int32_t width, int32_t height,
144                                      struct wl_array *states,
145                                      uint32_t serial)
146 {
147     vout_window_t *wnd = data;
148     const uint32_t *state;
149 
150     msg_Dbg(wnd, "new configuration: %"PRId32"x%"PRId32" (serial: %"PRIu32")",
151             width, height, serial);
152     wl_array_for_each(state, states)
153     {
154         msg_Dbg(wnd, " - state 0x%04"PRIX32, *state);
155     }
156 
157     /* Zero width or zero height means client (we) should choose.
158      * DO NOT REPORT those values to video output... */
159     if (width != 0 && height != 0)
160         vout_window_ReportSize(wnd,  width, height);
161 
162     /* TODO: report fullscreen state, not implemented in VLC */
163     xdg_surface_ack_configure(surface, serial);
164 }
165 
xdg_surface_close_cb(void * data,struct xdg_surface * surface)166 static void xdg_surface_close_cb(void *data, struct xdg_surface *surface)
167 {
168     vout_window_t *wnd = data;
169 
170     vout_window_ReportClose(wnd);
171     (void) surface;
172 }
173 
174 static const struct xdg_surface_listener xdg_surface_cbs =
175 {
176     xdg_surface_configure_cb,
177     xdg_surface_close_cb,
178 };
179 
xdg_shell_ping_cb(void * data,struct xdg_shell * shell,uint32_t serial)180 static void xdg_shell_ping_cb(void *data, struct xdg_shell *shell,
181                               uint32_t serial)
182 {
183     (void) data;
184     xdg_shell_pong(shell, serial);
185 }
186 
187 static const struct xdg_shell_listener xdg_shell_cbs =
188 {
189     xdg_shell_ping_cb,
190 };
191 
registry_global_cb(void * data,struct wl_registry * registry,uint32_t name,const char * iface,uint32_t vers)192 static void registry_global_cb(void *data, struct wl_registry *registry,
193                                uint32_t name, const char *iface, uint32_t vers)
194 {
195     vout_window_t *wnd = data;
196     vout_window_sys_t *sys = wnd->sys;
197 
198     msg_Dbg(wnd, "global %3"PRIu32": %s version %"PRIu32, name, iface, vers);
199 
200     if (!strcmp(iface, "wl_compositor"))
201         sys->compositor = wl_registry_bind(registry, name,
202                                            &wl_compositor_interface,
203                                            (vers < 2) ? vers : 2);
204     else
205     if (!strcmp(iface, "xdg_shell"))
206         sys->shell = wl_registry_bind(registry, name, &xdg_shell_interface, 1);
207     else
208     if (!strcmp(iface, "org_kde_kwin_server_decoration_manager"))
209         sys->deco_manager = wl_registry_bind(registry, name,
210                          &org_kde_kwin_server_decoration_manager_interface, 1);
211 }
212 
registry_global_remove_cb(void * data,struct wl_registry * registry,uint32_t name)213 static void registry_global_remove_cb(void *data, struct wl_registry *registry,
214                                       uint32_t name)
215 {
216     vout_window_t *wnd = data;
217 
218     msg_Dbg(wnd, "global remove %3"PRIu32, name);
219     (void) registry;
220 }
221 
222 static const struct wl_registry_listener registry_cbs =
223 {
224     registry_global_cb,
225     registry_global_remove_cb,
226 };
227 
228 /**
229  * Creates a Wayland shell surface.
230  */
Open(vout_window_t * wnd,const vout_window_cfg_t * cfg)231 static int Open(vout_window_t *wnd, const vout_window_cfg_t *cfg)
232 {
233     if (cfg->type != VOUT_WINDOW_TYPE_INVALID
234      && cfg->type != VOUT_WINDOW_TYPE_WAYLAND)
235         return VLC_EGENERIC;
236 
237     vout_window_sys_t *sys = malloc(sizeof (*sys));
238     if (unlikely(sys == NULL))
239         return VLC_ENOMEM;
240 
241     sys->compositor = NULL;
242     sys->shell = NULL;
243     sys->surface = NULL;
244     sys->deco_manager = NULL;
245     sys->deco = NULL;
246     wnd->sys = sys;
247 
248     /* Connect to the display server */
249     char *dpy_name = var_InheritString(wnd, "wl-display");
250     struct wl_display *display = wl_display_connect(dpy_name);
251 
252     free(dpy_name);
253 
254     if (display == NULL)
255     {
256         free(sys);
257         return VLC_EGENERIC;
258     }
259 
260     /* Find the interesting singleton(s) */
261     struct wl_registry *registry = wl_display_get_registry(display);
262     if (registry == NULL)
263         goto error;
264 
265     wl_registry_add_listener(registry, &registry_cbs, wnd);
266     wl_display_roundtrip(display);
267     wl_registry_destroy(registry);
268 
269     if (sys->compositor == NULL || sys->shell == NULL)
270         goto error;
271 
272     xdg_shell_use_unstable_version(sys->shell, XDG_SHELL_VERSION_CURRENT);
273     xdg_shell_add_listener(sys->shell, &xdg_shell_cbs, NULL);
274 
275     /* Create a surface */
276     struct wl_surface *surface = wl_compositor_create_surface(sys->compositor);
277     if (surface == NULL)
278         goto error;
279 
280     struct xdg_surface *xdg_surface =
281         xdg_shell_get_xdg_surface(sys->shell, surface);
282     if (xdg_surface == NULL)
283         goto error;
284 
285     sys->surface = xdg_surface;
286 
287     xdg_surface_add_listener(xdg_surface, &xdg_surface_cbs, wnd);
288 
289     char *title = var_InheritString(wnd, "video-title");
290     xdg_surface_set_title(xdg_surface,
291                           (title != NULL) ? title : _("VLC media player"));
292     free(title);
293 
294     char *app_id = var_InheritString(wnd, "app-id");
295     if (app_id != NULL)
296     {
297         xdg_surface_set_app_id(xdg_surface, app_id);
298         free(app_id);
299     }
300 
301     xdg_surface_set_window_geometry(xdg_surface, 0, 0,
302                                     cfg->width, cfg->height);
303     vout_window_ReportSize(wnd, cfg->width, cfg->height);
304 
305     const uint_fast32_t deco_mode =
306         var_InheritBool(wnd, "video-deco")
307             ? ORG_KDE_KWIN_SERVER_DECORATION_MODE_SERVER
308             : ORG_KDE_KWIN_SERVER_DECORATION_MODE_CLIENT;
309 
310     if (sys->deco_manager != NULL)
311         sys->deco = org_kde_kwin_server_decoration_manager_create(
312                                                    sys->deco_manager, surface);
313     if (sys->deco != NULL)
314         org_kde_kwin_server_decoration_request_mode(sys->deco, deco_mode);
315     else if (deco_mode == ORG_KDE_KWIN_SERVER_DECORATION_MODE_SERVER)
316         msg_Err(wnd, "server-side decoration not supported");
317 
318     //if (var_InheritBool (wnd, "keyboard-events"))
319     //    do_something();
320 
321     wl_display_flush(display);
322 
323     wnd->type = VOUT_WINDOW_TYPE_WAYLAND;
324     wnd->handle.wl = surface;
325     wnd->display.wl = display;
326     wnd->control = Control;
327 
328     vout_window_SetFullScreen(wnd, cfg->is_fullscreen);
329 
330     if (vlc_clone(&sys->thread, Thread, wnd, VLC_THREAD_PRIORITY_LOW))
331         goto error;
332 
333     return VLC_SUCCESS;
334 
335 error:
336     if (sys->deco != NULL)
337         org_kde_kwin_server_decoration_destroy(sys->deco);
338     if (sys->deco_manager != NULL)
339         org_kde_kwin_server_decoration_manager_destroy(sys->deco_manager);
340     if (sys->surface != NULL)
341         xdg_surface_destroy(sys->surface);
342     if (sys->shell != NULL)
343         xdg_shell_destroy(sys->shell);
344     if (sys->compositor != NULL)
345         wl_compositor_destroy(sys->compositor);
346     wl_display_disconnect(display);
347     free(sys);
348     return VLC_EGENERIC;
349 }
350 
351 /**
352  * Destroys a XDG shell surface.
353  */
Close(vout_window_t * wnd)354 static void Close(vout_window_t *wnd)
355 {
356     vout_window_sys_t *sys = wnd->sys;
357 
358     vlc_cancel(sys->thread);
359     vlc_join(sys->thread, NULL);
360 
361     if (sys->deco != NULL)
362         org_kde_kwin_server_decoration_destroy(sys->deco);
363     if (sys->deco_manager != NULL)
364         org_kde_kwin_server_decoration_manager_destroy(sys->deco_manager);
365     xdg_surface_destroy(sys->surface);
366     wl_surface_destroy(wnd->handle.wl);
367     xdg_shell_destroy(sys->shell);
368     wl_compositor_destroy(sys->compositor);
369     wl_display_disconnect(wnd->display.wl);
370     free(sys);
371 }
372 
373 
374 #define DISPLAY_TEXT N_("Wayland display")
375 #define DISPLAY_LONGTEXT N_( \
376     "Video will be rendered with this Wayland display. " \
377     "If empty, the default display will be used.")
378 
379 vlc_module_begin()
380     set_shortname(N_("XDG shell"))
381     set_description(N_("XDG shell surface"))
382     set_category(CAT_VIDEO)
383     set_subcategory(SUBCAT_VIDEO_VOUT)
384     set_capability("vout window", 20)
385     set_callbacks(Open, Close)
386 
387     add_string("wl-display", NULL, DISPLAY_TEXT, DISPLAY_LONGTEXT, true)
388 vlc_module_end()
389