Atlas - SDL_waylandvideo.c

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