Atlas - SDL_waylandwindow.c

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