The open source OpenXR runtime
1// Copyright 2019-2020, Collabora, Ltd.
2// SPDX-License-Identifier: BSL-1.0
3/*!
4 * @file
5 * @brief XCB 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 <xcb/xcb.h>
12#include <xcb/randr.h>
13
14#include "util/u_misc.h"
15#include "xrt/xrt_compiler.h"
16#include "main/comp_window.h"
17
18
19/*
20 *
21 * Private structs.
22 *
23 */
24
25/*!
26 * Xcb display, xrandr output.
27 */
28struct comp_window_xcb_display
29{
30 char *name;
31 struct
32 {
33 int16_t x;
34 int16_t y;
35 } position;
36
37 struct
38 {
39 uint16_t width;
40 uint16_t height;
41 } size;
42};
43
44/*!
45 * A xcb connection and window.
46 *
47 * @implements comp_target_swapchain
48 */
49struct comp_window_xcb
50{
51 struct comp_target_swapchain base;
52
53 xcb_connection_t *connection;
54 xcb_window_t window;
55 xcb_screen_t *screen;
56
57 xcb_atom_t atom_wm_protocols;
58 xcb_atom_t atom_wm_delete_window;
59
60 struct comp_window_xcb_display *displays;
61 uint16_t display_count;
62};
63
64
65/*
66 *
67 * Pre declare functions.
68 *
69 */
70
71static void
72comp_window_xcb_destroy(struct comp_target *ct);
73
74static void
75comp_window_xcb_flush(struct comp_target *ct);
76
77XRT_MAYBE_UNUSED static void
78comp_window_xcb_list_screens(struct comp_window_xcb *w, xcb_screen_t *screen);
79
80static bool
81comp_window_xcb_init(struct comp_target *ct);
82
83static struct comp_window_xcb_display *
84comp_window_xcb_current_display(struct comp_window_xcb *w);
85
86static bool
87comp_window_xcb_init_swapchain(struct comp_target *ct, uint32_t width, uint32_t height);
88
89static int
90comp_window_xcb_connect(struct comp_window_xcb *w);
91
92static void
93comp_window_xcb_create_window(struct comp_window_xcb *w, VkExtent2D extent);
94
95static void
96comp_window_xcb_get_randr_outputs(struct comp_window_xcb *w);
97
98static void
99comp_window_xcb_connect_delete_event(struct comp_window_xcb *w);
100
101static void
102comp_window_xcb_set_full_screen(struct comp_window_xcb *w);
103
104static xcb_atom_t
105comp_window_xcb_get_atom(struct comp_window_xcb *w, const char *name);
106
107static VkResult
108comp_window_xcb_create_surface(struct comp_window_xcb *w, VkSurfaceKHR *out_surface);
109
110static void
111comp_window_xcb_update_window_title(struct comp_target *ct, const char *title);
112
113
114/*
115 *
116 * Functions.
117 *
118 */
119
120static inline struct vk_bundle *
121get_vk(struct comp_window_xcb *cwx)
122{
123 return &cwx->base.base.c->base.vk;
124}
125
126struct comp_target *
127comp_window_xcb_create(struct comp_compositor *c)
128{
129 struct comp_window_xcb *w = U_TYPED_CALLOC(struct comp_window_xcb);
130
131 /*
132 * The display timing code has been tested on XCB,
133 * and is know to be broken when using VK_PRESENT_MODE_IMMEDIATE_KHR.
134 */
135 comp_target_swapchain_init_and_set_fnptrs(&w->base, COMP_TARGET_FORCE_FAKE_DISPLAY_TIMING);
136
137 w->base.base.name = "xcb";
138 w->base.display = VK_NULL_HANDLE;
139 w->base.base.destroy = comp_window_xcb_destroy;
140 w->base.base.flush = comp_window_xcb_flush;
141 w->base.base.init_pre_vulkan = comp_window_xcb_init;
142 w->base.base.init_post_vulkan = comp_window_xcb_init_swapchain;
143 w->base.base.set_title = comp_window_xcb_update_window_title;
144 w->base.base.c = c;
145
146 return &w->base.base;
147}
148
149static void
150comp_window_xcb_destroy(struct comp_target *ct)
151{
152 struct comp_window_xcb *w_xcb = (struct comp_window_xcb *)ct;
153
154 comp_target_swapchain_cleanup(&w_xcb->base);
155
156 xcb_destroy_window(w_xcb->connection, w_xcb->window);
157 xcb_disconnect(w_xcb->connection);
158
159 for (uint16_t i = 0; i > w_xcb->display_count; i++)
160 free(w_xcb->displays[i].name);
161
162 free(w_xcb->displays);
163
164 free(ct);
165}
166
167static void
168comp_window_xcb_list_screens(struct comp_window_xcb *w, xcb_screen_t *screen)
169{
170 COMP_DEBUG(w->base.base.c, "Screen 0 %dx%d", screen->width_in_pixels, screen->height_in_pixels);
171 comp_window_xcb_get_randr_outputs(w);
172
173 for (uint16_t i = 0; i < w->display_count; i++) {
174 struct comp_window_xcb_display *d = &w->displays[i];
175 COMP_DEBUG(w->base.base.c, "%d: %s %dx%d [%d, %d]", i, d->name, d->size.width, d->size.height,
176 d->position.x, d->position.y);
177 }
178}
179
180static bool
181select_new_current_display(struct comp_target *ct)
182{
183 struct comp_window_xcb *w_xcb = (struct comp_window_xcb *)ct;
184 for (uint32_t i = 0; i < w_xcb->display_count; i++) {
185 if (w_xcb->displays[i].size.width != 0 && w_xcb->displays[i].size.height != 0) {
186 w_xcb->base.base.c->settings.display = i;
187 COMP_DEBUG(ct->c, "Select new current display %d: %s", i, w_xcb->displays[i].name);
188 return true;
189 }
190 }
191
192 return false;
193}
194
195static bool
196comp_window_xcb_init(struct comp_target *ct)
197{
198 struct comp_window_xcb *w_xcb = (struct comp_window_xcb *)ct;
199
200 if (!comp_window_xcb_connect(w_xcb)) {
201 return false;
202 }
203
204 xcb_screen_iterator_t iter = xcb_setup_roots_iterator(xcb_get_setup(w_xcb->connection));
205
206 w_xcb->screen = iter.data;
207
208 if (ct->c->settings.fullscreen) {
209 comp_window_xcb_get_randr_outputs(w_xcb);
210
211 if (ct->c->settings.display > (int)w_xcb->display_count - 1) {
212 COMP_DEBUG(ct->c,
213 "Requested display %d, but only %d "
214 "displays are available.",
215 ct->c->settings.display, w_xcb->display_count);
216
217 ct->c->settings.display = 0;
218 struct comp_window_xcb_display *d = comp_window_xcb_current_display(w_xcb);
219 COMP_DEBUG(ct->c, "Selecting '%s' instead.", d->name);
220 }
221
222 if (ct->c->settings.display == -1)
223 ct->c->settings.display = 0;
224
225 struct comp_window_xcb_display *d = comp_window_xcb_current_display(w_xcb);
226
227 if (d->size.width == 0 || d->size.height == 0) {
228 COMP_WARN(ct->c, "Selected display %d has no size", w_xcb->base.base.c->settings.display);
229 if (select_new_current_display(ct)) {
230 d = comp_window_xcb_current_display(w_xcb);
231 COMP_WARN(ct->c, "Falling back to display %d: %s", w_xcb->base.base.c->settings.display,
232 d->name);
233 } else {
234 COMP_ERROR(ct->c, "No suitable display found, disabling fullscreen");
235 w_xcb->base.base.c->settings.fullscreen = false;
236 }
237 }
238
239 if (d->size.width != 0 && d->size.height != 0) {
240 COMP_DEBUG(ct->c, "Setting window size %dx%d.", d->size.width, d->size.height);
241
242 VkExtent2D extent = {d->size.width, d->size.height};
243 comp_target_swapchain_override_extents(&w_xcb->base, extent);
244 }
245 }
246
247 // The extent of the window we are about to create.
248 VkExtent2D extent = {ct->c->settings.preferred.width, ct->c->settings.preferred.height};
249
250 // If we require a particular size, use that.
251 if (w_xcb->base.override.compositor_extent) {
252 extent = w_xcb->base.override.extent;
253 }
254
255 // We can now create the window.
256 comp_window_xcb_create_window(w_xcb, extent);
257
258 comp_window_xcb_connect_delete_event(w_xcb);
259
260 if (ct->c->settings.fullscreen) {
261 comp_window_xcb_set_full_screen(w_xcb);
262 }
263
264 xcb_map_window(w_xcb->connection, w_xcb->window);
265
266 return true;
267}
268
269static struct comp_window_xcb_display *
270comp_window_xcb_current_display(struct comp_window_xcb *w)
271{
272 return &w->displays[w->base.base.c->settings.display];
273}
274
275static void
276comp_window_xcb_flush(struct comp_target *ct)
277{
278 (void)ct;
279}
280
281static bool
282comp_window_xcb_init_swapchain(struct comp_target *ct, uint32_t width, uint32_t height)
283{
284 struct comp_window_xcb *w_xcb = (struct comp_window_xcb *)ct;
285 VkResult ret;
286
287 ret = comp_window_xcb_create_surface(w_xcb, &w_xcb->base.surface.handle);
288 if (ret != VK_SUCCESS) {
289 return false;
290 }
291
292 return true;
293}
294
295static int
296comp_window_xcb_connect(struct comp_window_xcb *w)
297{
298 w->connection = xcb_connect(NULL, NULL);
299 return !xcb_connection_has_error(w->connection);
300}
301
302static void
303comp_window_xcb_create_window(struct comp_window_xcb *w, VkExtent2D extent)
304{
305 w->window = xcb_generate_id(w->connection);
306
307 int x = 0;
308 int y = 0;
309
310 if (w->base.base.c->settings.fullscreen) {
311 x = comp_window_xcb_current_display(w)->position.x;
312 y = comp_window_xcb_current_display(w)->position.y;
313 }
314
315 uint32_t value_list = XCB_EVENT_MASK_STRUCTURE_NOTIFY;
316
317 xcb_create_window( //
318 w->connection, // conn
319 XCB_COPY_FROM_PARENT, // depth
320 w->window, // wid
321 w->screen->root, // parent
322 x, // x
323 y, // y
324 extent.width, // width
325 extent.height, // height
326 0, // border_width
327 XCB_WINDOW_CLASS_INPUT_OUTPUT, // _class
328 w->screen->root_visual, // visual
329 XCB_CW_EVENT_MASK, // value_mask
330 &value_list); // value_list
331}
332
333static void
334comp_window_xcb_get_randr_outputs(struct comp_window_xcb *w)
335{
336 xcb_randr_get_screen_resources_cookie_t resources_cookie =
337 xcb_randr_get_screen_resources(w->connection, w->screen->root);
338 xcb_randr_get_screen_resources_reply_t *resources_reply =
339 xcb_randr_get_screen_resources_reply(w->connection, resources_cookie, NULL);
340 xcb_randr_output_t *xcb_outputs = xcb_randr_get_screen_resources_outputs(resources_reply);
341
342 w->display_count = xcb_randr_get_screen_resources_outputs_length(resources_reply);
343 if (w->display_count < 1)
344 COMP_ERROR(w->base.base.c, "Failed to retrieve randr outputs");
345
346 w->displays = calloc(w->display_count, sizeof(struct comp_window_xcb_display));
347
348 for (int i = 0; i < w->display_count; i++) {
349 xcb_randr_get_output_info_cookie_t output_cookie =
350 xcb_randr_get_output_info(w->connection, xcb_outputs[i], XCB_CURRENT_TIME);
351 xcb_randr_get_output_info_reply_t *output_reply =
352 xcb_randr_get_output_info_reply(w->connection, output_cookie, NULL);
353
354 if (output_reply->connection != XCB_RANDR_CONNECTION_CONNECTED || output_reply->crtc == XCB_NONE) {
355 free(output_reply);
356 continue;
357 }
358
359 xcb_randr_get_crtc_info_cookie_t crtc_cookie =
360 xcb_randr_get_crtc_info(w->connection, output_reply->crtc, XCB_CURRENT_TIME);
361 xcb_randr_get_crtc_info_reply_t *crtc_reply =
362 xcb_randr_get_crtc_info_reply(w->connection, crtc_cookie, NULL);
363
364 uint8_t *name = xcb_randr_get_output_info_name(output_reply);
365 int name_len = xcb_randr_get_output_info_name_length(output_reply);
366
367 w->displays[i] = (struct comp_window_xcb_display){
368 .name = U_TYPED_ARRAY_CALLOC(char, name_len + 1),
369 .position = {crtc_reply->x, crtc_reply->y},
370 .size = {crtc_reply->width, crtc_reply->height},
371 };
372
373 memcpy(w->displays[i].name, name, name_len);
374 w->displays[i].name[name_len] = '\0';
375
376 COMP_DEBUG(w->base.base.c, "randr output %d: %s: %dx%d", i, w->displays[i].name, crtc_reply->width,
377 crtc_reply->height);
378
379 free(crtc_reply);
380 free(output_reply);
381 }
382
383 free(resources_reply);
384}
385
386static void
387comp_window_xcb_connect_delete_event(struct comp_window_xcb *w)
388{
389 w->atom_wm_protocols = comp_window_xcb_get_atom(w, "WM_PROTOCOLS");
390 w->atom_wm_delete_window = comp_window_xcb_get_atom(w, "WM_DELETE_WINDOW");
391 xcb_change_property(w->connection, XCB_PROP_MODE_REPLACE, w->window, w->atom_wm_protocols, XCB_ATOM_ATOM, 32, 1,
392 &w->atom_wm_delete_window);
393}
394
395static void
396comp_window_xcb_set_full_screen(struct comp_window_xcb *w)
397{
398 xcb_atom_t atom_wm_state = comp_window_xcb_get_atom(w, "_NET_WM_STATE");
399 xcb_atom_t atom_wm_fullscreen = comp_window_xcb_get_atom(w, "_NET_WM_STATE_FULLSCREEN");
400 xcb_change_property(w->connection, XCB_PROP_MODE_REPLACE, w->window, atom_wm_state, XCB_ATOM_ATOM, 32, 1,
401 &atom_wm_fullscreen);
402}
403
404static xcb_atom_t
405comp_window_xcb_get_atom(struct comp_window_xcb *w, const char *name)
406{
407 xcb_intern_atom_cookie_t cookie;
408 xcb_intern_atom_reply_t *reply;
409 xcb_atom_t atom;
410
411 cookie = xcb_intern_atom(w->connection, 0, strlen(name), name);
412 reply = xcb_intern_atom_reply(w->connection, cookie, NULL);
413 if (reply) {
414 atom = reply->atom;
415 } else {
416 atom = XCB_NONE;
417 }
418
419 free(reply);
420 return atom;
421}
422
423static VkResult
424comp_window_xcb_create_surface(struct comp_window_xcb *w, VkSurfaceKHR *out_surface)
425{
426 struct vk_bundle *vk = get_vk(w);
427 VkResult ret;
428
429 VkXcbSurfaceCreateInfoKHR surface_info = {
430 .sType = VK_STRUCTURE_TYPE_XCB_SURFACE_CREATE_INFO_KHR,
431 .connection = w->connection,
432 .window = w->window,
433 };
434
435 VkSurfaceKHR surface = VK_NULL_HANDLE;
436 ret = vk->vkCreateXcbSurfaceKHR( //
437 vk->instance, //
438 &surface_info, //
439 NULL, //
440 &surface); //
441 if (ret != VK_SUCCESS) {
442 COMP_ERROR(w->base.base.c, "vkCreateXcbSurfaceKHR: %s", vk_result_string(ret));
443 return ret;
444 }
445
446 VK_NAME_SURFACE(vk, surface, "comp_window_xcb surface");
447 *out_surface = surface;
448
449 return VK_SUCCESS;
450}
451
452static void
453comp_window_xcb_update_window_title(struct comp_target *ct, const char *title)
454{
455 struct comp_window_xcb *w_xcb = (struct comp_window_xcb *)ct;
456
457 xcb_change_property(w_xcb->connection, XCB_PROP_MODE_REPLACE, w_xcb->window, XCB_ATOM_WM_NAME, XCB_ATOM_STRING,
458 8, strlen(title), title);
459}
460
461
462/*
463 *
464 * Factory
465 *
466 */
467
468static const char *instance_extensions[] = {
469 VK_KHR_XCB_SURFACE_EXTENSION_NAME,
470};
471
472static bool
473detect(const struct comp_target_factory *ctf, struct comp_compositor *c)
474{
475 return false;
476}
477
478static bool
479create_target(const struct comp_target_factory *ctf, struct comp_compositor *c, struct comp_target **out_ct)
480{
481 struct comp_target *ct = comp_window_xcb_create(c);
482 if (ct == NULL) {
483 return false;
484 }
485
486 COMP_DEBUG(c, "Using VK_PRESENT_MODE_IMMEDIATE_KHR for xcb window")
487 c->settings.present_mode = VK_PRESENT_MODE_IMMEDIATE_KHR;
488
489 *out_ct = ct;
490
491 return true;
492}
493
494const struct comp_target_factory comp_target_factory_xcb = {
495 .name = "X11(XCB) Windowed",
496 .identifier = "x11",
497 .requires_vulkan_for_create = false,
498 .is_deferred = true,
499 .required_instance_version = 0,
500 .required_instance_extensions = instance_extensions,
501 .required_instance_extension_count = ARRAY_SIZE(instance_extensions),
502 .optional_device_extensions = NULL,
503 .optional_device_extension_count = 0,
504 .detect = detect,
505 .create_target = create_target,
506};