Atlas - SDL_waylandvideo.c
Home / ext / SDL / src / video / wayland Lines: 1 | Size: 70361 bytes [Download] [Show on GitHub] [Search similar files] [Raw] [Raw (proxy)][FILE BEGIN]1/* 2 Simple DirectMedia Layer 3 Copyright (C) 1997-2026 Sam Lantinga <[email protected]> 4 5 This software is provided 'as-is', without any express or implied 6 warranty. In no event will the authors be held liable for any damages 7 arising from the use of this software. 8 9 Permission is granted to anyone to use this software for any purpose, 10 including commercial applications, and to alter it and redistribute it 11 freely, subject to the following restrictions: 12 13 1. The origin of this software must not be misrepresented; you must not 14 claim that you wrote the original software. If you use this software 15 in a product, an acknowledgment in the product documentation would be 16 appreciated but is not required. 17 2. Altered source versions must be plainly marked as such, and must not be 18 misrepresented as being the original software. 19 3. This notice may not be removed or altered from any source distribution. 20*/ 21 22#include "SDL_internal.h" 23 24#ifdef SDL_VIDEO_DRIVER_WAYLAND 25 26#include "../../core/linux/SDL_system_theme.h" 27#include "../../core/linux/SDL_progressbar.h" 28#include "../../core/unix/SDL_gtk.h" 29#include "../../events/SDL_events_c.h" 30 31#include "SDL_waylandclipboard.h" 32#include "SDL_waylandcolor.h" 33#include "SDL_waylandevents_c.h" 34#include "SDL_waylandkeyboard.h" 35#include "SDL_waylandmessagebox.h" 36#include "SDL_waylandmouse.h" 37#include "SDL_waylandopengles.h" 38#include "SDL_waylandvideo.h" 39#include "SDL_waylandvulkan.h" 40#include "SDL_waylandwindow.h" 41 42#include <fcntl.h> 43#include <sys/types.h> 44#include <unistd.h> 45#include <errno.h> 46#include <xkbcommon/xkbcommon.h> 47 48#include <wayland-util.h> 49 50#include "alpha-modifier-v1-client-protocol.h" 51#include "cursor-shape-v1-client-protocol.h" 52#include "fractional-scale-v1-client-protocol.h" 53#include "frog-color-management-v1-client-protocol.h" 54#include "idle-inhibit-unstable-v1-client-protocol.h" 55#include "input-timestamps-unstable-v1-client-protocol.h" 56#include "keyboard-shortcuts-inhibit-unstable-v1-client-protocol.h" 57#include "pointer-constraints-unstable-v1-client-protocol.h" 58#include "primary-selection-unstable-v1-client-protocol.h" 59#include "relative-pointer-unstable-v1-client-protocol.h" 60#include "tablet-v2-client-protocol.h" 61#include "text-input-unstable-v3-client-protocol.h" 62#include "viewporter-client-protocol.h" 63#include "xdg-activation-v1-client-protocol.h" 64#include "xdg-decoration-unstable-v1-client-protocol.h" 65#include "xdg-dialog-v1-client-protocol.h" 66#include "xdg-foreign-unstable-v2-client-protocol.h" 67#include "xdg-output-unstable-v1-client-protocol.h" 68#include "xdg-shell-client-protocol.h" 69#include "xdg-toplevel-icon-v1-client-protocol.h" 70#include "color-management-v1-client-protocol.h" 71#include "pointer-warp-v1-client-protocol.h" 72#include "pointer-gestures-unstable-v1-client-protocol.h" 73#include "single-pixel-buffer-v1-client-protocol.h" 74 75#ifdef HAVE_LIBDECOR_H 76#include <libdecor.h> 77#endif 78 79#define WAYLANDVID_DRIVER_NAME "wayland" 80 81// Clamp core protocol versions on older versions of libwayland. 82#if SDL_WAYLAND_CHECK_VERSION(1, 22, 0) 83#define SDL_WL_COMPOSITOR_VERSION 6 84#else 85#define SDL_WL_COMPOSITOR_VERSION 4 86#endif 87 88#if SDL_WAYLAND_CHECK_VERSION(1, 24, 0) 89#define SDL_WL_SEAT_VERSION 10 90#elif SDL_WAYLAND_CHECK_VERSION(1, 22, 0) 91#define SDL_WL_SEAT_VERSION 9 92#elif SDL_WAYLAND_CHECK_VERSION(1, 21, 0) 93#define SDL_WL_SEAT_VERSION 8 94#else 95#define SDL_WL_SEAT_VERSION 5 96#endif 97 98#if SDL_WAYLAND_CHECK_VERSION(1, 20, 0) 99#define SDL_WL_OUTPUT_VERSION 4 100#else 101#define SDL_WL_OUTPUT_VERSION 3 102#endif 103 104#if SDL_WAYLAND_CHECK_VERSION(1, 24, 0) 105#define SDL_WL_SHM_VERSION 2 106#else 107#define SDL_WL_SHM_VERSION 1 108#endif 109 110// The SDL libwayland-client minimum is 1.18, which supports version 3. 111#define SDL_WL_DATA_DEVICE_VERSION 3 112 113// wl_fixes was introduced in 1.24.0 114#if SDL_WAYLAND_CHECK_VERSION(1, 24, 0) 115#define SDL_WL_FIXES_VERSION 1 116#endif 117 118#ifdef SDL_USE_LIBDBUS 119#include "../../core/linux/SDL_dbus.h" 120 121#define DISPLAY_INFO_NODE "org.gnome.Mutter.DisplayConfig" 122#define DISPLAY_INFO_PATH "/org/gnome/Mutter/DisplayConfig" 123#define DISPLAY_INFO_METHOD "GetCurrentState" 124#endif 125 126/* GNOME doesn't expose displays in any particular order, but we can find the 127 * primary display and its logical coordinates via a DBus method. 128 */ 129static bool Wayland_GetGNOMEPrimaryDisplayCoordinates(int *x, int *y) 130{ 131#ifdef SDL_USE_LIBDBUS 132 SDL_DBusContext *dbus = SDL_DBus_GetContext(); 133 if (dbus == NULL) { 134 return false; 135 } 136 DBusMessage *reply = NULL; 137 DBusMessageIter iter[3]; 138 DBusMessage *msg = dbus->message_new_method_call(DISPLAY_INFO_NODE, 139 DISPLAY_INFO_PATH, 140 DISPLAY_INFO_NODE, 141 DISPLAY_INFO_METHOD); 142 143 if (msg) { 144 reply = dbus->connection_send_with_reply_and_block(dbus->session_conn, msg, DBUS_TIMEOUT_USE_DEFAULT, NULL); 145 dbus->message_unref(msg); 146 } 147 148 if (reply) { 149 // Serial (don't care) 150 dbus->message_iter_init(reply, &iter[0]); 151 if (dbus->message_iter_get_arg_type(&iter[0]) != DBUS_TYPE_UINT32) { 152 goto error; 153 } 154 155 // Physical monitor array (don't care) 156 dbus->message_iter_next(&iter[0]); 157 if (dbus->message_iter_get_arg_type(&iter[0]) != DBUS_TYPE_ARRAY) { 158 goto error; 159 } 160 161 // Logical monitor array of structs 162 dbus->message_iter_next(&iter[0]); 163 if (dbus->message_iter_get_arg_type(&iter[0]) != DBUS_TYPE_ARRAY) { 164 goto error; 165 } 166 167 // First logical monitor struct 168 dbus->message_iter_recurse(&iter[0], &iter[1]); 169 if (dbus->message_iter_get_arg_type(&iter[1]) != DBUS_TYPE_STRUCT) { 170 goto error; 171 } 172 173 do { 174 int logical_x, logical_y; 175 dbus_bool_t primary; 176 177 // Logical X 178 dbus->message_iter_recurse(&iter[1], &iter[2]); 179 if (dbus->message_iter_get_arg_type(&iter[2]) != DBUS_TYPE_INT32) { 180 goto error; 181 } 182 dbus->message_iter_get_basic(&iter[2], &logical_x); 183 184 // Logical Y 185 dbus->message_iter_next(&iter[2]); 186 if (dbus->message_iter_get_arg_type(&iter[2]) != DBUS_TYPE_INT32) { 187 goto error; 188 } 189 dbus->message_iter_get_basic(&iter[2], &logical_y); 190 191 // Scale (don't care) 192 dbus->message_iter_next(&iter[2]); 193 if (dbus->message_iter_get_arg_type(&iter[2]) != DBUS_TYPE_DOUBLE) { 194 goto error; 195 } 196 197 // Transform (don't care) 198 dbus->message_iter_next(&iter[2]); 199 if (dbus->message_iter_get_arg_type(&iter[2]) != DBUS_TYPE_UINT32) { 200 goto error; 201 } 202 203 // Primary display boolean 204 dbus->message_iter_next(&iter[2]); 205 if (dbus->message_iter_get_arg_type(&iter[2]) != DBUS_TYPE_BOOLEAN) { 206 goto error; 207 } 208 dbus->message_iter_get_basic(&iter[2], &primary); 209 210 if (primary) { 211 *x = logical_x; 212 *y = logical_y; 213 214 // We found the primary display: success. 215 dbus->message_unref(reply); 216 return true; 217 } 218 } while (dbus->message_iter_next(&iter[1])); 219 } 220 221error: 222 if (reply) { 223 dbus->message_unref(reply); 224 } 225#endif 226 return false; 227} 228 229// Sort the list of displays into a deterministic order 230static int SDLCALL Wayland_DisplayPositionCompare(const void *a, const void *b) 231{ 232 const SDL_DisplayData *da = *(SDL_DisplayData **)a; 233 const SDL_DisplayData *db = *(SDL_DisplayData **)b; 234 const bool a_at_origin = da->logical.x == 0 && da->logical.y == 0; 235 const bool b_at_origin = db->logical.x == 0 && db->logical.y == 0; 236 237 // Sort the display at 0,0 to be beginning of the list, as that will be the fallback primary. 238 if (a_at_origin && !b_at_origin) { 239 return -1; 240 } 241 if (b_at_origin && !a_at_origin) { 242 return 1; 243 } 244 245 if (da->logical.x < db->logical.x) { 246 return -1; 247 } 248 if (da->logical.x > db->logical.x) { 249 return 1; 250 } 251 if (da->logical.y < db->logical.y) { 252 return -1; 253 } 254 if (da->logical.y > db->logical.y) { 255 return 1; 256 } 257 258 // If no position information is available, use the connector name. 259 if (da->wl_output_name && db->wl_output_name) { 260 return SDL_strcmp(da->wl_output_name, db->wl_output_name); 261 } 262 263 return 0; 264} 265 266/* Wayland doesn't have the native concept of a primary display, but there are clients that 267 * will base their resolution lists on, or automatically make themselves fullscreen on, the 268 * first listed output, which can lead to problems if the first listed output isn't 269 * necessarily the best display for this. This attempts to find a primary display, first by 270 * querying the GNOME DBus property, then trying to determine the 'best' display if that fails. 271 * If all displays are equal, the one at position 0,0 will become the primary. 272 * 273 * The primary is determined by the following criteria, in order: 274 * - Landscape is preferred over portrait 275 * - The highest native resolution 276 * - A higher HDR range is preferred 277 * - Higher refresh is preferred (ignoring small differences) 278 * - Lower scale values are preferred (larger display) 279 */ 280static int Wayland_GetPrimaryDisplay(SDL_VideoData *vid) 281{ 282 static const int REFRESH_DELTA = 4000; 283 284 // Query the DBus interface to see if the coordinates of the primary display are exposed. 285 int x, y; 286 if (Wayland_GetGNOMEPrimaryDisplayCoordinates(&x, &y)) { 287 for (int i = 0; i < vid->output_count; ++i) { 288 if (vid->output_list[i]->logical.x == x && vid->output_list[i]->logical.y == y) { 289 return i; 290 } 291 } 292 } 293 294 // Otherwise, choose the 'best' display. 295 int best_width = 0; 296 int best_height = 0; 297 double best_scale = 0.0; 298 float best_headroom = 0.0f; 299 int best_refresh = 0; 300 bool best_is_landscape = false; 301 int best_index = 0; 302 303 for (int i = 0; i < vid->output_count; ++i) { 304 const SDL_DisplayData *d = vid->output_list[i]; 305 const bool is_landscape = d->orientation != SDL_ORIENTATION_PORTRAIT && d->orientation != SDL_ORIENTATION_PORTRAIT_FLIPPED; 306 bool have_new_best = false; 307 308 if (!best_is_landscape && is_landscape) { // Favor landscape over portrait displays. 309 have_new_best = true; 310 } else if (!best_is_landscape || is_landscape) { // Ignore portrait displays if a landscape was already found. 311 if (d->pixel.width > best_width || d->pixel.height > best_height) { 312 have_new_best = true; 313 } else if (d->pixel.width == best_width && d->pixel.height == best_height) { 314 if (d->HDR.HDR_headroom > best_headroom) { // Favor a higher HDR luminance range 315 have_new_best = true; 316 } else if (d->HDR.HDR_headroom == best_headroom) { 317 if (d->refresh - best_refresh > REFRESH_DELTA) { // Favor a higher refresh rate, but ignore small differences (e.g. 59.97 vs 60.1) 318 have_new_best = true; 319 } else if (d->scale_factor < best_scale && SDL_abs(d->refresh - best_refresh) <= REFRESH_DELTA) { 320 // Prefer a lower scale display if the difference in refresh rate is small. 321 have_new_best = true; 322 } 323 } 324 } 325 } 326 327 if (have_new_best) { 328 best_width = d->pixel.width; 329 best_height = d->pixel.height; 330 best_scale = d->scale_factor; 331 best_headroom = d->HDR.HDR_headroom; 332 best_refresh = d->refresh; 333 best_is_landscape = is_landscape; 334 best_index = i; 335 } 336 } 337 338 return best_index; 339} 340 341static void Wayland_SortOutputsByPriorityHint(SDL_VideoData *vid) 342{ 343 const char *name_hint = SDL_GetHint(SDL_HINT_VIDEO_DISPLAY_PRIORITY); 344 345 if (name_hint) { 346 char *saveptr; 347 char *str = SDL_strdup(name_hint); 348 SDL_DisplayData **sorted_list = SDL_malloc(sizeof(SDL_DisplayData *) * vid->output_count); 349 350 if (str && sorted_list) { 351 int sorted_index = 0; 352 353 // Sort the requested displays to the front of the list. 354 const char *token = SDL_strtok_r(str, ",", &saveptr); 355 while (token) { 356 for (int i = 0; i < vid->output_count; ++i) { 357 SDL_DisplayData *d = vid->output_list[i]; 358 if (d && d->wl_output_name && SDL_strcmp(token, d->wl_output_name) == 0) { 359 sorted_list[sorted_index++] = d; 360 vid->output_list[i] = NULL; 361 break; 362 } 363 } 364 365 token = SDL_strtok_r(NULL, ",", &saveptr); 366 } 367 368 // Append the remaining outputs to the end of the list. 369 for (int i = 0; i < vid->output_count; ++i) { 370 if (vid->output_list[i]) { 371 sorted_list[sorted_index++] = vid->output_list[i]; 372 } 373 } 374 375 // Copy the sorted list to the output list. 376 SDL_memcpy(vid->output_list, sorted_list, sizeof(SDL_DisplayData *) * vid->output_count); 377 } 378 379 SDL_free(str); 380 SDL_free(sorted_list); 381 } 382} 383 384static void Wayland_DeriveOutputPixelCoordinates(SDL_VideoData *vid) 385{ 386 /* Ensure outputs are not overlapping in the pixel coordinate space. 387 * 388 * This is a simple algorithm that offsets display positions by the 389 * logical/pixel difference if they are to the right of and/or below a scaled 390 * display. It can leave gaps in certain scenarios, but it works well enough 391 * in most cases. 392 * 393 * Patches for a more sophisticated algorithm are welcome. 394 */ 395 for ( int i = 0; i < vid->output_count; ++i) { 396 SDL_DisplayData *d = vid->output_list[i]; 397 d->pixel.x = d->logical.x; 398 d->pixel.y = d->logical.y; 399 } 400 401 for (int i = 0; i < vid->output_count; ++i) { 402 SDL_DisplayData *d1 = vid->output_list[i]; 403 if (d1->logical.width != d1->pixel.width || d1->logical.height != d1->pixel.height) { 404 const int x_adj = d1->pixel.width - d1->logical.width; 405 const int y_adj = d1->pixel.height - d1->logical.height; 406 407 // Don't adjust for scale values less than 1.0. 408 if (x_adj > 0 && y_adj > 0) { 409 for (int j = 0; j < vid->output_count; ++j) { 410 SDL_DisplayData *d2 = vid->output_list[j]; 411 if (d2->logical.x > d1->logical.x) { 412 d2->pixel.x += x_adj; 413 } 414 if (d2->logical.y > d1->logical.y) { 415 d2->pixel.y += y_adj; 416 } 417 } 418 } 419 } 420 } 421} 422 423static void Wayland_SortOutputs(SDL_VideoData *vid) 424{ 425 // Sort by position or connector name, so the order of outputs is deterministic. 426 SDL_qsort(vid->output_list, vid->output_count, sizeof(SDL_DisplayData *), Wayland_DisplayPositionCompare); 427 428 // Derive the output pixel coordinates if scale to display is enabled. 429 if (vid->scale_to_display_enabled) { 430 Wayland_DeriveOutputPixelCoordinates(vid); 431 } 432 433 // Find a suitable primary display and move it to the front of the list. 434 const int primary_index = Wayland_GetPrimaryDisplay(vid); 435 if (primary_index) { 436 SDL_DisplayData *primary = vid->output_list[primary_index]; 437 SDL_memmove(&vid->output_list[1], &vid->output_list[0], sizeof(SDL_DisplayData *) * primary_index); 438 vid->output_list[0] = primary; 439 } 440 441 // Apply the ordering hint, if specified. 442 Wayland_SortOutputsByPriorityHint(vid); 443} 444 445static void Wayland_RefreshWindowPositions() 446{ 447 SDL_VideoDevice *vid = SDL_GetVideoDevice(); 448 449 for (SDL_Window *w = vid->windows; w; w = w->next) { 450 Wayland_UpdateWindowPosition(w); 451 } 452} 453 454static void handle_wl_output_done(void *data, struct wl_output *output); 455 456// Initialization/Query functions 457static bool Wayland_VideoInit(SDL_VideoDevice *_this); 458static bool Wayland_GetDisplayBounds(SDL_VideoDevice *_this, SDL_VideoDisplay *display, SDL_Rect *rect); 459static void Wayland_VideoQuit(SDL_VideoDevice *_this); 460 461static const char *SDL_WAYLAND_surface_tag = "sdl-window"; 462static const char *SDL_WAYLAND_output_tag = "sdl-output"; 463 464void SDL_WAYLAND_register_surface(struct wl_surface *surface) 465{ 466 wl_proxy_set_tag((struct wl_proxy *)surface, &SDL_WAYLAND_surface_tag); 467} 468 469void SDL_WAYLAND_register_output(struct wl_output *output) 470{ 471 wl_proxy_set_tag((struct wl_proxy *)output, &SDL_WAYLAND_output_tag); 472} 473 474bool SDL_WAYLAND_own_surface(struct wl_surface *surface) 475{ 476 return wl_proxy_get_tag((struct wl_proxy *)surface) == &SDL_WAYLAND_surface_tag; 477} 478 479bool SDL_WAYLAND_own_output(struct wl_output *output) 480{ 481 return wl_proxy_get_tag((struct wl_proxy *)output) == &SDL_WAYLAND_output_tag; 482} 483 484/* External surfaces may have their own user data attached, the modification of which 485 * can cause problems with external toolkits. Instead, external windows are kept in 486 * their own list, and a search is conducted to find a matching surface. 487 */ 488static struct wl_list external_window_list; 489 490void Wayland_AddWindowDataToExternalList(SDL_WindowData *data) 491{ 492 WAYLAND_wl_list_insert(&external_window_list, &data->external_window_list_link); 493} 494 495void Wayland_RemoveWindowDataFromExternalList(SDL_WindowData *data) 496{ 497 WAYLAND_wl_list_remove(&data->external_window_list_link); 498} 499 500SDL_WindowData *Wayland_GetWindowDataForOwnedSurface(struct wl_surface *surface) 501{ 502 if (SDL_WAYLAND_own_surface(surface)) { 503 return (SDL_WindowData *)wl_surface_get_user_data(surface); 504 } else if (!WAYLAND_wl_list_empty(&external_window_list)) { 505 SDL_WindowData *p; 506 wl_list_for_each (p, &external_window_list, external_window_list_link) { 507 if (p->surface == surface) { 508 return p; 509 } 510 } 511 } 512 513 return NULL; 514} 515 516struct wl_event_queue *Wayland_DisplayCreateQueue(struct wl_display *display, const char *name) 517{ 518#ifdef SDL_VIDEO_DRIVER_WAYLAND_DYNAMIC 519 if (WAYLAND_wl_display_create_queue_with_name) { 520 return WAYLAND_wl_display_create_queue_with_name(display, name); 521 } 522#elif SDL_WAYLAND_CHECK_VERSION(1, 23, 0) 523 return WAYLAND_wl_display_create_queue_with_name(display, name); 524#endif 525 return WAYLAND_wl_display_create_queue(display); 526} 527 528static void Wayland_DeleteDevice(SDL_VideoDevice *device) 529{ 530 SDL_VideoData *data = device->internal; 531 if (data->display && !data->display_externally_owned) { 532 WAYLAND_wl_display_disconnect(data->display); 533 SDL_ClearProperty(SDL_GetGlobalProperties(), SDL_PROP_GLOBAL_VIDEO_WAYLAND_WL_DISPLAY_POINTER); 534 } 535 SDL_free(data); 536 SDL_free(device); 537 SDL_WAYLAND_UnloadSymbols(); 538} 539 540typedef struct 541{ 542 bool has_fifo_v1; 543 struct wl_fixes *wl_fixes; 544} SDL_WaylandPreferredData; 545 546static void wayland_preferred_check_handle_global(void *data, struct wl_registry *registry, uint32_t id, 547 const char *interface, uint32_t version) 548{ 549 SDL_WaylandPreferredData *d = data; 550 551 if (SDL_strcmp(interface, "wp_fifo_manager_v1") == 0) { 552 d->has_fifo_v1 = true; 553 } 554#ifdef SDL_WL_FIXES_VERSION 555 else if (SDL_strcmp(interface, wl_fixes_interface.name) == 0) { 556 d->wl_fixes = wl_registry_bind(registry, id, &wl_fixes_interface, SDL_min(SDL_WL_FIXES_VERSION, version)); 557 } 558#endif 559} 560 561static void wayland_preferred_check_remove_global(void *data, struct wl_registry *registry, uint32_t id) 562{ 563 // No need to do anything here. 564} 565 566static const struct wl_registry_listener preferred_registry_listener = { 567 wayland_preferred_check_handle_global, 568 wayland_preferred_check_remove_global 569}; 570 571static bool Wayland_IsPreferred(struct wl_display *display) 572{ 573 struct wl_registry *registry = wl_display_get_registry(display); 574 SDL_WaylandPreferredData preferred_data = { 0 }; 575 576 if (!registry) { 577 SDL_SetError("Failed to get the Wayland registry"); 578 return false; 579 } 580 581 wl_registry_add_listener(registry, &preferred_registry_listener, &preferred_data); 582 583 WAYLAND_wl_display_roundtrip(display); 584 585 if (preferred_data.wl_fixes) { 586 wl_fixes_destroy_registry(preferred_data.wl_fixes, registry); 587 wl_fixes_destroy(preferred_data.wl_fixes); 588 } 589 wl_registry_destroy(registry); 590 591 if (!preferred_data.has_fifo_v1) { 592 SDL_LogInfo(SDL_LOG_CATEGORY_VIDEO, "This compositor lacks support for the fifo-v1 protocol; falling back to XWayland for GPU performance reasons (set SDL_VIDEO_DRIVER=wayland to override)"); 593 } 594 return preferred_data.has_fifo_v1; 595} 596 597static SDL_VideoDevice *Wayland_CreateDevice(bool require_preferred_protocols) 598{ 599 SDL_VideoDevice *device; 600 SDL_VideoData *data; 601 struct wl_display *display = SDL_GetPointerProperty(SDL_GetGlobalProperties(), 602 SDL_PROP_GLOBAL_VIDEO_WAYLAND_WL_DISPLAY_POINTER, NULL); 603 bool display_is_external = !!display; 604 605 // Are we trying to connect to, or are currently in, a Wayland session? 606 if (!SDL_getenv("WAYLAND_DISPLAY")) { 607 const char *session = SDL_getenv("XDG_SESSION_TYPE"); 608 if (session && SDL_strcasecmp(session, "wayland") != 0) { 609 SDL_LogDebug(SDL_LOG_CATEGORY_VIDEO, "Wayland initialization failed: no Wayland session available"); 610 return NULL; 611 } 612 } 613 614 if (!SDL_WAYLAND_LoadSymbols()) { 615 return NULL; 616 } 617 618 if (!display) { 619 display = WAYLAND_wl_display_connect(NULL); 620 if (!display) { 621 SDL_WAYLAND_UnloadSymbols(); 622 SDL_LogDebug(SDL_LOG_CATEGORY_VIDEO, "Failed to connect to the Wayland display server: %s", strerror(errno)); 623 return NULL; 624 } 625 } 626 627 /* 628 * If we are checking for preferred Wayland, then let's query for 629 * fifo-v1's existence, so we don't regress GPU-bound performance 630 * and frame-pacing by default due to swapchain starvation. 631 */ 632 if (require_preferred_protocols && !Wayland_IsPreferred(display)) { 633 if (!display_is_external) { 634 WAYLAND_wl_display_disconnect(display); 635 } 636 SDL_WAYLAND_UnloadSymbols(); 637 return NULL; 638 } 639 640 data = SDL_calloc(1, sizeof(*data)); 641 if (!data) { 642 if (!display_is_external) { 643 WAYLAND_wl_display_disconnect(display); 644 } 645 SDL_WAYLAND_UnloadSymbols(); 646 return NULL; 647 } 648 649 data->initializing = true; 650 data->display = display; 651 data->display_externally_owned = display_is_external; 652 data->scale_to_display_enabled = SDL_GetHintBoolean(SDL_HINT_VIDEO_WAYLAND_SCALE_TO_DISPLAY, false); 653 WAYLAND_wl_list_init(&data->seat_list); 654 WAYLAND_wl_list_init(&external_window_list); 655 656 // Initialize all variables that we clean on shutdown 657 device = SDL_calloc(1, sizeof(SDL_VideoDevice)); 658 if (!device) { 659 SDL_free(data); 660 if (!display_is_external) { 661 WAYLAND_wl_display_disconnect(display); 662 } 663 SDL_WAYLAND_UnloadSymbols(); 664 return NULL; 665 } 666 667 if (!display_is_external) { 668 SDL_SetPointerProperty(SDL_GetGlobalProperties(), 669 SDL_PROP_GLOBAL_VIDEO_WAYLAND_WL_DISPLAY_POINTER, display); 670 } 671 672 device->internal = data; 673 674 // Set the function pointers 675 device->VideoInit = Wayland_VideoInit; 676 device->VideoQuit = Wayland_VideoQuit; 677 device->GetDisplayBounds = Wayland_GetDisplayBounds; 678 device->SuspendScreenSaver = Wayland_SuspendScreenSaver; 679 680 device->PumpEvents = Wayland_PumpEvents; 681 device->WaitEventTimeout = Wayland_WaitEventTimeout; 682 device->SendWakeupEvent = Wayland_SendWakeupEvent; 683 684#ifdef SDL_VIDEO_OPENGL_EGL 685 device->GL_SwapWindow = Wayland_GLES_SwapWindow; 686 device->GL_GetSwapInterval = Wayland_GLES_GetSwapInterval; 687 device->GL_SetSwapInterval = Wayland_GLES_SetSwapInterval; 688 device->GL_MakeCurrent = Wayland_GLES_MakeCurrent; 689 device->GL_CreateContext = Wayland_GLES_CreateContext; 690 device->GL_LoadLibrary = Wayland_GLES_LoadLibrary; 691 device->GL_UnloadLibrary = Wayland_GLES_UnloadLibrary; 692 device->GL_GetProcAddress = Wayland_GLES_GetProcAddress; 693 device->GL_DestroyContext = Wayland_GLES_DestroyContext; 694 device->GL_SetDefaultProfileConfig = Wayland_GLES_SetDefaultProfileConfig; 695 device->GL_GetEGLSurface = Wayland_GLES_GetEGLSurface; 696#endif 697 698 device->CreateSDLWindow = Wayland_CreateWindow; 699 device->ShowWindow = Wayland_ShowWindow; 700 device->HideWindow = Wayland_HideWindow; 701 device->RaiseWindow = Wayland_RaiseWindow; 702 device->SetWindowFullscreen = Wayland_SetWindowFullscreen; 703 device->MaximizeWindow = Wayland_MaximizeWindow; 704 device->MinimizeWindow = Wayland_MinimizeWindow; 705 device->SetWindowMouseRect = Wayland_SetWindowMouseRect; 706 device->SetWindowMouseGrab = Wayland_SetWindowMouseGrab; 707 device->SetWindowKeyboardGrab = Wayland_SetWindowKeyboardGrab; 708 device->RestoreWindow = Wayland_RestoreWindow; 709 device->SetWindowBordered = Wayland_SetWindowBordered; 710 device->SetWindowResizable = Wayland_SetWindowResizable; 711 device->SetWindowPosition = Wayland_SetWindowPosition; 712 device->SetWindowSize = Wayland_SetWindowSize; 713 device->SetWindowAspectRatio = Wayland_SetWindowAspectRatio; 714 device->SetWindowMinimumSize = Wayland_SetWindowMinimumSize; 715 device->SetWindowMaximumSize = Wayland_SetWindowMaximumSize; 716 device->SetWindowParent = Wayland_SetWindowParent; 717 device->SetWindowModal = Wayland_SetWindowModal; 718 device->SetWindowOpacity = Wayland_SetWindowOpacity; 719 device->SetWindowTitle = Wayland_SetWindowTitle; 720 device->SetWindowIcon = Wayland_SetWindowIcon; 721 device->GetWindowSizeInPixels = Wayland_GetWindowSizeInPixels; 722 device->GetWindowContentScale = Wayland_GetWindowContentScale; 723 device->GetWindowICCProfile = Wayland_GetWindowICCProfile; 724 device->GetDisplayForWindow = Wayland_GetDisplayForWindow; 725 device->DestroyWindow = Wayland_DestroyWindow; 726 device->SetWindowHitTest = Wayland_SetWindowHitTest; 727 device->FlashWindow = Wayland_FlashWindow; 728#ifdef SDL_USE_LIBDBUS 729 device->ApplyWindowProgress = DBUS_ApplyWindowProgress; 730#endif // SDL_USE_LIBDBUS 731 device->HasScreenKeyboardSupport = Wayland_HasScreenKeyboardSupport; 732 device->ShowWindowSystemMenu = Wayland_ShowWindowSystemMenu; 733 device->SyncWindow = Wayland_SyncWindow; 734 device->SetWindowFocusable = Wayland_SetWindowFocusable; 735 device->ReconfigureWindow = Wayland_ReconfigureWindow; 736 737#ifdef SDL_USE_LIBDBUS 738 if (SDL_SystemTheme_Init()) 739 device->system_theme = SDL_SystemTheme_Get(); 740#endif 741 742 device->GetTextMimeTypes = Wayland_GetTextMimeTypes; 743 device->SetClipboardData = Wayland_SetClipboardData; 744 device->GetClipboardData = Wayland_GetClipboardData; 745 device->HasClipboardData = Wayland_HasClipboardData; 746 device->StartTextInput = Wayland_StartTextInput; 747 device->StopTextInput = Wayland_StopTextInput; 748 device->UpdateTextInputArea = Wayland_UpdateTextInputArea; 749 750#ifdef SDL_VIDEO_VULKAN 751 device->Vulkan_LoadLibrary = Wayland_Vulkan_LoadLibrary; 752 device->Vulkan_UnloadLibrary = Wayland_Vulkan_UnloadLibrary; 753 device->Vulkan_GetInstanceExtensions = Wayland_Vulkan_GetInstanceExtensions; 754 device->Vulkan_CreateSurface = Wayland_Vulkan_CreateSurface; 755 device->Vulkan_DestroySurface = Wayland_Vulkan_DestroySurface; 756 device->Vulkan_GetPresentationSupport = Wayland_Vulkan_GetPresentationSupport; 757#endif 758 759 device->free = Wayland_DeleteDevice; 760 761 device->device_caps = VIDEO_DEVICE_CAPS_MODE_SWITCHING_EMULATED | 762 VIDEO_DEVICE_CAPS_HAS_POPUP_WINDOW_SUPPORT | 763 VIDEO_DEVICE_CAPS_SENDS_FULLSCREEN_DIMENSIONS | 764 VIDEO_DEVICE_CAPS_SENDS_DISPLAY_CHANGES | 765 VIDEO_DEVICE_CAPS_SENDS_HDR_CHANGES; 766 767 return device; 768} 769 770static SDL_VideoDevice *Wayland_Preferred_CreateDevice(void) 771{ 772 return Wayland_CreateDevice(true); 773} 774 775static SDL_VideoDevice *Wayland_Fallback_CreateDevice(void) 776{ 777 return Wayland_CreateDevice(false); 778} 779 780VideoBootStrap Wayland_preferred_bootstrap = { 781 WAYLANDVID_DRIVER_NAME, "SDL Wayland video driver", 782 Wayland_Preferred_CreateDevice, 783 Wayland_ShowMessageBox, 784 true 785}; 786 787VideoBootStrap Wayland_bootstrap = { 788 WAYLANDVID_DRIVER_NAME, "SDL Wayland video driver", 789 Wayland_Fallback_CreateDevice, 790 Wayland_ShowMessageBox, 791 false 792}; 793 794static void handle_xdg_output_logical_position(void *data, struct zxdg_output_v1 *xdg_output, int32_t x, int32_t y) 795{ 796 SDL_DisplayData *internal = (SDL_DisplayData *)data; 797 798 internal->geometry_changed |= internal->logical.x != x || internal->logical.y != y; 799 internal->logical.x = x; 800 internal->logical.y = y; 801 internal->has_logical_position = true; 802} 803 804static void handle_xdg_output_logical_size(void *data, struct zxdg_output_v1 *xdg_output, int32_t width, int32_t height) 805{ 806 SDL_DisplayData *internal = (SDL_DisplayData *)data; 807 808 internal->geometry_changed |= internal->logical.width != width || internal->logical.height != height; 809 internal->logical.width = width; 810 internal->logical.height = height; 811 internal->has_logical_size = true; 812} 813 814static void handle_xdg_output_done(void *data, struct zxdg_output_v1 *xdg_output) 815{ 816 SDL_DisplayData *internal = data; 817 818 /* 819 * xdg-output.done events are deprecated and only apply below version 3 of the protocol. 820 * A wl-output.done event will be emitted in version 3 or higher. 821 */ 822 if (zxdg_output_v1_get_version(internal->xdg_output) < 3) { 823 handle_wl_output_done(data, internal->output); 824 } 825} 826 827static void handle_xdg_output_name(void *data, struct zxdg_output_v1 *xdg_output, const char *name) 828{ 829 SDL_DisplayData *internal = (SDL_DisplayData *)data; 830 831 // Deprecated as of wl_output v4. 832 if (wl_output_get_version(internal->output) < WL_OUTPUT_NAME_SINCE_VERSION && 833 internal->display == 0) { 834 SDL_free(internal->wl_output_name); 835 internal->wl_output_name = SDL_strdup(name); 836 } 837} 838 839static void handle_xdg_output_description(void *data, struct zxdg_output_v1 *xdg_output, const char *description) 840{ 841 SDL_DisplayData *internal = (SDL_DisplayData *)data; 842 843 // Deprecated as of wl_output v4. 844 if (wl_output_get_version(internal->output) < WL_OUTPUT_DESCRIPTION_SINCE_VERSION && 845 internal->display == 0) { 846 // xdg-output descriptions, if available, supersede wl-output model names. 847 SDL_free(internal->placeholder.name); 848 internal->placeholder.name = SDL_strdup(description); 849 } 850} 851 852static const struct zxdg_output_v1_listener xdg_output_listener = { 853 handle_xdg_output_logical_position, 854 handle_xdg_output_logical_size, 855 handle_xdg_output_done, 856 handle_xdg_output_name, 857 handle_xdg_output_description, 858}; 859 860static void AddEmulatedModes(SDL_DisplayData *dispdata, int native_width, int native_height) 861{ 862 struct EmulatedMode 863 { 864 int w; 865 int h; 866 }; 867 868 // Resolution lists courtesy of XWayland 869 const struct EmulatedMode mode_list[] = { 870 // 16:9 (1.77) 871 { 7680, 4320 }, 872 { 6144, 3160 }, 873 { 5120, 2880 }, 874 { 4096, 2304 }, 875 { 3840, 2160 }, 876 { 3200, 1800 }, 877 { 2880, 1620 }, 878 { 2560, 1440 }, 879 { 2048, 1152 }, 880 { 1920, 1080 }, 881 { 1600, 900 }, 882 { 1368, 768 }, 883 { 1280, 720 }, 884 { 864, 486 }, 885 886 // 16:10 (1.6) 887 { 2560, 1600 }, 888 { 1920, 1200 }, 889 { 1680, 1050 }, 890 { 1440, 900 }, 891 { 1280, 800 }, 892 893 // 3:2 (1.5) 894 { 720, 480 }, 895 896 // 4:3 (1.33) 897 { 2048, 1536 }, 898 { 1920, 1440 }, 899 { 1600, 1200 }, 900 { 1440, 1080 }, 901 { 1400, 1050 }, 902 { 1280, 1024 }, 903 { 1280, 960 }, 904 { 1152, 864 }, 905 { 1024, 768 }, 906 { 800, 600 }, 907 { 640, 480 } 908 }; 909 910 int i; 911 SDL_DisplayMode mode; 912 SDL_VideoDisplay *dpy = dispdata->display ? SDL_GetVideoDisplay(dispdata->display) : &dispdata->placeholder; 913 const bool rot_90 = native_width < native_height; // Reverse width/height for portrait displays. 914 915 for (i = 0; i < SDL_arraysize(mode_list); ++i) { 916 SDL_zero(mode); 917 mode.format = dpy->desktop_mode.format; 918 mode.refresh_rate_numerator = dpy->desktop_mode.refresh_rate_numerator; 919 mode.refresh_rate_denominator = dpy->desktop_mode.refresh_rate_denominator; 920 921 if (rot_90) { 922 mode.w = mode_list[i].h; 923 mode.h = mode_list[i].w; 924 } else { 925 mode.w = mode_list[i].w; 926 mode.h = mode_list[i].h; 927 } 928 929 // Only add modes that are smaller than the native mode. 930 if ((mode.w < native_width && mode.h < native_height) || 931 (mode.w < native_width && mode.h == native_height) || 932 (mode.w == native_width && mode.h < native_height)) { 933 SDL_AddFullscreenDisplayMode(dpy, &mode); 934 } 935 } 936} 937 938static void handle_wl_output_geometry(void *data, struct wl_output *output, int x, int y, 939 int physical_width, int physical_height, int subpixel, 940 const char *make, const char *model, int transform) 941{ 942 SDL_DisplayData *internal = (SDL_DisplayData *)data; 943 944 // Apply the change from wl-output only if xdg-output is not supported 945 if (!internal->has_logical_position) { 946 internal->geometry_changed |= internal->logical.x != x || internal->logical.y != y; 947 internal->logical.x = x; 948 internal->logical.y = y; 949 } 950 internal->physical.width_mm = physical_width; 951 internal->physical.height_mm = physical_height; 952 953 // The model is only used for the output name if wl_output or xdg-output haven't provided a description. 954 if (internal->display == 0 && !internal->placeholder.name) { 955 internal->placeholder.name = SDL_strdup(model); 956 } 957 958 internal->transform = transform; 959#define TF_CASE(in, out) \ 960 case WL_OUTPUT_TRANSFORM_##in: \ 961 internal->orientation = SDL_ORIENTATION_##out; \ 962 break; 963 if (internal->physical.width_mm >= internal->physical.height_mm) { 964 switch (transform) { 965 TF_CASE(NORMAL, LANDSCAPE) 966 TF_CASE(90, PORTRAIT) 967 TF_CASE(180, LANDSCAPE_FLIPPED) 968 TF_CASE(270, PORTRAIT_FLIPPED) 969 TF_CASE(FLIPPED, LANDSCAPE_FLIPPED) 970 TF_CASE(FLIPPED_90, PORTRAIT_FLIPPED) 971 TF_CASE(FLIPPED_180, LANDSCAPE) 972 TF_CASE(FLIPPED_270, PORTRAIT) 973 } 974 } else { 975 switch (transform) { 976 TF_CASE(NORMAL, PORTRAIT) 977 TF_CASE(90, LANDSCAPE) 978 TF_CASE(180, PORTRAIT_FLIPPED) 979 TF_CASE(270, LANDSCAPE_FLIPPED) 980 TF_CASE(FLIPPED, PORTRAIT_FLIPPED) 981 TF_CASE(FLIPPED_90, LANDSCAPE_FLIPPED) 982 TF_CASE(FLIPPED_180, PORTRAIT) 983 TF_CASE(FLIPPED_270, LANDSCAPE) 984 } 985 } 986#undef TF_CASE 987} 988 989static void handle_wl_output_mode(void *data, struct wl_output *output, uint32_t flags, 990 int width, int height, int refresh) 991{ 992 SDL_DisplayData *internal = (SDL_DisplayData *)data; 993 994 if (flags & WL_OUTPUT_MODE_CURRENT) { 995 internal->geometry_changed |= internal->pixel.width != width || internal->pixel.height != height; 996 internal->pixel.width = width; 997 internal->pixel.height = height; 998 999 /* 1000 * Don't rotate this yet, wl-output coordinates are transformed in 1001 * handle_done and xdg-output coordinates are pre-transformed. 1002 */ 1003 if (!internal->has_logical_size) { 1004 internal->geometry_changed |= internal->logical.width != width || internal->logical.height != height; 1005 internal->logical.width = width; 1006 internal->logical.height = height; 1007 } 1008 1009 internal->refresh = refresh; 1010 } 1011} 1012 1013static void handle_wl_output_done(void *data, struct wl_output *output) 1014{ 1015 const bool mode_emulation_enabled = SDL_GetHintBoolean(SDL_HINT_VIDEO_WAYLAND_MODE_EMULATION, true); 1016 SDL_DisplayData *internal = (SDL_DisplayData *)data; 1017 SDL_VideoData *video = internal->videodata; 1018 SDL_DisplayMode native_mode, desktop_mode; 1019 1020 /* 1021 * When using xdg-output, two wl-output.done events will be emitted: 1022 * one at the completion of wl-display and one at the completion of xdg-output. 1023 * 1024 * All required events must be received before proceeding. 1025 */ 1026 const int event_await_count = 1 + (internal->xdg_output != NULL); 1027 1028 internal->wl_output_done_count = SDL_min(internal->wl_output_done_count + 1, event_await_count + 1); 1029 1030 if (internal->wl_output_done_count < event_await_count) { 1031 return; 1032 } 1033 1034 // If the display was already created, reset and rebuild the mode list. 1035 SDL_VideoDisplay *dpy = SDL_GetVideoDisplay(internal->display); 1036 if (dpy) { 1037 SDL_ResetFullscreenDisplayModes(dpy); 1038 } 1039 1040 // The native display resolution 1041 SDL_zero(native_mode); 1042 native_mode.format = SDL_PIXELFORMAT_XRGB8888; 1043 1044 // Transform the pixel values, if necessary. 1045 if (internal->transform & WL_OUTPUT_TRANSFORM_90) { 1046 native_mode.w = internal->pixel.height; 1047 native_mode.h = internal->pixel.width; 1048 } else { 1049 native_mode.w = internal->pixel.width; 1050 native_mode.h = internal->pixel.height; 1051 } 1052 native_mode.refresh_rate_numerator = internal->refresh; 1053 native_mode.refresh_rate_denominator = 1000; 1054 1055 if (internal->has_logical_size) { // If xdg-output is present... 1056 if (native_mode.w != internal->logical.width || native_mode.h != internal->logical.height) { 1057 // ...and the compositor scales the logical viewport... 1058 if (video->viewporter) { 1059 // ...and viewports are supported, calculate the true scale of the output. 1060 internal->scale_factor = (double)native_mode.w / (double)internal->logical.width; 1061 } else { 1062 // ...otherwise, the 'native' pixel values are a multiple of the logical screen size. 1063 internal->pixel.width = internal->logical.width * (int)internal->scale_factor; 1064 internal->pixel.height = internal->logical.height * (int)internal->scale_factor; 1065 } 1066 } else { 1067 /* ...and the output viewport is not scaled in the global compositing 1068 * space, the output dimensions need to be divided by the scale factor. 1069 */ 1070 internal->logical.width /= (int)internal->scale_factor; 1071 internal->logical.height /= (int)internal->scale_factor; 1072 } 1073 } else { 1074 /* Calculate the points from the pixel values, if xdg-output isn't present. 1075 * Use the native mode pixel values since they are pre-transformed. 1076 */ 1077 internal->logical.width = native_mode.w / (int)internal->scale_factor; 1078 internal->logical.height = native_mode.h / (int)internal->scale_factor; 1079 } 1080 1081 // The scaled desktop mode 1082 SDL_zero(desktop_mode); 1083 desktop_mode.format = SDL_PIXELFORMAT_XRGB8888; 1084 1085 if (!video->scale_to_display_enabled) { 1086 desktop_mode.w = internal->logical.width; 1087 desktop_mode.h = internal->logical.height; 1088 desktop_mode.pixel_density = (float)internal->scale_factor; 1089 } else { 1090 desktop_mode.w = native_mode.w; 1091 desktop_mode.h = native_mode.h; 1092 desktop_mode.pixel_density = 1.0f; 1093 } 1094 1095 desktop_mode.refresh_rate_numerator = internal->refresh; 1096 desktop_mode.refresh_rate_denominator = 1000; 1097 1098 if (internal->display > 0) { 1099 dpy = SDL_GetVideoDisplay(internal->display); 1100 } else { 1101 dpy = &internal->placeholder; 1102 } 1103 1104 if (video->scale_to_display_enabled) { 1105 SDL_SetDisplayContentScale(dpy, (float)internal->scale_factor); 1106 } 1107 1108 // Set the desktop display mode. 1109 SDL_SetDesktopDisplayMode(dpy, &desktop_mode); 1110 1111 // Expose the unscaled, native resolution if the scale is 1.0 or viewports are available... 1112 if (internal->scale_factor == 1.0 || video->viewporter) { 1113 SDL_AddFullscreenDisplayMode(dpy, &native_mode); 1114 if (native_mode.w != desktop_mode.w || 1115 native_mode.h != desktop_mode.h) { 1116 SDL_AddFullscreenDisplayMode(dpy, &desktop_mode); 1117 } 1118 } else { 1119 // ...otherwise expose the integer scaled variants of the desktop resolution down to 1. 1120 desktop_mode.pixel_density = 1.0f; 1121 1122 for (int i = (int)internal->scale_factor; i > 0; --i) { 1123 desktop_mode.w = internal->logical.width * i; 1124 desktop_mode.h = internal->logical.height * i; 1125 SDL_AddFullscreenDisplayMode(dpy, &desktop_mode); 1126 } 1127 } 1128 1129 // Add emulated modes if wp_viewporter is supported and mode emulation is enabled. 1130 if (video->viewporter && mode_emulation_enabled) { 1131 // The transformed display pixel width/height must be used here. 1132 AddEmulatedModes(internal, native_mode.w, native_mode.h); 1133 } 1134 1135 if (internal->display == 0) { 1136 // First time getting display info, initialize the VideoDisplay 1137 if (internal->physical.width_mm >= internal->physical.height_mm) { 1138 internal->placeholder.natural_orientation = SDL_ORIENTATION_LANDSCAPE; 1139 } else { 1140 internal->placeholder.natural_orientation = SDL_ORIENTATION_PORTRAIT; 1141 } 1142 internal->placeholder.current_orientation = internal->orientation; 1143 internal->placeholder.internal = internal; 1144 1145 internal->placeholder.props = SDL_CreateProperties(); 1146 SDL_SetPointerProperty(internal->placeholder.props, SDL_PROP_DISPLAY_WAYLAND_WL_OUTPUT_POINTER, internal->output); 1147 1148 // During initialization, the displays will be added after enumeration is complete. 1149 if (!video->initializing) { 1150 if (video->wp_color_manager_v1) { 1151 Wayland_GetColorInfoForOutput(internal, false); 1152 } 1153 if (video->scale_to_display_enabled) { 1154 Wayland_DeriveOutputPixelCoordinates(video); 1155 } 1156 1157 internal->display = SDL_AddVideoDisplay(&internal->placeholder, true); 1158 Wayland_RefreshWindowPositions(); 1159 1160 SDL_free(internal->placeholder.name); 1161 SDL_zero(internal->placeholder); 1162 } 1163 } else { 1164 SDL_SendDisplayEvent(dpy, SDL_EVENT_DISPLAY_ORIENTATION, internal->orientation, 0); 1165 1166 if (internal->geometry_changed) { 1167 if (video->scale_to_display_enabled) { 1168 Wayland_DeriveOutputPixelCoordinates(video); 1169 } 1170 1171 SDL_SendDisplayEvent(dpy, SDL_EVENT_DISPLAY_MOVED, 0, 0); 1172 Wayland_RefreshWindowPositions(); 1173 } 1174 } 1175 1176 internal->geometry_changed = false; 1177} 1178 1179static void handle_wl_output_scale(void *data, struct wl_output *output, int32_t factor) 1180{ 1181 SDL_DisplayData *internal = (SDL_DisplayData *)data; 1182 internal->scale_factor = factor; 1183} 1184 1185static void handle_wl_output_name(void *data, struct wl_output *wl_output, const char *name) 1186{ 1187 SDL_DisplayData *internal = (SDL_DisplayData *)data; 1188 1189 SDL_free(internal->wl_output_name); 1190 internal->wl_output_name = SDL_strdup(name); 1191} 1192 1193static void handle_wl_output_description(void *data, struct wl_output *wl_output, const char *description) 1194{ 1195 SDL_DisplayData *internal = (SDL_DisplayData *)data; 1196 1197 if (internal->display == 0) { 1198 // The description, if available, supersedes the model name. 1199 SDL_free(internal->placeholder.name); 1200 internal->placeholder.name = SDL_strdup(description); 1201 } 1202} 1203 1204static const struct wl_output_listener output_listener = { 1205 handle_wl_output_geometry, // Version 1 1206 handle_wl_output_mode, // Version 1 1207 handle_wl_output_done, // Version 2 1208 handle_wl_output_scale, // Version 2 1209 handle_wl_output_name, // Version 4 1210 handle_wl_output_description // Version 4 1211}; 1212 1213static void handle_output_image_description_changed(void *data, 1214 struct wp_color_management_output_v1 *wp_color_management_output_v1) 1215{ 1216 SDL_DisplayData *display = (SDL_DisplayData *)data; 1217 Wayland_GetColorInfoForOutput(display, false); 1218} 1219 1220static const struct wp_color_management_output_v1_listener wp_color_management_output_listener = { 1221 handle_output_image_description_changed 1222}; 1223 1224static bool Wayland_add_display(SDL_VideoData *d, uint32_t id, uint32_t version) 1225{ 1226 struct wl_output *output; 1227 SDL_DisplayData *data; 1228 1229 output = wl_registry_bind(d->registry, id, &wl_output_interface, version); 1230 if (!output) { 1231 return SDL_SetError("Failed to retrieve output."); 1232 } 1233 data = (SDL_DisplayData *)SDL_calloc(1, sizeof(*data)); 1234 data->videodata = d; 1235 data->output = output; 1236 data->registry_id = id; 1237 data->scale_factor = 1.0f; 1238 1239 wl_output_add_listener(output, &output_listener, data); 1240 SDL_WAYLAND_register_output(output); 1241 1242 // Keep a list of outputs for sorting and deferred protocol initialization. 1243 if (d->output_count == d->output_max) { 1244 d->output_max += 4; 1245 d->output_list = SDL_realloc(d->output_list, sizeof(SDL_DisplayData *) * d->output_max); 1246 } 1247 d->output_list[d->output_count++] = data; 1248 1249 if (data->videodata->xdg_output_manager) { 1250 data->xdg_output = zxdg_output_manager_v1_get_xdg_output(data->videodata->xdg_output_manager, output); 1251 zxdg_output_v1_add_listener(data->xdg_output, &xdg_output_listener, data); 1252 } 1253 if (data->videodata->wp_color_manager_v1) { 1254 data->wp_color_management_output = wp_color_manager_v1_get_output(data->videodata->wp_color_manager_v1, output); 1255 wp_color_management_output_v1_add_listener(data->wp_color_management_output, &wp_color_management_output_listener, data); 1256 1257 // If not initializing, this will be queried synchronously in wl_output.done. 1258 if (d->initializing) { 1259 Wayland_GetColorInfoForOutput(data, true); 1260 } 1261 } 1262 return true; 1263} 1264 1265static void Wayland_free_display(SDL_VideoDisplay *display, bool send_event) 1266{ 1267 if (display) { 1268 SDL_DisplayData *display_data = display->internal; 1269 1270 /* A preceding surface leave event is not guaranteed when an output is removed, 1271 * so ensure that no window continues to hold a reference to a removed output. 1272 */ 1273 for (SDL_Window *window = SDL_GetVideoDevice()->windows; window; window = window->next) { 1274 Wayland_RemoveOutputFromWindow(window->internal, display_data); 1275 } 1276 1277 SDL_free(display_data->wl_output_name); 1278 1279 if (display_data->wp_color_management_output) { 1280 Wayland_FreeColorInfoState(display_data->color_info_state); 1281 wp_color_management_output_v1_destroy(display_data->wp_color_management_output); 1282 } 1283 1284 if (display_data->xdg_output) { 1285 zxdg_output_v1_destroy(display_data->xdg_output); 1286 } 1287 1288 if (wl_output_get_version(display_data->output) >= WL_OUTPUT_RELEASE_SINCE_VERSION) { 1289 wl_output_release(display_data->output); 1290 } else { 1291 wl_output_destroy(display_data->output); 1292 } 1293 1294 SDL_DelVideoDisplay(display->id, send_event); 1295 } 1296} 1297 1298static void Wayland_FinalizeDisplays(SDL_VideoData *vid) 1299{ 1300 Wayland_SortOutputs(vid); 1301 1302 for(int i = 0; i < vid->output_count; ++i) { 1303 SDL_DisplayData *d = vid->output_list[i]; 1304 d->display = SDL_AddVideoDisplay(&d->placeholder, false); 1305 SDL_free(d->placeholder.name); 1306 SDL_zero(d->placeholder); 1307 } 1308} 1309 1310static void Wayland_init_xdg_output(SDL_VideoData *d) 1311{ 1312 for (int i = 0; i < d->output_count; ++i) { 1313 SDL_DisplayData *disp = d->output_list[i]; 1314 disp->xdg_output = zxdg_output_manager_v1_get_xdg_output(disp->videodata->xdg_output_manager, disp->output); 1315 zxdg_output_v1_add_listener(disp->xdg_output, &xdg_output_listener, disp); 1316 } 1317} 1318 1319static void Wayland_InitColorManager(SDL_VideoData *d) 1320{ 1321 for (int i = 0; i < d->output_count; ++i) { 1322 SDL_DisplayData *disp = d->output_list[i]; 1323 disp->wp_color_management_output = wp_color_manager_v1_get_output(disp->videodata->wp_color_manager_v1, disp->output); 1324 wp_color_management_output_v1_add_listener(disp->wp_color_management_output, &wp_color_management_output_listener, disp); 1325 Wayland_GetColorInfoForOutput(disp, true); 1326 } 1327} 1328 1329static void handle_xdg_wm_base_ping(void *data, struct xdg_wm_base *xdg, uint32_t serial) 1330{ 1331 xdg_wm_base_pong(xdg, serial); 1332} 1333 1334static const struct xdg_wm_base_listener _xdg_wm_base_listener = { 1335 handle_xdg_wm_base_ping 1336}; 1337 1338#ifdef HAVE_LIBDECOR_H 1339static void libdecor_error(struct libdecor *context, 1340 enum libdecor_error error, 1341 const char *message) 1342{ 1343 SDL_LogError(SDL_LOG_CATEGORY_VIDEO, "libdecor error (%d): %s", error, message); 1344} 1345 1346static struct libdecor_interface libdecor_interface = { 1347 libdecor_error 1348}; 1349#endif 1350 1351static void handle_registry_global(void *data, struct wl_registry *registry, uint32_t id, 1352 const char *interface, uint32_t version) 1353{ 1354 SDL_VideoData *d = data; 1355 1356 if (SDL_strcmp(interface, wl_compositor_interface.name) == 0) { 1357 d->compositor = wl_registry_bind(d->registry, id, &wl_compositor_interface, SDL_min(SDL_WL_COMPOSITOR_VERSION, version)); 1358 } else if (SDL_strcmp(interface, wl_subcompositor_interface.name) == 0) { 1359 d->subcompositor = wl_registry_bind(d->registry, id, &wl_subcompositor_interface, 1); 1360 } else if (SDL_strcmp(interface, wl_output_interface.name) == 0) { 1361 Wayland_add_display(d, id, SDL_min(version, SDL_WL_OUTPUT_VERSION)); 1362 } else if (SDL_strcmp(interface, wl_seat_interface.name) == 0) { 1363 struct wl_seat *seat = wl_registry_bind(d->registry, id, &wl_seat_interface, SDL_min(SDL_WL_SEAT_VERSION, version)); 1364 Wayland_DisplayCreateSeat(d, seat, id); 1365 } else if (SDL_strcmp(interface, xdg_wm_base_interface.name) == 0) { 1366 d->shell.xdg = wl_registry_bind(d->registry, id, &xdg_wm_base_interface, SDL_min(version, 7)); 1367 xdg_wm_base_add_listener(d->shell.xdg, &_xdg_wm_base_listener, NULL); 1368 } else if (SDL_strcmp(interface, wl_shm_interface.name) == 0) { 1369 d->shm = wl_registry_bind(registry, id, &wl_shm_interface, SDL_min(SDL_WL_SHM_VERSION, version)); 1370 } else if (SDL_strcmp(interface, zwp_relative_pointer_manager_v1_interface.name) == 0) { 1371 d->relative_pointer_manager = wl_registry_bind(d->registry, id, &zwp_relative_pointer_manager_v1_interface, 1); 1372 } else if (SDL_strcmp(interface, zwp_pointer_constraints_v1_interface.name) == 0) { 1373 d->pointer_constraints = wl_registry_bind(d->registry, id, &zwp_pointer_constraints_v1_interface, 1); 1374 } else if (SDL_strcmp(interface, zwp_keyboard_shortcuts_inhibit_manager_v1_interface.name) == 0) { 1375 d->key_inhibitor_manager = wl_registry_bind(d->registry, id, &zwp_keyboard_shortcuts_inhibit_manager_v1_interface, 1); 1376 } else if (SDL_strcmp(interface, zwp_idle_inhibit_manager_v1_interface.name) == 0) { 1377 d->idle_inhibit_manager = wl_registry_bind(d->registry, id, &zwp_idle_inhibit_manager_v1_interface, 1); 1378 } else if (SDL_strcmp(interface, xdg_activation_v1_interface.name) == 0) { 1379 d->activation_manager = wl_registry_bind(d->registry, id, &xdg_activation_v1_interface, 1); 1380 } else if (SDL_strcmp(interface, zwp_text_input_manager_v3_interface.name) == 0) { 1381 d->text_input_manager = wl_registry_bind(d->registry, id, &zwp_text_input_manager_v3_interface, 1); 1382 Wayland_DisplayInitTextInputManager(d, id); 1383 } else if (SDL_strcmp(interface, wl_data_device_manager_interface.name) == 0) { 1384 d->data_device_manager = wl_registry_bind(d->registry, id, &wl_data_device_manager_interface, SDL_min(3, version)); 1385 Wayland_DisplayInitDataDeviceManager(d); 1386 } else if (SDL_strcmp(interface, zwp_primary_selection_device_manager_v1_interface.name) == 0) { 1387 d->primary_selection_device_manager = wl_registry_bind(d->registry, id, &zwp_primary_selection_device_manager_v1_interface, 1); 1388 Wayland_DisplayInitPrimarySelectionDeviceManager(d); 1389 } else if (SDL_strcmp(interface, zxdg_decoration_manager_v1_interface.name) == 0) { 1390 d->decoration_manager = wl_registry_bind(d->registry, id, &zxdg_decoration_manager_v1_interface, 1); 1391 } else if (SDL_strcmp(interface, zwp_tablet_manager_v2_interface.name) == 0) { 1392 d->tablet_manager = wl_registry_bind(d->registry, id, &zwp_tablet_manager_v2_interface, 1); 1393 Wayland_DisplayInitTabletManager(d); 1394 } else if (SDL_strcmp(interface, zxdg_output_manager_v1_interface.name) == 0) { 1395 version = SDL_min(version, 3); // Versions 1 through 3 are supported. 1396 d->xdg_output_manager = wl_registry_bind(d->registry, id, &zxdg_output_manager_v1_interface, version); 1397 Wayland_init_xdg_output(d); 1398 } else if (SDL_strcmp(interface, wp_viewporter_interface.name) == 0) { 1399 d->viewporter = wl_registry_bind(d->registry, id, &wp_viewporter_interface, 1); 1400 } else if (SDL_strcmp(interface, wp_fractional_scale_manager_v1_interface.name) == 0) { 1401 d->fractional_scale_manager = wl_registry_bind(d->registry, id, &wp_fractional_scale_manager_v1_interface, 1); 1402 } else if (SDL_strcmp(interface, zwp_input_timestamps_manager_v1_interface.name) == 0) { 1403 d->input_timestamps_manager = wl_registry_bind(d->registry, id, &zwp_input_timestamps_manager_v1_interface, 1); 1404 Wayland_DisplayInitInputTimestampManager(d); 1405 } else if (SDL_strcmp(interface, wp_cursor_shape_manager_v1_interface.name) == 0) { 1406 d->cursor_shape_manager = wl_registry_bind(d->registry, id, &wp_cursor_shape_manager_v1_interface, 1); 1407 Wayland_DisplayInitCursorShapeManager(d); 1408 } else if (SDL_strcmp(interface, zxdg_exporter_v2_interface.name) == 0) { 1409 d->zxdg_exporter_v2 = wl_registry_bind(d->registry, id, &zxdg_exporter_v2_interface, 1); 1410 } else if (SDL_strcmp(interface, xdg_wm_dialog_v1_interface.name) == 0) { 1411 d->xdg_wm_dialog_v1 = wl_registry_bind(d->registry, id, &xdg_wm_dialog_v1_interface, 1); 1412 } else if (SDL_strcmp(interface, wp_alpha_modifier_v1_interface.name) == 0) { 1413 d->wp_alpha_modifier_v1 = wl_registry_bind(d->registry, id, &wp_alpha_modifier_v1_interface, 1); 1414 } else if (SDL_strcmp(interface, xdg_toplevel_icon_manager_v1_interface.name) == 0) { 1415 d->xdg_toplevel_icon_manager_v1 = wl_registry_bind(d->registry, id, &xdg_toplevel_icon_manager_v1_interface, 1); 1416 } else if (SDL_strcmp(interface, frog_color_management_factory_v1_interface.name) == 0) { 1417 d->frog_color_management_factory_v1 = wl_registry_bind(d->registry, id, &frog_color_management_factory_v1_interface, 1); 1418 } else if (SDL_strcmp(interface, wp_color_manager_v1_interface.name) == 0) { 1419 d->wp_color_manager_v1 = wl_registry_bind(d->registry, id, &wp_color_manager_v1_interface, SDL_min(version, 2)); 1420 Wayland_InitColorManager(d); 1421 } else if (SDL_strcmp(interface, wp_pointer_warp_v1_interface.name) == 0) { 1422 d->wp_pointer_warp_v1 = wl_registry_bind(d->registry, id, &wp_pointer_warp_v1_interface, 1); 1423 } else if (SDL_strcmp(interface, zwp_pointer_gestures_v1_interface.name) == 0) { 1424 d->zwp_pointer_gestures = wl_registry_bind(d->registry, id, &zwp_pointer_gestures_v1_interface, SDL_min(version, 3)); 1425 Wayland_DisplayInitPointerGestureManager(d); 1426 } else if (SDL_strcmp(interface, wp_single_pixel_buffer_manager_v1_interface.name) == 0) { 1427 d->single_pixel_buffer_manager = wl_registry_bind(d->registry, id, &wp_single_pixel_buffer_manager_v1_interface, 1); 1428 } 1429#ifdef SDL_WL_FIXES_VERSION 1430 else if (SDL_strcmp(interface, wl_fixes_interface.name) == 0) { 1431 d->wl_fixes = wl_registry_bind(d->registry, id, &wl_fixes_interface, SDL_min(SDL_WL_FIXES_VERSION, version)); 1432 } 1433#endif 1434} 1435 1436static void handle_registry_remove_global(void *data, struct wl_registry *registry, uint32_t id) 1437{ 1438 SDL_VideoData *d = data; 1439 1440 // We don't get an interface, just an ID, so check outputs and seats. 1441 for (int i = 0; i < d->output_count; ++i) { 1442 SDL_DisplayData *disp = d->output_list[i]; 1443 if (disp->registry_id == id) { 1444 Wayland_free_display(SDL_GetVideoDisplay(disp->display), true); 1445 1446 if (i < d->output_count) { 1447 SDL_memmove(&d->output_list[i], &d->output_list[i + 1], sizeof(SDL_DisplayData *) * (d->output_count - i - 1)); 1448 } 1449 1450 d->output_count--; 1451 return; 1452 } 1453 } 1454 1455 SDL_WaylandSeat *seat, *temp; 1456 wl_list_for_each_safe (seat, temp, &d->seat_list, link) 1457 { 1458 if (seat->registry_id == id) { 1459 Wayland_SeatDestroy(seat, false); 1460 return; 1461 } 1462 } 1463} 1464 1465static const struct wl_registry_listener registry_listener = { 1466 handle_registry_global, 1467 handle_registry_remove_global 1468}; 1469 1470#ifdef HAVE_LIBDECOR_H 1471static bool should_use_libdecor(SDL_VideoData *data, bool ignore_xdg) 1472{ 1473 if (!SDL_WAYLAND_HAVE_WAYLAND_LIBDECOR) { 1474 return false; 1475 } 1476 1477 if (SDL_GetHintBoolean(SDL_HINT_VIDEO_WAYLAND_PREFER_LIBDECOR, false)) { 1478 return true; 1479 } 1480 1481 if (ignore_xdg) { 1482 return true; 1483 } 1484 1485 if (data->decoration_manager) { 1486 return false; 1487 } 1488 1489 return true; 1490} 1491 1492static void LibdecorNew(SDL_VideoData *data) 1493{ 1494 data->shell.libdecor = libdecor_new(data->display, &libdecor_interface); 1495} 1496 1497// Called in another thread, but the UI thread is blocked in SDL_WaitThread 1498// during that time, so it should be OK to dereference data without locks 1499static int SDLCALL LibdecorNewInThread(void *data) 1500{ 1501 LibdecorNew((SDL_VideoData *)data); 1502 return 0; 1503} 1504#endif 1505 1506bool Wayland_LoadLibdecor(SDL_VideoData *data, bool ignore_xdg) 1507{ 1508#ifdef HAVE_LIBDECOR_H 1509 if (data->shell.libdecor != NULL) { 1510 return true; // Already loaded! 1511 } 1512 if (should_use_libdecor(data, ignore_xdg)) { 1513 if (SDL_CanUseGtk()) { 1514 LibdecorNew(data); 1515 } else { 1516 // Intentionally initialize libdecor in a non-main thread 1517 // so that it will not use its GTK plugin, but instead will 1518 // fall back to the Cairo or dummy plugin 1519 SDL_Thread *thread = SDL_CreateThread(LibdecorNewInThread, "SDL_LibdecorNew", (void *)data); 1520 // Note that the other thread now "owns" data until we have 1521 // waited for it, so don't touch data here 1522 SDL_WaitThread(thread, NULL); 1523 } 1524 1525 return data->shell.libdecor != NULL; 1526 } 1527#endif 1528 return false; 1529} 1530 1531bool Wayland_VideoInit(SDL_VideoDevice *_this) 1532{ 1533 SDL_VideoData *data = _this->internal; 1534 1535 data->xkb_context = WAYLAND_xkb_context_new(0); 1536 if (!data->xkb_context) { 1537 return SDL_SetError("Failed to create XKB context"); 1538 } 1539 1540 data->registry = wl_display_get_registry(data->display); 1541 if (!data->registry) { 1542 return SDL_SetError("Failed to get the Wayland registry"); 1543 } 1544 1545 wl_registry_add_listener(data->registry, ®istry_listener, data); 1546 1547 // First roundtrip to receive all registry objects. 1548 WAYLAND_wl_display_roundtrip(data->display); 1549 1550 // Require viewports and xdg-output for display scaling. 1551 if (data->scale_to_display_enabled) { 1552 if (!data->viewporter) { 1553 SDL_LogError(SDL_LOG_CATEGORY_VIDEO, "wayland: Display scaling requires the missing 'wp_viewporter' protocol: disabling"); 1554 data->scale_to_display_enabled = false; 1555 } 1556 if (!data->xdg_output_manager) { 1557 SDL_LogError(SDL_LOG_CATEGORY_VIDEO, "wayland: Display scaling requires the missing 'zxdg_output_manager_v1' protocol: disabling"); 1558 data->scale_to_display_enabled = false; 1559 } 1560 } 1561 1562 // Now that we have all the protocols, load libdecor if applicable 1563 Wayland_LoadLibdecor(data, false); 1564 1565 // Second roundtrip to receive all output events. 1566 WAYLAND_wl_display_roundtrip(data->display); 1567 1568 Wayland_FinalizeDisplays(data); 1569 1570 Wayland_InitMouse(data); 1571 Wayland_InitKeyboard(_this); 1572 1573 if (data->primary_selection_device_manager) { 1574 _this->SetPrimarySelectionText = Wayland_SetPrimarySelectionText; 1575 _this->GetPrimarySelectionText = Wayland_GetPrimarySelectionText; 1576 _this->HasPrimarySelectionText = Wayland_HasPrimarySelectionText; 1577 } 1578 1579 data->initializing = false; 1580 1581 return true; 1582} 1583 1584static bool Wayland_GetDisplayBounds(SDL_VideoDevice *_this, SDL_VideoDisplay *display, SDL_Rect *rect) 1585{ 1586 SDL_VideoData *viddata = _this->internal; 1587 SDL_DisplayData *internal = display->internal; 1588 1589 if (!viddata->scale_to_display_enabled) { 1590 rect->x = internal->logical.x; 1591 rect->y = internal->logical.y; 1592 } else { 1593 rect->x = internal->pixel.x; 1594 rect->y = internal->pixel.y; 1595 } 1596 1597 // When an emulated, exclusive fullscreen window has focus, treat the mode dimensions as the display bounds. 1598 if (display->fullscreen_window && 1599 display->fullscreen_window->fullscreen_exclusive && 1600 display->fullscreen_window->internal->active && 1601 display->fullscreen_window->current_fullscreen_mode.w != 0 && 1602 display->fullscreen_window->current_fullscreen_mode.h != 0) { 1603 rect->w = display->fullscreen_window->current_fullscreen_mode.w; 1604 rect->h = display->fullscreen_window->current_fullscreen_mode.h; 1605 } else { 1606 if (!viddata->scale_to_display_enabled) { 1607 rect->w = display->current_mode->w; 1608 rect->h = display->current_mode->h; 1609 } else if (internal->transform & WL_OUTPUT_TRANSFORM_90) { 1610 rect->w = internal->pixel.height; 1611 rect->h = internal->pixel.width; 1612 } else { 1613 rect->w = internal->pixel.width; 1614 rect->h = internal->pixel.height; 1615 } 1616 } 1617 return true; 1618} 1619 1620static void Wayland_VideoCleanup(SDL_VideoDevice *_this) 1621{ 1622 SDL_VideoData *data = _this->internal; 1623 SDL_WaylandSeat *seat, *tmp; 1624 1625 for (int i = _this->num_displays - 1; i >= 0; --i) { 1626 SDL_VideoDisplay *display = _this->displays[i]; 1627 Wayland_free_display(display, false); 1628 } 1629 SDL_free(data->output_list); 1630 1631 wl_list_for_each_safe (seat, tmp, &data->seat_list, link) { 1632 Wayland_SeatDestroy(seat, true); 1633 } 1634 1635 Wayland_FiniMouse(data); 1636 1637 if (data->pointer_constraints) { 1638 zwp_pointer_constraints_v1_destroy(data->pointer_constraints); 1639 data->pointer_constraints = NULL; 1640 } 1641 1642 if (data->relative_pointer_manager) { 1643 zwp_relative_pointer_manager_v1_destroy(data->relative_pointer_manager); 1644 data->relative_pointer_manager = NULL; 1645 } 1646 1647 if (data->activation_manager) { 1648 xdg_activation_v1_destroy(data->activation_manager); 1649 data->activation_manager = NULL; 1650 } 1651 1652 if (data->idle_inhibit_manager) { 1653 zwp_idle_inhibit_manager_v1_destroy(data->idle_inhibit_manager); 1654 data->idle_inhibit_manager = NULL; 1655 } 1656 1657 if (data->key_inhibitor_manager) { 1658 zwp_keyboard_shortcuts_inhibit_manager_v1_destroy(data->key_inhibitor_manager); 1659 data->key_inhibitor_manager = NULL; 1660 } 1661 1662 Wayland_QuitKeyboard(_this); 1663 1664 if (data->text_input_manager) { 1665 zwp_text_input_manager_v3_destroy(data->text_input_manager); 1666 data->text_input_manager = NULL; 1667 } 1668 1669 if (data->xkb_context) { 1670 WAYLAND_xkb_context_unref(data->xkb_context); 1671 data->xkb_context = NULL; 1672 } 1673 1674 if (data->tablet_manager) { 1675 zwp_tablet_manager_v2_destroy((struct zwp_tablet_manager_v2 *)data->tablet_manager); 1676 data->tablet_manager = NULL; 1677 } 1678 1679 if (data->data_device_manager) { 1680 wl_data_device_manager_destroy(data->data_device_manager); 1681 data->data_device_manager = NULL; 1682 } 1683 1684 if (data->shm) { 1685 if (wl_shm_get_version(data->shm) >= WL_SHM_RELEASE_SINCE_VERSION) { 1686 wl_shm_release(data->shm); 1687 } else { 1688 wl_shm_destroy(data->shm); 1689 } 1690 data->shm = NULL; 1691 } 1692 1693 if (data->shell.xdg) { 1694 xdg_wm_base_destroy(data->shell.xdg); 1695 data->shell.xdg = NULL; 1696 } 1697 1698 if (data->decoration_manager) { 1699 zxdg_decoration_manager_v1_destroy(data->decoration_manager); 1700 data->decoration_manager = NULL; 1701 } 1702 1703 if (data->xdg_output_manager) { 1704 zxdg_output_manager_v1_destroy(data->xdg_output_manager); 1705 data->xdg_output_manager = NULL; 1706 } 1707 1708 if (data->viewporter) { 1709 wp_viewporter_destroy(data->viewporter); 1710 data->viewporter = NULL; 1711 } 1712 1713 if (data->primary_selection_device_manager) { 1714 zwp_primary_selection_device_manager_v1_destroy(data->primary_selection_device_manager); 1715 data->primary_selection_device_manager = NULL; 1716 } 1717 1718 if (data->fractional_scale_manager) { 1719 wp_fractional_scale_manager_v1_destroy(data->fractional_scale_manager); 1720 data->fractional_scale_manager = NULL; 1721 } 1722 1723 if (data->input_timestamps_manager) { 1724 zwp_input_timestamps_manager_v1_destroy(data->input_timestamps_manager); 1725 data->input_timestamps_manager = NULL; 1726 } 1727 1728 if (data->cursor_shape_manager) { 1729 wp_cursor_shape_manager_v1_destroy(data->cursor_shape_manager); 1730 data->cursor_shape_manager = NULL; 1731 } 1732 1733 if (data->zxdg_exporter_v2) { 1734 zxdg_exporter_v2_destroy(data->zxdg_exporter_v2); 1735 data->zxdg_exporter_v2 = NULL; 1736 } 1737 1738 if (data->xdg_wm_dialog_v1) { 1739 xdg_wm_dialog_v1_destroy(data->xdg_wm_dialog_v1); 1740 data->xdg_wm_dialog_v1 = NULL; 1741 } 1742 1743 if (data->wp_alpha_modifier_v1) { 1744 wp_alpha_modifier_v1_destroy(data->wp_alpha_modifier_v1); 1745 data->wp_alpha_modifier_v1 = NULL; 1746 } 1747 1748 if (data->xdg_toplevel_icon_manager_v1) { 1749 xdg_toplevel_icon_manager_v1_destroy(data->xdg_toplevel_icon_manager_v1); 1750 data->xdg_toplevel_icon_manager_v1 = NULL; 1751 } 1752 1753 if (data->frog_color_management_factory_v1) { 1754 frog_color_management_factory_v1_destroy(data->frog_color_management_factory_v1); 1755 data->frog_color_management_factory_v1 = NULL; 1756 } 1757 1758 if (data->wp_color_manager_v1) { 1759 wp_color_manager_v1_destroy(data->wp_color_manager_v1); 1760 data->wp_color_manager_v1 = NULL; 1761 } 1762 1763 if (data->wp_pointer_warp_v1) { 1764 wp_pointer_warp_v1_destroy(data->wp_pointer_warp_v1); 1765 data->wp_pointer_warp_v1 = NULL; 1766 } 1767 1768 if (data->zwp_pointer_gestures) { 1769 if (zwp_pointer_gestures_v1_get_version(data->zwp_pointer_gestures) >= ZWP_POINTER_GESTURES_V1_RELEASE_SINCE_VERSION) { 1770 zwp_pointer_gestures_v1_release(data->zwp_pointer_gestures); 1771 } else { 1772 zwp_pointer_gestures_v1_destroy(data->zwp_pointer_gestures); 1773 } 1774 data->zwp_pointer_gestures = NULL; 1775 } 1776 1777 if (data->single_pixel_buffer_manager) { 1778 wp_single_pixel_buffer_manager_v1_destroy(data->single_pixel_buffer_manager); 1779 data->single_pixel_buffer_manager = NULL; 1780 } 1781 1782 if (data->subcompositor) { 1783 wl_subcompositor_destroy(data->subcompositor); 1784 data->subcompositor = NULL; 1785 } 1786 1787 if (data->compositor) { 1788 wl_compositor_destroy(data->compositor); 1789 data->compositor = NULL; 1790 } 1791 1792 if (data->registry) { 1793 if (data->wl_fixes) { 1794 wl_fixes_destroy_registry(data->wl_fixes, data->registry); 1795 wl_fixes_destroy(data->wl_fixes); 1796 data->wl_fixes = NULL; 1797 } 1798 wl_registry_destroy(data->registry); 1799 data->registry = NULL; 1800 } 1801} 1802 1803static bool Wayland_VideoReconnect(SDL_VideoDevice *_this) 1804{ 1805#if 0 // TODO RECONNECT: Uncomment all when https://invent.kde.org/plasma/kwin/-/wikis/Restarting is completed 1806 SDL_VideoData *data = _this->internal; 1807 1808 SDL_Window *window = NULL; 1809 1810 SDL_GLContext current_ctx = SDL_GL_GetCurrentContext(); 1811 SDL_Window *current_window = SDL_GL_GetCurrentWindow(); 1812 1813 SDL_GL_MakeCurrent(NULL, NULL); 1814 Wayland_VideoCleanup(_this); 1815 1816 SDL_ResetKeyboard(); 1817 SDL_ResetMouse(); 1818 if (WAYLAND_wl_display_reconnect(data->display) < 0) { 1819 return false; 1820 } 1821 1822 Wayland_VideoInit(_this); 1823 1824 window = _this->windows; 1825 while (window) { 1826 /* We're going to cheat _just_ for a second and strip the OpenGL flag. 1827 * The Wayland driver actually forces it in CreateWindow, and 1828 * RecreateWindow does a bunch of unloading/loading of libGL, so just 1829 * strip the flag so RecreateWindow doesn't mess with the GL context, 1830 * and CreateWindow will add it right back! 1831 * -flibit 1832 */ 1833 window->flags &= ~SDL_WINDOW_OPENGL; 1834 1835 SDL_RecreateWindow(window, window->flags); 1836 window = window->next; 1837 } 1838 1839 Wayland_RecreateCursors(); 1840 1841 if (current_window && current_ctx) { 1842 SDL_GL_MakeCurrent (current_window, current_ctx); 1843 } 1844 return true; 1845#else 1846 return false; 1847#endif // 0 1848} 1849 1850bool Wayland_HandleDisplayDisconnected(SDL_VideoDevice *_this) 1851{ 1852 SDL_VideoData *video_data = _this->internal; 1853 1854 /* Something has failed with the Wayland connection -- for example, 1855 * the compositor may have shut down and closed its end of the socket, 1856 * or there is a library-specific error. 1857 * 1858 * Try to recover once, then quit. 1859 */ 1860 if (video_data->display_disconnected) { 1861 return false; 1862 } 1863 1864 if (Wayland_VideoReconnect(_this)) { 1865 return true; 1866 } 1867 1868 video_data->display_disconnected = true; 1869 SDL_LogError(SDL_LOG_CATEGORY_VIDEO, "Wayland display connection closed by server (fatal)"); 1870 1871 // Only send a single quit message, as application shutdown might call SDL_PumpEvents(). 1872 SDL_SendQuit(); 1873 1874 return false; 1875} 1876 1877void Wayland_VideoQuit(SDL_VideoDevice *_this) 1878{ 1879 Wayland_VideoCleanup(_this); 1880 1881#ifdef HAVE_LIBDECOR_H 1882 SDL_VideoData *data = _this->internal; 1883 if (data->shell.libdecor) { 1884 libdecor_unref(data->shell.libdecor); 1885 data->shell.libdecor = NULL; 1886 } 1887#endif 1888} 1889 1890#endif // SDL_VIDEO_DRIVER_WAYLAND 1891[FILE END](C) 2025 0x4248 (C) 2025 4248 Media and 4248 Systems, All part of 0x4248 See LICENCE files for more information. Not all files are by 0x4248 always check Licencing.