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