The open source OpenXR runtime
1// Copyright 2019-2020, Collabora, Ltd.
2// SPDX-License-Identifier: BSL-1.0
3/*!
4 * @file
5 * @brief Wayland window code.
6 * @author Lubosz Sarnecki <lubosz.sarnecki@collabora.com>
7 * @author Jakob Bornecrantz <jakob@collabora.com>
8 * @ingroup comp_main
9 */
10
11#include <errno.h>
12#include <linux/input.h>
13#include <poll.h>
14#include <stdlib.h>
15#include <string.h>
16#include <wayland-client.h>
17
18#include "xdg-shell-client-protocol.h"
19#include "xrt/xrt_compiler.h"
20#include "main/comp_window.h"
21#include "util/u_misc.h"
22
23
24/*
25 *
26 * Private structs.
27 *
28 */
29
30/*!
31 * A Wayland connection and window.
32 *
33 * @implements comp_target_swapchain
34 */
35struct comp_window_wayland
36{
37 struct comp_target_swapchain base;
38
39 struct wl_display *display;
40 struct wl_compositor *compositor;
41 struct wl_surface *surface;
42
43 struct xdg_wm_base *wm_base;
44 struct xdg_surface *xdg_surface;
45 struct xdg_toplevel *xdg_toplevel;
46
47 bool fullscreen_requested;
48};
49
50
51/*
52 *
53 * Pre declare functions.
54 *
55 */
56
57static void
58comp_window_wayland_destroy(struct comp_target *ct);
59
60static bool
61comp_window_wayland_init(struct comp_target *ct);
62
63static void
64comp_window_wayland_update_window_title(struct comp_target *ct, const char *title);
65
66static void
67comp_window_wayland_registry_global(struct comp_window_wayland *w,
68 struct wl_registry *registry,
69 uint32_t name,
70 const char *interface);
71
72static void
73comp_window_wayland_fullscreen(struct comp_window_wayland *w);
74
75static bool
76comp_window_wayland_init_swapchain(struct comp_target *ct, uint32_t width, uint32_t height);
77
78static VkResult
79comp_window_wayland_create_surface(struct comp_window_wayland *w, VkSurfaceKHR *out_surface);
80
81static void
82comp_window_wayland_flush(struct comp_target *ct);
83
84static void
85comp_window_wayland_configure(struct comp_window_wayland *w, int32_t width, int32_t height);
86
87
88/*
89 *
90 * Functions.
91 *
92 */
93
94static inline struct vk_bundle *
95get_vk(struct comp_window_wayland *cww)
96{
97 return &cww->base.base.c->base.vk;
98}
99
100struct comp_target *
101comp_window_wayland_create(struct comp_compositor *c)
102{
103 struct comp_window_wayland *w = U_TYPED_CALLOC(struct comp_window_wayland);
104
105 // The display timing code hasn't been tested on Wayland and may be broken.
106 comp_target_swapchain_init_and_set_fnptrs(&w->base, COMP_TARGET_FORCE_FAKE_DISPLAY_TIMING);
107
108 w->base.base.name = "wayland";
109 w->base.display = VK_NULL_HANDLE;
110 w->base.base.destroy = comp_window_wayland_destroy;
111 w->base.base.flush = comp_window_wayland_flush;
112 w->base.base.init_pre_vulkan = comp_window_wayland_init;
113 w->base.base.init_post_vulkan = comp_window_wayland_init_swapchain;
114 w->base.base.set_title = comp_window_wayland_update_window_title;
115 w->base.base.c = c;
116
117 return &w->base.base;
118}
119
120static void
121comp_window_wayland_destroy(struct comp_target *ct)
122{
123 struct comp_window_wayland *cww = (struct comp_window_wayland *)ct;
124
125 comp_target_swapchain_cleanup(&cww->base);
126
127 if (cww->xdg_toplevel) {
128 xdg_toplevel_destroy(cww->xdg_toplevel);
129 }
130 if (cww->xdg_surface) {
131 xdg_surface_destroy(cww->xdg_surface);
132 }
133 if (cww->wm_base) {
134 xdg_wm_base_destroy(cww->wm_base);
135 }
136 if (cww->surface) {
137 wl_surface_destroy(cww->surface);
138 cww->surface = NULL;
139 }
140 if (cww->compositor) {
141 wl_compositor_destroy(cww->compositor);
142 cww->compositor = NULL;
143 }
144 if (cww->display) {
145 wl_display_disconnect(cww->display);
146 cww->display = NULL;
147 }
148
149 free(ct);
150}
151
152static void
153comp_window_wayland_update_window_title(struct comp_target *ct, const char *title)
154{
155 struct comp_window_wayland *w_wayland = (struct comp_window_wayland *)ct;
156 xdg_toplevel_set_title(w_wayland->xdg_toplevel, title);
157}
158
159static void
160comp_window_wayland_fullscreen(struct comp_window_wayland *w)
161{
162 xdg_toplevel_set_fullscreen(w->xdg_toplevel, NULL);
163 wl_surface_commit(w->surface);
164}
165
166static void
167_xdg_surface_configure_cb(void *data, struct xdg_surface *surface, uint32_t serial)
168{
169 xdg_surface_ack_configure(surface, serial);
170}
171
172static void
173_xdg_toplevel_configure_cb(
174 void *data, struct xdg_toplevel *toplevel, int32_t width, int32_t height, struct wl_array *states)
175{
176 struct comp_window_wayland *w = (struct comp_window_wayland *)data;
177 comp_window_wayland_configure(w, width, height);
178}
179
180static const struct xdg_surface_listener xdg_surface_listener = {
181 _xdg_surface_configure_cb,
182};
183
184static void
185_xdg_toplevel_close_cb(void *data, struct xdg_toplevel *toplevel)
186{}
187
188static const struct xdg_toplevel_listener xdg_toplevel_listener = {
189 _xdg_toplevel_configure_cb,
190 _xdg_toplevel_close_cb,
191#if XDG_TOPLEVEL_CONFIGURE_BOUNDS_SINCE_VERSION >= 4
192 NULL,
193#endif
194#if XDG_TOPLEVEL_WM_CAPABILITIES_SINCE_VERSION >= 5
195 NULL,
196#endif
197};
198
199static void
200_xdg_wm_base_ping_cb(void *data, struct xdg_wm_base *wm_base, uint32_t serial)
201{
202 xdg_wm_base_pong(wm_base, serial);
203}
204
205static const struct xdg_wm_base_listener xdg_wm_base_listener = {
206 _xdg_wm_base_ping_cb,
207};
208
209static bool
210comp_window_wayland_init_swapchain(struct comp_target *ct, uint32_t width, uint32_t height)
211{
212 struct comp_window_wayland *w_wayland = (struct comp_window_wayland *)ct;
213 VkResult ret;
214
215 ret = comp_window_wayland_create_surface(w_wayland, &w_wayland->base.surface.handle);
216 if (ret != VK_SUCCESS) {
217 COMP_ERROR(ct->c, "Failed to create surface!");
218 return false;
219 }
220
221 xdg_toplevel_set_min_size(w_wayland->xdg_toplevel, width, height);
222 xdg_toplevel_set_max_size(w_wayland->xdg_toplevel, width, height);
223
224 return true;
225}
226
227static VkResult
228comp_window_wayland_create_surface(struct comp_window_wayland *w, VkSurfaceKHR *out_surface)
229{
230 struct vk_bundle *vk = get_vk(w);
231 VkResult ret;
232
233 VkWaylandSurfaceCreateInfoKHR surface_info = {
234 .sType = VK_STRUCTURE_TYPE_WAYLAND_SURFACE_CREATE_INFO_KHR,
235 .display = w->display,
236 .surface = w->surface,
237 };
238
239 VkSurfaceKHR surface = VK_NULL_HANDLE;
240 ret = vk->vkCreateWaylandSurfaceKHR( //
241 vk->instance, //
242 &surface_info, //
243 NULL, //
244 &surface); //
245 if (ret != VK_SUCCESS) {
246 COMP_ERROR(w->base.base.c, "vkCreateWaylandSurfaceKHR: %s", vk_result_string(ret));
247 return ret;
248 }
249
250 VK_NAME_SURFACE(vk, surface, "comp_window_wayland surface");
251 *out_surface = surface;
252
253 return VK_SUCCESS;
254}
255
256static void
257comp_window_wayland_flush(struct comp_target *ct)
258{
259 struct comp_window_wayland *w_wayland = (struct comp_window_wayland *)ct;
260
261 while (wl_display_prepare_read(w_wayland->display) != 0)
262 wl_display_dispatch_pending(w_wayland->display);
263 if (wl_display_flush(w_wayland->display) < 0 && errno != EAGAIN) {
264 wl_display_cancel_read(w_wayland->display);
265 return;
266 }
267
268 struct pollfd fds[] = {
269 {
270 .fd = wl_display_get_fd(w_wayland->display),
271 .events = POLLIN,
272 .revents = 0,
273 },
274 };
275
276 if (poll(fds, 1, 0) > 0) {
277 wl_display_read_events(w_wayland->display);
278 wl_display_dispatch_pending(w_wayland->display);
279 } else {
280 wl_display_cancel_read(w_wayland->display);
281 }
282}
283
284static void
285_registry_global_remove_cb(void *data, struct wl_registry *registry, uint32_t name)
286{}
287
288static void
289_registry_global_cb(void *data, struct wl_registry *registry, uint32_t name, const char *interface, uint32_t version)
290{
291 struct comp_window_wayland *w = (struct comp_window_wayland *)data;
292 // vik_log_d("Interface: %s Version %d", interface, version);
293 comp_window_wayland_registry_global(w, registry, name, interface);
294}
295
296static const struct wl_registry_listener registry_listener = {
297 _registry_global_cb,
298 _registry_global_remove_cb,
299};
300
301static void
302comp_window_wayland_registry_global(struct comp_window_wayland *w,
303 struct wl_registry *registry,
304 uint32_t name,
305 const char *interface)
306{
307 if (strcmp(interface, "wl_compositor") == 0) {
308 w->compositor = (struct wl_compositor *)wl_registry_bind(registry, name, &wl_compositor_interface, 4);
309 } else if (strcmp(interface, "xdg_wm_base") == 0) {
310 w->wm_base = (struct xdg_wm_base *)wl_registry_bind(registry, name, &xdg_wm_base_interface, 1);
311 xdg_wm_base_add_listener(w->wm_base, &xdg_wm_base_listener, w);
312 }
313}
314
315static bool
316comp_window_wayland_init(struct comp_target *ct)
317{
318 struct comp_window_wayland *w_wayland = (struct comp_window_wayland *)ct;
319
320 w_wayland->display = wl_display_connect(NULL);
321 if (!w_wayland->display) {
322 return false;
323 }
324
325 struct wl_registry *registry = wl_display_get_registry(w_wayland->display);
326 wl_registry_add_listener(registry, ®istry_listener, w_wayland);
327
328 wl_display_roundtrip(w_wayland->display);
329
330 wl_registry_destroy(registry);
331
332 w_wayland->surface = wl_compositor_create_surface(w_wayland->compositor);
333
334 if (!w_wayland->wm_base) {
335 COMP_ERROR(ct->c, "Compositor is missing xdg-shell support");
336 }
337
338 w_wayland->xdg_surface = xdg_wm_base_get_xdg_surface(w_wayland->wm_base, w_wayland->surface);
339
340 xdg_surface_add_listener(w_wayland->xdg_surface, &xdg_surface_listener, w_wayland);
341
342 w_wayland->xdg_toplevel = xdg_surface_get_toplevel(w_wayland->xdg_surface);
343
344 xdg_toplevel_add_listener(w_wayland->xdg_toplevel, &xdg_toplevel_listener, w_wayland);
345 /* basic defaults */
346 xdg_toplevel_set_app_id(w_wayland->xdg_toplevel, "openxr");
347 xdg_toplevel_set_title(w_wayland->xdg_toplevel, "OpenXR application");
348
349 wl_surface_commit(w_wayland->surface);
350
351 return true;
352}
353
354static void
355comp_window_wayland_configure(struct comp_window_wayland *w, int32_t width, int32_t height)
356{
357 if (w->base.base.c->settings.fullscreen && !w->fullscreen_requested) {
358 COMP_DEBUG(w->base.base.c, "Setting full screen");
359 comp_window_wayland_fullscreen(w);
360 w->fullscreen_requested = true;
361 }
362}
363
364
365/*
366 *
367 * Factory
368 *
369 */
370
371static const char *instance_extensions[] = {
372 VK_KHR_WAYLAND_SURFACE_EXTENSION_NAME,
373};
374
375static bool
376detect(const struct comp_target_factory *ctf, struct comp_compositor *c)
377{
378 return false;
379}
380
381static bool
382create_target(const struct comp_target_factory *ctf, struct comp_compositor *c, struct comp_target **out_ct)
383{
384 struct comp_target *ct = comp_window_wayland_create(c);
385 if (ct == NULL) {
386 return false;
387 }
388
389 *out_ct = ct;
390
391 return true;
392}
393
394const struct comp_target_factory comp_target_factory_wayland = {
395 .name = "Wayland Windowed",
396 .identifier = "wayland",
397 .requires_vulkan_for_create = false,
398 .is_deferred = false,
399 .required_instance_version = 0,
400 .required_instance_extensions = instance_extensions,
401 .required_instance_extension_count = ARRAY_SIZE(instance_extensions),
402 .optional_device_extensions = NULL,
403 .optional_device_extension_count = 0,
404 .detect = detect,
405 .create_target = create_target,
406};