Atlas - SDL_waylandwindow.c
Home / ext / SDL / src / video / wayland Lines: 1 | Size: 140761 bytes [Download] [Show on GitHub] [Search similar files] [Raw] [Raw (proxy)][FILE BEGIN]1/* 2 Simple DirectMedia Layer 3 Copyright (C) 1997-2025 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 <sys/mman.h> 27 28#include "../SDL_sysvideo.h" 29#include "../../events/SDL_events_c.h" 30#include "../../core/unix/SDL_appid.h" 31#include "../SDL_egl_c.h" 32#include "SDL_waylandevents_c.h" 33#include "SDL_waylandwindow.h" 34#include "SDL_waylandvideo.h" 35#include "../../SDL_hints_c.h" 36#include "SDL_waylandcolor.h" 37 38#include "alpha-modifier-v1-client-protocol.h" 39#include "xdg-shell-client-protocol.h" 40#include "xdg-decoration-unstable-v1-client-protocol.h" 41#include "idle-inhibit-unstable-v1-client-protocol.h" 42#include "xdg-activation-v1-client-protocol.h" 43#include "viewporter-client-protocol.h" 44#include "fractional-scale-v1-client-protocol.h" 45#include "xdg-foreign-unstable-v2-client-protocol.h" 46#include "xdg-dialog-v1-client-protocol.h" 47#include "frog-color-management-v1-client-protocol.h" 48#include "xdg-toplevel-icon-v1-client-protocol.h" 49#include "color-management-v1-client-protocol.h" 50 51#ifdef HAVE_LIBDECOR_H 52#include <libdecor.h> 53#endif 54 55static double GetWindowScale(SDL_Window *window) 56{ 57 return (window->flags & SDL_WINDOW_HIGH_PIXEL_DENSITY) || window->internal->scale_to_display ? window->internal->scale_factor : 1.0; 58} 59 60// These are point->pixel->point round trip safe; the inverse is not round trip safe due to rounding. 61static int PointToPixel(SDL_Window *window, int point) 62{ 63 /* Rounds halfway away from zero as per the Wayland fractional scaling protocol spec. 64 * Wayland scale units are in units of 1/120, so the offset is required to correct for 65 * rounding errors when using certain scale values. 66 */ 67 return point ? SDL_max((int)SDL_lround((double)point * GetWindowScale(window) + 1e-6), 1) : 0; 68} 69 70static int PixelToPoint(SDL_Window *window, int pixel) 71{ 72 return pixel ? SDL_max((int)SDL_lround((double)pixel / GetWindowScale(window)), 1) : 0; 73} 74 75/* According to the Wayland spec: 76 * 77 * "If the [fullscreen] surface doesn't cover the whole output, the compositor will 78 * position the surface in the center of the output and compensate with border fill 79 * covering the rest of the output. The content of the border fill is undefined, but 80 * should be assumed to be in some way that attempts to blend into the surrounding area 81 * (e.g. solid black)." 82 * 83 * - KDE, as of 5.27, still doesn't do this 84 * - GNOME prior to 43 didn't do this (older versions are still found in many LTS distros) 85 * 86 * Default to 'stretch' for now, until things have moved forward enough that the default 87 * can be changed to 'aspect'. 88 */ 89enum WaylandModeScale 90{ 91 WAYLAND_MODE_SCALE_UNDEFINED, 92 WAYLAND_MODE_SCALE_ASPECT, 93 WAYLAND_MODE_SCALE_STRETCH, 94 WAYLAND_MODE_SCALE_NONE 95}; 96 97static enum WaylandModeScale GetModeScaleMethod(void) 98{ 99 static enum WaylandModeScale scale_mode = WAYLAND_MODE_SCALE_UNDEFINED; 100 101 if (scale_mode == WAYLAND_MODE_SCALE_UNDEFINED) { 102 const char *scale_hint = SDL_GetHint(SDL_HINT_VIDEO_WAYLAND_MODE_SCALING); 103 104 if (scale_hint) { 105 if (!SDL_strcasecmp(scale_hint, "aspect")) { 106 scale_mode = WAYLAND_MODE_SCALE_ASPECT; 107 } else if (!SDL_strcasecmp(scale_hint, "none")) { 108 scale_mode = WAYLAND_MODE_SCALE_NONE; 109 } else { 110 scale_mode = WAYLAND_MODE_SCALE_STRETCH; 111 } 112 } else { 113 scale_mode = WAYLAND_MODE_SCALE_STRETCH; 114 } 115 } 116 117 return scale_mode; 118} 119 120static void GetBufferSize(SDL_Window *window, int *width, int *height) 121{ 122 SDL_WindowData *data = window->internal; 123 int buf_width; 124 int buf_height; 125 126 // Exclusive fullscreen modes always have a pixel density of 1 127 if (data->is_fullscreen && window->fullscreen_exclusive) { 128 buf_width = window->current_fullscreen_mode.w; 129 buf_height = window->current_fullscreen_mode.h; 130 } else if (!data->scale_to_display) { 131 // Round fractional backbuffer sizes halfway away from zero. 132 buf_width = PointToPixel(window, data->requested.logical_width); 133 buf_height = PointToPixel(window, data->requested.logical_height); 134 } else { 135 buf_width = data->requested.pixel_width; 136 buf_height = data->requested.pixel_height; 137 } 138 139 if (width) { 140 *width = buf_width; 141 } 142 if (height) { 143 *height = buf_height; 144 } 145} 146 147static void SetMinMaxDimensions(SDL_Window *window) 148{ 149 SDL_WindowData *wind = window->internal; 150 int min_width, min_height, max_width, max_height; 151 152 if ((window->flags & SDL_WINDOW_FULLSCREEN) || wind->fullscreen_deadline_count) { 153 min_width = 0; 154 min_height = 0; 155 max_width = 0; 156 max_height = 0; 157 } else if (window->flags & SDL_WINDOW_RESIZABLE) { 158 int adj_w = SDL_max(window->min_w, wind->system_limits.min_width); 159 int adj_h = SDL_max(window->min_h, wind->system_limits.min_height); 160 if (wind->scale_to_display) { 161 adj_w = PixelToPoint(window, adj_w); 162 adj_h = PixelToPoint(window, adj_h); 163 } 164 min_width = adj_w; 165 min_height = adj_h; 166 167 adj_w = window->max_w ? SDL_max(window->max_w, wind->system_limits.min_width) : 0; 168 adj_h = window->max_h ? SDL_max(window->max_h, wind->system_limits.min_height) : 0; 169 if (wind->scale_to_display) { 170 adj_w = PixelToPoint(window, adj_w); 171 adj_h = PixelToPoint(window, adj_h); 172 } 173 max_width = adj_w; 174 max_height = adj_h; 175 } else { 176 min_width = wind->current.logical_width; 177 min_height = wind->current.logical_height; 178 max_width = wind->current.logical_width; 179 max_height = wind->current.logical_height; 180 } 181 182#ifdef HAVE_LIBDECOR_H 183 if (wind->shell_surface_type == WAYLAND_SHELL_SURFACE_TYPE_LIBDECOR) { 184 if (!wind->shell_surface.libdecor.frame) { 185 return; // Can't do anything yet, wait for ShowWindow 186 } 187 /* No need to change these values if the window is non-resizable, 188 * as libdecor will just overwrite them internally. 189 */ 190 if (libdecor_frame_has_capability(wind->shell_surface.libdecor.frame, LIBDECOR_ACTION_RESIZE)) { 191 libdecor_frame_set_min_content_size(wind->shell_surface.libdecor.frame, 192 min_width, 193 min_height); 194 libdecor_frame_set_max_content_size(wind->shell_surface.libdecor.frame, 195 max_width, 196 max_height); 197 } 198 } else 199#endif 200 if (wind->shell_surface_type == WAYLAND_SHELL_SURFACE_TYPE_XDG_TOPLEVEL) { 201 if (!wind->shell_surface.xdg.toplevel.xdg_toplevel) { 202 return; // Can't do anything yet, wait for ShowWindow 203 } 204 xdg_toplevel_set_min_size(wind->shell_surface.xdg.toplevel.xdg_toplevel, 205 min_width, 206 min_height); 207 xdg_toplevel_set_max_size(wind->shell_surface.xdg.toplevel.xdg_toplevel, 208 max_width, 209 max_height); 210 } 211} 212 213static void EnsurePopupPositionIsValid(SDL_Window *window, int *x, int *y) 214{ 215 int adj_count = 0; 216 217 /* Per the xdg-positioner spec, child popup windows must intersect or at 218 * least be partially adjoining the parent window. 219 * 220 * Failure to ensure this on a compositor that enforces this restriction 221 * can result in behavior ranging from the window being spuriously closed 222 * to a protocol violation. 223 */ 224 if (*x + window->w <= 0) { 225 *x = -window->w; 226 ++adj_count; 227 } 228 if (*y + window->h <= 0) { 229 *y = -window->h; 230 ++adj_count; 231 } 232 if (*x >= window->parent->w) { 233 *x = window->parent->w; 234 ++adj_count; 235 } 236 if (*y >= window->parent->h) { 237 *y = window->parent->h; 238 ++adj_count; 239 } 240 241 /* If adjustment was required on the x and y axes, the popup is aligned with 242 * the parent corner-to-corner and is neither overlapping nor adjoining, so it 243 * must be nudged by 1 to be considered adjoining. 244 */ 245 if (adj_count > 1) { 246 *x += *x < 0 ? 1 : -1; 247 } 248} 249 250static void AdjustPopupOffset(SDL_Window *popup, int *x, int *y) 251{ 252 // Adjust the popup positioning, if necessary 253#ifdef HAVE_LIBDECOR_H 254 if (popup->parent->internal->shell_surface_type == WAYLAND_SHELL_SURFACE_TYPE_LIBDECOR) { 255 int adj_x, adj_y; 256 libdecor_frame_translate_coordinate(popup->parent->internal->shell_surface.libdecor.frame, 257 *x, *y, &adj_x, &adj_y); 258 *x = adj_x; 259 *y = adj_y; 260 } 261#endif 262} 263 264static void RepositionPopup(SDL_Window *window, bool use_current_position) 265{ 266 SDL_WindowData *wind = window->internal; 267 268 if (wind->shell_surface_type == WAYLAND_SHELL_SURFACE_TYPE_XDG_POPUP && 269 wind->shell_surface.xdg.popup.xdg_positioner && 270 xdg_popup_get_version(wind->shell_surface.xdg.popup.xdg_popup) >= XDG_POPUP_REPOSITION_SINCE_VERSION) { 271 int x = use_current_position ? window->x : window->pending.x; 272 int y = use_current_position ? window->y : window->pending.y; 273 274 EnsurePopupPositionIsValid(window, &x, &y); 275 if (wind->scale_to_display) { 276 x = PixelToPoint(window->parent, x); 277 y = PixelToPoint(window->parent, y); 278 } 279 AdjustPopupOffset(window, &x, &y); 280 xdg_positioner_set_anchor_rect(wind->shell_surface.xdg.popup.xdg_positioner, 0, 0, window->parent->internal->current.logical_width, window->parent->internal->current.logical_height); 281 xdg_positioner_set_size(wind->shell_surface.xdg.popup.xdg_positioner, wind->current.logical_width, wind->current.logical_height); 282 xdg_positioner_set_offset(wind->shell_surface.xdg.popup.xdg_positioner, x, y); 283 xdg_popup_reposition(wind->shell_surface.xdg.popup.xdg_popup, 284 wind->shell_surface.xdg.popup.xdg_positioner, 285 0); 286 } 287} 288 289static void SetSurfaceOpaqueRegion(SDL_WindowData *wind, bool is_opaque) 290{ 291 SDL_VideoData *viddata = wind->waylandData; 292 293 if (is_opaque) { 294 struct wl_region *region = wl_compositor_create_region(viddata->compositor); 295 wl_region_add(region, 0, 0, 296 wind->current.logical_width, wind->current.logical_height); 297 wl_surface_set_opaque_region(wind->surface, region); 298 wl_region_destroy(region); 299 } else { 300 wl_surface_set_opaque_region(wind->surface, NULL); 301 } 302} 303 304static void ConfigureWindowGeometry(SDL_Window *window) 305{ 306 SDL_WindowData *data = window->internal; 307 const double scale_factor = GetWindowScale(window); 308 const int old_pixel_width = data->current.pixel_width; 309 const int old_pixel_height = data->current.pixel_height; 310 int window_width, window_height; 311 bool window_size_changed; 312 313 // Set the drawable backbuffer size. 314 GetBufferSize(window, &data->current.pixel_width, &data->current.pixel_height); 315 const bool buffer_size_changed = data->current.pixel_width != old_pixel_width || 316 data->current.pixel_height != old_pixel_height; 317 318 if (data->egl_window && buffer_size_changed) { 319 WAYLAND_wl_egl_window_resize(data->egl_window, 320 data->current.pixel_width, 321 data->current.pixel_height, 322 0, 0); 323 } 324 325 if (data->is_fullscreen && window->fullscreen_exclusive) { 326 int output_width; 327 int output_height; 328 window_width = window->current_fullscreen_mode.w; 329 window_height = window->current_fullscreen_mode.h; 330 331 output_width = data->requested.logical_width; 332 output_height = data->requested.logical_height; 333 334 switch (GetModeScaleMethod()) { 335 case WAYLAND_MODE_SCALE_NONE: 336 /* The Wayland spec states that the advertised fullscreen dimensions are a maximum. 337 * Windows can request a smaller size, but exceeding these dimensions is a protocol violation, 338 * thus, modes that exceed the output size still need to be scaled with a viewport. 339 */ 340 if (window_width <= output_width && window_height <= output_height) { 341 output_width = window_width; 342 output_height = window_height; 343 344 break; 345 } 346 SDL_FALLTHROUGH; 347 case WAYLAND_MODE_SCALE_ASPECT: 348 { 349 const float output_ratio = (float)output_width / (float)output_height; 350 const float mode_ratio = (float)window_width / (float)window_height; 351 352 if (output_ratio > mode_ratio) { 353 output_width = SDL_lroundf((float)window_width * ((float)output_height / (float)window_height)); 354 } else if (output_ratio < mode_ratio) { 355 output_height = SDL_lroundf((float)window_height * ((float)output_width / (float)window_width)); 356 } 357 } break; 358 default: 359 break; 360 } 361 362 window_size_changed = window_width != window->w || window_height != window->h || 363 data->current.logical_width != output_width || data->current.logical_height != output_height; 364 365 if (window_size_changed || buffer_size_changed) { 366 if (data->viewport) { 367 wp_viewport_set_destination(data->viewport, output_width, output_height); 368 369 data->current.logical_width = output_width; 370 data->current.logical_height = output_height; 371 } else { 372 // Calculate the integer scale from the mode and output. 373 const int32_t int_scale = SDL_max(window->current_fullscreen_mode.w / output_width, 1); 374 375 wl_surface_set_buffer_scale(data->surface, int_scale); 376 data->current.logical_width = window->current_fullscreen_mode.w; 377 data->current.logical_height = window->current_fullscreen_mode.h; 378 } 379 380 data->pointer_scale.x = (double)window_width / (double)data->current.logical_width; 381 data->pointer_scale.y = (double)window_height / (double)data->current.logical_height; 382 } 383 } else { 384 window_width = data->requested.logical_width; 385 window_height = data->requested.logical_height; 386 387 window_size_changed = window_width != data->current.logical_width || window_height != data->current.logical_height; 388 389 if (window_size_changed || buffer_size_changed) { 390 if (data->viewport) { 391 wp_viewport_set_destination(data->viewport, window_width, window_height); 392 } else if (window->flags & SDL_WINDOW_HIGH_PIXEL_DENSITY) { 393 // Don't change this if the DPI awareness flag is unset, as an application may have set this manually on a custom or external surface. 394 wl_surface_set_buffer_scale(data->surface, (int32_t)scale_factor); 395 } 396 397 // Clamp the physical window size to the system minimum required size. 398 data->current.logical_width = SDL_max(window_width, data->system_limits.min_width); 399 data->current.logical_height = SDL_max(window_height, data->system_limits.min_height); 400 401 if (!data->scale_to_display) { 402 data->pointer_scale.x = 1.0; 403 data->pointer_scale.y = 1.0; 404 } else { 405 data->pointer_scale.x = scale_factor; 406 data->pointer_scale.y = scale_factor; 407 } 408 } 409 } 410 411 /* 412 * The surface geometry, opaque region and pointer confinement region only 413 * need to be recalculated if the output size has changed. 414 */ 415 if (window_size_changed) { 416 /* XXX: This is a hack and only set on the xdg-toplevel path when viewports 417 * aren't supported to avoid a potential protocol violation if a buffer 418 * with an old size is committed. 419 */ 420 if (!data->viewport && data->shell_surface_type == WAYLAND_SHELL_SURFACE_TYPE_XDG_TOPLEVEL && data->shell_surface.xdg.surface) { 421 xdg_surface_set_window_geometry(data->shell_surface.xdg.surface, 0, 0, data->current.logical_width, data->current.logical_height); 422 } 423 424 SetSurfaceOpaqueRegion(data, !(window->flags & SDL_WINDOW_TRANSPARENT) && window->opacity == 1.0f); 425 426 // Ensure that child popup windows are still in bounds. 427 for (SDL_Window *child = window->first_child; child; child = child->next_sibling) { 428 RepositionPopup(child, true); 429 } 430 } 431 432 /* Update the min/max dimensions, primarily if the state was changed, and for non-resizable 433 * xdg-toplevel windows where the limits should match the window size. 434 */ 435 SetMinMaxDimensions(window); 436 437 // Unconditionally send the window and drawable size, the video core will deduplicate when required. 438 if (!data->scale_to_display) { 439 SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_RESIZED, window_width, window_height); 440 } else { 441 SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_RESIZED, data->current.pixel_width, data->current.pixel_height); 442 } 443 SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED, data->current.pixel_width, data->current.pixel_height); 444 445 /* Send an exposure event if the window is in the shown state and the size has changed, 446 * even if the window is occluded, as the client needs to commit a new frame for the 447 * changes to take effect. 448 * 449 * The occlusion state is immediately set again afterward, if necessary. 450 */ 451 if (data->shell_surface_status == WAYLAND_SHELL_SURFACE_STATUS_SHOWN) { 452 if ((buffer_size_changed || window_size_changed) || 453 (!data->suspended && (window->flags & SDL_WINDOW_OCCLUDED))) { 454 SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_EXPOSED, 0, 0); 455 } 456 457 if (data->suspended) { 458 SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_OCCLUDED, 0, 0); 459 } 460 } 461} 462 463static void CommitLibdecorFrame(SDL_Window *window) 464{ 465#ifdef HAVE_LIBDECOR_H 466 SDL_WindowData *wind = window->internal; 467 468 if (wind->shell_surface_type == WAYLAND_SHELL_SURFACE_TYPE_LIBDECOR && wind->shell_surface.libdecor.frame) { 469 struct libdecor_state *state = libdecor_state_new(wind->current.logical_width, wind->current.logical_height); 470 libdecor_frame_commit(wind->shell_surface.libdecor.frame, state, NULL); 471 libdecor_state_free(state); 472 } 473#endif 474} 475 476static void fullscreen_deadline_handler(void *data, struct wl_callback *callback, uint32_t callback_data) 477{ 478 // Get the window from the ID as it may have been destroyed 479 SDL_WindowID windowID = (SDL_WindowID)((uintptr_t)data); 480 SDL_Window *window = SDL_GetWindowFromID(windowID); 481 482 if (window && window->internal) { 483 window->internal->fullscreen_deadline_count--; 484 } 485 486 wl_callback_destroy(callback); 487} 488 489static struct wl_callback_listener fullscreen_deadline_listener = { 490 fullscreen_deadline_handler 491}; 492 493static void maximized_restored_deadline_handler(void *data, struct wl_callback *callback, uint32_t callback_data) 494{ 495 // Get the window from the ID as it may have been destroyed 496 SDL_WindowID windowID = (SDL_WindowID)((uintptr_t)data); 497 SDL_Window *window = SDL_GetWindowFromID(windowID); 498 499 if (window && window->internal) { 500 window->internal->maximized_restored_deadline_count--; 501 } 502 503 wl_callback_destroy(callback); 504} 505 506static struct wl_callback_listener maximized_restored_deadline_listener = { 507 maximized_restored_deadline_handler 508}; 509 510static void FlushPendingEvents(SDL_Window *window) 511{ 512 // Serialize and restore the pending flags, as they may be overwritten while flushing. 513 const bool last_position_pending = window->last_position_pending; 514 const bool last_size_pending = window->last_size_pending; 515 516 while (window->internal->fullscreen_deadline_count || window->internal->maximized_restored_deadline_count) { 517 WAYLAND_wl_display_roundtrip(window->internal->waylandData->display); 518 } 519 520 window->last_position_pending = last_position_pending; 521 window->last_size_pending = last_size_pending; 522} 523 524/* While we can't get window position from the compositor, we do at least know 525 * what monitor we're on, so let's send move events that put the window at the 526 * center of the whatever display the wl_surface_listener events give us. 527 */ 528static void Wayland_move_window(SDL_Window *window) 529{ 530 SDL_WindowData *wind = window->internal; 531 SDL_DisplayData *display; 532 SDL_DisplayID *displays; 533 534 if (wind->outputs && wind->num_outputs) { 535 display = wind->outputs[wind->num_outputs - 1]; 536 } else { 537 // A window may not be on any displays if minimized. 538 return; 539 } 540 541 displays = SDL_GetDisplays(NULL); 542 if (displays) { 543 for (int i = 0; displays[i]; ++i) { 544 if (SDL_GetDisplayDriverData(displays[i]) == display) { 545 /* We want to send a very very specific combination here: 546 * 547 * 1. A coordinate that tells the application what display we're on 548 * 2. Exactly (0, 0) 549 * 550 * Part 1 is useful information but is also really important for 551 * ensuring we end up on the right display for fullscreen, while 552 * part 2 is important because numerous applications use a specific 553 * combination of GetWindowPosition and GetGlobalMouseState, and of 554 * course neither are supported by Wayland. Since global mouse will 555 * fall back to just GetMouseState, we need the window position to 556 * be zero so the cursor math works without it going off in some 557 * random direction. See UE5 Editor for a notable example of this! 558 * 559 * This may be an issue some day if we're ever able to implement 560 * SDL_GetDisplayUsableBounds! 561 * 562 * -flibit 563 */ 564 565 if (wind->last_displayID != displays[i]) { 566 wind->last_displayID = displays[i]; 567 if (wind->shell_surface_type != WAYLAND_SHELL_SURFACE_TYPE_XDG_POPUP) { 568 SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_MOVED, display->x, display->y); 569 SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_DISPLAY_CHANGED, wind->last_displayID, 0); 570 } 571 } 572 break; 573 } 574 } 575 SDL_free(displays); 576 } 577} 578 579static void SetFullscreen(SDL_Window *window, struct wl_output *output, bool fullscreen) 580{ 581 SDL_WindowData *wind = window->internal; 582 SDL_VideoData *viddata = wind->waylandData; 583 584#ifdef HAVE_LIBDECOR_H 585 if (wind->shell_surface_type == WAYLAND_SHELL_SURFACE_TYPE_LIBDECOR) { 586 if (!wind->shell_surface.libdecor.frame) { 587 return; // Can't do anything yet, wait for ShowWindow 588 } 589 590 wind->fullscreen_exclusive = output ? window->fullscreen_exclusive : false; 591 ++wind->fullscreen_deadline_count; 592 if (fullscreen) { 593 Wayland_SetWindowResizable(SDL_GetVideoDevice(), window, true); 594 wl_surface_commit(wind->surface); 595 596 libdecor_frame_set_fullscreen(wind->shell_surface.libdecor.frame, output); 597 } else { 598 libdecor_frame_unset_fullscreen(wind->shell_surface.libdecor.frame); 599 } 600 } else 601#endif 602 if (wind->shell_surface_type == WAYLAND_SHELL_SURFACE_TYPE_XDG_TOPLEVEL) { 603 if (wind->shell_surface.xdg.toplevel.xdg_toplevel == NULL) { 604 return; // Can't do anything yet, wait for ShowWindow 605 } 606 607 wind->fullscreen_exclusive = output ? window->fullscreen_exclusive : false; 608 ++wind->fullscreen_deadline_count; 609 if (fullscreen) { 610 Wayland_SetWindowResizable(SDL_GetVideoDevice(), window, true); 611 wl_surface_commit(wind->surface); 612 613 xdg_toplevel_set_fullscreen(wind->shell_surface.xdg.toplevel.xdg_toplevel, output); 614 } else { 615 xdg_toplevel_unset_fullscreen(wind->shell_surface.xdg.toplevel.xdg_toplevel); 616 } 617 } 618 619 // Queue a deadline event 620 struct wl_callback *cb = wl_display_sync(viddata->display); 621 wl_callback_add_listener(cb, &fullscreen_deadline_listener, (void *)((uintptr_t)window->id)); 622} 623 624static void UpdateWindowFullscreen(SDL_Window *window, bool fullscreen) 625{ 626 SDL_WindowData *wind = window->internal; 627 628 wind->is_fullscreen = fullscreen; 629 630 if (fullscreen) { 631 if (!(window->flags & SDL_WINDOW_FULLSCREEN)) { 632 SDL_copyp(&window->current_fullscreen_mode, &window->requested_fullscreen_mode); 633 SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_ENTER_FULLSCREEN, 0, 0); 634 SDL_UpdateFullscreenMode(window, SDL_FULLSCREEN_OP_ENTER, false); 635 636 /* Set the output for exclusive fullscreen windows when entering fullscreen from a 637 * compositor event, or if the fullscreen parameters were changed between the initial 638 * fullscreen request and now, to ensure that the window is on the correct output, 639 * as requested by the client. 640 */ 641 if (window->fullscreen_exclusive && (!wind->fullscreen_exclusive || !wind->fullscreen_was_positioned)) { 642 SDL_VideoDisplay *disp = SDL_GetVideoDisplay(window->current_fullscreen_mode.displayID); 643 if (disp) { 644 wind->fullscreen_was_positioned = true; 645 SetFullscreen(window, disp->internal->output, true); 646 } 647 } 648 } 649 } else { 650 // Don't change the fullscreen flags if the window is hidden or being hidden. 651 if ((window->flags & SDL_WINDOW_FULLSCREEN) && !window->is_hiding && !(window->flags & SDL_WINDOW_HIDDEN)) { 652 SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_LEAVE_FULLSCREEN, 0, 0); 653 SDL_UpdateFullscreenMode(window, SDL_FULLSCREEN_OP_LEAVE, false); 654 wind->fullscreen_was_positioned = false; 655 656 /* Send a move event, in case it was deferred while the fullscreen window was moving and 657 * on multiple outputs. 658 */ 659 Wayland_move_window(window); 660 } 661 } 662} 663 664static const struct wl_callback_listener surface_frame_listener; 665 666static void surface_frame_done(void *data, struct wl_callback *cb, uint32_t time) 667{ 668 SDL_WindowData *wind = (SDL_WindowData *)data; 669 670 /* XXX: This is needed to work around an Nvidia egl-wayland bug due to buffer coordinates 671 * being used with wl_surface_damage, which causes part of the output to not be 672 * updated when using a viewport with an output region larger than the source region. 673 */ 674 if (wl_compositor_get_version(wind->waylandData->compositor) >= WL_SURFACE_DAMAGE_BUFFER_SINCE_VERSION) { 675 wl_surface_damage_buffer(wind->surface, 0, 0, SDL_MAX_SINT32, SDL_MAX_SINT32); 676 } else { 677 wl_surface_damage(wind->surface, 0, 0, SDL_MAX_SINT32, SDL_MAX_SINT32); 678 } 679 680 if (wind->shell_surface_type == WAYLAND_SHELL_SURFACE_TYPE_XDG_TOPLEVEL) { 681 if (wind->pending_config_ack) { 682 wind->pending_config_ack = false; 683 ConfigureWindowGeometry(wind->sdlwindow); 684 xdg_surface_ack_configure(wind->shell_surface.xdg.surface, wind->shell_surface.xdg.serial); 685 } 686 } else { 687 wind->resizing = false; 688 } 689 690 if (wind->shell_surface_status == WAYLAND_SHELL_SURFACE_STATUS_WAITING_FOR_FRAME) { 691 wind->shell_surface_status = WAYLAND_SHELL_SURFACE_STATUS_SHOWN; 692 693 // If any child windows are waiting on this window to be shown, show them now 694 for (SDL_Window *w = wind->sdlwindow->first_child; w; w = w->next_sibling) { 695 if (w->internal->shell_surface_status == WAYLAND_SHELL_SURFACE_STATUS_SHOW_PENDING) { 696 Wayland_ShowWindow(SDL_GetVideoDevice(), w); 697 } else if (w->internal->reparenting_required) { 698 Wayland_SetWindowParent(SDL_GetVideoDevice(), w, w->parent); 699 if (w->flags & SDL_WINDOW_MODAL) { 700 Wayland_SetWindowModal(SDL_GetVideoDevice(), w, true); 701 } 702 } 703 } 704 705 /* If the window was initially set to the suspended state, send the occluded event now, 706 * as we don't want to mark the window as occluded until at least one frame has been submitted. 707 */ 708 if (wind->suspended) { 709 SDL_SendWindowEvent(wind->sdlwindow, SDL_EVENT_WINDOW_OCCLUDED, 0, 0); 710 } 711 } 712 713 wl_callback_destroy(cb); 714 wind->surface_frame_callback = wl_surface_frame(wind->surface); 715 wl_callback_add_listener(wind->surface_frame_callback, &surface_frame_listener, data); 716} 717 718static const struct wl_callback_listener surface_frame_listener = { 719 surface_frame_done 720}; 721 722static const struct wl_callback_listener gles_swap_frame_listener; 723 724static void gles_swap_frame_done(void *data, struct wl_callback *cb, uint32_t time) 725{ 726 SDL_WindowData *wind = (SDL_WindowData *)data; 727 SDL_SetAtomicInt(&wind->swap_interval_ready, 1); // mark window as ready to present again. 728 729 // reset this callback to fire again once a new frame was presented and compositor wants the next one. 730 wind->gles_swap_frame_callback = wl_surface_frame(wind->gles_swap_frame_surface_wrapper); 731 wl_callback_destroy(cb); 732 wl_callback_add_listener(wind->gles_swap_frame_callback, &gles_swap_frame_listener, data); 733} 734 735static const struct wl_callback_listener gles_swap_frame_listener = { 736 gles_swap_frame_done 737}; 738 739static void handle_xdg_surface_configure(void *data, struct xdg_surface *xdg, uint32_t serial) 740{ 741 SDL_WindowData *wind = (SDL_WindowData *)data; 742 SDL_Window *window = wind->sdlwindow; 743 744 /* Interactive resizes are throttled by acking and committing only the most recent configuration at 745 * the next frame callback, or certain combinations of clients and compositors can exhibit severe lag 746 * when resizing. 747 */ 748 wind->shell_surface.xdg.serial = serial; 749 if (!wind->resizing) { 750 wind->pending_config_ack = false; 751 ConfigureWindowGeometry(window); 752 xdg_surface_ack_configure(xdg, serial); 753 } else { 754 wind->pending_config_ack = true; 755 } 756 757 if (wind->shell_surface_status == WAYLAND_SHELL_SURFACE_STATUS_WAITING_FOR_CONFIGURE) { 758 wind->shell_surface_status = WAYLAND_SHELL_SURFACE_STATUS_WAITING_FOR_FRAME; 759 } 760} 761 762static const struct xdg_surface_listener _xdg_surface_listener = { 763 handle_xdg_surface_configure 764}; 765 766static void handle_xdg_toplevel_configure(void *data, 767 struct xdg_toplevel *xdg_toplevel, 768 int32_t width, 769 int32_t height, 770 struct wl_array *states) 771{ 772 SDL_WindowData *wind = (SDL_WindowData *)data; 773 SDL_Window *window = wind->sdlwindow; 774 775 enum xdg_toplevel_state *state; 776 bool fullscreen = false; 777 bool maximized = false; 778 bool floating = true; 779 bool tiled = false; 780 bool active = false; 781 bool resizing = false; 782 bool suspended = false; 783 wind->toplevel_constraints = 0; 784 wl_array_for_each (state, states) { 785 switch (*state) { 786 case XDG_TOPLEVEL_STATE_FULLSCREEN: 787 fullscreen = true; 788 floating = false; 789 break; 790 case XDG_TOPLEVEL_STATE_MAXIMIZED: 791 maximized = true; 792 floating = false; 793 break; 794 case XDG_TOPLEVEL_STATE_RESIZING: 795 resizing = true; 796 break; 797 case XDG_TOPLEVEL_STATE_ACTIVATED: 798 active = true; 799 break; 800 case XDG_TOPLEVEL_STATE_TILED_LEFT: 801 case XDG_TOPLEVEL_STATE_TILED_RIGHT: 802 case XDG_TOPLEVEL_STATE_TILED_TOP: 803 case XDG_TOPLEVEL_STATE_TILED_BOTTOM: 804 tiled = true; 805 floating = false; 806 break; 807 case XDG_TOPLEVEL_STATE_SUSPENDED: 808 suspended = true; 809 break; 810 case XDG_TOPLEVEL_STATE_CONSTRAINED_LEFT: 811 wind->toplevel_constraints |= WAYLAND_TOPLEVEL_CONSTRAINED_LEFT; 812 break; 813 case XDG_TOPLEVEL_STATE_CONSTRAINED_RIGHT: 814 wind->toplevel_constraints |= WAYLAND_TOPLEVEL_CONSTRAINED_RIGHT; 815 break; 816 case XDG_TOPLEVEL_STATE_CONSTRAINED_TOP: 817 wind->toplevel_constraints |= WAYLAND_TOPLEVEL_CONSTRAINED_TOP; 818 break; 819 case XDG_TOPLEVEL_STATE_CONSTRAINED_BOTTOM: 820 wind->toplevel_constraints |= WAYLAND_TOPLEVEL_CONSTRAINED_BOTTOM; 821 break; 822 default: 823 break; 824 } 825 } 826 827 UpdateWindowFullscreen(window, fullscreen); 828 829 /* Always send a maximized/restore event; if the event is redundant it will 830 * automatically be discarded (see src/events/SDL_windowevents.c) 831 * 832 * No, we do not get minimize events from xdg-shell, however, the minimized 833 * state can be programmatically set. The meaning of 'minimized' is compositor 834 * dependent, but in general, we can assume that the flag should remain set until 835 * the next focused configure event occurs. 836 */ 837 if (active || !(window->flags & SDL_WINDOW_MINIMIZED)) { 838 if (window->flags & SDL_WINDOW_MINIMIZED) { 839 // If we were minimized, send a restored event before possibly sending maximized. 840 SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_RESTORED, 0, 0); 841 } 842 SDL_SendWindowEvent(window, 843 (maximized && !fullscreen) ? SDL_EVENT_WINDOW_MAXIMIZED : SDL_EVENT_WINDOW_RESTORED, 844 0, 0); 845 } 846 847 if (!fullscreen) { 848 /* xdg_toplevel spec states that this is a suggestion. 849 * Ignore if less than or greater than max/min size. 850 */ 851 if (window->flags & SDL_WINDOW_RESIZABLE) { 852 if (width == 0 || height == 0) { 853 /* This happens when the compositor indicates that the size is 854 * up to the client, so use the cached window size here. 855 */ 856 if (floating) { 857 width = window->floating.w; 858 height = window->floating.h; 859 860 // Clamp the window to the toplevel bounds, if any are set. 861 if (wind->shell_surface_status == WAYLAND_SHELL_SURFACE_STATUS_WAITING_FOR_CONFIGURE && 862 wind->toplevel_bounds.width && wind->toplevel_bounds.height) { 863 width = SDL_min(wind->toplevel_bounds.width, width); 864 height = SDL_min(wind->toplevel_bounds.height, height); 865 } 866 } else { 867 width = window->windowed.w; 868 height = window->windowed.h; 869 } 870 871 if (!wind->scale_to_display) { 872 wind->requested.logical_width = width; 873 wind->requested.logical_height = height; 874 } else { 875 wind->requested.pixel_width = width; 876 wind->requested.pixel_height = height; 877 width = wind->requested.logical_width = PixelToPoint(window, width); 878 height = wind->requested.logical_height = PixelToPoint(window, height); 879 } 880 } else { 881 /* Don't apply the supplied dimensions if they haven't changed from the last configuration 882 * event, or a newer size set programmatically can be overwritten by old data. 883 */ 884 if (width != wind->last_configure.width || height != wind->last_configure.height) { 885 wind->requested.logical_width = width; 886 wind->requested.logical_height = height; 887 888 if (wind->scale_to_display) { 889 wind->requested.pixel_width = PointToPixel(window, width); 890 wind->requested.pixel_height = PointToPixel(window, height); 891 } 892 } 893 } 894 } else { 895 /* If we're a fixed-size window, we know our size for sure. 896 * Always assume the configure is wrong. 897 */ 898 if (!wind->scale_to_display) { 899 width = wind->requested.logical_width = window->floating.w; 900 height = wind->requested.logical_height = window->floating.h; 901 } else { 902 wind->requested.pixel_width = window->floating.w; 903 wind->requested.pixel_height = window->floating.h; 904 width = wind->requested.logical_width = PixelToPoint(window, window->floating.w); 905 height = wind->requested.logical_height = PixelToPoint(window, window->floating.h); 906 } 907 } 908 909 /* Notes on the spec: 910 * 911 * - The content limits are only a hint, which the compositor is free to ignore, 912 * so apply them manually when appropriate. 913 * 914 * - Maximized windows must have their exact dimensions respected, thus they must 915 * not be resized, or a protocol violation can occur. 916 * 917 * - When resizing a window, the width/height are maximum values, so aspect ratio 918 * correction can't resize beyond the existing dimensions, or a protocol violation 919 * can occur. In practice, nothing seems to kill clients that do this, but doing 920 * so causes GNOME to glitch out. 921 */ 922 if (!maximized) { 923 if (!wind->scale_to_display) { 924 if (window->max_w > 0) { 925 wind->requested.logical_width = SDL_min(wind->requested.logical_width, window->max_w); 926 } 927 wind->requested.logical_width = SDL_max(wind->requested.logical_width, window->min_w); 928 929 if (window->max_h > 0) { 930 wind->requested.logical_height = SDL_min(wind->requested.logical_height, window->max_h); 931 } 932 wind->requested.logical_height = SDL_max(wind->requested.logical_height, window->min_h); 933 934 // Aspect correction. 935 const float aspect = (float)wind->requested.logical_width / (float)wind->requested.logical_height; 936 937 if (window->min_aspect != 0.f && aspect < window->min_aspect) { 938 wind->requested.logical_height = SDL_lroundf((float)wind->requested.logical_width / window->min_aspect); 939 } else if (window->max_aspect != 0.f && aspect > window->max_aspect) { 940 wind->requested.logical_width = SDL_lroundf((float)wind->requested.logical_height * window->max_aspect); 941 } 942 } else { 943 if (window->max_w > 0) { 944 wind->requested.pixel_width = SDL_min(wind->requested.pixel_width, window->max_w); 945 } 946 wind->requested.pixel_width = SDL_max(wind->requested.pixel_width, window->min_w); 947 948 if (window->max_h > 0) { 949 wind->requested.pixel_height = SDL_min(wind->requested.pixel_height, window->max_h); 950 } 951 wind->requested.pixel_height = SDL_max(wind->requested.pixel_height, window->min_h); 952 953 // Aspect correction. 954 const float aspect = (float)wind->requested.pixel_width / (float)wind->requested.pixel_height; 955 956 if (window->min_aspect != 0.f && aspect < window->min_aspect) { 957 wind->requested.pixel_height = SDL_lroundf((float)wind->requested.pixel_width / window->min_aspect); 958 } else if (window->max_aspect != 0.f && aspect > window->max_aspect) { 959 wind->requested.pixel_width = SDL_lroundf((float)wind->requested.pixel_height * window->max_aspect); 960 } 961 962 wind->requested.logical_width = PixelToPoint(window, wind->requested.pixel_width); 963 wind->requested.logical_height = PixelToPoint(window, wind->requested.pixel_height); 964 } 965 } 966 } else { 967 // Fullscreen windows know their exact size. 968 if (width == 0 || height == 0) { 969 width = wind->requested.logical_width; 970 height = wind->requested.logical_height; 971 } else { 972 wind->requested.logical_width = width; 973 wind->requested.logical_height = height; 974 } 975 976 if (wind->scale_to_display) { 977 wind->requested.pixel_width = PointToPixel(window, width); 978 wind->requested.pixel_height = PointToPixel(window, height); 979 } 980 } 981 982 wind->last_configure.width = width; 983 wind->last_configure.height = height; 984 wind->floating = floating; 985 wind->suspended = suspended; 986 wind->active = active; 987 window->tiled = tiled; 988 wind->resizing = resizing; 989} 990 991static void handle_xdg_toplevel_close(void *data, struct xdg_toplevel *xdg_toplevel) 992{ 993 SDL_WindowData *window = (SDL_WindowData *)data; 994 SDL_SendWindowEvent(window->sdlwindow, SDL_EVENT_WINDOW_CLOSE_REQUESTED, 0, 0); 995} 996 997static void handle_xdg_toplevel_configure_bounds(void *data, 998 struct xdg_toplevel *xdg_toplevel, 999 int32_t width, int32_t height) 1000{ 1001 SDL_WindowData *window = (SDL_WindowData *)data; 1002 window->toplevel_bounds.width = width; 1003 window->toplevel_bounds.height = height; 1004} 1005 1006static void handle_xdg_toplevel_wm_capabilities(void *data, 1007 struct xdg_toplevel *xdg_toplevel, 1008 struct wl_array *capabilities) 1009{ 1010 SDL_WindowData *wind = (SDL_WindowData *)data; 1011 enum xdg_toplevel_wm_capabilities *wm_cap; 1012 1013 wind->wm_caps = 0; 1014 1015 wl_array_for_each (wm_cap, capabilities) { 1016 switch (*wm_cap) { 1017 case XDG_TOPLEVEL_WM_CAPABILITIES_WINDOW_MENU: 1018 wind->wm_caps |= WAYLAND_WM_CAPS_WINDOW_MENU; 1019 break; 1020 case XDG_TOPLEVEL_WM_CAPABILITIES_MAXIMIZE: 1021 wind->wm_caps |= WAYLAND_WM_CAPS_MAXIMIZE; 1022 break; 1023 case XDG_TOPLEVEL_WM_CAPABILITIES_FULLSCREEN: 1024 wind->wm_caps |= WAYLAND_WM_CAPS_FULLSCREEN; 1025 break; 1026 case XDG_TOPLEVEL_WM_CAPABILITIES_MINIMIZE: 1027 wind->wm_caps |= WAYLAND_WM_CAPS_MINIMIZE; 1028 break; 1029 default: 1030 break; 1031 } 1032 } 1033} 1034 1035static const struct xdg_toplevel_listener toplevel_listener_xdg = { 1036 handle_xdg_toplevel_configure, 1037 handle_xdg_toplevel_close, 1038 handle_xdg_toplevel_configure_bounds, // Version 4 1039 handle_xdg_toplevel_wm_capabilities // Version 5 1040}; 1041 1042static void handle_xdg_popup_configure(void *data, 1043 struct xdg_popup *xdg_popup, 1044 int32_t x, 1045 int32_t y, 1046 int32_t width, 1047 int32_t height) 1048{ 1049 SDL_WindowData *wind = (SDL_WindowData *)data; 1050 int offset_x = 0, offset_y = 0; 1051 1052 // Adjust the position if it was offset for libdecor 1053 AdjustPopupOffset(wind->sdlwindow, &offset_x, &offset_y); 1054 x -= offset_x; 1055 y -= offset_y; 1056 1057 /* This happens when the compositor indicates that the size is 1058 * up to the client, so use the cached window size here. 1059 */ 1060 if (width == 0 || height == 0) { 1061 width = wind->sdlwindow->floating.w; 1062 height = wind->sdlwindow->floating.h; 1063 } 1064 1065 /* Don't apply the supplied dimensions if they haven't changed from the last configuration 1066 * event, or a newer size set programmatically can be overwritten by old data. 1067 */ 1068 if (width != wind->last_configure.width || height != wind->last_configure.height) { 1069 wind->requested.logical_width = width; 1070 wind->requested.logical_height = height; 1071 1072 if (wind->scale_to_display) { 1073 wind->requested.pixel_width = PointToPixel(wind->sdlwindow, width); 1074 wind->requested.pixel_height = PointToPixel(wind->sdlwindow, height); 1075 } 1076 } 1077 1078 if (wind->scale_to_display) { 1079 x = PointToPixel(wind->sdlwindow->parent, x); 1080 y = PointToPixel(wind->sdlwindow->parent, y); 1081 } 1082 1083 SDL_SendWindowEvent(wind->sdlwindow, SDL_EVENT_WINDOW_MOVED, x, y); 1084 1085 wind->last_configure.width = width; 1086 wind->last_configure.height = height; 1087 1088 if (wind->shell_surface_status == WAYLAND_SHELL_SURFACE_STATUS_WAITING_FOR_CONFIGURE) { 1089 wind->shell_surface_status = WAYLAND_SHELL_SURFACE_STATUS_WAITING_FOR_FRAME; 1090 } 1091} 1092 1093static void handle_xdg_popup_done(void *data, struct xdg_popup *xdg_popup) 1094{ 1095 SDL_WindowData *window = (SDL_WindowData *)data; 1096 SDL_SendWindowEvent(window->sdlwindow, SDL_EVENT_WINDOW_CLOSE_REQUESTED, 0, 0); 1097} 1098 1099static void handle_xdg_popup_repositioned(void *data, 1100 struct xdg_popup *xdg_popup, 1101 uint32_t token) 1102{ 1103 // No-op, configure does all the work we care about 1104} 1105 1106static const struct xdg_popup_listener _xdg_popup_listener = { 1107 handle_xdg_popup_configure, 1108 handle_xdg_popup_done, 1109 handle_xdg_popup_repositioned 1110}; 1111 1112static void handle_xdg_toplevel_decoration_configure(void *data, 1113 struct zxdg_toplevel_decoration_v1 *zxdg_toplevel_decoration_v1, 1114 uint32_t mode) 1115{ 1116 SDL_Window *window = (SDL_Window *)data; 1117 SDL_WindowData *internal = window->internal; 1118 SDL_VideoDevice *device = SDL_GetVideoDevice(); 1119 1120 /* If the compositor tries to force CSD anyway, bail on direct XDG support 1121 * and fall back to libdecor, it will handle these events from then on. 1122 * 1123 * To do this we have to fully unmap, then map with libdecor loaded. 1124 */ 1125 if (mode == ZXDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE) { 1126 if (window->flags & SDL_WINDOW_BORDERLESS) { 1127 // borderless windows do request CSD, so we got what we wanted 1128 return; 1129 } 1130 if (!Wayland_LoadLibdecor(internal->waylandData, true)) { 1131 // libdecor isn't available, so no borders for you... oh well 1132 return; 1133 } 1134 WAYLAND_wl_display_roundtrip(internal->waylandData->display); 1135 1136 Wayland_HideWindow(device, window); 1137 internal->shell_surface_type = WAYLAND_SHELL_SURFACE_TYPE_LIBDECOR; 1138 Wayland_ShowWindow(device, window); 1139 } 1140} 1141 1142static const struct zxdg_toplevel_decoration_v1_listener xdg_toplevel_decoration_listener = { 1143 handle_xdg_toplevel_decoration_configure 1144}; 1145 1146#ifdef HAVE_LIBDECOR_H 1147/* 1148 * XXX: Hack for older versions of libdecor that lack the function to query the 1149 * minimum content size limit. The internal limits must always be overridden 1150 * to ensure that very small windows don't cause errors or crashes. 1151 * 1152 * On libdecor >= 0.1.2, which exposes the function to get the minimum content 1153 * size limit, this function is a no-op. 1154 * 1155 * Can be removed if the minimum required version of libdecor is raised to 1156 * 0.1.2 or higher. 1157 */ 1158static void OverrideLibdecorLimits(SDL_Window *window) 1159{ 1160#ifdef SDL_VIDEO_DRIVER_WAYLAND_DYNAMIC_LIBDECOR 1161 if (!libdecor_frame_get_min_content_size) { 1162 libdecor_frame_set_min_content_size(window->internal->shell_surface.libdecor.frame, window->min_w, window->min_h); 1163 } 1164#elif !SDL_LIBDECOR_CHECK_VERSION(0, 2, 0) 1165 libdecor_frame_set_min_content_size(window->internal->shell_surface.libdecor.frame, window->min_w, window->min_h); 1166#endif 1167} 1168 1169/* 1170 * NOTE: Retrieves the minimum content size limits, if the function for doing so is available. 1171 * On versions of libdecor that lack the minimum content size retrieval function, this 1172 * function is a no-op. 1173 * 1174 * Can be replaced with a direct call if the minimum required version of libdecor is raised 1175 * to 0.1.2 or higher. 1176 */ 1177static void LibdecorGetMinContentSize(struct libdecor_frame *frame, int *min_w, int *min_h) 1178{ 1179#ifdef SDL_VIDEO_DRIVER_WAYLAND_DYNAMIC_LIBDECOR 1180 if (libdecor_frame_get_min_content_size != NULL) { 1181 libdecor_frame_get_min_content_size(frame, min_w, min_h); 1182 } 1183#elif SDL_LIBDECOR_CHECK_VERSION(0, 2, 0) 1184 libdecor_frame_get_min_content_size(frame, min_w, min_h); 1185#endif 1186} 1187 1188static void decoration_frame_configure(struct libdecor_frame *frame, 1189 struct libdecor_configuration *configuration, 1190 void *user_data) 1191{ 1192 SDL_WindowData *wind = (SDL_WindowData *)user_data; 1193 SDL_Window *window = wind->sdlwindow; 1194 1195 enum libdecor_window_state window_state; 1196 int width, height; 1197 1198 bool prev_fullscreen = wind->is_fullscreen; 1199 bool active = false; 1200 bool fullscreen = false; 1201 bool maximized = false; 1202 bool tiled = false; 1203 bool suspended = false; 1204 bool resizing = false; 1205 wind->toplevel_constraints = 0; 1206 1207 static const enum libdecor_window_state tiled_states = (LIBDECOR_WINDOW_STATE_TILED_LEFT | LIBDECOR_WINDOW_STATE_TILED_RIGHT | 1208 LIBDECOR_WINDOW_STATE_TILED_TOP | LIBDECOR_WINDOW_STATE_TILED_BOTTOM); 1209 1210 // Window State 1211 if (libdecor_configuration_get_window_state(configuration, &window_state)) { 1212 fullscreen = (window_state & LIBDECOR_WINDOW_STATE_FULLSCREEN) != 0; 1213 maximized = (window_state & LIBDECOR_WINDOW_STATE_MAXIMIZED) != 0; 1214 active = (window_state & LIBDECOR_WINDOW_STATE_ACTIVE) != 0; 1215 tiled = (window_state & tiled_states) != 0; 1216#if SDL_LIBDECOR_CHECK_VERSION(0, 2, 0) 1217 suspended = (window_state & LIBDECOR_WINDOW_STATE_SUSPENDED) != 0; 1218#endif 1219#if SDL_LIBDECOR_CHECK_VERSION(0, 3, 0) 1220 resizing = (window_state & LIBDECOR_WINDOW_STATE_RESIZING) != 0; 1221 1222 if (window_state & LIBDECOR_WINDOW_STATE_CONSTRAINED_LEFT) { 1223 wind->toplevel_constraints |= WAYLAND_TOPLEVEL_CONSTRAINED_LEFT; 1224 } 1225 if (window_state & LIBDECOR_WINDOW_STATE_CONSTRAINED_RIGHT) { 1226 wind->toplevel_constraints |= WAYLAND_TOPLEVEL_CONSTRAINED_RIGHT; 1227 } 1228 if (window_state & LIBDECOR_WINDOW_STATE_CONSTRAINED_TOP) { 1229 wind->toplevel_constraints |= WAYLAND_TOPLEVEL_CONSTRAINED_TOP; 1230 } 1231 if (window_state & LIBDECOR_WINDOW_STATE_CONSTRAINED_BOTTOM) { 1232 wind->toplevel_constraints |= WAYLAND_TOPLEVEL_CONSTRAINED_BOTTOM; 1233 } 1234#endif 1235 } 1236 const bool floating = !(fullscreen || maximized || tiled); 1237 1238 UpdateWindowFullscreen(window, fullscreen); 1239 1240 /* Always send a maximized/restore event; if the event is redundant it will 1241 * automatically be discarded (see src/events/SDL_windowevents.c) 1242 * 1243 * No, we do not get minimize events from libdecor, however, the minimized 1244 * state can be programmatically set. The meaning of 'minimized' is compositor 1245 * dependent, but in general, we can assume that the flag should remain set until 1246 * the next focused configure event occurs. 1247 */ 1248 if (active || !(window->flags & SDL_WINDOW_MINIMIZED)) { 1249 if (window->flags & SDL_WINDOW_MINIMIZED) { 1250 // If we were minimized, send a restored event before possibly sending maximized. 1251 SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_RESTORED, 0, 0); 1252 } 1253 SDL_SendWindowEvent(window, 1254 (maximized && !fullscreen) ? SDL_EVENT_WINDOW_MAXIMIZED : SDL_EVENT_WINDOW_RESTORED, 1255 0, 0); 1256 } 1257 1258 /* For fullscreen or fixed-size windows we know our size. 1259 * Always assume the configure is wrong. 1260 */ 1261 if (fullscreen) { 1262 if (!libdecor_configuration_get_content_size(configuration, frame, &width, &height)) { 1263 width = wind->requested.logical_width; 1264 height = wind->requested.logical_height; 1265 } else { 1266 // Fullscreen windows know their exact size. 1267 wind->requested.logical_width = width; 1268 wind->requested.logical_height = height; 1269 1270 if (wind->scale_to_display) { 1271 wind->requested.pixel_width = PointToPixel(window, width); 1272 wind->requested.pixel_height = PointToPixel(window, height); 1273 } 1274 } 1275 } else { 1276 if (!(window->flags & SDL_WINDOW_RESIZABLE)) { 1277 /* If we're a fixed-size window, we know our size for sure. 1278 * Always assume the configure is wrong. 1279 */ 1280 if (!wind->scale_to_display) { 1281 width = wind->requested.logical_width = window->floating.w; 1282 height = wind->requested.logical_height = window->floating.h; 1283 } else { 1284 wind->requested.pixel_width = window->floating.w; 1285 wind->requested.pixel_height = window->floating.h; 1286 width = wind->requested.logical_width = PixelToPoint(window, window->floating.w); 1287 height = wind->requested.logical_height = PixelToPoint(window, window->floating.h); 1288 } 1289 1290 OverrideLibdecorLimits(window); 1291 } else { 1292 /* XXX: The libdecor cairo plugin sends bogus content sizes that add the 1293 * height of the title bar when transitioning from a fixed-size to 1294 * floating state. Ignore the sent window dimensions in this case, 1295 * in favor of the cached value to avoid the window increasing in 1296 * size after every state transition. 1297 * 1298 * https://gitlab.freedesktop.org/libdecor/libdecor/-/issues/34 1299 */ 1300 if ((floating && (!wind->floating && !(window->flags & SDL_WINDOW_BORDERLESS))) || 1301 !libdecor_configuration_get_content_size(configuration, frame, &width, &height)) { 1302 /* This happens when we're being restored from a non-floating state, 1303 * or the compositor indicates that the size is up to the client, so 1304 * used the cached window size here. 1305 */ 1306 if (floating) { 1307 width = window->floating.w; 1308 height = window->floating.h; 1309 1310 // Clamp the window to the toplevel bounds, if any are set. 1311 if (wind->shell_surface_status == WAYLAND_SHELL_SURFACE_STATUS_WAITING_FOR_CONFIGURE && 1312 wind->toplevel_bounds.width && wind->toplevel_bounds.height) { 1313 width = SDL_min(wind->toplevel_bounds.width, width); 1314 height = SDL_min(wind->toplevel_bounds.height, height); 1315 } 1316 } else { 1317 width = window->windowed.w; 1318 height = window->windowed.h; 1319 } 1320 1321 if (!wind->scale_to_display) { 1322 wind->requested.logical_width = width; 1323 wind->requested.logical_height = height; 1324 } else { 1325 wind->requested.pixel_width = width; 1326 wind->requested.pixel_height = height; 1327 width = wind->requested.logical_width = PixelToPoint(window, width); 1328 height = wind->requested.logical_height = PixelToPoint(window, height); 1329 } 1330 } else { 1331 /* Don't apply the supplied dimensions if they haven't changed from the last configuration 1332 * event, or a newer size set programmatically can be overwritten by old data. 1333 * 1334 * If a client takes a long time to present the first frame after creating the window, a 1335 * configure event to set the suspended state may arrive with the content size increased 1336 * by the decoration dimensions, which should also be ignored. 1337 */ 1338 if ((width != wind->last_configure.width || height != wind->last_configure.height) && 1339 !(wind->shell_surface_status == WAYLAND_SHELL_SURFACE_STATUS_WAITING_FOR_FRAME && wind->suspended != suspended)) { 1340 wind->requested.logical_width = width; 1341 wind->requested.logical_height = height; 1342 1343 if (wind->scale_to_display) { 1344 wind->requested.pixel_width = PointToPixel(window, width); 1345 wind->requested.pixel_height = PointToPixel(window, height); 1346 } 1347 } 1348 } 1349 } 1350 1351 /* Notes on the spec: 1352 * 1353 * - The content limits are only a hint, which the compositor is free to ignore, 1354 * so apply them manually when appropriate. 1355 * 1356 * - Maximized windows must have their exact dimensions respected, thus they must 1357 * not be resized, or a protocol violation can occur. 1358 * 1359 * - When resizing a window, the width/height are maximum values, so aspect ratio 1360 * correction can't resize beyond the existing dimensions, or a protocol violation 1361 * can occur. In practice, nothing seems to kill clients that do this, but doing 1362 * so causes GNOME to glitch out. 1363 */ 1364 if (!maximized) { 1365 if (!wind->scale_to_display) { 1366 if (window->max_w > 0) { 1367 wind->requested.logical_width = SDL_min(wind->requested.logical_width, window->max_w); 1368 } 1369 wind->requested.logical_width = SDL_max(wind->requested.logical_width, window->min_w); 1370 1371 if (window->max_h > 0) { 1372 wind->requested.logical_height = SDL_min(wind->requested.logical_height, window->max_h); 1373 } 1374 wind->requested.logical_height = SDL_max(wind->requested.logical_height, window->min_h); 1375 1376 // Aspect correction. 1377 const float aspect = (float)wind->requested.logical_width / (float)wind->requested.logical_height; 1378 1379 if (window->min_aspect != 0.f && aspect < window->min_aspect) { 1380 wind->requested.logical_height = SDL_lroundf((float)wind->requested.logical_width / window->min_aspect); 1381 } else if (window->max_aspect != 0.f && aspect > window->max_aspect) { 1382 wind->requested.logical_width = SDL_lroundf((float)wind->requested.logical_height * window->max_aspect); 1383 } 1384 } else { 1385 if (window->max_w > 0) { 1386 wind->requested.pixel_width = SDL_min(wind->requested.pixel_width, window->max_w); 1387 } 1388 wind->requested.pixel_width = SDL_max(wind->requested.pixel_width, window->min_w); 1389 1390 if (window->max_h > 0) { 1391 wind->requested.pixel_height = SDL_min(wind->requested.pixel_height, window->max_h); 1392 } 1393 wind->requested.pixel_height = SDL_max(wind->requested.pixel_height, window->min_h); 1394 1395 // Aspect correction. 1396 const float aspect = (float)wind->requested.pixel_width / (float)wind->requested.pixel_height; 1397 1398 if (window->min_aspect != 0.f && aspect < window->min_aspect) { 1399 wind->requested.pixel_height = SDL_lroundf((float)wind->requested.pixel_width / window->min_aspect); 1400 } else if (window->max_aspect != 0.f && aspect > window->max_aspect) { 1401 wind->requested.pixel_width = SDL_lroundf((float)wind->requested.pixel_height * window->max_aspect); 1402 } 1403 1404 wind->requested.logical_width = PixelToPoint(window, wind->requested.pixel_width); 1405 wind->requested.logical_height = PixelToPoint(window, wind->requested.pixel_height); 1406 } 1407 } 1408 } 1409 1410 // Store the new state. 1411 const bool started_resize = !wind->resizing && resizing; 1412 wind->last_configure.width = width; 1413 wind->last_configure.height = height; 1414 wind->floating = floating; 1415 wind->suspended = suspended; 1416 wind->active = active; 1417 window->tiled = tiled; 1418 wind->resizing = resizing; 1419 1420 // Update the window manager capabilities. 1421#if SDL_LIBDECOR_CHECK_VERSION(0, 3, 0) 1422 enum libdecor_wm_capabilities caps; 1423#ifdef SDL_VIDEO_DRIVER_WAYLAND_DYNAMIC_LIBDECOR 1424 if (libdecor_frame_get_wm_capabilities) { 1425 caps = libdecor_frame_get_wm_capabilities(wind->shell_surface.libdecor.frame); 1426#else 1427 caps = libdecor_frame_get_wm_capabilities(wind->shell_surface.libdecor.frame); 1428 { 1429#endif 1430 wind->wm_caps = 0; 1431 wind->wm_caps |= caps & LIBDECOR_WM_CAPABILITIES_WINDOW_MENU ? WAYLAND_WM_CAPS_WINDOW_MENU : 0; 1432 wind->wm_caps |= caps & LIBDECOR_WM_CAPABILITIES_MAXIMIZE ? WAYLAND_WM_CAPS_MAXIMIZE : 0; 1433 wind->wm_caps |= caps & LIBDECOR_WM_CAPABILITIES_FULLSCREEN ? WAYLAND_WM_CAPS_FULLSCREEN : 0; 1434 wind->wm_caps |= caps & LIBDECOR_WM_CAPABILITIES_MINIMIZE ? WAYLAND_WM_CAPS_MINIMIZE : 0; 1435 } 1436#endif 1437 1438 if (!wind->resizing || started_resize) { 1439 /* Calculate the new window geometry and commit the changes on the libdecor side. 1440 * 1441 * XXX: This will potentially leave un-acked configurations, but libdecor invalidates the 1442 * configuration upon returning from the frame event, so there is nothing that can be 1443 * done, unless libdecor adds the ability to copy or refcount the configuration state 1444 * to apply later. 1445 */ 1446 ConfigureWindowGeometry(window); 1447 struct libdecor_state *state = libdecor_state_new(wind->current.logical_width, wind->current.logical_height); 1448 libdecor_frame_commit(frame, state, configuration); 1449 libdecor_state_free(state); 1450 } 1451 1452 if (wind->shell_surface_status == WAYLAND_SHELL_SURFACE_STATUS_WAITING_FOR_CONFIGURE) { 1453 LibdecorGetMinContentSize(frame, &wind->system_limits.min_width, &wind->system_limits.min_height); 1454 wind->shell_surface_status = WAYLAND_SHELL_SURFACE_STATUS_WAITING_FOR_FRAME; 1455 } 1456 1457 /* Update the resize capability if this config event was the result of the 1458 * compositor taking a window out of fullscreen. Since this will change the 1459 * capabilities and commit a new frame state with the last known content 1460 * dimension, this has to be called after the new state has been committed 1461 * and the new content dimensions were updated. 1462 */ 1463 if (prev_fullscreen && !wind->is_fullscreen) { 1464 Wayland_SetWindowResizable(SDL_GetVideoDevice(), window, 1465 !!(window->flags & SDL_WINDOW_RESIZABLE)); 1466 } 1467} 1468 1469static void decoration_frame_close(struct libdecor_frame *frame, void *user_data) 1470{ 1471 SDL_SendWindowEvent(((SDL_WindowData *)user_data)->sdlwindow, SDL_EVENT_WINDOW_CLOSE_REQUESTED, 0, 0); 1472} 1473 1474static void decoration_frame_commit(struct libdecor_frame *frame, void *user_data) 1475{ 1476 /* libdecor decoration subsurfaces are synchronous, so the client needs to 1477 * commit a frame to trigger an update of the decoration surfaces. 1478 */ 1479 SDL_WindowData *wind = (SDL_WindowData *)user_data; 1480 if (!wind->suspended && wind->shell_surface_status == WAYLAND_SHELL_SURFACE_STATUS_SHOWN) { 1481 SDL_SendWindowEvent(wind->sdlwindow, SDL_EVENT_WINDOW_EXPOSED, 0, 0); 1482 } 1483} 1484 1485static void decoration_dismiss_popup(struct libdecor_frame *frame, const char *seat_name, void *user_data) 1486{ 1487 // NOP 1488} 1489 1490#if SDL_LIBDECOR_CHECK_VERSION(0, 3, 0) 1491static void decoration_frame_bounds(struct libdecor_frame *frame, int width, int height, void *user_data) 1492{ 1493 SDL_WindowData *window = (SDL_WindowData *)user_data; 1494 window->toplevel_bounds.width = width; 1495 window->toplevel_bounds.height = height; 1496} 1497#endif 1498 1499static struct libdecor_frame_interface libdecor_frame_interface = { 1500 decoration_frame_configure, 1501 decoration_frame_close, 1502 decoration_frame_commit, 1503 decoration_dismiss_popup, 1504#if SDL_LIBDECOR_CHECK_VERSION(0, 3, 0) 1505 decoration_frame_bounds 1506#endif 1507}; 1508#endif 1509 1510static void Wayland_HandlePreferredScaleChanged(SDL_WindowData *window_data, double factor) 1511{ 1512 const double old_factor = window_data->scale_factor; 1513 1514 // Round the scale factor if viewports aren't available. 1515 if (!window_data->viewport) { 1516 factor = SDL_ceil(factor); 1517 } 1518 1519 if (factor != old_factor) { 1520 window_data->scale_factor = factor; 1521 1522 if (window_data->scale_to_display) { 1523 /* If the window is in the floating state with a user/application specified size, calculate the new 1524 * logical size from the backbuffer size. Otherwise, use the fixed underlying logical size to calculate 1525 * the new backbuffer dimensions. 1526 */ 1527 if (window_data->floating) { 1528 window_data->requested.logical_width = PixelToPoint(window_data->sdlwindow, window_data->requested.pixel_width); 1529 window_data->requested.logical_height = PixelToPoint(window_data->sdlwindow, window_data->requested.pixel_height); 1530 } else { 1531 window_data->requested.pixel_width = PointToPixel(window_data->sdlwindow, window_data->requested.logical_width); 1532 window_data->requested.pixel_height = PointToPixel(window_data->sdlwindow, window_data->requested.logical_height); 1533 } 1534 } 1535 1536 if (window_data->sdlwindow->flags & SDL_WINDOW_HIGH_PIXEL_DENSITY || window_data->scale_to_display) { 1537 ConfigureWindowGeometry(window_data->sdlwindow); 1538 CommitLibdecorFrame(window_data->sdlwindow); 1539 } 1540 } 1541} 1542 1543static void Wayland_MaybeUpdateScaleFactor(SDL_WindowData *window) 1544{ 1545 double factor; 1546 int i; 1547 1548 /* If the fractional scale protocol is present or the core protocol supports the 1549 * preferred buffer scale event, the compositor will explicitly tell the application 1550 * what scale it wants via these events, so don't try to determine the scale factor 1551 * from which displays the surface has entered. 1552 */ 1553 if (window->fractional_scale || wl_surface_get_version(window->surface) >= WL_SURFACE_PREFERRED_BUFFER_SCALE_SINCE_VERSION) { 1554 return; 1555 } 1556 1557 if (window->num_outputs != 0) { 1558 // Check every display's factor, use the highest 1559 factor = 0.0; 1560 for (i = 0; i < window->num_outputs; i++) { 1561 SDL_DisplayData *internal = window->outputs[i]; 1562 factor = SDL_max(factor, internal->scale_factor); 1563 } 1564 } else { 1565 // All outputs removed, just fall back. 1566 factor = window->scale_factor; 1567 } 1568 1569 Wayland_HandlePreferredScaleChanged(window, factor); 1570} 1571 1572void Wayland_RemoveOutputFromWindow(SDL_WindowData *window, SDL_DisplayData *display_data) 1573{ 1574 for (int i = 0; i < window->num_outputs; i++) { 1575 if (window->outputs[i] == display_data) { // remove this one 1576 if (i == (window->num_outputs - 1)) { 1577 window->outputs[i] = NULL; 1578 } else { 1579 SDL_memmove(&window->outputs[i], 1580 &window->outputs[i + 1], 1581 sizeof(SDL_DisplayData *) * ((window->num_outputs - i) - 1)); 1582 } 1583 window->num_outputs--; 1584 i--; 1585 } 1586 } 1587 1588 if (window->num_outputs == 0) { 1589 SDL_free(window->outputs); 1590 window->outputs = NULL; 1591 } else if (!window->is_fullscreen || window->num_outputs == 1) { 1592 Wayland_move_window(window->sdlwindow); 1593 Wayland_MaybeUpdateScaleFactor(window); 1594 } 1595} 1596 1597static void handle_surface_enter(void *data, struct wl_surface *surface, struct wl_output *output) 1598{ 1599 SDL_WindowData *window = data; 1600 SDL_DisplayData *internal = wl_output_get_user_data(output); 1601 SDL_DisplayData **new_outputs; 1602 1603 if (!SDL_WAYLAND_own_output(output) || !SDL_WAYLAND_own_surface(surface)) { 1604 return; 1605 } 1606 1607 new_outputs = SDL_realloc(window->outputs, 1608 sizeof(SDL_DisplayData *) * (window->num_outputs + 1)); 1609 if (!new_outputs) { 1610 return; 1611 } 1612 window->outputs = new_outputs; 1613 window->outputs[window->num_outputs++] = internal; 1614 1615 // Update the scale factor after the move so that fullscreen outputs are updated. 1616 if (!window->is_fullscreen || window->num_outputs == 1) { 1617 Wayland_move_window(window->sdlwindow); 1618 Wayland_MaybeUpdateScaleFactor(window); 1619 } 1620} 1621 1622static void handle_surface_leave(void *data, struct wl_surface *surface, struct wl_output *output) 1623{ 1624 SDL_WindowData *window = (SDL_WindowData *)data; 1625 1626 if (!SDL_WAYLAND_own_output(output) || !SDL_WAYLAND_own_surface(surface)) { 1627 return; 1628 } 1629 1630 Wayland_RemoveOutputFromWindow(window, (SDL_DisplayData *)wl_output_get_user_data(output)); 1631} 1632 1633static void handle_surface_preferred_buffer_scale(void *data, struct wl_surface *wl_surface, int32_t factor) 1634{ 1635 SDL_WindowData *wind = data; 1636 1637 /* The spec is unclear on how this interacts with the fractional scaling protocol, 1638 * so, for now, assume that the fractional scaling protocol takes priority and 1639 * only listen to this event if the fractional scaling protocol is not present. 1640 */ 1641 if (!wind->fractional_scale) { 1642 Wayland_HandlePreferredScaleChanged(data, (double)factor); 1643 } 1644} 1645 1646static void handle_surface_preferred_buffer_transform(void *data, struct wl_surface *wl_surface, uint32_t transform) 1647{ 1648 // Nothing to do here. 1649} 1650 1651static const struct wl_surface_listener surface_listener = { 1652 handle_surface_enter, 1653 handle_surface_leave, 1654 handle_surface_preferred_buffer_scale, 1655 handle_surface_preferred_buffer_transform 1656}; 1657 1658static void handle_fractional_scale_preferred(void *data, struct wp_fractional_scale_v1 *wp_fractional_scale_v1, uint32_t scale) 1659{ 1660 const double factor = (double)scale / 120.; // 120 is a magic number defined in the spec as a common denominator 1661 Wayland_HandlePreferredScaleChanged(data, factor); 1662} 1663 1664static const struct wp_fractional_scale_v1_listener fractional_scale_listener = { 1665 handle_fractional_scale_preferred 1666}; 1667 1668static void frog_preferred_metadata_handler(void *data, struct frog_color_managed_surface *frog_color_managed_surface, uint32_t transfer_function, 1669 uint32_t output_display_primary_red_x, uint32_t output_display_primary_red_y, 1670 uint32_t output_display_primary_green_x, uint32_t output_display_primary_green_y, 1671 uint32_t output_display_primary_blue_x, uint32_t output_display_primary_blue_y, 1672 uint32_t output_white_point_x, uint32_t output_white_point_y, 1673 uint32_t max_luminance, uint32_t min_luminance, 1674 uint32_t max_full_frame_luminance) 1675{ 1676 SDL_WindowData *wind = (SDL_WindowData *)data; 1677 SDL_HDROutputProperties HDR; 1678 1679 SDL_zero(HDR); 1680 1681 switch (transfer_function) { 1682 case FROG_COLOR_MANAGED_SURFACE_TRANSFER_FUNCTION_ST2084_PQ: 1683 /* ITU-R BT.2408-7 (Sept 2023) has the reference PQ white level at 203 nits, 1684 * while older Dolby documentation claims a reference level of 100 nits. 1685 * 1686 * Use 203 nits for now. 1687 */ 1688 HDR.HDR_headroom = max_luminance / 203.0f; 1689 break; 1690 case FROG_COLOR_MANAGED_SURFACE_TRANSFER_FUNCTION_SCRGB_LINEAR: 1691 HDR.HDR_headroom = max_luminance / 80.0f; 1692 break; 1693 case FROG_COLOR_MANAGED_SURFACE_TRANSFER_FUNCTION_UNDEFINED: 1694 case FROG_COLOR_MANAGED_SURFACE_TRANSFER_FUNCTION_SRGB: 1695 case FROG_COLOR_MANAGED_SURFACE_TRANSFER_FUNCTION_GAMMA_22: 1696 default: 1697 HDR.HDR_headroom = 1.0f; 1698 break; 1699 } 1700 1701 HDR.SDR_white_level = 1.0f; 1702 SDL_SetWindowHDRProperties(wind->sdlwindow, &HDR, true); 1703} 1704 1705static const struct frog_color_managed_surface_listener frog_surface_listener = { 1706 frog_preferred_metadata_handler 1707}; 1708 1709 1710static void handle_surface_feedback_preferred_changed2(void *data, 1711 struct wp_color_management_surface_feedback_v1 *wp_color_management_surface_feedback_v1, 1712 uint32_t identity_hi, uint32_t identity_lo) 1713{ 1714 SDL_WindowData *wind = (SDL_WindowData *)data; 1715 Wayland_GetColorInfoForWindow(wind, false); 1716} 1717 1718static void handle_surface_feedback_preferred_changed(void *data, 1719 struct wp_color_management_surface_feedback_v1 *wp_color_management_surface_feedback_v1, 1720 uint32_t identity) 1721{ 1722 handle_surface_feedback_preferred_changed2(data, wp_color_management_surface_feedback_v1, 0, identity); 1723} 1724 1725static const struct wp_color_management_surface_feedback_v1_listener color_management_surface_feedback_listener = { 1726 handle_surface_feedback_preferred_changed, 1727 handle_surface_feedback_preferred_changed2 1728}; 1729 1730static void Wayland_SetKeyboardFocus(SDL_Window *window, bool set_focus) 1731{ 1732 SDL_Window *toplevel = window; 1733 1734 // Find the toplevel parent 1735 while (SDL_WINDOW_IS_POPUP(toplevel)) { 1736 toplevel = toplevel->parent; 1737 } 1738 1739 toplevel->keyboard_focus = window; 1740 1741 if (set_focus && !window->is_hiding && !window->is_destroying) { 1742 SDL_SetKeyboardFocus(window); 1743 } 1744} 1745 1746bool Wayland_SetWindowHitTest(SDL_Window *window, bool enabled) 1747{ 1748 return true; // just succeed, the real work is done elsewhere. 1749} 1750 1751static struct xdg_toplevel *GetToplevelForWindow(SDL_WindowData *wind) 1752{ 1753 if (wind) { 1754 /* Libdecor crashes on attempts to unset the parent by passing null, which is allowed by the 1755 * toplevel spec, so just use the raw xdg-toplevel instead (that's what libdecor does 1756 * internally anyways). 1757 */ 1758#ifdef HAVE_LIBDECOR_H 1759 if (wind->shell_surface_type == WAYLAND_SHELL_SURFACE_TYPE_LIBDECOR && wind->shell_surface.libdecor.frame) { 1760 return libdecor_frame_get_xdg_toplevel(wind->shell_surface.libdecor.frame); 1761 } else 1762#endif 1763 if (wind->shell_surface_type == WAYLAND_SHELL_SURFACE_TYPE_XDG_TOPLEVEL && wind->shell_surface.xdg.toplevel.xdg_toplevel) { 1764 return wind->shell_surface.xdg.toplevel.xdg_toplevel; 1765 } 1766 } 1767 1768 return NULL; 1769} 1770 1771bool Wayland_SetWindowParent(SDL_VideoDevice *_this, SDL_Window *window, SDL_Window *parent_window) 1772{ 1773 SDL_WindowData *child_data = window->internal; 1774 SDL_WindowData *parent_data = parent_window ? parent_window->internal : NULL; 1775 1776 child_data->reparenting_required = false; 1777 1778 if (parent_data && parent_data->shell_surface_status != WAYLAND_SHELL_SURFACE_STATUS_SHOWN) { 1779 // Need to wait for the parent to become mapped, or it's the same as setting a null parent. 1780 child_data->reparenting_required = true; 1781 return true; 1782 } 1783 1784 struct xdg_toplevel *child_toplevel = GetToplevelForWindow(child_data); 1785 struct xdg_toplevel *parent_toplevel = GetToplevelForWindow(parent_data); 1786 1787 if (child_toplevel) { 1788 xdg_toplevel_set_parent(child_toplevel, parent_toplevel); 1789 } 1790 1791 return true; 1792} 1793 1794bool Wayland_SetWindowModal(SDL_VideoDevice *_this, SDL_Window *window, bool modal) 1795{ 1796 SDL_VideoData *viddata = _this->internal; 1797 SDL_WindowData *data = window->internal; 1798 SDL_WindowData *parent_data = window->parent->internal; 1799 1800 if (parent_data->shell_surface_status != WAYLAND_SHELL_SURFACE_STATUS_SHOWN) { 1801 // Need to wait for the parent to become mapped before changing modal status. 1802 data->reparenting_required = true; 1803 return true; 1804 } else { 1805 data->reparenting_required = false; 1806 } 1807 1808 struct xdg_toplevel *toplevel = GetToplevelForWindow(data); 1809 1810 if (toplevel) { 1811 if (viddata->xdg_wm_dialog_v1) { 1812 if (modal) { 1813 if (!data->xdg_dialog_v1) { 1814 data->xdg_dialog_v1 = xdg_wm_dialog_v1_get_xdg_dialog(viddata->xdg_wm_dialog_v1, toplevel); 1815 } 1816 1817 xdg_dialog_v1_set_modal(data->xdg_dialog_v1); 1818 } else if (data->xdg_dialog_v1) { 1819 xdg_dialog_v1_unset_modal(data->xdg_dialog_v1); 1820 } 1821 } 1822 } 1823 1824 return true; 1825} 1826 1827static void show_hide_sync_handler(void *data, struct wl_callback *callback, uint32_t callback_data) 1828{ 1829 // Get the window from the ID as it may have been destroyed 1830 SDL_WindowID windowID = (SDL_WindowID)((uintptr_t)data); 1831 SDL_Window *window = SDL_GetWindowFromID(windowID); 1832 1833 if (window && window->internal) { 1834 SDL_WindowData *wind = window->internal; 1835 wind->show_hide_sync_required = false; 1836 } 1837 1838 wl_callback_destroy(callback); 1839} 1840 1841static struct wl_callback_listener show_hide_sync_listener = { 1842 show_hide_sync_handler 1843}; 1844 1845static void exported_handle_handler(void *data, struct zxdg_exported_v2 *zxdg_exported_v2, const char *handle) 1846{ 1847 SDL_WindowData *wind = (SDL_WindowData *)data; 1848 SDL_PropertiesID props = SDL_GetWindowProperties(wind->sdlwindow); 1849 1850 SDL_SetStringProperty(props, SDL_PROP_WINDOW_WAYLAND_XDG_TOPLEVEL_EXPORT_HANDLE_STRING, handle); 1851} 1852 1853static struct zxdg_exported_v2_listener exported_v2_listener = { 1854 exported_handle_handler 1855}; 1856 1857void Wayland_ShowWindow(SDL_VideoDevice *_this, SDL_Window *window) 1858{ 1859 SDL_VideoData *c = _this->internal; 1860 SDL_WindowData *data = window->internal; 1861 SDL_PropertiesID props = SDL_GetWindowProperties(window); 1862 1863 // Custom surfaces don't get toplevels and are always considered 'shown'; nothing to do here. 1864 if (data->shell_surface_type == WAYLAND_SHELL_SURFACE_TYPE_CUSTOM) { 1865 return; 1866 } 1867 1868 /* If this is a child window, the parent *must* be in the final shown state, 1869 * meaning that it has received a configure event, followed by a frame callback. 1870 * If not, a race condition can result, with effects ranging from the child 1871 * window to spuriously closing to protocol errors. 1872 * 1873 * If waiting on the parent window, set the pending status and the window will 1874 * be shown when the parent is in the shown state. 1875 */ 1876 if (window->parent) { 1877 if (window->parent->internal->shell_surface_status != WAYLAND_SHELL_SURFACE_STATUS_SHOWN) { 1878 data->shell_surface_status = WAYLAND_SHELL_SURFACE_STATUS_SHOW_PENDING; 1879 return; 1880 } 1881 } 1882 1883 // Always roundtrip to ensure there are no pending buffer attachments. 1884 do { 1885 WAYLAND_wl_display_roundtrip(c->display); 1886 } while (data->show_hide_sync_required); 1887 1888 data->shell_surface_status = WAYLAND_SHELL_SURFACE_STATUS_WAITING_FOR_CONFIGURE; 1889 1890 /* Detach any previous buffers before resetting everything, otherwise when 1891 * calling this a second time you'll get an annoying protocol error! 1892 * 1893 * FIXME: This was originally moved to HideWindow, which _should_ make 1894 * sense, but for whatever reason UE5's popups require that this actually 1895 * be in both places at once? Possibly from renderers making commits? I can't 1896 * fully remember if this location caused crashes or if I was fixing a pair 1897 * of Hide/Show calls. In any case, UE gives us a pretty good test and having 1898 * both detach calls passes. This bug may be relevant if I'm wrong: 1899 * 1900 * https://bugs.kde.org/show_bug.cgi?id=448856 1901 * 1902 * -flibit 1903 */ 1904 wl_surface_attach(data->surface, NULL, 0, 0); 1905 wl_surface_commit(data->surface); 1906 1907 // Create the shell surface and map the toplevel/popup 1908#ifdef HAVE_LIBDECOR_H 1909 if (data->shell_surface_type == WAYLAND_SHELL_SURFACE_TYPE_LIBDECOR) { 1910 data->shell_surface.libdecor.frame = libdecor_decorate(c->shell.libdecor, 1911 data->surface, 1912 &libdecor_frame_interface, 1913 data); 1914 if (!data->shell_surface.libdecor.frame) { 1915 SDL_LogError(SDL_LOG_CATEGORY_VIDEO, "Failed to create libdecor frame!"); 1916 } else { 1917 libdecor_frame_set_app_id(data->shell_surface.libdecor.frame, data->app_id); 1918 libdecor_frame_map(data->shell_surface.libdecor.frame); 1919 if (window->flags & SDL_WINDOW_BORDERLESS) { 1920 // Note: Calling this with 'true' immediately after mapping will cause the libdecor Cairo plugin to crash. 1921 libdecor_frame_set_visibility(data->shell_surface.libdecor.frame, false); 1922 } 1923 1924 if (c->zxdg_exporter_v2) { 1925 data->exported = zxdg_exporter_v2_export_toplevel(c->zxdg_exporter_v2, data->surface); 1926 zxdg_exported_v2_add_listener(data->exported, &exported_v2_listener, data); 1927 } 1928 1929 if (c->xdg_toplevel_icon_manager_v1 && data->xdg_toplevel_icon_v1) { 1930 xdg_toplevel_icon_manager_v1_set_icon(_this->internal->xdg_toplevel_icon_manager_v1, 1931 libdecor_frame_get_xdg_toplevel(data->shell_surface.libdecor.frame), 1932 data->xdg_toplevel_icon_v1); 1933 } 1934 1935 SDL_SetPointerProperty(props, SDL_PROP_WINDOW_WAYLAND_XDG_SURFACE_POINTER, libdecor_frame_get_xdg_surface(data->shell_surface.libdecor.frame)); 1936 SDL_SetPointerProperty(props, SDL_PROP_WINDOW_WAYLAND_XDG_TOPLEVEL_POINTER, libdecor_frame_get_xdg_toplevel(data->shell_surface.libdecor.frame)); 1937 } 1938 } else 1939#endif 1940 if (data->shell_surface_type == WAYLAND_SHELL_SURFACE_TYPE_XDG_TOPLEVEL || data->shell_surface_type == WAYLAND_SHELL_SURFACE_TYPE_XDG_POPUP) { 1941 data->shell_surface.xdg.surface = xdg_wm_base_get_xdg_surface(c->shell.xdg, data->surface); 1942 xdg_surface_set_user_data(data->shell_surface.xdg.surface, data); 1943 xdg_surface_add_listener(data->shell_surface.xdg.surface, &_xdg_surface_listener, data); 1944 SDL_SetPointerProperty(SDL_GetWindowProperties(window), SDL_PROP_WINDOW_WAYLAND_XDG_SURFACE_POINTER, data->shell_surface.xdg.surface); 1945 1946 if (data->shell_surface_type == WAYLAND_SHELL_SURFACE_TYPE_XDG_POPUP) { 1947 SDL_Window *parent = window->parent; 1948 SDL_WindowData *parent_data = parent->internal; 1949 struct xdg_surface *parent_xdg_surface = NULL; 1950 int position_x = 0, position_y = 0; 1951 1952 // Configure the popup parameters 1953#ifdef HAVE_LIBDECOR_H 1954 if (parent_data->shell_surface_type == WAYLAND_SHELL_SURFACE_TYPE_LIBDECOR) { 1955 parent_xdg_surface = libdecor_frame_get_xdg_surface(parent_data->shell_surface.libdecor.frame); 1956 } else 1957#endif 1958 if (parent_data->shell_surface_type == WAYLAND_SHELL_SURFACE_TYPE_XDG_TOPLEVEL || 1959 parent_data->shell_surface_type == WAYLAND_SHELL_SURFACE_TYPE_XDG_POPUP) { 1960 parent_xdg_surface = parent_data->shell_surface.xdg.surface; 1961 } 1962 1963 // Set up the positioner for the popup and configure the constraints 1964 data->shell_surface.xdg.popup.xdg_positioner = xdg_wm_base_create_positioner(c->shell.xdg); 1965 xdg_positioner_set_anchor(data->shell_surface.xdg.popup.xdg_positioner, XDG_POSITIONER_ANCHOR_TOP_LEFT); 1966 xdg_positioner_set_anchor_rect(data->shell_surface.xdg.popup.xdg_positioner, 0, 0, parent->internal->current.logical_width, parent->internal->current.logical_height); 1967 1968 const Uint32 constraint = window->constrain_popup ? (XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_SLIDE_X | XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_SLIDE_Y) : XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_NONE; 1969 xdg_positioner_set_constraint_adjustment(data->shell_surface.xdg.popup.xdg_positioner, constraint); 1970 xdg_positioner_set_gravity(data->shell_surface.xdg.popup.xdg_positioner, XDG_POSITIONER_GRAVITY_BOTTOM_RIGHT); 1971 xdg_positioner_set_size(data->shell_surface.xdg.popup.xdg_positioner, data->current.logical_width, data->current.logical_height); 1972 1973 // Set the popup initial position 1974 position_x = window->last_position_pending ? window->pending.x : window->x; 1975 position_y = window->last_position_pending ? window->pending.y : window->y; 1976 EnsurePopupPositionIsValid(window, &position_x, &position_y); 1977 if (data->scale_to_display) { 1978 position_x = PixelToPoint(window->parent, position_x); 1979 position_y = PixelToPoint(window->parent, position_y); 1980 } 1981 AdjustPopupOffset(window, &position_x, &position_y); 1982 xdg_positioner_set_offset(data->shell_surface.xdg.popup.xdg_positioner, position_x, position_y); 1983 1984 // Assign the popup role 1985 data->shell_surface.xdg.popup.xdg_popup = xdg_surface_get_popup(data->shell_surface.xdg.surface, 1986 parent_xdg_surface, 1987 data->shell_surface.xdg.popup.xdg_positioner); 1988 xdg_popup_add_listener(data->shell_surface.xdg.popup.xdg_popup, &_xdg_popup_listener, data); 1989 1990 if (window->flags & SDL_WINDOW_TOOLTIP) { 1991 struct wl_region *region; 1992 1993 // Tooltips can't be interacted with, so turn off the input region to avoid blocking anything behind them 1994 region = wl_compositor_create_region(c->compositor); 1995 wl_region_add(region, 0, 0, 0, 0); 1996 wl_surface_set_input_region(data->surface, region); 1997 wl_region_destroy(region); 1998 } else if ((window->flags & SDL_WINDOW_POPUP_MENU) && !(window->flags & SDL_WINDOW_NOT_FOCUSABLE)) { 1999 Wayland_SetKeyboardFocus(window, true); 2000 } 2001 2002 SDL_SetPointerProperty(props, SDL_PROP_WINDOW_WAYLAND_XDG_POPUP_POINTER, data->shell_surface.xdg.popup.xdg_popup); 2003 SDL_SetPointerProperty(props, SDL_PROP_WINDOW_WAYLAND_XDG_POSITIONER_POINTER, data->shell_surface.xdg.popup.xdg_positioner); 2004 } else { 2005 data->shell_surface.xdg.toplevel.xdg_toplevel = xdg_surface_get_toplevel(data->shell_surface.xdg.surface); 2006 xdg_toplevel_set_app_id(data->shell_surface.xdg.toplevel.xdg_toplevel, data->app_id); 2007 xdg_toplevel_add_listener(data->shell_surface.xdg.toplevel.xdg_toplevel, &toplevel_listener_xdg, data); 2008 2009 // Create the window decorations 2010 if (c->decoration_manager) { 2011 data->server_decoration = zxdg_decoration_manager_v1_get_toplevel_decoration(c->decoration_manager, data->shell_surface.xdg.toplevel.xdg_toplevel); 2012 zxdg_toplevel_decoration_v1_add_listener(data->server_decoration, &xdg_toplevel_decoration_listener, window); 2013 const enum zxdg_toplevel_decoration_v1_mode mode = !(window->flags & SDL_WINDOW_BORDERLESS) ? ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE : ZXDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE; 2014 zxdg_toplevel_decoration_v1_set_mode(data->server_decoration, mode); 2015 } 2016 2017 if (c->zxdg_exporter_v2) { 2018 data->exported = zxdg_exporter_v2_export_toplevel(c->zxdg_exporter_v2, data->surface); 2019 zxdg_exported_v2_add_listener(data->exported, &exported_v2_listener, data); 2020 } 2021 2022 if (c->xdg_toplevel_icon_manager_v1 && data->xdg_toplevel_icon_v1) { 2023 xdg_toplevel_icon_manager_v1_set_icon(_this->internal->xdg_toplevel_icon_manager_v1, 2024 data->shell_surface.xdg.toplevel.xdg_toplevel, 2025 data->xdg_toplevel_icon_v1); 2026 } 2027 2028 SDL_SetPointerProperty(props, SDL_PROP_WINDOW_WAYLAND_XDG_TOPLEVEL_POINTER, data->shell_surface.xdg.toplevel.xdg_toplevel); 2029 } 2030 } 2031 2032 // Restore state that was set prior to this call 2033 Wayland_SetWindowParent(_this, window, window->parent); 2034 2035 if (window->flags & SDL_WINDOW_MODAL) { 2036 Wayland_SetWindowModal(_this, window, true); 2037 } 2038 2039 Wayland_SetWindowTitle(_this, window); 2040 2041 /* We have to wait until the surface gets a "configure" event, or use of 2042 * this surface will fail. This is a new rule for xdg_shell. 2043 */ 2044#ifdef HAVE_LIBDECOR_H 2045 if (data->shell_surface_type == WAYLAND_SHELL_SURFACE_TYPE_LIBDECOR) { 2046 if (data->shell_surface.libdecor.frame) { 2047 while (data->shell_surface_status == WAYLAND_SHELL_SURFACE_STATUS_WAITING_FOR_CONFIGURE) { 2048 if (libdecor_dispatch(c->shell.libdecor, -1) < 0) { 2049 if (!Wayland_HandleDisplayDisconnected(_this)) { 2050 return; 2051 } 2052 } 2053 if (WAYLAND_wl_display_dispatch_pending(c->display) < 0) { 2054 if (!Wayland_HandleDisplayDisconnected(_this)) { 2055 return; 2056 } 2057 } 2058 } 2059 } 2060 } else 2061#endif 2062 if (data->shell_surface_type == WAYLAND_SHELL_SURFACE_TYPE_XDG_POPUP || data->shell_surface_type == WAYLAND_SHELL_SURFACE_TYPE_XDG_TOPLEVEL) { 2063 /* Unlike libdecor we need to call this explicitly to prevent a deadlock. 2064 * libdecor will call this as part of their configure event! 2065 * -flibit 2066 */ 2067 wl_surface_commit(data->surface); 2068 if (data->shell_surface.xdg.surface) { 2069 while (data->shell_surface_status == WAYLAND_SHELL_SURFACE_STATUS_WAITING_FOR_CONFIGURE) { 2070 if (WAYLAND_wl_display_dispatch(c->display) < 0) { 2071 if (!Wayland_HandleDisplayDisconnected(_this)) { 2072 return; 2073 } 2074 } 2075 } 2076 } 2077 } else { 2078 // Nothing to see here, just commit. 2079 wl_surface_commit(data->surface); 2080 } 2081 2082 // Make sure the window can't be resized to 0 or it can be spuriously closed by the window manager. 2083 data->system_limits.min_width = SDL_max(data->system_limits.min_width, 1); 2084 data->system_limits.min_height = SDL_max(data->system_limits.min_height, 1); 2085 2086 /* Unlike the rest of window state we have to set this _after_ flushing the 2087 * display, because we need to create the decorations before possibly hiding 2088 * them immediately afterward. 2089 */ 2090#ifdef HAVE_LIBDECOR_H 2091 if (data->shell_surface_type == WAYLAND_SHELL_SURFACE_TYPE_LIBDECOR) { 2092 // Libdecor plugins can enforce minimum window sizes, so adjust if the initial window size is too small. 2093 if (window->windowed.w < data->system_limits.min_width || 2094 window->windowed.h < data->system_limits.min_height) { 2095 2096 // Warn if the window frame will be larger than the content surface. 2097 SDL_LogWarn(SDL_LOG_CATEGORY_VIDEO, 2098 "Window dimensions (%i, %i) are smaller than the system enforced minimum (%i, %i); window borders will be larger than the content surface.", 2099 window->windowed.w, window->windowed.h, data->system_limits.min_width, data->system_limits.min_height); 2100 2101 data->current.logical_width = SDL_max(window->windowed.w, data->system_limits.min_width); 2102 data->current.logical_height = SDL_max(window->windowed.h, data->system_limits.min_height); 2103 CommitLibdecorFrame(window); 2104 } 2105 } 2106#endif 2107 Wayland_SetWindowResizable(_this, window, !!(window->flags & SDL_WINDOW_RESIZABLE)); 2108 2109 // We're finally done putting the window together, raise if possible 2110 if (c->activation_manager) { 2111 /* Note that we don't check for empty strings, as that is still 2112 * considered a valid activation token! 2113 */ 2114 const char *activation_token = SDL_getenv("XDG_ACTIVATION_TOKEN"); 2115 if (activation_token) { 2116 xdg_activation_v1_activate(c->activation_manager, 2117 activation_token, 2118 data->surface); 2119 2120 // Clear this variable, per the protocol's request 2121 SDL_unsetenv_unsafe("XDG_ACTIVATION_TOKEN"); 2122 } 2123 } 2124 2125 data->show_hide_sync_required = true; 2126 struct wl_callback *cb = wl_display_sync(_this->internal->display); 2127 wl_callback_add_listener(cb, &show_hide_sync_listener, (void *)((uintptr_t)window->id)); 2128 2129 data->showing_window = true; 2130 SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_SHOWN, 0, 0); 2131 data->showing_window = false; 2132 2133 // Send an exposure event to signal that the client should draw. 2134 if (data->shell_surface_status == WAYLAND_SHELL_SURFACE_STATUS_WAITING_FOR_FRAME) { 2135 SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_EXPOSED, 0, 0); 2136 } 2137} 2138 2139static void Wayland_ReleasePopup(SDL_VideoDevice *_this, SDL_Window *popup) 2140{ 2141 SDL_WindowData *popupdata; 2142 2143 // Basic sanity checks to weed out the weird popup closures 2144 if (!SDL_ObjectValid(popup, SDL_OBJECT_TYPE_WINDOW)) { 2145 return; 2146 } 2147 popupdata = popup->internal; 2148 if (!popupdata) { 2149 return; 2150 } 2151 2152 // This may already be freed by a parent popup! 2153 if (popupdata->shell_surface.xdg.popup.xdg_popup == NULL) { 2154 return; 2155 } 2156 2157 if ((popup->flags & SDL_WINDOW_POPUP_MENU) && !(popup->flags & SDL_WINDOW_NOT_FOCUSABLE)) { 2158 SDL_Window *new_focus; 2159 const bool set_focus = SDL_ShouldRelinquishPopupFocus(popup, &new_focus); 2160 Wayland_SetKeyboardFocus(new_focus, set_focus); 2161 } 2162 2163 xdg_popup_destroy(popupdata->shell_surface.xdg.popup.xdg_popup); 2164 xdg_positioner_destroy(popupdata->shell_surface.xdg.popup.xdg_positioner); 2165 popupdata->shell_surface.xdg.popup.xdg_popup = NULL; 2166 popupdata->shell_surface.xdg.popup.xdg_positioner = NULL; 2167 2168 SDL_PropertiesID props = SDL_GetWindowProperties(popup); 2169 SDL_SetPointerProperty(props, SDL_PROP_WINDOW_WAYLAND_XDG_POPUP_POINTER, NULL); 2170 SDL_SetPointerProperty(props, SDL_PROP_WINDOW_WAYLAND_XDG_POSITIONER_POINTER, NULL); 2171} 2172 2173void Wayland_HideWindow(SDL_VideoDevice *_this, SDL_Window *window) 2174{ 2175 SDL_VideoData *data = _this->internal; 2176 SDL_WindowData *wind = window->internal; 2177 SDL_PropertiesID props = SDL_GetWindowProperties(window); 2178 2179 // Custom surfaces have nothing to destroy and are always considered to be 'shown'; nothing to do here. 2180 if (wind->shell_surface_type == WAYLAND_SHELL_SURFACE_TYPE_CUSTOM) { 2181 return; 2182 } 2183 2184 /* The window was shown, but the sync point hasn't yet been reached. 2185 * Pump events to avoid a possible protocol violation. 2186 */ 2187 if (wind->show_hide_sync_required) { 2188 WAYLAND_wl_display_roundtrip(data->display); 2189 } 2190 2191 wind->shell_surface_status = WAYLAND_SHELL_SURFACE_STATUS_HIDDEN; 2192 2193 if (wind->server_decoration) { 2194 zxdg_toplevel_decoration_v1_destroy(wind->server_decoration); 2195 wind->server_decoration = NULL; 2196 } 2197 2198 // Clean up the export handle. 2199 if (wind->exported) { 2200 zxdg_exported_v2_destroy(wind->exported); 2201 wind->exported = NULL; 2202 2203 SDL_SetStringProperty(props, SDL_PROP_WINDOW_WAYLAND_XDG_TOPLEVEL_EXPORT_HANDLE_STRING, NULL); 2204 } 2205 2206 if (wind->xdg_dialog_v1) { 2207 xdg_dialog_v1_destroy(wind->xdg_dialog_v1); 2208 wind->xdg_dialog_v1 = NULL; 2209 } 2210 2211#ifdef HAVE_LIBDECOR_H 2212 if (wind->shell_surface_type == WAYLAND_SHELL_SURFACE_TYPE_LIBDECOR) { 2213 if (wind->shell_surface.libdecor.frame) { 2214 libdecor_frame_unref(wind->shell_surface.libdecor.frame); 2215 2216 SDL_SetPointerProperty(props, SDL_PROP_WINDOW_WAYLAND_XDG_SURFACE_POINTER, NULL); 2217 SDL_SetPointerProperty(props, SDL_PROP_WINDOW_WAYLAND_XDG_TOPLEVEL_POINTER, NULL); 2218 } 2219 } else 2220#endif 2221 { 2222 if (wind->shell_surface_type == WAYLAND_SHELL_SURFACE_TYPE_XDG_POPUP) { 2223 Wayland_ReleasePopup(_this, window); 2224 } else if (wind->shell_surface.xdg.toplevel.xdg_toplevel) { 2225 xdg_toplevel_destroy(wind->shell_surface.xdg.toplevel.xdg_toplevel); 2226 SDL_SetPointerProperty(props, SDL_PROP_WINDOW_WAYLAND_XDG_TOPLEVEL_POINTER, NULL); 2227 } 2228 2229 if (wind->shell_surface.xdg.surface) { 2230 xdg_surface_destroy(wind->shell_surface.xdg.surface); 2231 SDL_SetPointerProperty(props, SDL_PROP_WINDOW_WAYLAND_XDG_SURFACE_POINTER, NULL); 2232 } 2233 } 2234 2235 // Attach a null buffer to unmap the surface. 2236 wl_surface_attach(wind->surface, NULL, 0, 0); 2237 wl_surface_commit(wind->surface); 2238 2239 SDL_zero(wind->shell_surface); 2240 wind->show_hide_sync_required = true; 2241 struct wl_callback *cb = wl_display_sync(_this->internal->display); 2242 wl_callback_add_listener(cb, &show_hide_sync_listener, (void *)((uintptr_t)window->id)); 2243} 2244 2245static void handle_xdg_activation_done(void *data, 2246 struct xdg_activation_token_v1 *xdg_activation_token_v1, 2247 const char *token) 2248{ 2249 SDL_WindowData *window = data; 2250 if (xdg_activation_token_v1 == window->activation_token) { 2251 xdg_activation_v1_activate(window->waylandData->activation_manager, 2252 token, 2253 window->surface); 2254 xdg_activation_token_v1_destroy(window->activation_token); 2255 window->activation_token = NULL; 2256 } 2257} 2258 2259static const struct xdg_activation_token_v1_listener xdg_activation_listener = { 2260 handle_xdg_activation_done 2261}; 2262 2263/* The xdg-activation protocol considers "activation" to be one of two things: 2264 * 2265 * 1: Raising a window to the top and flashing the titlebar 2266 * 2: Flashing the titlebar while keeping the window where it is 2267 * 2268 * As you might expect from Wayland, the general policy is to go with #2 unless 2269 * the client can prove to the compositor beyond a reasonable doubt that raising 2270 * the window will not be malicious behavior. 2271 * 2272 * For SDL this means RaiseWindow and FlashWindow both use the same protocol, 2273 * but in different ways: RaiseWindow will provide as _much_ information as 2274 * possible while FlashWindow will provide as _little_ information as possible, 2275 * to nudge the compositor into doing what we want. 2276 * 2277 * This isn't _strictly_ what the protocol says will happen, but this is what 2278 * current implementations are doing (as of writing, YMMV in the far distant 2279 * future). 2280 * 2281 * -flibit 2282 */ 2283static void Wayland_activate_window(SDL_VideoData *data, SDL_WindowData *target_wind, bool set_serial) 2284{ 2285 SDL_WaylandSeat *seat = data->last_implicit_grab_seat; 2286 SDL_WindowData *focus = NULL; 2287 2288 if (seat) { 2289 focus = seat->keyboard.focus; 2290 if (!focus) { 2291 focus = seat->pointer.focus; 2292 } 2293 } 2294 2295 struct wl_surface *requesting_surface = focus ? focus->surface : NULL; 2296 2297 if (data->activation_manager) { 2298 if (target_wind->activation_token) { 2299 // We're about to overwrite this with a new request 2300 xdg_activation_token_v1_destroy(target_wind->activation_token); 2301 } 2302 2303 target_wind->activation_token = xdg_activation_v1_get_activation_token(data->activation_manager); 2304 xdg_activation_token_v1_add_listener(target_wind->activation_token, 2305 &xdg_activation_listener, 2306 target_wind); 2307 2308 /* Note that we are not setting the app_id here. 2309 * 2310 * Hypothetically we could set the app_id from data->classname, but 2311 * that part of the API is for _external_ programs, not ourselves. 2312 * 2313 * -flibit 2314 */ 2315 if (requesting_surface) { 2316 // This specifies the surface from which the activation request is originating, not the activation target surface. 2317 xdg_activation_token_v1_set_surface(target_wind->activation_token, requesting_surface); 2318 } 2319 if (set_serial && seat && seat->wl_seat) { 2320 xdg_activation_token_v1_set_serial(target_wind->activation_token, seat->last_implicit_grab_serial, seat->wl_seat); 2321 } 2322 xdg_activation_token_v1_commit(target_wind->activation_token); 2323 } 2324} 2325 2326void Wayland_RaiseWindow(SDL_VideoDevice *_this, SDL_Window *window) 2327{ 2328 Wayland_activate_window(_this->internal, window->internal, true); 2329} 2330 2331bool Wayland_FlashWindow(SDL_VideoDevice *_this, SDL_Window *window, SDL_FlashOperation operation) 2332{ 2333 /* Not setting the serial will specify 'urgency' without switching focus as per 2334 * https://gitlab.freedesktop.org/wayland/wayland-protocols/-/merge_requests/9#note_854977 2335 */ 2336 Wayland_activate_window(_this->internal, window->internal, false); 2337 return true; 2338} 2339 2340SDL_FullscreenResult Wayland_SetWindowFullscreen(SDL_VideoDevice *_this, SDL_Window *window, 2341 SDL_VideoDisplay *display, SDL_FullscreenOp fullscreen) 2342{ 2343 SDL_WindowData *wind = window->internal; 2344 struct wl_output *output = display->internal->output; 2345 2346 // Custom surfaces have no toplevel to make fullscreen. 2347 if (wind->shell_surface_type == WAYLAND_SHELL_SURFACE_TYPE_CUSTOM) { 2348 return SDL_FULLSCREEN_FAILED; 2349 } 2350 2351 // Drop fullscreen leave requests when showing the window. 2352 if (wind->showing_window && fullscreen == SDL_FULLSCREEN_OP_LEAVE) { 2353 return SDL_FULLSCREEN_SUCCEEDED; 2354 } 2355 2356 if (wind->show_hide_sync_required) { 2357 WAYLAND_wl_display_roundtrip(_this->internal->display); 2358 } 2359 2360 // Flushing old events pending a new one, ignore this request. 2361 if (wind->drop_fullscreen_requests) { 2362 return SDL_FULLSCREEN_SUCCEEDED; 2363 } 2364 2365 wind->drop_fullscreen_requests = true; 2366 FlushPendingEvents(window); 2367 wind->drop_fullscreen_requests = false; 2368 2369 // Nothing to do if the window is not fullscreen, and this isn't an explicit enter request. 2370 if (!wind->is_fullscreen) { 2371 if (fullscreen == SDL_FULLSCREEN_OP_UPDATE) { 2372 // Request was out of date; signal the video core not to update any state. 2373 return SDL_FULLSCREEN_PENDING; 2374 } else if (fullscreen == SDL_FULLSCREEN_OP_LEAVE) { 2375 // Already not fullscreen; nothing to do. 2376 return SDL_FULLSCREEN_SUCCEEDED; 2377 } 2378 } 2379 2380 // Don't send redundant fullscreen set/unset events. 2381 if (!!fullscreen != wind->is_fullscreen) { 2382 wind->fullscreen_was_positioned = !!fullscreen; 2383 2384 /* Only use the specified output if an exclusive mode is being used, or a position was explicitly requested 2385 * before entering fullscreen desktop. Otherwise, let the compositor handle placement, as it has more 2386 * information about where the window is and where it should go, particularly if fullscreen is being requested 2387 * before the window is mapped, or the window spans multiple outputs. 2388 */ 2389 if (!window->fullscreen_exclusive) { 2390 if (window->undefined_x || window->undefined_y || 2391 (wind->num_outputs && !window->last_position_pending)) { 2392 output = NULL; 2393 } 2394 } 2395 SetFullscreen(window, output, !!fullscreen); 2396 } else if (wind->is_fullscreen) { 2397 /* 2398 * If the window is already fullscreen, this is likely a request to switch between 2399 * fullscreen and fullscreen desktop, change outputs, or change the video mode. 2400 * 2401 * If the window is already positioned on the target output, just update the 2402 * window geometry. 2403 */ 2404 if (wind->last_displayID != display->id) { 2405 wind->fullscreen_was_positioned = true; 2406 SetFullscreen(window, output, true); 2407 } else { 2408 ConfigureWindowGeometry(window); 2409 CommitLibdecorFrame(window); 2410 2411 return SDL_FULLSCREEN_SUCCEEDED; 2412 } 2413 } 2414 2415 return SDL_FULLSCREEN_PENDING; 2416} 2417 2418void Wayland_RestoreWindow(SDL_VideoDevice *_this, SDL_Window *window) 2419{ 2420 SDL_WindowData *wind = window->internal; 2421 2422 // Drop restore requests when showing the window. 2423 if (wind->showing_window) { 2424 return; 2425 } 2426 2427 // Not currently fullscreen or maximized, and no state pending; nothing to do. 2428 if (!(window->flags & (SDL_WINDOW_FULLSCREEN | SDL_WINDOW_MAXIMIZED)) && 2429 !wind->fullscreen_deadline_count && !wind->maximized_restored_deadline_count) { 2430 return; 2431 } 2432 2433#ifdef HAVE_LIBDECOR_H 2434 if (wind->shell_surface_type == WAYLAND_SHELL_SURFACE_TYPE_LIBDECOR) { 2435 if (!wind->shell_surface.libdecor.frame) { 2436 return; // Can't do anything yet, wait for ShowWindow 2437 } 2438 libdecor_frame_unset_maximized(wind->shell_surface.libdecor.frame); 2439 2440 ++wind->maximized_restored_deadline_count; 2441 struct wl_callback *cb = wl_display_sync(_this->internal->display); 2442 wl_callback_add_listener(cb, &maximized_restored_deadline_listener, (void *)((uintptr_t)window->id)); 2443 } else 2444#endif 2445 // Note that xdg-shell does NOT provide a way to unset minimize! 2446 if (wind->shell_surface_type == WAYLAND_SHELL_SURFACE_TYPE_XDG_TOPLEVEL) { 2447 if (wind->shell_surface.xdg.toplevel.xdg_toplevel == NULL) { 2448 return; // Can't do anything yet, wait for ShowWindow 2449 } 2450 xdg_toplevel_unset_maximized(wind->shell_surface.xdg.toplevel.xdg_toplevel); 2451 2452 ++wind->maximized_restored_deadline_count; 2453 struct wl_callback *cb = wl_display_sync(_this->internal->display); 2454 wl_callback_add_listener(cb, &maximized_restored_deadline_listener, (void *)((uintptr_t)window->id)); 2455 } 2456} 2457 2458void Wayland_SetWindowBordered(SDL_VideoDevice *_this, SDL_Window *window, bool bordered) 2459{ 2460 SDL_WindowData *wind = window->internal; 2461 const SDL_VideoData *viddata = (const SDL_VideoData *)_this->internal; 2462 2463#ifdef HAVE_LIBDECOR_H 2464 if (wind->shell_surface_type == WAYLAND_SHELL_SURFACE_TYPE_LIBDECOR) { 2465 if (wind->shell_surface.libdecor.frame) { 2466 libdecor_frame_set_visibility(wind->shell_surface.libdecor.frame, bordered); 2467 } 2468 } else 2469#endif 2470 if (wind->shell_surface_type == WAYLAND_SHELL_SURFACE_TYPE_XDG_TOPLEVEL) { 2471 if ((viddata->decoration_manager) && (wind->server_decoration)) { 2472 const enum zxdg_toplevel_decoration_v1_mode mode = bordered ? ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE : ZXDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE; 2473 zxdg_toplevel_decoration_v1_set_mode(wind->server_decoration, mode); 2474 } 2475 } 2476} 2477 2478void Wayland_SetWindowResizable(SDL_VideoDevice *_this, SDL_Window *window, bool resizable) 2479{ 2480#ifdef HAVE_LIBDECOR_H 2481 const SDL_WindowData *wind = window->internal; 2482 2483 if (wind->shell_surface_type == WAYLAND_SHELL_SURFACE_TYPE_LIBDECOR) { 2484 if (!wind->shell_surface.libdecor.frame) { 2485 return; // Can't do anything yet, wait for ShowWindow 2486 } 2487 if (libdecor_frame_has_capability(wind->shell_surface.libdecor.frame, LIBDECOR_ACTION_RESIZE)) { 2488 if (!resizable) { 2489 libdecor_frame_unset_capabilities(wind->shell_surface.libdecor.frame, LIBDECOR_ACTION_RESIZE); 2490 } 2491 } else if (resizable) { 2492 libdecor_frame_set_capabilities(wind->shell_surface.libdecor.frame, LIBDECOR_ACTION_RESIZE); 2493 } 2494 } 2495#endif 2496 2497 /* When changing the resize capability on libdecor windows, the limits must always 2498 * be reapplied, as when libdecor changes states, it overwrites the values internally. 2499 */ 2500 SetMinMaxDimensions(window); 2501 CommitLibdecorFrame(window); 2502} 2503 2504void Wayland_MaximizeWindow(SDL_VideoDevice *_this, SDL_Window *window) 2505{ 2506 SDL_VideoData *viddata = _this->internal; 2507 SDL_WindowData *wind = window->internal; 2508 2509 if (wind->show_hide_sync_required) { 2510 WAYLAND_wl_display_roundtrip(_this->internal->display); 2511 } 2512 2513 // Not fullscreen, already maximized, and no state pending; nothing to do. 2514 if (!(window->flags & SDL_WINDOW_FULLSCREEN) && (window->flags & SDL_WINDOW_MAXIMIZED) && 2515 !wind->fullscreen_deadline_count && !wind->maximized_restored_deadline_count) { 2516 return; 2517 } 2518 2519#ifdef HAVE_LIBDECOR_H 2520 if (wind->shell_surface_type == WAYLAND_SHELL_SURFACE_TYPE_LIBDECOR) { 2521 if (!wind->shell_surface.libdecor.frame) { 2522 return; // Can't do anything yet, wait for ShowWindow 2523 } 2524 2525 // Commit to preserve any pending size data. 2526 wl_surface_commit(wind->surface); 2527 libdecor_frame_set_maximized(wind->shell_surface.libdecor.frame); 2528 2529 ++wind->maximized_restored_deadline_count; 2530 struct wl_callback *cb = wl_display_sync(viddata->display); 2531 wl_callback_add_listener(cb, &maximized_restored_deadline_listener, (void *)((uintptr_t)window->id)); 2532 } else 2533#endif 2534 if (wind->shell_surface_type == WAYLAND_SHELL_SURFACE_TYPE_XDG_TOPLEVEL) { 2535 if (wind->shell_surface.xdg.toplevel.xdg_toplevel == NULL) { 2536 return; // Can't do anything yet, wait for ShowWindow 2537 } 2538 2539 // Commit to preserve any pending size data. 2540 wl_surface_commit(wind->surface); 2541 xdg_toplevel_set_maximized(wind->shell_surface.xdg.toplevel.xdg_toplevel); 2542 2543 ++wind->maximized_restored_deadline_count; 2544 struct wl_callback *cb = wl_display_sync(viddata->display); 2545 wl_callback_add_listener(cb, &maximized_restored_deadline_listener, (void *)((uintptr_t)window->id)); 2546 } 2547} 2548 2549void Wayland_MinimizeWindow(SDL_VideoDevice *_this, SDL_Window *window) 2550{ 2551 SDL_WindowData *wind = window->internal; 2552 2553 if (!(wind->wm_caps & WAYLAND_WM_CAPS_MINIMIZE)) { 2554 return; 2555 } 2556 2557#ifdef HAVE_LIBDECOR_H 2558 if (wind->shell_surface_type == WAYLAND_SHELL_SURFACE_TYPE_LIBDECOR) { 2559 if (!wind->shell_surface.libdecor.frame) { 2560 return; // Can't do anything yet, wait for ShowWindow 2561 } 2562 libdecor_frame_set_minimized(wind->shell_surface.libdecor.frame); 2563 SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_MINIMIZED, 0, 0); 2564 } else 2565#endif 2566 if (wind->shell_surface_type == WAYLAND_SHELL_SURFACE_TYPE_XDG_TOPLEVEL) { 2567 if (wind->shell_surface.xdg.toplevel.xdg_toplevel == NULL) { 2568 return; // Can't do anything yet, wait for ShowWindow 2569 } 2570 xdg_toplevel_set_minimized(wind->shell_surface.xdg.toplevel.xdg_toplevel); 2571 SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_MINIMIZED, 0, 0); 2572 } 2573} 2574 2575bool Wayland_SetWindowMouseRect(SDL_VideoDevice *_this, SDL_Window *window) 2576{ 2577 SDL_VideoData *data = _this->internal; 2578 2579 /* This may look suspiciously like SetWindowGrab, despite SetMouseRect not 2580 * implicitly doing a grab. And you're right! Wayland doesn't let us mess 2581 * around with mouse focus whatsoever, so it just happens to be that the 2582 * work that we can do in these two functions ends up being the same. 2583 * 2584 * Just know that this call lets you confine with a rect, SetWindowGrab 2585 * lets you confine without a rect. 2586 */ 2587 if (!data->pointer_constraints) { 2588 return SDL_SetError("Failed to grab mouse: compositor lacks support for the required zwp_pointer_constraints_v1 protocol"); 2589 } 2590 Wayland_DisplayUpdatePointerGrabs(data, window->internal); 2591 return true; 2592} 2593 2594bool Wayland_SetWindowMouseGrab(SDL_VideoDevice *_this, SDL_Window *window, bool grabbed) 2595{ 2596 SDL_VideoData *data = _this->internal; 2597 if (!data->pointer_constraints) { 2598 return SDL_SetError("Failed to grab mouse: compositor lacks support for the required zwp_pointer_constraints_v1 protocol"); 2599 } 2600 Wayland_DisplayUpdatePointerGrabs(data, window->internal); 2601 return true; 2602} 2603 2604bool Wayland_SetWindowKeyboardGrab(SDL_VideoDevice *_this, SDL_Window *window, bool grabbed) 2605{ 2606 SDL_VideoData *data = _this->internal; 2607 if (!data->key_inhibitor_manager) { 2608 return SDL_SetError("Failed to grab keyboard: compositor lacks support for the required zwp_keyboard_shortcuts_inhibit_manager_v1 protocol"); 2609 } 2610 Wayland_DisplayUpdateKeyboardGrabs(data, window->internal); 2611 return true; 2612} 2613 2614bool Wayland_ReconfigureWindow(SDL_VideoDevice *_this, SDL_Window *window, SDL_WindowFlags flags) 2615{ 2616 SDL_WindowData *data = window->internal; 2617 2618 if (data->shell_surface_status == WAYLAND_SHELL_SURFACE_STATUS_SHOWN) { 2619 // Window is already mapped; abort. 2620 return false; 2621 } 2622 2623 /* The caller guarantees that only one of the GL or Vulkan flags will be set, 2624 * and the window will have no previous video flags. 2625 */ 2626 if (flags & SDL_WINDOW_OPENGL) { 2627 if (!data->egl_window) { 2628 data->egl_window = WAYLAND_wl_egl_window_create(data->surface, data->current.pixel_width, data->current.pixel_height); 2629 } 2630 2631#ifdef SDL_VIDEO_OPENGL_EGL 2632 // Create the GLES window surface 2633 data->egl_surface = SDL_EGL_CreateSurface(_this, window, (NativeWindowType)data->egl_window); 2634 2635 if (data->egl_surface == EGL_NO_SURFACE) { 2636 return false; // SDL_EGL_CreateSurface should have set error 2637 } 2638#endif 2639 2640 if (!data->gles_swap_frame_event_queue) { 2641 data->gles_swap_frame_event_queue = WAYLAND_wl_display_create_queue(data->waylandData->display); 2642 data->gles_swap_frame_surface_wrapper = WAYLAND_wl_proxy_create_wrapper(data->surface); 2643 WAYLAND_wl_proxy_set_queue((struct wl_proxy *)data->gles_swap_frame_surface_wrapper, data->gles_swap_frame_event_queue); 2644 data->gles_swap_frame_callback = wl_surface_frame(data->gles_swap_frame_surface_wrapper); 2645 wl_callback_add_listener(data->gles_swap_frame_callback, &gles_swap_frame_listener, data); 2646 } 2647 2648 return true; 2649 } else if (flags & SDL_WINDOW_VULKAN) { 2650 // Nothing to configure for Vulkan. 2651 return true; 2652 } 2653 2654 return false; 2655} 2656 2657bool Wayland_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window, SDL_PropertiesID create_props) 2658{ 2659 SDL_WindowData *data; 2660 SDL_VideoData *c = _this->internal; 2661 struct wl_surface *external_surface = (struct wl_surface *)SDL_GetPointerProperty(create_props, SDL_PROP_WINDOW_CREATE_WAYLAND_WL_SURFACE_POINTER, 2662 (struct wl_surface *)SDL_GetPointerProperty(create_props, "sdl2-compat.external_window", NULL)); 2663 const bool custom_surface_role = (external_surface != NULL) || SDL_GetBooleanProperty(create_props, SDL_PROP_WINDOW_CREATE_WAYLAND_SURFACE_ROLE_CUSTOM_BOOLEAN, false); 2664 const bool create_egl_window = !!(window->flags & SDL_WINDOW_OPENGL) || 2665 SDL_GetBooleanProperty(create_props, SDL_PROP_WINDOW_CREATE_WAYLAND_CREATE_EGL_WINDOW_BOOLEAN, false); 2666 2667 data = SDL_calloc(1, sizeof(*data)); 2668 if (!data) { 2669 return false; 2670 } 2671 2672 window->internal = data; 2673 2674 if (window->x == SDL_WINDOWPOS_UNDEFINED) { 2675 window->x = 0; 2676 } 2677 if (window->y == SDL_WINDOWPOS_UNDEFINED) { 2678 window->y = 0; 2679 } 2680 2681 data->waylandData = c; 2682 data->sdlwindow = window; 2683 2684 // Default to all capabilities 2685 data->wm_caps = WAYLAND_WM_CAPS_ALL; 2686 2687 data->scale_factor = 1.0; 2688 2689 if (SDL_WINDOW_IS_POPUP(window)) { 2690 data->scale_to_display = window->parent->internal->scale_to_display; 2691 data->scale_factor = window->parent->internal->scale_factor; 2692 EnsurePopupPositionIsValid(window, &window->x, &window->y); 2693 } else { 2694 for (int i = 0; i < _this->num_displays; i++) { 2695 data->scale_factor = SDL_max(data->scale_factor, _this->displays[i]->internal->scale_factor); 2696 } 2697 } 2698 2699 data->outputs = NULL; 2700 data->num_outputs = 0; 2701 data->scale_to_display = c->scale_to_display_enabled; 2702 2703 // Cache the app_id at creation time, as it may change before the window is mapped. 2704 data->app_id = SDL_strdup(SDL_GetAppID()); 2705 2706 if (!data->scale_to_display) { 2707 data->requested.logical_width = window->floating.w; 2708 data->requested.logical_height = window->floating.h; 2709 } else { 2710 data->requested.logical_width = PixelToPoint(window, window->floating.w); 2711 data->requested.logical_height = PixelToPoint(window, window->floating.h); 2712 data->requested.pixel_width = window->floating.w; 2713 data->requested.pixel_height = window->floating.h; 2714 } 2715 2716 if (!external_surface) { 2717 data->surface = wl_compositor_create_surface(c->compositor); 2718 wl_surface_add_listener(data->surface, &surface_listener, data); 2719 wl_surface_set_user_data(data->surface, data); 2720 SDL_WAYLAND_register_surface(data->surface); 2721 } else { 2722 window->flags |= SDL_WINDOW_EXTERNAL; 2723 data->surface = external_surface; 2724 2725 /* External surfaces are registered by being put in a list, as changing tags or userdata 2726 * can cause problems with external toolkits. 2727 */ 2728 Wayland_AddWindowDataToExternalList(data); 2729 } 2730 2731 /* Always attach a viewport and fractional scale manager if available and the surface is not custom/external, 2732 * or the custom/external surface was explicitly flagged as high pixel density aware, which signals that the 2733 * application wants SDL to handle scaling. 2734 */ 2735 if (!custom_surface_role || (window->flags & SDL_WINDOW_HIGH_PIXEL_DENSITY)) { 2736 if (c->viewporter) { 2737 data->viewport = wp_viewporter_get_viewport(c->viewporter, data->surface); 2738 2739 // The viewport always uses the entire buffer. 2740 wp_viewport_set_source(data->viewport, 2741 wl_fixed_from_int(-1), wl_fixed_from_int(-1), 2742 wl_fixed_from_int(-1), wl_fixed_from_int(-1)); 2743 } 2744 if (c->fractional_scale_manager) { 2745 data->fractional_scale = wp_fractional_scale_manager_v1_get_fractional_scale(c->fractional_scale_manager, data->surface); 2746 wp_fractional_scale_v1_add_listener(data->fractional_scale, &fractional_scale_listener, data); 2747 } 2748 } 2749 2750 if (!custom_surface_role) { 2751 if (c->wp_color_manager_v1) { 2752 data->wp_color_management_surface_feedback = wp_color_manager_v1_get_surface_feedback(c->wp_color_manager_v1, data->surface); 2753 wp_color_management_surface_feedback_v1_add_listener(data->wp_color_management_surface_feedback, &color_management_surface_feedback_listener, data); 2754 Wayland_GetColorInfoForWindow(data, true); 2755 } else if (c->frog_color_management_factory_v1) { 2756 data->frog_color_managed_surface = frog_color_management_factory_v1_get_color_managed_surface(c->frog_color_management_factory_v1, data->surface); 2757 frog_color_managed_surface_add_listener(data->frog_color_managed_surface, &frog_surface_listener, data); 2758 } 2759 2760 if (c->wp_alpha_modifier_v1) { 2761 data->wp_alpha_modifier_surface_v1 = wp_alpha_modifier_v1_get_surface(c->wp_alpha_modifier_v1, data->surface); 2762 wp_alpha_modifier_surface_v1_set_multiplier(data->wp_alpha_modifier_surface_v1, SDL_MAX_UINT32); 2763 } 2764 } 2765 2766 // Must be called before EGL configuration to set the drawable backbuffer size. 2767 ConfigureWindowGeometry(window); 2768 2769 /* Fire a callback when the compositor wants a new frame rendered. 2770 * Right now this only matters for OpenGL; we use this callback to add a 2771 * wait timeout that avoids getting deadlocked by the compositor when the 2772 * window isn't visible. 2773 */ 2774 if (window->flags & SDL_WINDOW_OPENGL) { 2775 data->gles_swap_frame_event_queue = WAYLAND_wl_display_create_queue(data->waylandData->display); 2776 data->gles_swap_frame_surface_wrapper = WAYLAND_wl_proxy_create_wrapper(data->surface); 2777 WAYLAND_wl_proxy_set_queue((struct wl_proxy *)data->gles_swap_frame_surface_wrapper, data->gles_swap_frame_event_queue); 2778 data->gles_swap_frame_callback = wl_surface_frame(data->gles_swap_frame_surface_wrapper); 2779 wl_callback_add_listener(data->gles_swap_frame_callback, &gles_swap_frame_listener, data); 2780 } 2781 2782 // No frame callback on external surfaces as it may already have one attached. 2783 if (!external_surface) { 2784 // Fire a callback when the compositor wants a new frame to set the surface damage region. 2785 data->surface_frame_callback = wl_surface_frame(data->surface); 2786 wl_callback_add_listener(data->surface_frame_callback, &surface_frame_listener, data); 2787 } 2788 2789 if (window->flags & SDL_WINDOW_TRANSPARENT) { 2790 if (_this->gl_config.alpha_size == 0) { 2791 _this->gl_config.alpha_size = 8; 2792 } 2793 } 2794 2795 if (create_egl_window) { 2796 data->egl_window = WAYLAND_wl_egl_window_create(data->surface, data->current.pixel_width, data->current.pixel_height); 2797 } 2798 2799#ifdef SDL_VIDEO_OPENGL_EGL 2800 if (window->flags & SDL_WINDOW_OPENGL) { 2801 // Create the GLES window surface 2802 data->egl_surface = SDL_EGL_CreateSurface(_this, window, (NativeWindowType)data->egl_window); 2803 2804 if (data->egl_surface == EGL_NO_SURFACE) { 2805 return false; // SDL_EGL_CreateSurface should have set error 2806 } 2807 } 2808#endif 2809 2810 // We may need to create an idle inhibitor for this new window 2811 Wayland_SuspendScreenSaver(_this); 2812 2813 if (!custom_surface_role) { 2814#ifdef HAVE_LIBDECOR_H 2815 if (c->shell.libdecor && !SDL_WINDOW_IS_POPUP(window)) { 2816 data->shell_surface_type = WAYLAND_SHELL_SURFACE_TYPE_LIBDECOR; 2817 } else 2818#endif 2819 if (c->shell.xdg) { 2820 if (SDL_WINDOW_IS_POPUP(window)) { 2821 data->shell_surface_type = WAYLAND_SHELL_SURFACE_TYPE_XDG_POPUP; 2822 } else { 2823 data->shell_surface_type = WAYLAND_SHELL_SURFACE_TYPE_XDG_TOPLEVEL; 2824 } 2825 } // All other cases will be WAYLAND_SURFACE_UNKNOWN 2826 } else { 2827 // Roleless and external surfaces are always considered to be in the shown state by the backend. 2828 data->shell_surface_type = WAYLAND_SHELL_SURFACE_TYPE_CUSTOM; 2829 data->shell_surface_status = WAYLAND_SHELL_SURFACE_STATUS_SHOWN; 2830 } 2831 2832 if (SDL_GetHintBoolean(SDL_HINT_VIDEO_DOUBLE_BUFFER, false)) { 2833 data->double_buffer = true; 2834 } 2835 2836 SDL_PropertiesID props = SDL_GetWindowProperties(window); 2837 SDL_SetPointerProperty(props, SDL_PROP_WINDOW_WAYLAND_DISPLAY_POINTER, data->waylandData->display); 2838 SDL_SetPointerProperty(props, SDL_PROP_WINDOW_WAYLAND_SURFACE_POINTER, data->surface); 2839 SDL_SetPointerProperty(props, SDL_PROP_WINDOW_WAYLAND_VIEWPORT_POINTER, data->viewport); 2840 SDL_SetPointerProperty(props, SDL_PROP_WINDOW_WAYLAND_EGL_WINDOW_POINTER, data->egl_window); 2841 2842 data->hit_test_result = SDL_HITTEST_NORMAL; 2843 2844 return true; 2845} 2846 2847void Wayland_SetWindowMinimumSize(SDL_VideoDevice *_this, SDL_Window *window) 2848{ 2849 // Will be committed when Wayland_SetWindowSize() is called by the video core. 2850 SetMinMaxDimensions(window); 2851} 2852 2853void Wayland_SetWindowMaximumSize(SDL_VideoDevice *_this, SDL_Window *window) 2854{ 2855 // Will be committed when Wayland_SetWindowSize() is called by the video core. 2856 SetMinMaxDimensions(window); 2857} 2858 2859bool Wayland_SetWindowPosition(SDL_VideoDevice *_this, SDL_Window *window) 2860{ 2861 SDL_WindowData *wind = window->internal; 2862 2863 // Only popup windows can be positioned relative to the parent. 2864 if (wind->shell_surface_type == WAYLAND_SHELL_SURFACE_TYPE_XDG_POPUP) { 2865 if (wind->shell_surface.xdg.popup.xdg_popup && 2866 xdg_popup_get_version(wind->shell_surface.xdg.popup.xdg_popup) < XDG_POPUP_REPOSITION_SINCE_VERSION) { 2867 return SDL_Unsupported(); 2868 } 2869 2870 RepositionPopup(window, false); 2871 return true; 2872 } else if (wind->shell_surface_type == WAYLAND_SHELL_SURFACE_TYPE_LIBDECOR || wind->shell_surface_type == WAYLAND_SHELL_SURFACE_TYPE_XDG_TOPLEVEL) { 2873 /* Catch up on any pending state before attempting to change the fullscreen window 2874 * display via a set fullscreen call to make sure the window doesn't have a pending 2875 * leave fullscreen event that it might override. 2876 */ 2877 FlushPendingEvents(window); 2878 2879 if (wind->is_fullscreen) { 2880 SDL_VideoDisplay *display = SDL_GetVideoDisplayForFullscreenWindow(window); 2881 if (display && wind->last_displayID != display->id) { 2882 struct wl_output *output = display->internal->output; 2883 SetFullscreen(window, output, true); 2884 2885 return true; 2886 } 2887 } 2888 } 2889 return SDL_SetError("wayland cannot position non-popup windows"); 2890} 2891 2892void Wayland_SetWindowSize(SDL_VideoDevice *_this, SDL_Window *window) 2893{ 2894 SDL_WindowData *wind = window->internal; 2895 2896 /* Flush any pending state operations, as fullscreen windows do not get 2897 * explicitly resized, not strictly obeying the size of a maximized window 2898 * is a protocol violation, and pending restore events might result in a 2899 * configure event overwriting the requested size. 2900 * 2901 * Calling this on a custom surface is informative, so the size must 2902 * always be passed through. 2903 */ 2904 FlushPendingEvents(window); 2905 2906 // Maximized and fullscreen windows don't get resized. 2907 if (!(window->flags & (SDL_WINDOW_FULLSCREEN | SDL_WINDOW_MAXIMIZED)) || 2908 wind->shell_surface_type == WAYLAND_SHELL_SURFACE_TYPE_CUSTOM) { 2909 if (!wind->scale_to_display) { 2910 wind->requested.logical_width = window->pending.w; 2911 wind->requested.logical_height = window->pending.h; 2912 } else { 2913 wind->requested.logical_width = PixelToPoint(window, window->pending.w); 2914 wind->requested.logical_height = PixelToPoint(window, window->pending.h); 2915 wind->requested.pixel_width = window->pending.w; 2916 wind->requested.pixel_height = window->pending.h; 2917 } 2918 2919 ConfigureWindowGeometry(window); 2920 } else { 2921 // Can't resize the window. 2922 window->last_size_pending = false; 2923 } 2924 2925 // Always commit, as this may be in response to a min/max limit change. 2926 CommitLibdecorFrame(window); 2927} 2928 2929void Wayland_GetWindowSizeInPixels(SDL_VideoDevice *_this, SDL_Window *window, int *w, int *h) 2930{ 2931 SDL_WindowData *data = window->internal; 2932 2933 *w = data->current.pixel_width; 2934 *h = data->current.pixel_height; 2935} 2936 2937float Wayland_GetWindowContentScale(SDL_VideoDevice *_this, SDL_Window *window) 2938{ 2939 SDL_WindowData *wind = window->internal; 2940 2941 if (window->flags & SDL_WINDOW_HIGH_PIXEL_DENSITY || wind->scale_to_display || wind->fullscreen_exclusive) { 2942 return (float)wind->scale_factor; 2943 } 2944 2945 return 1.0f; 2946} 2947 2948SDL_DisplayID Wayland_GetDisplayForWindow(SDL_VideoDevice *_this, SDL_Window *window) 2949{ 2950 SDL_WindowData *wind = window->internal; 2951 2952 if (wind) { 2953 return wind->last_displayID; 2954 } 2955 2956 return 0; 2957} 2958 2959bool Wayland_SetWindowOpacity(SDL_VideoDevice *_this, SDL_Window *window, float opacity) 2960{ 2961 SDL_WindowData *wind = window->internal; 2962 2963 if (wind->wp_alpha_modifier_surface_v1) { 2964 SetSurfaceOpaqueRegion(wind, !(window->flags & SDL_WINDOW_TRANSPARENT) && opacity == 1.0f); 2965 wp_alpha_modifier_surface_v1_set_multiplier(wind->wp_alpha_modifier_surface_v1, (Uint32)((double)SDL_MAX_UINT32 * (double)opacity)); 2966 2967 return true; 2968 } 2969 2970 return SDL_SetError("wayland: set window opacity failed; compositor lacks support for the required wp_alpha_modifier_v1 protocol"); 2971} 2972 2973void Wayland_SetWindowTitle(SDL_VideoDevice *_this, SDL_Window *window) 2974{ 2975 SDL_WindowData *wind = window->internal; 2976 const char *title = window->title ? window->title : ""; 2977 2978#ifdef HAVE_LIBDECOR_H 2979 if (wind->shell_surface_type == WAYLAND_SHELL_SURFACE_TYPE_LIBDECOR && wind->shell_surface.libdecor.frame) { 2980 libdecor_frame_set_title(wind->shell_surface.libdecor.frame, title); 2981 } else 2982#endif 2983 if (wind->shell_surface_type == WAYLAND_SHELL_SURFACE_TYPE_XDG_TOPLEVEL && wind->shell_surface.xdg.toplevel.xdg_toplevel) { 2984 xdg_toplevel_set_title(wind->shell_surface.xdg.toplevel.xdg_toplevel, title); 2985 } 2986} 2987 2988static int icon_sort_callback(const void *a, const void *b) 2989{ 2990 SDL_Surface *s1 = (SDL_Surface *)a; 2991 SDL_Surface *s2 = (SDL_Surface *)b; 2992 2993 return (s1->w * s1->h) <= (s2->w * s2->h) ? -1 : 1; 2994} 2995 2996bool Wayland_SetWindowIcon(SDL_VideoDevice *_this, SDL_Window *window, SDL_Surface *icon) 2997{ 2998 SDL_WindowData *wind = window->internal; 2999 Wayland_SHMPool *shm_pool = NULL; 3000 struct xdg_toplevel *toplevel = NULL; 3001 3002 if (!_this->internal->xdg_toplevel_icon_manager_v1) { 3003 return SDL_SetError("wayland: cannot set icon; required xdg_toplevel_icon_v1 protocol not supported"); 3004 } 3005 3006 if (icon->w != icon->h) { 3007 return SDL_SetError("wayland: icon width and height must be equal, got %ix%i", icon->w, icon->h); 3008 } 3009 3010 int image_count = 0; 3011 SDL_Surface **images = SDL_GetSurfaceImages(icon, &image_count); 3012 if (!images || !image_count) { 3013 return false; 3014 } 3015 3016 // Release the old icon resources. 3017 if (wind->xdg_toplevel_icon_v1) { 3018 xdg_toplevel_icon_v1_destroy(wind->xdg_toplevel_icon_v1); 3019 wind->xdg_toplevel_icon_v1 = NULL; 3020 } 3021 3022 for (int i = 0; i < wind->icon_buffer_count; ++i) { 3023 wl_buffer_destroy(wind->icon_buffers[i]); 3024 } 3025 wind->icon_buffer_count = 0; 3026 3027 wind->xdg_toplevel_icon_v1 = xdg_toplevel_icon_manager_v1_create_icon(_this->internal->xdg_toplevel_icon_manager_v1); 3028 wind->icon_buffers = SDL_realloc(wind->icon_buffers, image_count * sizeof(struct wl_buffer *)); 3029 if (!wind->icon_buffers) { 3030 goto failure_cleanup; 3031 } 3032 3033 // Calculate the size of the buffer pool. 3034 size_t pool_size = 0; 3035 for (int i = 0; i < image_count; ++i) { 3036 // Ignore non-square images; if we got here, we know that at least the base image is square. 3037 if (images[i]->w == images[i]->h) { 3038 pool_size += images[i]->w * images[i]->h * 4; 3039 } else { 3040 SDL_LogWarn(SDL_LOG_CATEGORY_VIDEO, "wayland: icon width and height must be equal, got %ix%i for image level %i; skipping", images[i]->w, images[i]->h, i); 3041 } 3042 } 3043 3044 // Sort the images in ascending order by size. 3045 SDL_qsort(images, image_count, sizeof(SDL_Surface *), icon_sort_callback); 3046 3047 shm_pool = Wayland_AllocSHMPool(pool_size); 3048 if (!shm_pool) { 3049 SDL_SetError("wayland: failed to allocate SHM pool for the icon"); 3050 goto failure_cleanup; 3051 } 3052 3053 for (int i = 0; i < image_count; ++i) { 3054 if (images[i]->w == images[i]->h) { 3055 SDL_Surface *surface = images[i]; 3056 3057 // Choose the largest image for each integer scale, ignoring any below the base size. 3058 const int scale = (int)SDL_floor((double)surface->w / (double)icon->w); 3059 if (!scale) { 3060 continue; 3061 } 3062 3063 void *buffer_mem; 3064 struct wl_buffer *buffer = Wayland_AllocBufferFromPool(shm_pool, surface->w, surface->h, &buffer_mem); 3065 if (!buffer) { 3066 SDL_SetError("wayland: failed to allocate SHM buffer for the icon"); 3067 goto failure_cleanup; 3068 } 3069 3070 wind->icon_buffers[wind->icon_buffer_count++] = buffer; 3071 3072 if (surface->format != SDL_PIXELFORMAT_ARGB8888) { 3073 surface = SDL_ConvertSurface(surface, SDL_PIXELFORMAT_ARGB8888); 3074 if (!surface) { 3075 SDL_SetError("wayland: unable to convert surface to ARGB8888 format"); 3076 goto failure_cleanup; 3077 } 3078 } 3079 3080 SDL_PremultiplyAlpha(surface->w, surface->h, surface->format, surface->pixels, surface->pitch, 3081 SDL_PIXELFORMAT_ARGB8888, buffer_mem, surface->w * 4, true); 3082 3083 xdg_toplevel_icon_v1_add_buffer(wind->xdg_toplevel_icon_v1, buffer, scale); 3084 3085 // Clean up the temporary conversion surface. 3086 if (surface != images[i]) { 3087 SDL_DestroySurface(surface); 3088 } 3089 } 3090 } 3091 3092#ifdef HAVE_LIBDECOR_H 3093 if (wind->shell_surface_type == WAYLAND_SHELL_SURFACE_TYPE_LIBDECOR && wind->shell_surface.libdecor.frame) { 3094 toplevel = libdecor_frame_get_xdg_toplevel(wind->shell_surface.libdecor.frame); 3095 } else 3096#endif 3097 if (wind->shell_surface_type == WAYLAND_SHELL_SURFACE_TYPE_XDG_TOPLEVEL && wind->shell_surface.xdg.toplevel.xdg_toplevel) { 3098 toplevel = wind->shell_surface.xdg.toplevel.xdg_toplevel; 3099 } 3100 3101 if (toplevel) { 3102 xdg_toplevel_icon_manager_v1_set_icon(_this->internal->xdg_toplevel_icon_manager_v1, toplevel, wind->xdg_toplevel_icon_v1); 3103 } 3104 3105 Wayland_ReleaseSHMPool(shm_pool); 3106 SDL_free(images); 3107 3108 return true; 3109 3110failure_cleanup: 3111 3112 SDL_free(images); 3113 3114 if (wind->xdg_toplevel_icon_v1) { 3115 xdg_toplevel_icon_v1_destroy(wind->xdg_toplevel_icon_v1); 3116 wind->xdg_toplevel_icon_v1 = NULL; 3117 } 3118 3119 Wayland_ReleaseSHMPool(shm_pool); 3120 3121 for (int i = 0; i < wind->icon_buffer_count; ++i) { 3122 wl_buffer_destroy(wind->icon_buffers[i]); 3123 } 3124 SDL_free(wind->icon_buffers); 3125 wind->icon_buffers = NULL; 3126 wind->icon_buffer_count = 0; 3127 3128 return false; 3129} 3130 3131void *Wayland_GetWindowICCProfile(SDL_VideoDevice *_this, SDL_Window *window, size_t *size) 3132{ 3133 SDL_WindowData *wind = window->internal; 3134 void *ret = NULL; 3135 3136 if (wind->icc_size > 0) { 3137 void *icc_map = mmap(NULL, wind->icc_size, PROT_READ, MAP_PRIVATE, wind->icc_fd, 0); 3138 if (icc_map != MAP_FAILED) { 3139 ret = SDL_malloc(wind->icc_size); 3140 if (ret) { 3141 *size = wind->icc_size; 3142 SDL_memcpy(ret, icc_map, *size); 3143 } 3144 munmap(icc_map, wind->icc_size); 3145 } 3146 } 3147 3148 return ret; 3149} 3150 3151bool Wayland_SyncWindow(SDL_VideoDevice *_this, SDL_Window *window) 3152{ 3153 SDL_WindowData *wind = window->internal; 3154 3155 do { 3156 WAYLAND_wl_display_roundtrip(_this->internal->display); 3157 } while (wind->fullscreen_deadline_count || wind->maximized_restored_deadline_count); 3158 3159 return true; 3160} 3161 3162bool Wayland_SetWindowFocusable(SDL_VideoDevice *_this, SDL_Window *window, bool focusable) 3163{ 3164 if (window->flags & SDL_WINDOW_POPUP_MENU) { 3165 if (!(window->flags & SDL_WINDOW_HIDDEN)) { 3166 if (!focusable && (window->flags & SDL_WINDOW_INPUT_FOCUS)) { 3167 SDL_Window *new_focus; 3168 const bool set_focus = SDL_ShouldRelinquishPopupFocus(window, &new_focus); 3169 Wayland_SetKeyboardFocus(new_focus, set_focus); 3170 } else if (focusable) { 3171 if (SDL_ShouldFocusPopup(window)) { 3172 Wayland_SetKeyboardFocus(window, true); 3173 } 3174 } 3175 } 3176 3177 return true; 3178 } 3179 3180 return SDL_SetError("wayland: focus can only be toggled on popup menu windows"); 3181} 3182 3183void Wayland_ShowWindowSystemMenu(SDL_Window *window, int x, int y) 3184{ 3185 SDL_WindowData *wind = window->internal; 3186 SDL_WaylandSeat *seat = wind->waylandData->last_implicit_grab_seat; 3187 3188 if (!seat) { 3189 return; 3190 } 3191 3192 if (wind->scale_to_display) { 3193 x = PixelToPoint(window, x); 3194 y = PixelToPoint(window, y); 3195 } 3196 3197#ifdef HAVE_LIBDECOR_H 3198 if (wind->shell_surface_type == WAYLAND_SHELL_SURFACE_TYPE_LIBDECOR) { 3199 if (wind->shell_surface.libdecor.frame) { 3200 libdecor_frame_show_window_menu(wind->shell_surface.libdecor.frame, seat->wl_seat, seat->last_implicit_grab_serial, x, y); 3201 } 3202 } else 3203#endif 3204 if (wind->shell_surface_type == WAYLAND_SHELL_SURFACE_TYPE_XDG_TOPLEVEL) { 3205 if (wind->shell_surface.xdg.toplevel.xdg_toplevel) { 3206 xdg_toplevel_show_window_menu(wind->shell_surface.xdg.toplevel.xdg_toplevel, seat->wl_seat, seat->last_implicit_grab_serial, x, y); 3207 } 3208 } 3209} 3210 3211bool Wayland_SuspendScreenSaver(SDL_VideoDevice *_this) 3212{ 3213 SDL_VideoData *data = _this->internal; 3214 3215#ifdef SDL_USE_LIBDBUS 3216 if (SDL_DBus_ScreensaverInhibit(_this->suspend_screensaver)) { 3217 return true; 3218 } 3219#endif 3220 3221 /* The idle_inhibit_unstable_v1 protocol suspends the screensaver 3222 on a per wl_surface basis, but SDL assumes that suspending 3223 the screensaver can be done independently of any window. 3224 3225 To reconcile these differences, we propagate the idle inhibit 3226 state to each window. If there is no window active, we will 3227 be able to inhibit idle once the first window is created. 3228 */ 3229 if (data->idle_inhibit_manager) { 3230 SDL_Window *window = _this->windows; 3231 while (window) { 3232 SDL_WindowData *win_data = window->internal; 3233 3234 if (_this->suspend_screensaver && !win_data->idle_inhibitor) { 3235 win_data->idle_inhibitor = 3236 zwp_idle_inhibit_manager_v1_create_inhibitor(data->idle_inhibit_manager, 3237 win_data->surface); 3238 } else if (!_this->suspend_screensaver && win_data->idle_inhibitor) { 3239 zwp_idle_inhibitor_v1_destroy(win_data->idle_inhibitor); 3240 win_data->idle_inhibitor = NULL; 3241 } 3242 3243 window = window->next; 3244 } 3245 } 3246 3247 return true; 3248} 3249 3250void Wayland_DestroyWindow(SDL_VideoDevice *_this, SDL_Window *window) 3251{ 3252 SDL_VideoData *data = _this->internal; 3253 SDL_WindowData *wind = window->internal; 3254 3255 if (data && wind) { 3256 /* Roundtrip before destroying the window to make sure that it has received input leave events, so that 3257 * no internal structures are left pointing to the destroyed window. 3258 */ 3259 if (wind->show_hide_sync_required) { 3260 WAYLAND_wl_display_roundtrip(data->display); 3261 } 3262 3263 /* The compositor should have relinquished keyboard, pointer, touch, and tablet tool focus when the toplevel 3264 * window was destroyed upon being hidden, but there is no guarantee of this, so ensure that all references 3265 * to the window held by seats are released before destroying the underlying surface and struct. 3266 */ 3267 Wayland_DisplayRemoveWindowReferencesFromSeats(data, wind); 3268 3269#ifdef SDL_VIDEO_OPENGL_EGL 3270 if (wind->egl_surface) { 3271 SDL_EGL_DestroySurface(_this, wind->egl_surface); 3272 } 3273#endif 3274 if (wind->egl_window) { 3275 WAYLAND_wl_egl_window_destroy(wind->egl_window); 3276 } 3277 3278 if (wind->idle_inhibitor) { 3279 zwp_idle_inhibitor_v1_destroy(wind->idle_inhibitor); 3280 } 3281 3282 if (wind->activation_token) { 3283 xdg_activation_token_v1_destroy(wind->activation_token); 3284 } 3285 3286 if (wind->viewport) { 3287 wp_viewport_destroy(wind->viewport); 3288 } 3289 3290 if (wind->fractional_scale) { 3291 wp_fractional_scale_v1_destroy(wind->fractional_scale); 3292 } 3293 3294 if (wind->wp_alpha_modifier_surface_v1) { 3295 wp_alpha_modifier_surface_v1_destroy(wind->wp_alpha_modifier_surface_v1); 3296 } 3297 3298 if (wind->frog_color_managed_surface) { 3299 frog_color_managed_surface_destroy(wind->frog_color_managed_surface); 3300 } 3301 3302 if (wind->wp_color_management_surface_feedback) { 3303 Wayland_FreeColorInfoState(wind->color_info_state); 3304 wp_color_management_surface_feedback_v1_destroy(wind->wp_color_management_surface_feedback); 3305 } 3306 3307 SDL_free(wind->outputs); 3308 SDL_free(wind->app_id); 3309 3310 if (wind->gles_swap_frame_callback) { 3311 wl_callback_destroy(wind->gles_swap_frame_callback); 3312 WAYLAND_wl_proxy_wrapper_destroy(wind->gles_swap_frame_surface_wrapper); 3313 WAYLAND_wl_event_queue_destroy(wind->gles_swap_frame_event_queue); 3314 } 3315 3316 if (wind->surface_frame_callback) { 3317 wl_callback_destroy(wind->surface_frame_callback); 3318 } 3319 3320 if (!(window->flags & SDL_WINDOW_EXTERNAL)) { 3321 wl_surface_destroy(wind->surface); 3322 } else { 3323 Wayland_RemoveWindowDataFromExternalList(wind); 3324 } 3325 3326 if (wind->xdg_toplevel_icon_v1) { 3327 xdg_toplevel_icon_v1_destroy(wind->xdg_toplevel_icon_v1); 3328 } 3329 3330 for (int i = 0; i < wind->icon_buffer_count; ++i) { 3331 wl_buffer_destroy(wind->icon_buffers[i]); 3332 } 3333 SDL_free(wind->icon_buffers); 3334 wind->icon_buffer_count = 0; 3335 3336 SDL_free(wind); 3337 } 3338 window->internal = NULL; 3339} 3340 3341#endif // SDL_VIDEO_DRIVER_WAYLAND 3342[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.