1 /**
2  * @file shell.c
3  * @brief Wayland shell surface provider module for VLC media player
4  */
5 /*****************************************************************************
6  * Copyright © 2014 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 
35 #include <vlc_common.h>
36 #include <vlc_plugin.h>
37 #include <vlc_vout_window.h>
38 
39 struct vout_window_sys_t
40 {
41     struct wl_compositor *compositor;
42     struct wl_output *output;
43     struct wl_shell *shell;
44     struct wl_shell_surface *shell_surface;
45 
46     uint32_t top_width;
47     uint32_t top_height;
48     uint32_t fs_width;
49     uint32_t fs_height;
50     bool fullscreen;
51 
52     vlc_mutex_t lock;
53     vlc_thread_t thread;
54 };
55 
cleanup_wl_display_read(void * data)56 static void cleanup_wl_display_read(void *data)
57 {
58     struct wl_display *display = data;
59 
60     wl_display_cancel_read(display);
61 }
62 
63 /** Background thread for Wayland shell events handling */
Thread(void * data)64 static void *Thread(void *data)
65 {
66     vout_window_t *wnd = data;
67     struct wl_display *display = wnd->display.wl;
68     struct pollfd ufd[1];
69 
70     int canc = vlc_savecancel();
71     vlc_cleanup_push(cleanup_wl_display_read, display);
72 
73     ufd[0].fd = wl_display_get_fd(display);
74     ufd[0].events = POLLIN;
75 
76     for (;;)
77     {
78         while (wl_display_prepare_read(display) != 0)
79             wl_display_dispatch_pending(display);
80 
81         wl_display_flush(display);
82         vlc_restorecancel(canc);
83 
84         while (poll(ufd, 1, -1) < 0);
85 
86         canc = vlc_savecancel();
87         wl_display_read_events(display);
88         wl_display_dispatch_pending(display);
89     }
90     vlc_assert_unreachable();
91     vlc_cleanup_pop();
92     //vlc_restorecancel(canc);
93     //return NULL;
94 }
95 
Control(vout_window_t * wnd,int cmd,va_list ap)96 static int Control(vout_window_t *wnd, int cmd, va_list ap)
97 {
98     vout_window_sys_t *sys = wnd->sys;
99     struct wl_display *display = wnd->display.wl;
100 
101     switch (cmd)
102     {
103         case VOUT_WINDOW_SET_STATE:
104             return VLC_EGENERIC;
105         case VOUT_WINDOW_SET_SIZE:
106         {
107             unsigned width = va_arg (ap, unsigned);
108             unsigned height = va_arg (ap, unsigned);
109 
110             vlc_mutex_lock(&sys->lock);
111             sys->top_width = width;
112             sys->top_height = height;
113 
114             if (!sys->fullscreen)
115                 vout_window_ReportSize(wnd, width, height);
116             vlc_mutex_unlock(&sys->lock);
117             break;
118         }
119         case VOUT_WINDOW_SET_FULLSCREEN:
120         {
121             bool fs = va_arg(ap, int);
122 
123             if (fs && sys->output != NULL)
124             {
125                 wl_shell_surface_set_fullscreen(sys->shell_surface, 1, 0,
126                                                 sys->output);
127                 vlc_mutex_lock(&sys->lock);
128                 sys->fullscreen = true;
129                 vout_window_ReportSize(wnd, sys->fs_width, sys->fs_height);
130                 vlc_mutex_unlock(&sys->lock);
131             }
132             else
133             {
134                 wl_shell_surface_set_toplevel(sys->shell_surface);
135 
136                 vlc_mutex_lock(&sys->lock);
137                 sys->fullscreen = false;
138                 vout_window_ReportSize(wnd, sys->top_width, sys->top_height);
139                 vlc_mutex_unlock(&sys->lock);
140             }
141             break;
142         }
143 
144         default:
145             msg_Err(wnd, "request %d not implemented", cmd);
146             return VLC_EGENERIC;
147     }
148 
149     wl_display_flush(display);
150     return VLC_SUCCESS;
151 }
152 
output_geometry_cb(void * data,struct wl_output * output,int32_t x,int32_t y,int32_t width,int32_t height,int32_t subpixel,const char * vendor,const char * model,int32_t transform)153 static void output_geometry_cb(void *data, struct wl_output *output, int32_t x,
154                                int32_t y, int32_t width, int32_t height,
155                                int32_t subpixel, const char *vendor,
156                                const char *model, int32_t transform)
157 {
158     vout_window_t *wnd = data;
159 
160     msg_Dbg(wnd, "output geometry: %s %s %"PRId32"x%"PRId32"mm "
161             "@ %"PRId32"x%"PRId32" subpixel: %"PRId32" transform: %"PRId32,
162             vendor, model, width, height, x, y, subpixel, transform);
163     (void) output;
164 }
165 
output_mode_cb(void * data,struct wl_output * output,uint32_t flags,int32_t width,int32_t height,int32_t refresh)166 static void output_mode_cb(void *data, struct wl_output *output,
167                            uint32_t flags, int32_t width, int32_t height,
168                            int32_t refresh)
169 {
170     vout_window_t *wnd = data;
171     vout_window_sys_t *sys = wnd->sys;
172 
173     msg_Dbg(wnd, "output mode: 0x%08"PRIX32" %"PRId32"x%"PRId32
174             " %"PRId32"mHz%s", flags, width, height, refresh,
175             (flags & WL_OUTPUT_MODE_CURRENT) ? " (current)" : "");
176 
177     if (!(flags & WL_OUTPUT_MODE_CURRENT))
178         return;
179 
180     vlc_mutex_lock(&sys->lock);
181     sys->fs_width = width;
182     sys->fs_height = height;
183 
184     if (sys->fullscreen)
185         vout_window_ReportSize(wnd, width, height);
186     vlc_mutex_unlock(&sys->lock);
187 
188     (void) output;
189 }
190 
191 const struct wl_output_listener output_cbs =
192 {
193     output_geometry_cb,
194     output_mode_cb,
195     NULL,
196     NULL,
197 };
198 
shell_surface_ping_cb(void * data,struct wl_shell_surface * shell_surface,uint32_t serial)199 static void shell_surface_ping_cb(void *data,
200                                   struct wl_shell_surface *shell_surface,
201                                   uint32_t serial)
202 {
203     (void) data;
204     wl_shell_surface_pong(shell_surface, serial);
205 }
206 
shell_surface_configure_cb(void * data,struct wl_shell_surface * shell_surface,uint32_t edges,int32_t width,int32_t height)207 static void shell_surface_configure_cb(void *data,
208                                        struct wl_shell_surface *shell_surface,
209                                        uint32_t edges,
210                                        int32_t width, int32_t height)
211 {
212     vout_window_t *wnd = data;
213     vout_window_sys_t *sys = wnd->sys;
214 
215     msg_Dbg(wnd, "new configuration: %"PRId32"x%"PRId32, width, height);
216     vlc_mutex_lock(&sys->lock);
217     sys->top_width = width;
218     sys->top_height = height;
219 
220     if (!sys->fullscreen)
221         vout_window_ReportSize(wnd,  width, height);
222     vlc_mutex_unlock(&sys->lock);
223 
224     (void) shell_surface;
225     (void) edges;
226 }
227 
shell_surface_popup_done_cb(void * data,struct wl_shell_surface * shell_surface)228 static void shell_surface_popup_done_cb(void *data,
229                                         struct wl_shell_surface *shell_surface)
230 {
231     (void) data; (void) shell_surface;
232 }
233 
234 static const struct wl_shell_surface_listener shell_surface_cbs =
235 {
236     shell_surface_ping_cb,
237     shell_surface_configure_cb,
238     shell_surface_popup_done_cb,
239 };
240 
registry_global_cb(void * data,struct wl_registry * registry,uint32_t name,const char * iface,uint32_t vers)241 static void registry_global_cb(void *data, struct wl_registry *registry,
242                                uint32_t name, const char *iface, uint32_t vers)
243 {
244     vout_window_t *wnd = data;
245     vout_window_sys_t *sys = wnd->sys;
246 
247     msg_Dbg(wnd, "global %3"PRIu32": %s version %"PRIu32, name, iface, vers);
248 
249     if (!strcmp(iface, "wl_compositor"))
250         sys->compositor = wl_registry_bind(registry, name,
251                                            &wl_compositor_interface,
252                                            (vers < 2) ? vers : 2);
253     else
254     if (!strcmp(iface, "wl_output"))
255         sys->output = wl_registry_bind(registry, name, &wl_output_interface,
256                                        1);
257     else
258     if (!strcmp(iface, "wl_shell"))
259         sys->shell = wl_registry_bind(registry, name, &wl_shell_interface, 1);
260 }
261 
registry_global_remove_cb(void * data,struct wl_registry * registry,uint32_t name)262 static void registry_global_remove_cb(void *data, struct wl_registry *registry,
263                                       uint32_t name)
264 {
265     vout_window_t *wnd = data;
266 
267     msg_Dbg(wnd, "global remove %3"PRIu32, name);
268     (void) registry;
269 }
270 
271 static const struct wl_registry_listener registry_cbs =
272 {
273     registry_global_cb,
274     registry_global_remove_cb,
275 };
276 
277 /**
278  * Creates a Wayland shell surface.
279  */
Open(vout_window_t * wnd,const vout_window_cfg_t * cfg)280 static int Open(vout_window_t *wnd, const vout_window_cfg_t *cfg)
281 {
282     if (cfg->type != VOUT_WINDOW_TYPE_INVALID
283      && cfg->type != VOUT_WINDOW_TYPE_WAYLAND)
284         return VLC_EGENERIC;
285 
286     vout_window_sys_t *sys = malloc(sizeof (*sys));
287     if (unlikely(sys == NULL))
288         return VLC_ENOMEM;
289 
290     sys->compositor = NULL;
291     sys->output = NULL;
292     sys->shell = NULL;
293     sys->shell_surface = NULL;
294     sys->top_width = cfg->width;
295     sys->top_height = cfg->height;
296     sys->fs_width = cfg->width;
297     sys->fs_height = cfg->height;
298     sys->fullscreen = false;
299     vlc_mutex_init(&sys->lock);
300     wnd->sys = sys;
301 
302     /* Connect to the display server */
303     char *dpy_name = var_InheritString(wnd, "wl-display");
304     struct wl_display *display = wl_display_connect(dpy_name);
305 
306     free(dpy_name);
307 
308     if (display == NULL)
309     {
310         vlc_mutex_destroy(&sys->lock);
311         free(sys);
312         return VLC_EGENERIC;
313     }
314 
315     /* Find the interesting singleton(s) */
316     struct wl_registry *registry = wl_display_get_registry(display);
317     if (registry == NULL)
318         goto error;
319 
320     wl_registry_add_listener(registry, &registry_cbs, wnd);
321     wl_display_roundtrip(display);
322     wl_registry_destroy(registry);
323 
324     if (sys->compositor == NULL || sys->shell == NULL)
325         goto error;
326 
327     if (sys->output != NULL)
328         wl_output_add_listener(sys->output, &output_cbs, wnd);
329 
330     /* Create a surface */
331     struct wl_surface *surface = wl_compositor_create_surface(sys->compositor);
332     if (surface == NULL)
333         goto error;
334 
335     struct wl_shell_surface *shell_surface =
336         wl_shell_get_shell_surface(sys->shell, surface);
337     if (shell_surface == NULL)
338         goto error;
339 
340     sys->shell_surface = shell_surface;
341 
342     wl_shell_surface_add_listener(shell_surface, &shell_surface_cbs, wnd);
343     wl_shell_surface_set_class(shell_surface, PACKAGE_NAME);
344     wl_shell_surface_set_toplevel(shell_surface);
345 
346     char *title = var_InheritString(wnd, "video-title");
347     wl_shell_surface_set_title(shell_surface, title ? title
348                                                     : _("VLC media player"));
349     free(title);
350 
351     //if (var_InheritBool (wnd, "keyboard-events"))
352     //    do_something();
353 
354     wl_display_flush(display);
355 
356     wnd->type = VOUT_WINDOW_TYPE_WAYLAND;
357     wnd->handle.wl = surface;
358     wnd->display.wl = display;
359     wnd->control = Control;
360 
361     if (vlc_clone (&sys->thread, Thread, wnd, VLC_THREAD_PRIORITY_LOW))
362         goto error;
363 
364     vout_window_ReportSize(wnd, cfg->width, cfg->height);
365     vout_window_SetFullScreen(wnd, cfg->is_fullscreen);
366     return VLC_SUCCESS;
367 
368 error:
369     if (sys->shell_surface != NULL)
370         wl_shell_surface_destroy(sys->shell_surface);
371     if (sys->shell != NULL)
372         wl_shell_destroy(sys->shell);
373     if (sys->output != NULL)
374         wl_output_destroy(sys->output);
375     if (sys->compositor != NULL)
376         wl_compositor_destroy(sys->compositor);
377     wl_display_disconnect(display);
378     vlc_mutex_destroy(&sys->lock);
379     free(sys);
380     return VLC_EGENERIC;
381 }
382 
383 /**
384  * Destroys a Wayland shell surface.
385  */
Close(vout_window_t * wnd)386 static void Close(vout_window_t *wnd)
387 {
388     vout_window_sys_t *sys = wnd->sys;
389 
390     vlc_cancel(sys->thread);
391     vlc_join(sys->thread, NULL);
392 
393     wl_shell_surface_destroy(sys->shell_surface);
394     wl_surface_destroy(wnd->handle.wl);
395     wl_shell_destroy(sys->shell);
396     if (sys->output != NULL)
397         wl_output_destroy(sys->output);
398     wl_compositor_destroy(sys->compositor);
399     wl_display_disconnect(wnd->display.wl);
400     vlc_mutex_destroy(&sys->lock);
401     free(sys);
402 }
403 
404 
405 #define DISPLAY_TEXT N_("Wayland display")
406 #define DISPLAY_LONGTEXT N_( \
407     "Video will be rendered with this Wayland display. " \
408     "If empty, the default display will be used.")
409 
410 vlc_module_begin ()
411     set_shortname (N_("WL shell"))
412     set_description (N_("Wayland shell surface"))
413     set_category (CAT_VIDEO)
414     set_subcategory (SUBCAT_VIDEO_VOUT)
415     set_capability ("vout window", 10)
416     set_callbacks (Open, Close)
417 
418     add_string ("wl-display", NULL, DISPLAY_TEXT, DISPLAY_LONGTEXT, true)
419 vlc_module_end ()
420