From 30f41c02aec763d32e62351452da9ef582bc3472 Mon Sep 17 00:00:00 2001 From: 3gg <3gg@shellblade.net> Date: Fri, 6 Mar 2026 13:30:59 -0800 Subject: Move contrib libraries to contrib repo --- .../src/video/wayland/SDL_waylandwindow.c | 3125 -------------------- 1 file changed, 3125 deletions(-) delete mode 100644 contrib/SDL-3.2.8/src/video/wayland/SDL_waylandwindow.c (limited to 'contrib/SDL-3.2.8/src/video/wayland/SDL_waylandwindow.c') diff --git a/contrib/SDL-3.2.8/src/video/wayland/SDL_waylandwindow.c b/contrib/SDL-3.2.8/src/video/wayland/SDL_waylandwindow.c deleted file mode 100644 index 212fd54..0000000 --- a/contrib/SDL-3.2.8/src/video/wayland/SDL_waylandwindow.c +++ /dev/null @@ -1,3125 +0,0 @@ -/* - Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga - - This software is provided 'as-is', without any express or implied - warranty. In no event will the authors be held liable for any damages - arising from the use of this software. - - Permission is granted to anyone to use this software for any purpose, - including commercial applications, and to alter it and redistribute it - freely, subject to the following restrictions: - - 1. The origin of this software must not be misrepresented; you must not - claim that you wrote the original software. If you use this software - in a product, an acknowledgment in the product documentation would be - appreciated but is not required. - 2. Altered source versions must be plainly marked as such, and must not be - misrepresented as being the original software. - 3. This notice may not be removed or altered from any source distribution. -*/ - -#include "SDL_internal.h" - -#ifdef SDL_VIDEO_DRIVER_WAYLAND - -#include - -#include "../SDL_sysvideo.h" -#include "../../events/SDL_events_c.h" -#include "../../core/unix/SDL_appid.h" -#include "../SDL_egl_c.h" -#include "SDL_waylandevents_c.h" -#include "SDL_waylandwindow.h" -#include "SDL_waylandvideo.h" -#include "../../SDL_hints_c.h" -#include "SDL_waylandcolor.h" - -#include "alpha-modifier-v1-client-protocol.h" -#include "xdg-shell-client-protocol.h" -#include "xdg-decoration-unstable-v1-client-protocol.h" -#include "idle-inhibit-unstable-v1-client-protocol.h" -#include "xdg-activation-v1-client-protocol.h" -#include "viewporter-client-protocol.h" -#include "fractional-scale-v1-client-protocol.h" -#include "xdg-foreign-unstable-v2-client-protocol.h" -#include "xdg-dialog-v1-client-protocol.h" -#include "frog-color-management-v1-client-protocol.h" -#include "xdg-toplevel-icon-v1-client-protocol.h" -#include "color-management-v1-client-protocol.h" - -#ifdef HAVE_LIBDECOR_H -#include -#endif - -static double GetWindowScale(SDL_Window *window) -{ - return (window->flags & SDL_WINDOW_HIGH_PIXEL_DENSITY) || window->internal->scale_to_display ? window->internal->scale_factor : 1.0; -} - -// These are point->pixel->point round trip safe; the inverse is not round trip safe due to rounding. -static int PointToPixel(SDL_Window *window, int point) -{ - /* Rounds halfway away from zero as per the Wayland fractional scaling protocol spec. - * Wayland scale units are in units of 1/120, so the offset is required to correct for - * rounding errors when using certain scale values. - */ - return point ? SDL_max((int)SDL_lround((double)point * GetWindowScale(window) + 1e-6), 1) : 0; -} - -static int PixelToPoint(SDL_Window *window, int pixel) -{ - return pixel ? SDL_max((int)SDL_lround((double)pixel / GetWindowScale(window)), 1) : 0; -} - -/* According to the Wayland spec: - * - * "If the [fullscreen] surface doesn't cover the whole output, the compositor will - * position the surface in the center of the output and compensate with border fill - * covering the rest of the output. The content of the border fill is undefined, but - * should be assumed to be in some way that attempts to blend into the surrounding area - * (e.g. solid black)." - * - * - KDE, as of 5.27, still doesn't do this - * - GNOME prior to 43 didn't do this (older versions are still found in many LTS distros) - * - * Default to 'stretch' for now, until things have moved forward enough that the default - * can be changed to 'aspect'. - */ -enum WaylandModeScale -{ - WAYLAND_MODE_SCALE_UNDEFINED, - WAYLAND_MODE_SCALE_ASPECT, - WAYLAND_MODE_SCALE_STRETCH, - WAYLAND_MODE_SCALE_NONE -}; - -static enum WaylandModeScale GetModeScaleMethod(void) -{ - static enum WaylandModeScale scale_mode = WAYLAND_MODE_SCALE_UNDEFINED; - - if (scale_mode == WAYLAND_MODE_SCALE_UNDEFINED) { - const char *scale_hint = SDL_GetHint(SDL_HINT_VIDEO_WAYLAND_MODE_SCALING); - - if (scale_hint) { - if (!SDL_strcasecmp(scale_hint, "aspect")) { - scale_mode = WAYLAND_MODE_SCALE_ASPECT; - } else if (!SDL_strcasecmp(scale_hint, "none")) { - scale_mode = WAYLAND_MODE_SCALE_NONE; - } else { - scale_mode = WAYLAND_MODE_SCALE_STRETCH; - } - } else { - scale_mode = WAYLAND_MODE_SCALE_STRETCH; - } - } - - return scale_mode; -} - -static void GetBufferSize(SDL_Window *window, int *width, int *height) -{ - SDL_WindowData *data = window->internal; - int buf_width; - int buf_height; - - // Exclusive fullscreen modes always have a pixel density of 1 - if (data->is_fullscreen && window->fullscreen_exclusive) { - buf_width = window->current_fullscreen_mode.w; - buf_height = window->current_fullscreen_mode.h; - } else if (!data->scale_to_display) { - // Round fractional backbuffer sizes halfway away from zero. - buf_width = PointToPixel(window, data->requested.logical_width); - buf_height = PointToPixel(window, data->requested.logical_height); - } else { - buf_width = data->requested.pixel_width; - buf_height = data->requested.pixel_height; - } - - if (width) { - *width = buf_width; - } - if (height) { - *height = buf_height; - } -} - -static void SetMinMaxDimensions(SDL_Window *window) -{ - SDL_WindowData *wind = window->internal; - int min_width, min_height, max_width, max_height; - - if ((window->flags & SDL_WINDOW_FULLSCREEN) || wind->fullscreen_deadline_count) { - min_width = 0; - min_height = 0; - max_width = 0; - max_height = 0; - } else if (window->flags & SDL_WINDOW_RESIZABLE) { - int adj_w = SDL_max(window->min_w, wind->system_limits.min_width); - int adj_h = SDL_max(window->min_h, wind->system_limits.min_height); - if (wind->scale_to_display) { - adj_w = PixelToPoint(window, adj_w); - adj_h = PixelToPoint(window, adj_h); - } - min_width = adj_w; - min_height = adj_h; - - adj_w = window->max_w ? SDL_max(window->max_w, wind->system_limits.min_width) : 0; - adj_h = window->max_h ? SDL_max(window->max_h, wind->system_limits.min_height) : 0; - if (wind->scale_to_display) { - adj_w = PixelToPoint(window, adj_w); - adj_h = PixelToPoint(window, adj_h); - } - max_width = adj_w; - max_height = adj_h; - } else { - min_width = wind->current.logical_width; - min_height = wind->current.logical_height; - max_width = wind->current.logical_width; - max_height = wind->current.logical_height; - } - -#ifdef HAVE_LIBDECOR_H - if (wind->shell_surface_type == WAYLAND_SHELL_SURFACE_TYPE_LIBDECOR) { - if (!wind->shell_surface.libdecor.initial_configure_seen || !wind->shell_surface.libdecor.frame) { - return; // Can't do anything yet, wait for ShowWindow - } - /* No need to change these values if the window is non-resizable, - * as libdecor will just overwrite them internally. - */ - if (libdecor_frame_has_capability(wind->shell_surface.libdecor.frame, LIBDECOR_ACTION_RESIZE)) { - libdecor_frame_set_min_content_size(wind->shell_surface.libdecor.frame, - min_width, - min_height); - libdecor_frame_set_max_content_size(wind->shell_surface.libdecor.frame, - max_width, - max_height); - } - } else -#endif - if (wind->shell_surface_type == WAYLAND_SHELL_SURFACE_TYPE_XDG_TOPLEVEL) { - if (wind->shell_surface.xdg.toplevel.xdg_toplevel == NULL) { - return; // Can't do anything yet, wait for ShowWindow - } - xdg_toplevel_set_min_size(wind->shell_surface.xdg.toplevel.xdg_toplevel, - min_width, - min_height); - xdg_toplevel_set_max_size(wind->shell_surface.xdg.toplevel.xdg_toplevel, - max_width, - max_height); - } -} - -static void EnsurePopupPositionIsValid(SDL_Window *window, int *x, int *y) -{ - int adj_count = 0; - - /* Per the xdg-positioner spec, child popup windows must intersect or at - * least be partially adjacent to the parent window. - * - * Failure to ensure this on a compositor that enforces this restriction - * can result in behavior ranging from the window being spuriously closed - * to a protocol violation. - */ - if (*x + window->w < 0) { - *x = -window->w; - ++adj_count; - } - if (*y + window->h < 0) { - *y = -window->h; - ++adj_count; - } - if (*x > window->parent->w) { - *x = window->parent->w; - ++adj_count; - } - if (*y > window->parent->h) { - *y = window->parent->h; - ++adj_count; - } - - /* If adjustment was required on the x and y axes, the popup is aligned with - * the parent corner-to-corner and is neither overlapping nor adjacent, so it - * must be nudged by 1 to be considered adjacent. - */ - if (adj_count > 1) { - *x += *x < 0 ? 1 : -1; - } -} - -static void AdjustPopupOffset(SDL_Window *popup, int *x, int *y) -{ - // Adjust the popup positioning, if necessary -#ifdef HAVE_LIBDECOR_H - if (popup->parent->internal->shell_surface_type == WAYLAND_SHELL_SURFACE_TYPE_LIBDECOR) { - int adj_x, adj_y; - libdecor_frame_translate_coordinate(popup->parent->internal->shell_surface.libdecor.frame, - *x, *y, &adj_x, &adj_y); - *x = adj_x; - *y = adj_y; - } -#endif -} - -static void RepositionPopup(SDL_Window *window, bool use_current_position) -{ - SDL_WindowData *wind = window->internal; - - if (wind->shell_surface_type == WAYLAND_SHELL_SURFACE_TYPE_XDG_POPUP && - wind->shell_surface.xdg.popup.xdg_positioner && - xdg_popup_get_version(wind->shell_surface.xdg.popup.xdg_popup) >= XDG_POPUP_REPOSITION_SINCE_VERSION) { - int x = use_current_position ? window->x : window->pending.x; - int y = use_current_position ? window->y : window->pending.y; - - EnsurePopupPositionIsValid(window, &x, &y); - if (wind->scale_to_display) { - x = PixelToPoint(window->parent, x); - y = PixelToPoint(window->parent, y); - } - AdjustPopupOffset(window, &x, &y); - 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); - xdg_positioner_set_size(wind->shell_surface.xdg.popup.xdg_positioner, wind->current.logical_width, wind->current.logical_height); - xdg_positioner_set_offset(wind->shell_surface.xdg.popup.xdg_positioner, x, y); - xdg_popup_reposition(wind->shell_surface.xdg.popup.xdg_popup, - wind->shell_surface.xdg.popup.xdg_positioner, - 0); - } -} - -static void SetSurfaceOpaqueRegion(SDL_WindowData *wind, bool is_opaque) -{ - SDL_VideoData *viddata = wind->waylandData; - - if (is_opaque) { - struct wl_region *region = wl_compositor_create_region(viddata->compositor); - wl_region_add(region, 0, 0, - wind->current.logical_width, wind->current.logical_height); - wl_surface_set_opaque_region(wind->surface, region); - wl_region_destroy(region); - } else { - wl_surface_set_opaque_region(wind->surface, NULL); - } -} - -static bool ConfigureWindowGeometry(SDL_Window *window) -{ - SDL_WindowData *data = window->internal; - const double scale_factor = GetWindowScale(window); - const int old_pixel_width = data->current.pixel_width; - const int old_pixel_height = data->current.pixel_height; - int window_width, window_height; - bool window_size_changed; - - // Throttle interactive resize events to once per refresh cycle to prevent lag. - if (data->resizing) { - data->resizing = false; - - if (data->drop_interactive_resizes) { - return false; - } else { - data->drop_interactive_resizes = true; - } - } - - // Set the drawable backbuffer size. - GetBufferSize(window, &data->current.pixel_width, &data->current.pixel_height); - const bool buffer_size_changed = data->current.pixel_width != old_pixel_width || - data->current.pixel_height != old_pixel_height; - - if (data->egl_window && buffer_size_changed) { - WAYLAND_wl_egl_window_resize(data->egl_window, - data->current.pixel_width, - data->current.pixel_height, - 0, 0); - } - - if (data->is_fullscreen && window->fullscreen_exclusive) { - int output_width; - int output_height; - window_width = window->current_fullscreen_mode.w; - window_height = window->current_fullscreen_mode.h; - - output_width = data->requested.logical_width; - output_height = data->requested.logical_height; - - switch (GetModeScaleMethod()) { - case WAYLAND_MODE_SCALE_NONE: - /* The Wayland spec states that the advertised fullscreen dimensions are a maximum. - * Windows can request a smaller size, but exceeding these dimensions is a protocol violation, - * thus, modes that exceed the output size still need to be scaled with a viewport. - */ - if (window_width <= output_width && window_height <= output_height) { - output_width = window_width; - output_height = window_height; - - break; - } - SDL_FALLTHROUGH; - case WAYLAND_MODE_SCALE_ASPECT: - { - const float output_ratio = (float)output_width / (float)output_height; - const float mode_ratio = (float)window_width / (float)window_height; - - if (output_ratio > mode_ratio) { - output_width = SDL_lroundf((float)window_width * ((float)output_height / (float)window_height)); - } else if (output_ratio < mode_ratio) { - output_height = SDL_lroundf((float)window_height * ((float)output_width / (float)window_width)); - } - } break; - default: - break; - } - - window_size_changed = window_width != window->w || window_height != window->h || - data->current.logical_width != output_width || data->current.logical_height != output_height; - - if (window_size_changed || buffer_size_changed) { - if (data->viewport) { - wp_viewport_set_destination(data->viewport, output_width, output_height); - - data->current.logical_width = output_width; - data->current.logical_height = output_height; - } else { - // Calculate the integer scale from the mode and output. - const int32_t int_scale = SDL_max(window->current_fullscreen_mode.w / output_width, 1); - - wl_surface_set_buffer_scale(data->surface, int_scale); - data->current.logical_width = window->current_fullscreen_mode.w; - data->current.logical_height = window->current_fullscreen_mode.h; - } - - data->pointer_scale.x = (double)window_width / (double)data->current.logical_width; - data->pointer_scale.y = (double)window_height / (double)data->current.logical_height; - } - } else { - window_width = data->requested.logical_width; - window_height = data->requested.logical_height; - - window_size_changed = window_width != data->current.logical_width || window_height != data->current.logical_height; - - if (window_size_changed || buffer_size_changed) { - if (data->viewport) { - wp_viewport_set_destination(data->viewport, window_width, window_height); - } else if (window->flags & SDL_WINDOW_HIGH_PIXEL_DENSITY) { - // 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. - wl_surface_set_buffer_scale(data->surface, (int32_t)scale_factor); - } - - // Clamp the physical window size to the system minimum required size. - data->current.logical_width = SDL_max(window_width, data->system_limits.min_width); - data->current.logical_height = SDL_max(window_height, data->system_limits.min_height); - - if (!data->scale_to_display) { - data->pointer_scale.x = 1.0; - data->pointer_scale.y = 1.0; - } else { - data->pointer_scale.x = scale_factor; - data->pointer_scale.y = scale_factor; - } - } - } - - /* - * The surface geometry, opaque region and pointer confinement region only - * need to be recalculated if the output size has changed. - */ - if (window_size_changed) { - /* XXX: This is a hack and only set on the xdg-toplevel path when viewports - * aren't supported to avoid a potential protocol violation if a buffer - * with an old size is committed. - */ - if (!data->viewport && data->shell_surface_type == WAYLAND_SHELL_SURFACE_TYPE_XDG_TOPLEVEL && data->shell_surface.xdg.surface) { - xdg_surface_set_window_geometry(data->shell_surface.xdg.surface, 0, 0, data->current.logical_width, data->current.logical_height); - } - - SetSurfaceOpaqueRegion(data, !(window->flags & SDL_WINDOW_TRANSPARENT) && window->opacity == 1.0f); - - // Ensure that child popup windows are still in bounds. - for (SDL_Window *child = window->first_child; child; child = child->next_sibling) { - RepositionPopup(child, true); - } - } - - /* Update the min/max dimensions, primarily if the state was changed, and for non-resizable - * xdg-toplevel windows where the limits should match the window size. - */ - SetMinMaxDimensions(window); - - // Unconditionally send the window and drawable size, the video core will deduplicate when required. - if (!data->scale_to_display) { - SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_RESIZED, window_width, window_height); - } else { - SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_RESIZED, data->current.pixel_width, data->current.pixel_height); - } - SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED, data->current.pixel_width, data->current.pixel_height); - - /* Send an exposure event if the window is in the shown state and the size has changed, - * even if the window is occluded, as the client needs to commit a new frame for the - * changes to take effect. - * - * The occlusion state is immediately set again afterward, if necessary. - */ - if (data->shell_surface_status == WAYLAND_SHELL_SURFACE_STATUS_SHOWN) { - if ((buffer_size_changed || window_size_changed) || - (!data->suspended && (window->flags & SDL_WINDOW_OCCLUDED))) { - SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_EXPOSED, 0, 0); - } - - if (data->suspended) { - SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_OCCLUDED, 0, 0); - } - } - - return true; -} - -static void CommitLibdecorFrame(SDL_Window *window) -{ -#ifdef HAVE_LIBDECOR_H - SDL_WindowData *wind = window->internal; - - if (wind->shell_surface_type == WAYLAND_SHELL_SURFACE_TYPE_LIBDECOR && wind->shell_surface.libdecor.frame) { - struct libdecor_state *state = libdecor_state_new(wind->current.logical_width, wind->current.logical_height); - libdecor_frame_commit(wind->shell_surface.libdecor.frame, state, NULL); - libdecor_state_free(state); - } -#endif -} - -static void fullscreen_deadline_handler(void *data, struct wl_callback *callback, uint32_t callback_data) -{ - // Get the window from the ID as it may have been destroyed - SDL_WindowID windowID = (SDL_WindowID)((uintptr_t)data); - SDL_Window *window = SDL_GetWindowFromID(windowID); - - if (window && window->internal) { - window->internal->fullscreen_deadline_count--; - } - - wl_callback_destroy(callback); -} - -static struct wl_callback_listener fullscreen_deadline_listener = { - fullscreen_deadline_handler -}; - -static void maximized_restored_deadline_handler(void *data, struct wl_callback *callback, uint32_t callback_data) -{ - // Get the window from the ID as it may have been destroyed - SDL_WindowID windowID = (SDL_WindowID)((uintptr_t)data); - SDL_Window *window = SDL_GetWindowFromID(windowID); - - if (window && window->internal) { - window->internal->maximized_restored_deadline_count--; - } - - wl_callback_destroy(callback); -} - -static struct wl_callback_listener maximized_restored_deadline_listener = { - maximized_restored_deadline_handler -}; - -static void FlushPendingEvents(SDL_Window *window) -{ - // Serialize and restore the pending flags, as they may be overwritten while flushing. - const bool last_position_pending = window->last_position_pending; - const bool last_size_pending = window->last_size_pending; - - while (window->internal->fullscreen_deadline_count || window->internal->maximized_restored_deadline_count) { - WAYLAND_wl_display_roundtrip(window->internal->waylandData->display); - } - - window->last_position_pending = last_position_pending; - window->last_size_pending = last_size_pending; -} - -/* While we can't get window position from the compositor, we do at least know - * what monitor we're on, so let's send move events that put the window at the - * center of the whatever display the wl_surface_listener events give us. - */ -static void Wayland_move_window(SDL_Window *window) -{ - SDL_WindowData *wind = window->internal; - SDL_DisplayData *display; - SDL_DisplayID *displays; - - if (wind->outputs && wind->num_outputs) { - display = wind->outputs[wind->num_outputs - 1]; - } else { - // A window may not be on any displays if minimized. - return; - } - - displays = SDL_GetDisplays(NULL); - if (displays) { - for (int i = 0; displays[i]; ++i) { - if (SDL_GetDisplayDriverData(displays[i]) == display) { - /* We want to send a very very specific combination here: - * - * 1. A coordinate that tells the application what display we're on - * 2. Exactly (0, 0) - * - * Part 1 is useful information but is also really important for - * ensuring we end up on the right display for fullscreen, while - * part 2 is important because numerous applications use a specific - * combination of GetWindowPosition and GetGlobalMouseState, and of - * course neither are supported by Wayland. Since global mouse will - * fall back to just GetMouseState, we need the window position to - * be zero so the cursor math works without it going off in some - * random direction. See UE5 Editor for a notable example of this! - * - * This may be an issue some day if we're ever able to implement - * SDL_GetDisplayUsableBounds! - * - * -flibit - */ - - if (wind->last_displayID != displays[i]) { - wind->last_displayID = displays[i]; - if (wind->shell_surface_type != WAYLAND_SHELL_SURFACE_TYPE_XDG_POPUP) { - SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_MOVED, display->x, display->y); - SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_DISPLAY_CHANGED, wind->last_displayID, 0); - } - } - break; - } - } - SDL_free(displays); - } -} - -static void SetFullscreen(SDL_Window *window, struct wl_output *output) -{ - SDL_WindowData *wind = window->internal; - SDL_VideoData *viddata = wind->waylandData; - -#ifdef HAVE_LIBDECOR_H - if (wind->shell_surface_type == WAYLAND_SHELL_SURFACE_TYPE_LIBDECOR) { - if (!wind->shell_surface.libdecor.frame) { - return; // Can't do anything yet, wait for ShowWindow - } - - wind->fullscreen_exclusive = output ? window->fullscreen_exclusive : false; - ++wind->fullscreen_deadline_count; - if (output) { - Wayland_SetWindowResizable(SDL_GetVideoDevice(), window, true); - wl_surface_commit(wind->surface); - - libdecor_frame_set_fullscreen(wind->shell_surface.libdecor.frame, output); - } else { - libdecor_frame_unset_fullscreen(wind->shell_surface.libdecor.frame); - } - } else -#endif - if (wind->shell_surface_type == WAYLAND_SHELL_SURFACE_TYPE_XDG_TOPLEVEL) { - if (wind->shell_surface.xdg.toplevel.xdg_toplevel == NULL) { - return; // Can't do anything yet, wait for ShowWindow - } - - wind->fullscreen_exclusive = output ? window->fullscreen_exclusive : false; - ++wind->fullscreen_deadline_count; - if (output) { - Wayland_SetWindowResizable(SDL_GetVideoDevice(), window, true); - wl_surface_commit(wind->surface); - - xdg_toplevel_set_fullscreen(wind->shell_surface.xdg.toplevel.xdg_toplevel, output); - } else { - xdg_toplevel_unset_fullscreen(wind->shell_surface.xdg.toplevel.xdg_toplevel); - } - } - - // Queue a deadline event - struct wl_callback *cb = wl_display_sync(viddata->display); - wl_callback_add_listener(cb, &fullscreen_deadline_listener, (void *)((uintptr_t)window->id)); -} - -static void UpdateWindowFullscreen(SDL_Window *window, bool fullscreen) -{ - SDL_WindowData *wind = window->internal; - - wind->is_fullscreen = fullscreen; - - if (fullscreen) { - if (!(window->flags & SDL_WINDOW_FULLSCREEN)) { - SDL_copyp(&window->current_fullscreen_mode, &window->requested_fullscreen_mode); - SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_ENTER_FULLSCREEN, 0, 0); - SDL_UpdateFullscreenMode(window, SDL_FULLSCREEN_OP_ENTER, false); - - /* Set the output for exclusive fullscreen windows when entering fullscreen from a - * compositor event, or if the fullscreen parameters were changed between the initial - * fullscreen request and now, to ensure that the window is on the correct output, - * as requested by the client. - */ - if (window->fullscreen_exclusive && (!wind->fullscreen_exclusive || !wind->fullscreen_was_positioned)) { - SDL_VideoDisplay *disp = SDL_GetVideoDisplay(window->current_fullscreen_mode.displayID); - if (disp) { - wind->fullscreen_was_positioned = true; - SetFullscreen(window, disp->internal->output); - } - } - } - } else { - // Don't change the fullscreen flags if the window is hidden or being hidden. - if ((window->flags & SDL_WINDOW_FULLSCREEN) && !window->is_hiding && !(window->flags & SDL_WINDOW_HIDDEN)) { - SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_LEAVE_FULLSCREEN, 0, 0); - SDL_UpdateFullscreenMode(window, SDL_FULLSCREEN_OP_LEAVE, false); - wind->fullscreen_was_positioned = false; - - /* Send a move event, in case it was deferred while the fullscreen window was moving and - * on multiple outputs. - */ - Wayland_move_window(window); - } - } -} - -static const struct wl_callback_listener surface_frame_listener; - -static void surface_frame_done(void *data, struct wl_callback *cb, uint32_t time) -{ - SDL_WindowData *wind = (SDL_WindowData *)data; - - /* XXX: This is needed to work around an Nvidia egl-wayland bug due to buffer coordinates - * being used with wl_surface_damage, which causes part of the output to not be - * updated when using a viewport with an output region larger than the source region. - */ - if (wl_compositor_get_version(wind->waylandData->compositor) >= WL_SURFACE_DAMAGE_BUFFER_SINCE_VERSION) { - wl_surface_damage_buffer(wind->surface, 0, 0, SDL_MAX_SINT32, SDL_MAX_SINT32); - } else { - wl_surface_damage(wind->surface, 0, 0, SDL_MAX_SINT32, SDL_MAX_SINT32); - } - - wind->drop_interactive_resizes = false; - - if (wind->shell_surface_status == WAYLAND_SHELL_SURFACE_STATUS_WAITING_FOR_FRAME) { - wind->shell_surface_status = WAYLAND_SHELL_SURFACE_STATUS_SHOWN; - - // If any child windows are waiting on this window to be shown, show them now - for (SDL_Window *w = wind->sdlwindow->first_child; w; w = w->next_sibling) { - if (w->internal->shell_surface_status == WAYLAND_SHELL_SURFACE_STATUS_SHOW_PENDING) { - Wayland_ShowWindow(SDL_GetVideoDevice(), w); - } else if (w->internal->reparenting_required) { - Wayland_SetWindowParent(SDL_GetVideoDevice(), w, w->parent); - if (w->flags & SDL_WINDOW_MODAL) { - Wayland_SetWindowModal(SDL_GetVideoDevice(), w, true); - } - } - } - - /* If the window was initially set to the suspended state, send the occluded event now, - * as we don't want to mark the window as occluded until at least one frame has been submitted. - */ - if (wind->suspended) { - SDL_SendWindowEvent(wind->sdlwindow, SDL_EVENT_WINDOW_OCCLUDED, 0, 0); - } - } - - wl_callback_destroy(cb); - wind->surface_frame_callback = wl_surface_frame(wind->surface); - wl_callback_add_listener(wind->surface_frame_callback, &surface_frame_listener, data); -} - -static const struct wl_callback_listener surface_frame_listener = { - surface_frame_done -}; - -static const struct wl_callback_listener gles_swap_frame_listener; - -static void gles_swap_frame_done(void *data, struct wl_callback *cb, uint32_t time) -{ - SDL_WindowData *wind = (SDL_WindowData *)data; - SDL_SetAtomicInt(&wind->swap_interval_ready, 1); // mark window as ready to present again. - - // reset this callback to fire again once a new frame was presented and compositor wants the next one. - wind->gles_swap_frame_callback = wl_surface_frame(wind->gles_swap_frame_surface_wrapper); - wl_callback_destroy(cb); - wl_callback_add_listener(wind->gles_swap_frame_callback, &gles_swap_frame_listener, data); -} - -static const struct wl_callback_listener gles_swap_frame_listener = { - gles_swap_frame_done -}; - -static void handle_configure_xdg_shell_surface(void *data, struct xdg_surface *xdg, uint32_t serial) -{ - SDL_WindowData *wind = (SDL_WindowData *)data; - SDL_Window *window = wind->sdlwindow; - - if (ConfigureWindowGeometry(window)) { - xdg_surface_ack_configure(xdg, serial); - } - - wind->shell_surface.xdg.initial_configure_seen = true; -} - -static const struct xdg_surface_listener shell_surface_listener_xdg = { - handle_configure_xdg_shell_surface -}; - -static void handle_configure_xdg_toplevel(void *data, - struct xdg_toplevel *xdg_toplevel, - int32_t width, - int32_t height, - struct wl_array *states) -{ - SDL_WindowData *wind = (SDL_WindowData *)data; - SDL_Window *window = wind->sdlwindow; - - enum xdg_toplevel_state *state; - bool fullscreen = false; - bool maximized = false; - bool floating = true; - bool tiled = false; - bool active = false; - bool resizing = false; - bool suspended = false; - wl_array_for_each (state, states) { - switch (*state) { - case XDG_TOPLEVEL_STATE_FULLSCREEN: - fullscreen = true; - floating = false; - break; - case XDG_TOPLEVEL_STATE_MAXIMIZED: - maximized = true; - floating = false; - break; - case XDG_TOPLEVEL_STATE_RESIZING: - resizing = true; - break; - case XDG_TOPLEVEL_STATE_ACTIVATED: - active = true; - break; - case XDG_TOPLEVEL_STATE_TILED_LEFT: - case XDG_TOPLEVEL_STATE_TILED_RIGHT: - case XDG_TOPLEVEL_STATE_TILED_TOP: - case XDG_TOPLEVEL_STATE_TILED_BOTTOM: - tiled = true; - floating = false; - break; - case XDG_TOPLEVEL_STATE_SUSPENDED: - suspended = true; - break; - default: - break; - } - } - - UpdateWindowFullscreen(window, fullscreen); - - /* Always send a maximized/restore event; if the event is redundant it will - * automatically be discarded (see src/events/SDL_windowevents.c) - * - * No, we do not get minimize events from xdg-shell, however, the minimized - * state can be programmatically set. The meaning of 'minimized' is compositor - * dependent, but in general, we can assume that the flag should remain set until - * the next focused configure event occurs. - */ - if (active || !(window->flags & SDL_WINDOW_MINIMIZED)) { - if (window->flags & SDL_WINDOW_MINIMIZED) { - // If we were minimized, send a restored event before possibly sending maximized. - SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_RESTORED, 0, 0); - } - SDL_SendWindowEvent(window, - (maximized && !fullscreen) ? SDL_EVENT_WINDOW_MAXIMIZED : SDL_EVENT_WINDOW_RESTORED, - 0, 0); - } - - if (!fullscreen) { - /* xdg_toplevel spec states that this is a suggestion. - * Ignore if less than or greater than max/min size. - */ - if (window->flags & SDL_WINDOW_RESIZABLE) { - if (width == 0 || height == 0) { - /* This happens when the compositor indicates that the size is - * up to the client, so use the cached window size here. - */ - if (floating) { - width = window->floating.w; - height = window->floating.h; - - // Clamp the window to the toplevel bounds, if any are set. - if (wind->shell_surface_status == WAYLAND_SHELL_SURFACE_STATUS_WAITING_FOR_CONFIGURE && - wind->toplevel_bounds.width && wind->toplevel_bounds.height) { - width = SDL_min(wind->toplevel_bounds.width, width); - height = SDL_min(wind->toplevel_bounds.height, height); - } - } else { - width = window->windowed.w; - height = window->windowed.h; - } - - if (!wind->scale_to_display) { - wind->requested.logical_width = width; - wind->requested.logical_height = height; - } else { - wind->requested.pixel_width = width; - wind->requested.pixel_height = height; - width = wind->requested.logical_width = PixelToPoint(window, width); - height = wind->requested.logical_height = PixelToPoint(window, height); - } - } else { - /* Don't apply the supplied dimensions if they haven't changed from the last configuration - * event, or a newer size set programmatically can be overwritten by old data. - */ - if (width != wind->last_configure.width || height != wind->last_configure.height) { - wind->requested.logical_width = width; - wind->requested.logical_height = height; - - if (wind->scale_to_display) { - wind->requested.pixel_width = PointToPixel(window, width); - wind->requested.pixel_height = PointToPixel(window, height); - } - } - } - } else { - /* If we're a fixed-size window, we know our size for sure. - * Always assume the configure is wrong. - */ - if (!wind->scale_to_display) { - width = wind->requested.logical_width = window->floating.w; - height = wind->requested.logical_height = window->floating.h; - } else { - wind->requested.pixel_width = window->floating.w; - wind->requested.pixel_height = window->floating.h; - width = wind->requested.logical_width = PixelToPoint(window, window->floating.w); - height = wind->requested.logical_height = PixelToPoint(window, window->floating.h); - } - } - - /* Notes on the spec: - * - * - The content limits are only a hint, which the compositor is free to ignore, - * so apply them manually when appropriate. - * - * - Maximized windows must have their exact dimensions respected, thus they must - * not be resized, or a protocol violation can occur. - * - * - When resizing a window, the width/height are maximum values, so aspect ratio - * correction can't resize beyond the existing dimensions, or a protocol violation - * can occur. In practice, nothing seems to kill clients that do this, but doing - * so causes GNOME to glitch out. - */ - if (!maximized) { - if (!wind->scale_to_display) { - if (window->max_w > 0) { - wind->requested.logical_width = SDL_min(wind->requested.logical_width, window->max_w); - } - wind->requested.logical_width = SDL_max(wind->requested.logical_width, window->min_w); - - if (window->max_h > 0) { - wind->requested.logical_height = SDL_min(wind->requested.logical_height, window->max_h); - } - wind->requested.logical_height = SDL_max(wind->requested.logical_height, window->min_h); - - // Aspect correction. - const float aspect = (float)wind->requested.logical_width / (float)wind->requested.logical_height; - - if (window->min_aspect != 0.f && aspect < window->min_aspect) { - wind->requested.logical_height = SDL_lroundf((float)wind->requested.logical_width / window->min_aspect); - } else if (window->max_aspect != 0.f && aspect > window->max_aspect) { - wind->requested.logical_width = SDL_lroundf((float)wind->requested.logical_height * window->max_aspect); - } - } else { - if (window->max_w > 0) { - wind->requested.pixel_width = SDL_min(wind->requested.pixel_width, window->max_w); - } - wind->requested.pixel_width = SDL_max(wind->requested.pixel_width, window->min_w); - - if (window->max_h > 0) { - wind->requested.pixel_height = SDL_min(wind->requested.pixel_height, window->max_h); - } - wind->requested.pixel_height = SDL_max(wind->requested.pixel_height, window->min_h); - - // Aspect correction. - const float aspect = (float)wind->requested.pixel_width / (float)wind->requested.pixel_height; - - if (window->min_aspect != 0.f && aspect < window->min_aspect) { - wind->requested.pixel_height = SDL_lroundf((float)wind->requested.pixel_width / window->min_aspect); - } else if (window->max_aspect != 0.f && aspect > window->max_aspect) { - wind->requested.pixel_width = SDL_lroundf((float)wind->requested.pixel_height * window->max_aspect); - } - - wind->requested.logical_width = PixelToPoint(window, wind->requested.pixel_width); - wind->requested.logical_height = PixelToPoint(window, wind->requested.pixel_height); - } - } - } else { - // Fullscreen windows know their exact size. - if (width == 0 || height == 0) { - width = wind->requested.logical_width; - height = wind->requested.logical_height; - } else { - wind->requested.logical_width = width; - wind->requested.logical_height = height; - } - - if (wind->scale_to_display) { - wind->requested.pixel_width = PointToPixel(window, width); - wind->requested.pixel_height = PointToPixel(window, height); - } - } - - wind->last_configure.width = width; - wind->last_configure.height = height; - wind->floating = floating; - wind->suspended = suspended; - wind->active = active; - window->tiled = tiled; - wind->resizing = resizing; - - if (wind->shell_surface_status == WAYLAND_SHELL_SURFACE_STATUS_WAITING_FOR_CONFIGURE) { - wind->shell_surface_status = WAYLAND_SHELL_SURFACE_STATUS_WAITING_FOR_FRAME; - } -} - -static void handle_close_xdg_toplevel(void *data, struct xdg_toplevel *xdg_toplevel) -{ - SDL_WindowData *window = (SDL_WindowData *)data; - SDL_SendWindowEvent(window->sdlwindow, SDL_EVENT_WINDOW_CLOSE_REQUESTED, 0, 0); -} - -static void handle_xdg_configure_toplevel_bounds(void *data, - struct xdg_toplevel *xdg_toplevel, - int32_t width, int32_t height) -{ - SDL_WindowData *window = (SDL_WindowData *)data; - window->toplevel_bounds.width = width; - window->toplevel_bounds.height = height; -} - -static void handle_xdg_toplevel_wm_capabilities(void *data, - struct xdg_toplevel *xdg_toplevel, - struct wl_array *capabilities) -{ - SDL_WindowData *wind = (SDL_WindowData *)data; - enum xdg_toplevel_wm_capabilities *wm_cap; - - wind->wm_caps = 0; - - wl_array_for_each (wm_cap, capabilities) { - switch (*wm_cap) { - case XDG_TOPLEVEL_WM_CAPABILITIES_WINDOW_MENU: - wind->wm_caps |= WAYLAND_WM_CAPS_WINDOW_MENU; - break; - case XDG_TOPLEVEL_WM_CAPABILITIES_MAXIMIZE: - wind->wm_caps |= WAYLAND_WM_CAPS_MAXIMIZE; - break; - case XDG_TOPLEVEL_WM_CAPABILITIES_FULLSCREEN: - wind->wm_caps |= WAYLAND_WM_CAPS_FULLSCREEN; - break; - case XDG_TOPLEVEL_WM_CAPABILITIES_MINIMIZE: - wind->wm_caps |= WAYLAND_WM_CAPS_MINIMIZE; - break; - default: - break; - } - } -} - -static const struct xdg_toplevel_listener toplevel_listener_xdg = { - handle_configure_xdg_toplevel, - handle_close_xdg_toplevel, - handle_xdg_configure_toplevel_bounds, // Version 4 - handle_xdg_toplevel_wm_capabilities // Version 5 -}; - -static void handle_configure_xdg_popup(void *data, - struct xdg_popup *xdg_popup, - int32_t x, - int32_t y, - int32_t width, - int32_t height) -{ - SDL_WindowData *wind = (SDL_WindowData *)data; - int offset_x = 0, offset_y = 0; - - // Adjust the position if it was offset for libdecor - AdjustPopupOffset(wind->sdlwindow, &offset_x, &offset_y); - x -= offset_x; - y -= offset_y; - - /* This happens when the compositor indicates that the size is - * up to the client, so use the cached window size here. - */ - if (width == 0 || height == 0) { - width = wind->sdlwindow->floating.w; - height = wind->sdlwindow->floating.h; - } - - /* Don't apply the supplied dimensions if they haven't changed from the last configuration - * event, or a newer size set programmatically can be overwritten by old data. - */ - if (width != wind->last_configure.width || height != wind->last_configure.height) { - wind->requested.logical_width = width; - wind->requested.logical_height = height; - - if (wind->scale_to_display) { - wind->requested.pixel_width = PointToPixel(wind->sdlwindow, width); - wind->requested.pixel_height = PointToPixel(wind->sdlwindow, height); - } - } - - if (wind->scale_to_display) { - x = PointToPixel(wind->sdlwindow->parent, x); - y = PointToPixel(wind->sdlwindow->parent, y); - } - - SDL_SendWindowEvent(wind->sdlwindow, SDL_EVENT_WINDOW_MOVED, x, y); - - wind->last_configure.width = width; - wind->last_configure.height = height; - - if (wind->shell_surface_status == WAYLAND_SHELL_SURFACE_STATUS_WAITING_FOR_CONFIGURE) { - wind->shell_surface_status = WAYLAND_SHELL_SURFACE_STATUS_WAITING_FOR_FRAME; - } -} - -static void handle_done_xdg_popup(void *data, struct xdg_popup *xdg_popup) -{ - SDL_WindowData *window = (SDL_WindowData *)data; - SDL_SendWindowEvent(window->sdlwindow, SDL_EVENT_WINDOW_CLOSE_REQUESTED, 0, 0); -} - -static void handle_repositioned_xdg_popup(void *data, - struct xdg_popup *xdg_popup, - uint32_t token) -{ - // No-op, configure does all the work we care about -} - -static const struct xdg_popup_listener popup_listener_xdg = { - handle_configure_xdg_popup, - handle_done_xdg_popup, - handle_repositioned_xdg_popup -}; - -static void handle_configure_zxdg_decoration(void *data, - struct zxdg_toplevel_decoration_v1 *zxdg_toplevel_decoration_v1, - uint32_t mode) -{ - SDL_Window *window = (SDL_Window *)data; - SDL_WindowData *internal = window->internal; - SDL_VideoDevice *device = SDL_GetVideoDevice(); - - /* If the compositor tries to force CSD anyway, bail on direct XDG support - * and fall back to libdecor, it will handle these events from then on. - * - * To do this we have to fully unmap, then map with libdecor loaded. - */ - if (mode == ZXDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE) { - if (window->flags & SDL_WINDOW_BORDERLESS) { - // borderless windows do request CSD, so we got what we wanted - return; - } - if (!Wayland_LoadLibdecor(internal->waylandData, true)) { - // libdecor isn't available, so no borders for you... oh well - return; - } - WAYLAND_wl_display_roundtrip(internal->waylandData->display); - - Wayland_HideWindow(device, window); - SDL_zero(internal->shell_surface); - internal->shell_surface_type = WAYLAND_SHELL_SURFACE_TYPE_LIBDECOR; - - Wayland_ShowWindow(device, window); - } -} - -static const struct zxdg_toplevel_decoration_v1_listener decoration_listener = { - handle_configure_zxdg_decoration -}; - -#ifdef HAVE_LIBDECOR_H -/* - * XXX: Hack for older versions of libdecor that lack the function to query the - * minimum content size limit. The internal limits must always be overridden - * to ensure that very small windows don't cause errors or crashes. - * - * On libdecor >= 0.1.2, which exposes the function to get the minimum content - * size limit, this function is a no-op. - * - * Can be removed if the minimum required version of libdecor is raised to - * 0.1.2 or higher. - */ -static void OverrideLibdecorLimits(SDL_Window *window) -{ -#ifdef SDL_VIDEO_DRIVER_WAYLAND_DYNAMIC_LIBDECOR - if (!libdecor_frame_get_min_content_size) { - libdecor_frame_set_min_content_size(window->internal->shell_surface.libdecor.frame, window->min_w, window->min_h); - } -#elif !SDL_LIBDECOR_CHECK_VERSION(0, 2, 0) - libdecor_frame_set_min_content_size(window->internal->shell_surface.libdecor.frame, window->min_w, window->min_h); -#endif -} - -/* - * NOTE: Retrieves the minimum content size limits, if the function for doing so is available. - * On versions of libdecor that lack the minimum content size retrieval function, this - * function is a no-op. - * - * Can be replaced with a direct call if the minimum required version of libdecor is raised - * to 0.1.2 or higher. - */ -static void LibdecorGetMinContentSize(struct libdecor_frame *frame, int *min_w, int *min_h) -{ -#ifdef SDL_VIDEO_DRIVER_WAYLAND_DYNAMIC_LIBDECOR - if (libdecor_frame_get_min_content_size != NULL) { - libdecor_frame_get_min_content_size(frame, min_w, min_h); - } -#elif SDL_LIBDECOR_CHECK_VERSION(0, 2, 0) - libdecor_frame_get_min_content_size(frame, min_w, min_h); -#endif -} - -static void decoration_frame_configure(struct libdecor_frame *frame, - struct libdecor_configuration *configuration, - void *user_data) -{ - SDL_WindowData *wind = (SDL_WindowData *)user_data; - SDL_Window *window = wind->sdlwindow; - - enum libdecor_window_state window_state; - int width, height; - - bool prev_fullscreen = wind->is_fullscreen; - bool active = false; - bool fullscreen = false; - bool maximized = false; - bool tiled = false; - bool suspended = false; - bool resizing = false; - - static const enum libdecor_window_state tiled_states = (LIBDECOR_WINDOW_STATE_TILED_LEFT | LIBDECOR_WINDOW_STATE_TILED_RIGHT | - LIBDECOR_WINDOW_STATE_TILED_TOP | LIBDECOR_WINDOW_STATE_TILED_BOTTOM); - - // Window State - if (libdecor_configuration_get_window_state(configuration, &window_state)) { - fullscreen = (window_state & LIBDECOR_WINDOW_STATE_FULLSCREEN) != 0; - maximized = (window_state & LIBDECOR_WINDOW_STATE_MAXIMIZED) != 0; - active = (window_state & LIBDECOR_WINDOW_STATE_ACTIVE) != 0; - tiled = (window_state & tiled_states) != 0; -#if SDL_LIBDECOR_CHECK_VERSION(0, 2, 0) - suspended = (window_state & LIBDECOR_WINDOW_STATE_SUSPENDED) != 0; -#endif -#if SDL_LIBDECOR_CHECK_VERSION(0, 3, 0) - resizing = (window_state & LIBDECOR_WINDOW_STATE_RESIZING) != 0; -#endif - } - const bool floating = !(fullscreen || maximized || tiled); - - UpdateWindowFullscreen(window, fullscreen); - - /* Always send a maximized/restore event; if the event is redundant it will - * automatically be discarded (see src/events/SDL_windowevents.c) - * - * No, we do not get minimize events from libdecor, however, the minimized - * state can be programmatically set. The meaning of 'minimized' is compositor - * dependent, but in general, we can assume that the flag should remain set until - * the next focused configure event occurs. - */ - if (active || !(window->flags & SDL_WINDOW_MINIMIZED)) { - if (window->flags & SDL_WINDOW_MINIMIZED) { - // If we were minimized, send a restored event before possibly sending maximized. - SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_RESTORED, 0, 0); - } - SDL_SendWindowEvent(window, - (maximized && !fullscreen) ? SDL_EVENT_WINDOW_MAXIMIZED : SDL_EVENT_WINDOW_RESTORED, - 0, 0); - } - - /* For fullscreen or fixed-size windows we know our size. - * Always assume the configure is wrong. - */ - if (fullscreen) { - if (!libdecor_configuration_get_content_size(configuration, frame, &width, &height)) { - width = wind->requested.logical_width; - height = wind->requested.logical_height; - } else { - // Fullscreen windows know their exact size. - wind->requested.logical_width = width; - wind->requested.logical_height = height; - - if (wind->scale_to_display) { - wind->requested.pixel_width = PointToPixel(window, width); - wind->requested.pixel_height = PointToPixel(window, height); - } - } - } else { - if (!(window->flags & SDL_WINDOW_RESIZABLE)) { - /* If we're a fixed-size window, we know our size for sure. - * Always assume the configure is wrong. - */ - if (!wind->scale_to_display) { - width = wind->requested.logical_width = window->floating.w; - height = wind->requested.logical_height = window->floating.h; - } else { - wind->requested.pixel_width = window->floating.w; - wind->requested.pixel_height = window->floating.h; - width = wind->requested.logical_width = PixelToPoint(window, window->floating.w); - height = wind->requested.logical_height = PixelToPoint(window, window->floating.h); - } - - OverrideLibdecorLimits(window); - } else { - /* XXX: The libdecor cairo plugin sends bogus content sizes that add the - * height of the title bar when transitioning from a fixed-size to - * floating state. Ignore the sent window dimensions in this case, - * in favor of the cached value to avoid the window increasing in - * size after every state transition. - * - * https://gitlab.freedesktop.org/libdecor/libdecor/-/issues/34 - */ - if ((floating && (!wind->floating && !(window->flags & SDL_WINDOW_BORDERLESS))) || - !libdecor_configuration_get_content_size(configuration, frame, &width, &height)) { - /* This happens when we're being restored from a non-floating state, - * or the compositor indicates that the size is up to the client, so - * used the cached window size here. - */ - if (floating) { - width = window->floating.w; - height = window->floating.h; - } else { - width = window->windowed.w; - height = window->windowed.h; - } - - if (!wind->scale_to_display) { - wind->requested.logical_width = width; - wind->requested.logical_height = height; - } else { - wind->requested.pixel_width = width; - wind->requested.pixel_height = height; - width = wind->requested.logical_width = PixelToPoint(window, width); - height = wind->requested.logical_height = PixelToPoint(window, height); - } - } else { - /* Don't apply the supplied dimensions if they haven't changed from the last configuration - * event, or a newer size set programmatically can be overwritten by old data. - */ - if (width != wind->last_configure.width || height != wind->last_configure.height) { - wind->requested.logical_width = width; - wind->requested.logical_height = height; - - if (wind->scale_to_display) { - wind->requested.pixel_width = PointToPixel(window, width); - wind->requested.pixel_height = PointToPixel(window, height); - } - } - } - } - - /* Notes on the spec: - * - * - The content limits are only a hint, which the compositor is free to ignore, - * so apply them manually when appropriate. - * - * - Maximized windows must have their exact dimensions respected, thus they must - * not be resized, or a protocol violation can occur. - * - * - When resizing a window, the width/height are maximum values, so aspect ratio - * correction can't resize beyond the existing dimensions, or a protocol violation - * can occur. In practice, nothing seems to kill clients that do this, but doing - * so causes GNOME to glitch out. - */ - if (!maximized) { - if (!wind->scale_to_display) { - if (window->max_w > 0) { - wind->requested.logical_width = SDL_min(wind->requested.logical_width, window->max_w); - } - wind->requested.logical_width = SDL_max(wind->requested.logical_width, window->min_w); - - if (window->max_h > 0) { - wind->requested.logical_height = SDL_min(wind->requested.logical_height, window->max_h); - } - wind->requested.logical_height = SDL_max(wind->requested.logical_height, window->min_h); - - // Aspect correction. - const float aspect = (float)wind->requested.logical_width / (float)wind->requested.logical_height; - - if (window->min_aspect != 0.f && aspect < window->min_aspect) { - wind->requested.logical_height = SDL_lroundf((float)wind->requested.logical_width / window->min_aspect); - } else if (window->max_aspect != 0.f && aspect > window->max_aspect) { - wind->requested.logical_width = SDL_lroundf((float)wind->requested.logical_height * window->max_aspect); - } - } else { - if (window->max_w > 0) { - wind->requested.pixel_width = SDL_min(wind->requested.pixel_width, window->max_w); - } - wind->requested.pixel_width = SDL_max(wind->requested.pixel_width, window->min_w); - - if (window->max_h > 0) { - wind->requested.pixel_height = SDL_min(wind->requested.pixel_height, window->max_h); - } - wind->requested.pixel_height = SDL_max(wind->requested.pixel_height, window->min_h); - - // Aspect correction. - const float aspect = (float)wind->requested.pixel_width / (float)wind->requested.pixel_height; - - if (window->min_aspect != 0.f && aspect < window->min_aspect) { - wind->requested.pixel_height = SDL_lroundf((float)wind->requested.pixel_width / window->min_aspect); - } else if (window->max_aspect != 0.f && aspect > window->max_aspect) { - wind->requested.pixel_width = SDL_lroundf((float)wind->requested.pixel_height * window->max_aspect); - } - - wind->requested.logical_width = PixelToPoint(window, wind->requested.pixel_width); - wind->requested.logical_height = PixelToPoint(window, wind->requested.pixel_height); - } - } - } - - // Store the new state. - wind->last_configure.width = width; - wind->last_configure.height = height; - wind->floating = floating; - wind->suspended = suspended; - wind->active = active; - window->tiled = tiled; - wind->resizing = resizing; - - // Update the window manager capabilities. -#if SDL_LIBDECOR_CHECK_VERSION(0, 3, 0) - enum libdecor_wm_capabilities caps; -#ifdef SDL_VIDEO_DRIVER_WAYLAND_DYNAMIC_LIBDECOR - if (libdecor_frame_get_wm_capabilities) { - caps = libdecor_frame_get_wm_capabilities(wind->shell_surface.libdecor.frame); -#else - caps = libdecor_frame_get_wm_capabilities(wind->shell_surface.libdecor.frame); - { -#endif - wind->wm_caps = 0; - wind->wm_caps |= caps & LIBDECOR_WM_CAPABILITIES_WINDOW_MENU ? WAYLAND_WM_CAPS_WINDOW_MENU : 0; - wind->wm_caps |= caps & LIBDECOR_WM_CAPABILITIES_MAXIMIZE ? WAYLAND_WM_CAPS_MAXIMIZE : 0; - wind->wm_caps |= caps & LIBDECOR_WM_CAPABILITIES_FULLSCREEN ? WAYLAND_WM_CAPS_FULLSCREEN : 0; - wind->wm_caps |= caps & LIBDECOR_WM_CAPABILITIES_MINIMIZE ? WAYLAND_WM_CAPS_MINIMIZE : 0; - } -#endif - - // Calculate the new window geometry - if (ConfigureWindowGeometry(window)) { - // ... then commit the changes on the libdecor side. - struct libdecor_state *state = libdecor_state_new(wind->current.logical_width, wind->current.logical_height); - libdecor_frame_commit(frame, state, configuration); - libdecor_state_free(state); - } - - if (!wind->shell_surface.libdecor.initial_configure_seen) { - LibdecorGetMinContentSize(frame, &wind->system_limits.min_width, &wind->system_limits.min_height); - wind->shell_surface.libdecor.initial_configure_seen = true; - } - if (wind->shell_surface_status == WAYLAND_SHELL_SURFACE_STATUS_WAITING_FOR_CONFIGURE) { - wind->shell_surface_status = WAYLAND_SHELL_SURFACE_STATUS_WAITING_FOR_FRAME; - } - - /* Update the resize capability if this config event was the result of the - * compositor taking a window out of fullscreen. Since this will change the - * capabilities and commit a new frame state with the last known content - * dimension, this has to be called after the new state has been committed - * and the new content dimensions were updated. - */ - if (prev_fullscreen && !wind->is_fullscreen) { - Wayland_SetWindowResizable(SDL_GetVideoDevice(), window, - !!(window->flags & SDL_WINDOW_RESIZABLE)); - } -} - -static void decoration_frame_close(struct libdecor_frame *frame, void *user_data) -{ - SDL_SendWindowEvent(((SDL_WindowData *)user_data)->sdlwindow, SDL_EVENT_WINDOW_CLOSE_REQUESTED, 0, 0); -} - -static void decoration_frame_commit(struct libdecor_frame *frame, void *user_data) -{ - /* libdecor decoration subsurfaces are synchronous, so the client needs to - * commit a frame to trigger an update of the decoration surfaces. - */ - SDL_WindowData *wind = (SDL_WindowData *)user_data; - if (!wind->suspended && wind->shell_surface_status == WAYLAND_SHELL_SURFACE_STATUS_SHOWN) { - SDL_SendWindowEvent(wind->sdlwindow, SDL_EVENT_WINDOW_EXPOSED, 0, 0); - } -} - -static void decoration_dismiss_popup(struct libdecor_frame *frame, const char *seat_name, void *user_data) -{ - // NOP -} - -static struct libdecor_frame_interface libdecor_frame_interface = { - decoration_frame_configure, - decoration_frame_close, - decoration_frame_commit, - decoration_dismiss_popup -}; -#endif - -static void Wayland_HandlePreferredScaleChanged(SDL_WindowData *window_data, double factor) -{ - const double old_factor = window_data->scale_factor; - - // Round the scale factor if viewports aren't available. - if (!window_data->viewport) { - factor = SDL_ceil(factor); - } - - if (factor != old_factor) { - window_data->scale_factor = factor; - - if (window_data->scale_to_display) { - /* If the window is in the floating state with a user/application specified size, calculate the new - * logical size from the backbuffer size. Otherwise, use the fixed underlying logical size to calculate - * the new backbuffer dimensions. - */ - if (window_data->floating) { - window_data->requested.logical_width = PixelToPoint(window_data->sdlwindow, window_data->requested.pixel_width); - window_data->requested.logical_height = PixelToPoint(window_data->sdlwindow, window_data->requested.pixel_height); - } else { - window_data->requested.pixel_width = PointToPixel(window_data->sdlwindow, window_data->requested.logical_width); - window_data->requested.pixel_height = PointToPixel(window_data->sdlwindow, window_data->requested.logical_height); - } - } - - if (window_data->sdlwindow->flags & SDL_WINDOW_HIGH_PIXEL_DENSITY || window_data->scale_to_display) { - ConfigureWindowGeometry(window_data->sdlwindow); - CommitLibdecorFrame(window_data->sdlwindow); - } - } -} - -static void Wayland_MaybeUpdateScaleFactor(SDL_WindowData *window) -{ - double factor; - int i; - - /* If the fractional scale protocol is present or the core protocol supports the - * preferred buffer scale event, the compositor will explicitly tell the application - * what scale it wants via these events, so don't try to determine the scale factor - * from which displays the surface has entered. - */ - if (window->fractional_scale || wl_surface_get_version(window->surface) >= WL_SURFACE_PREFERRED_BUFFER_SCALE_SINCE_VERSION) { - return; - } - - if (window->num_outputs != 0) { - // Check every display's factor, use the highest - factor = 0.0; - for (i = 0; i < window->num_outputs; i++) { - SDL_DisplayData *internal = window->outputs[i]; - factor = SDL_max(factor, internal->scale_factor); - } - } else { - // All outputs removed, just fall back. - factor = window->scale_factor; - } - - Wayland_HandlePreferredScaleChanged(window, factor); -} - -void Wayland_RemoveOutputFromWindow(SDL_WindowData *window, SDL_DisplayData *display_data) -{ - for (int i = 0; i < window->num_outputs; i++) { - if (window->outputs[i] == display_data) { // remove this one - if (i == (window->num_outputs - 1)) { - window->outputs[i] = NULL; - } else { - SDL_memmove(&window->outputs[i], - &window->outputs[i + 1], - sizeof(SDL_DisplayData *) * ((window->num_outputs - i) - 1)); - } - window->num_outputs--; - i--; - } - } - - if (window->num_outputs == 0) { - SDL_free(window->outputs); - window->outputs = NULL; - } else if (!window->is_fullscreen || window->num_outputs == 1) { - Wayland_move_window(window->sdlwindow); - Wayland_MaybeUpdateScaleFactor(window); - } -} - -static void handle_surface_enter(void *data, struct wl_surface *surface, struct wl_output *output) -{ - SDL_WindowData *window = data; - SDL_DisplayData *internal = wl_output_get_user_data(output); - SDL_DisplayData **new_outputs; - - if (!SDL_WAYLAND_own_output(output) || !SDL_WAYLAND_own_surface(surface)) { - return; - } - - new_outputs = SDL_realloc(window->outputs, - sizeof(SDL_DisplayData *) * (window->num_outputs + 1)); - if (!new_outputs) { - return; - } - window->outputs = new_outputs; - window->outputs[window->num_outputs++] = internal; - - // Update the scale factor after the move so that fullscreen outputs are updated. - if (!window->is_fullscreen || window->num_outputs == 1) { - Wayland_move_window(window->sdlwindow); - Wayland_MaybeUpdateScaleFactor(window); - } -} - -static void handle_surface_leave(void *data, struct wl_surface *surface, struct wl_output *output) -{ - SDL_WindowData *window = (SDL_WindowData *)data; - - if (!SDL_WAYLAND_own_output(output) || !SDL_WAYLAND_own_surface(surface)) { - return; - } - - Wayland_RemoveOutputFromWindow(window, (SDL_DisplayData *)wl_output_get_user_data(output)); -} - -static void handle_preferred_buffer_scale(void *data, struct wl_surface *wl_surface, int32_t factor) -{ - SDL_WindowData *wind = data; - - /* The spec is unclear on how this interacts with the fractional scaling protocol, - * so, for now, assume that the fractional scaling protocol takes priority and - * only listen to this event if the fractional scaling protocol is not present. - */ - if (!wind->fractional_scale) { - Wayland_HandlePreferredScaleChanged(data, (double)factor); - } -} - -static void handle_preferred_buffer_transform(void *data, struct wl_surface *wl_surface, uint32_t transform) -{ - // Nothing to do here. -} - -static const struct wl_surface_listener surface_listener = { - handle_surface_enter, - handle_surface_leave, - handle_preferred_buffer_scale, - handle_preferred_buffer_transform -}; - -static void handle_preferred_fractional_scale(void *data, struct wp_fractional_scale_v1 *wp_fractional_scale_v1, uint32_t scale) -{ - const double factor = (double)scale / 120.; // 120 is a magic number defined in the spec as a common denominator - Wayland_HandlePreferredScaleChanged(data, factor); -} - -static const struct wp_fractional_scale_v1_listener fractional_scale_listener = { - handle_preferred_fractional_scale -}; - -static void frog_preferred_metadata_handler(void *data, struct frog_color_managed_surface *frog_color_managed_surface, uint32_t transfer_function, - uint32_t output_display_primary_red_x, uint32_t output_display_primary_red_y, - uint32_t output_display_primary_green_x, uint32_t output_display_primary_green_y, - uint32_t output_display_primary_blue_x, uint32_t output_display_primary_blue_y, - uint32_t output_white_point_x, uint32_t output_white_point_y, - uint32_t max_luminance, uint32_t min_luminance, - uint32_t max_full_frame_luminance) -{ - SDL_WindowData *wind = (SDL_WindowData *)data; - SDL_HDROutputProperties HDR; - - SDL_zero(HDR); - - switch (transfer_function) { - case FROG_COLOR_MANAGED_SURFACE_TRANSFER_FUNCTION_ST2084_PQ: - /* ITU-R BT.2408-7 (Sept 2023) has the reference PQ white level at 203 nits, - * while older Dolby documentation claims a reference level of 100 nits. - * - * Use 203 nits for now. - */ - HDR.HDR_headroom = max_luminance / 203.0f; - break; - case FROG_COLOR_MANAGED_SURFACE_TRANSFER_FUNCTION_SCRGB_LINEAR: - HDR.HDR_headroom = max_luminance / 80.0f; - break; - case FROG_COLOR_MANAGED_SURFACE_TRANSFER_FUNCTION_UNDEFINED: - case FROG_COLOR_MANAGED_SURFACE_TRANSFER_FUNCTION_SRGB: - case FROG_COLOR_MANAGED_SURFACE_TRANSFER_FUNCTION_GAMMA_22: - default: - HDR.HDR_headroom = 1.0f; - break; - } - - HDR.SDR_white_level = 1.0f; - SDL_SetWindowHDRProperties(wind->sdlwindow, &HDR, true); -} - -static const struct frog_color_managed_surface_listener frog_surface_listener = { - frog_preferred_metadata_handler -}; - -static void feedback_surface_preferred_changed(void *data, - struct wp_color_management_surface_feedback_v1 *wp_color_management_surface_feedback_v1, - uint32_t identity) -{ - SDL_WindowData *wind = (SDL_WindowData *)data; - Wayland_GetColorInfoForWindow(wind, false); -} - -static const struct wp_color_management_surface_feedback_v1_listener color_management_surface_feedback_listener = { - feedback_surface_preferred_changed -}; - -static void SetKeyboardFocus(SDL_Window *window, bool set_focus) -{ - SDL_Window *toplevel = window; - - // Find the toplevel parent - while (SDL_WINDOW_IS_POPUP(toplevel)) { - toplevel = toplevel->parent; - } - - toplevel->internal->keyboard_focus = window; - - if (set_focus && !window->is_hiding && !window->is_destroying) { - SDL_SetKeyboardFocus(window); - } -} - -bool Wayland_SetWindowHitTest(SDL_Window *window, bool enabled) -{ - return true; // just succeed, the real work is done elsewhere. -} - -static struct xdg_toplevel *GetToplevelForWindow(SDL_WindowData *wind) -{ - if (wind) { - /* Libdecor crashes on attempts to unset the parent by passing null, which is allowed by the - * toplevel spec, so just use the raw xdg-toplevel instead (that's what libdecor does - * internally anyways). - */ -#ifdef HAVE_LIBDECOR_H - if (wind->shell_surface_type == WAYLAND_SHELL_SURFACE_TYPE_LIBDECOR && wind->shell_surface.libdecor.frame) { - return libdecor_frame_get_xdg_toplevel(wind->shell_surface.libdecor.frame); - } else -#endif - if (wind->shell_surface_type == WAYLAND_SHELL_SURFACE_TYPE_XDG_TOPLEVEL && wind->shell_surface.xdg.toplevel.xdg_toplevel) { - return wind->shell_surface.xdg.toplevel.xdg_toplevel; - } - } - - return NULL; -} - -bool Wayland_SetWindowParent(SDL_VideoDevice *_this, SDL_Window *window, SDL_Window *parent_window) -{ - SDL_WindowData *child_data = window->internal; - SDL_WindowData *parent_data = parent_window ? parent_window->internal : NULL; - - child_data->reparenting_required = false; - - if (parent_data && parent_data->shell_surface_status != WAYLAND_SHELL_SURFACE_STATUS_SHOWN) { - // Need to wait for the parent to become mapped, or it's the same as setting a null parent. - child_data->reparenting_required = true; - return true; - } - - struct xdg_toplevel *child_toplevel = GetToplevelForWindow(child_data); - struct xdg_toplevel *parent_toplevel = GetToplevelForWindow(parent_data); - - if (child_toplevel) { - xdg_toplevel_set_parent(child_toplevel, parent_toplevel); - } - - return true; -} - -bool Wayland_SetWindowModal(SDL_VideoDevice *_this, SDL_Window *window, bool modal) -{ - SDL_VideoData *viddata = _this->internal; - SDL_WindowData *data = window->internal; - SDL_WindowData *parent_data = window->parent->internal; - - if (parent_data->shell_surface_status != WAYLAND_SHELL_SURFACE_STATUS_SHOWN) { - // Need to wait for the parent to become mapped before changing modal status. - data->reparenting_required = true; - return true; - } else { - data->reparenting_required = false; - } - - struct xdg_toplevel *toplevel = GetToplevelForWindow(data); - - if (toplevel) { - if (viddata->xdg_wm_dialog_v1) { - if (modal) { - if (!data->xdg_dialog_v1) { - data->xdg_dialog_v1 = xdg_wm_dialog_v1_get_xdg_dialog(viddata->xdg_wm_dialog_v1, toplevel); - } - - xdg_dialog_v1_set_modal(data->xdg_dialog_v1); - } else if (data->xdg_dialog_v1) { - xdg_dialog_v1_unset_modal(data->xdg_dialog_v1); - } - } - } - - return true; -} - -static void show_hide_sync_handler(void *data, struct wl_callback *callback, uint32_t callback_data) -{ - // Get the window from the ID as it may have been destroyed - SDL_WindowID windowID = (SDL_WindowID)((uintptr_t)data); - SDL_Window *window = SDL_GetWindowFromID(windowID); - - if (window && window->internal) { - SDL_WindowData *wind = window->internal; - wind->show_hide_sync_required = false; - } - - wl_callback_destroy(callback); -} - -static struct wl_callback_listener show_hide_sync_listener = { - show_hide_sync_handler -}; - -static void exported_handle_handler(void *data, struct zxdg_exported_v2 *zxdg_exported_v2, const char *handle) -{ - SDL_WindowData *wind = (SDL_WindowData*)data; - SDL_PropertiesID props = SDL_GetWindowProperties(wind->sdlwindow); - - SDL_SetStringProperty(props, SDL_PROP_WINDOW_WAYLAND_XDG_TOPLEVEL_EXPORT_HANDLE_STRING, handle); -} - -static struct zxdg_exported_v2_listener exported_v2_listener = { - exported_handle_handler -}; - -void Wayland_ShowWindow(SDL_VideoDevice *_this, SDL_Window *window) -{ - SDL_VideoData *c = _this->internal; - SDL_WindowData *data = window->internal; - SDL_PropertiesID props = SDL_GetWindowProperties(window); - - // Custom surfaces don't get toplevels and are always considered 'shown'; nothing to do here. - if (data->shell_surface_type == WAYLAND_SHELL_SURFACE_TYPE_CUSTOM) { - return; - } - - /* If this is a child window, the parent *must* be in the final shown state, - * meaning that it has received a configure event, followed by a frame callback. - * If not, a race condition can result, with effects ranging from the child - * window to spuriously closing to protocol errors. - * - * If waiting on the parent window, set the pending status and the window will - * be shown when the parent is in the shown state. - */ - if (window->parent) { - if (window->parent->internal->shell_surface_status != WAYLAND_SHELL_SURFACE_STATUS_SHOWN) { - data->shell_surface_status = WAYLAND_SHELL_SURFACE_STATUS_SHOW_PENDING; - return; - } - } - - /* The window was hidden, but the sync point hasn't yet been reached. - * Pump events to avoid a possible protocol violation. - */ - if (data->show_hide_sync_required) { - WAYLAND_wl_display_roundtrip(c->display); - } - - data->shell_surface_status = WAYLAND_SHELL_SURFACE_STATUS_WAITING_FOR_CONFIGURE; - - /* Detach any previous buffers before resetting everything, otherwise when - * calling this a second time you'll get an annoying protocol error! - * - * FIXME: This was originally moved to HideWindow, which _should_ make - * sense, but for whatever reason UE5's popups require that this actually - * be in both places at once? Possibly from renderers making commits? I can't - * fully remember if this location caused crashes or if I was fixing a pair - * of Hide/Show calls. In any case, UE gives us a pretty good test and having - * both detach calls passes. This bug may be relevant if I'm wrong: - * - * https://bugs.kde.org/show_bug.cgi?id=448856 - * - * -flibit - */ - wl_surface_attach(data->surface, NULL, 0, 0); - wl_surface_commit(data->surface); - - // Create the shell surface and map the toplevel/popup -#ifdef HAVE_LIBDECOR_H - if (data->shell_surface_type == WAYLAND_SHELL_SURFACE_TYPE_LIBDECOR) { - data->shell_surface.libdecor.frame = libdecor_decorate(c->shell.libdecor, - data->surface, - &libdecor_frame_interface, - data); - if (!data->shell_surface.libdecor.frame) { - SDL_LogError(SDL_LOG_CATEGORY_VIDEO, "Failed to create libdecor frame!"); - } else { - libdecor_frame_set_app_id(data->shell_surface.libdecor.frame, data->app_id); - libdecor_frame_map(data->shell_surface.libdecor.frame); - if (window->flags & SDL_WINDOW_BORDERLESS) { - // Note: Calling this with 'true' immediately after mapping will cause the libdecor Cairo plugin to crash. - libdecor_frame_set_visibility(data->shell_surface.libdecor.frame, false); - } - - if (c->zxdg_exporter_v2) { - data->exported = zxdg_exporter_v2_export_toplevel(c->zxdg_exporter_v2, data->surface); - zxdg_exported_v2_add_listener(data->exported, &exported_v2_listener, data); - } - - if (c->xdg_toplevel_icon_manager_v1 && data->xdg_toplevel_icon_v1) { - xdg_toplevel_icon_manager_v1_set_icon(_this->internal->xdg_toplevel_icon_manager_v1, - libdecor_frame_get_xdg_toplevel(data->shell_surface.libdecor.frame), - data->xdg_toplevel_icon_v1); - } - - SDL_SetPointerProperty(props, SDL_PROP_WINDOW_WAYLAND_XDG_SURFACE_POINTER, libdecor_frame_get_xdg_surface(data->shell_surface.libdecor.frame)); - SDL_SetPointerProperty(props, SDL_PROP_WINDOW_WAYLAND_XDG_TOPLEVEL_POINTER, libdecor_frame_get_xdg_toplevel(data->shell_surface.libdecor.frame)); - } - } else -#endif - if (data->shell_surface_type == WAYLAND_SHELL_SURFACE_TYPE_XDG_TOPLEVEL || data->shell_surface_type == WAYLAND_SHELL_SURFACE_TYPE_XDG_POPUP) { - data->shell_surface.xdg.surface = xdg_wm_base_get_xdg_surface(c->shell.xdg, data->surface); - xdg_surface_set_user_data(data->shell_surface.xdg.surface, data); - xdg_surface_add_listener(data->shell_surface.xdg.surface, &shell_surface_listener_xdg, data); - SDL_SetPointerProperty(SDL_GetWindowProperties(window), SDL_PROP_WINDOW_WAYLAND_XDG_SURFACE_POINTER, data->shell_surface.xdg.surface); - - if (data->shell_surface_type == WAYLAND_SHELL_SURFACE_TYPE_XDG_POPUP) { - SDL_Window *parent = window->parent; - SDL_WindowData *parent_data = parent->internal; - struct xdg_surface *parent_xdg_surface = NULL; - int position_x = 0, position_y = 0; - - // Configure the popup parameters -#ifdef HAVE_LIBDECOR_H - if (parent_data->shell_surface_type == WAYLAND_SHELL_SURFACE_TYPE_LIBDECOR) { - parent_xdg_surface = libdecor_frame_get_xdg_surface(parent_data->shell_surface.libdecor.frame); - } else -#endif - if (parent_data->shell_surface_type == WAYLAND_SHELL_SURFACE_TYPE_XDG_TOPLEVEL || - parent_data->shell_surface_type == WAYLAND_SHELL_SURFACE_TYPE_XDG_POPUP) { - parent_xdg_surface = parent_data->shell_surface.xdg.surface; - } - - // Set up the positioner for the popup and configure the constraints - data->shell_surface.xdg.popup.xdg_positioner = xdg_wm_base_create_positioner(c->shell.xdg); - xdg_positioner_set_anchor(data->shell_surface.xdg.popup.xdg_positioner, XDG_POSITIONER_ANCHOR_TOP_LEFT); - xdg_positioner_set_anchor_rect(data->shell_surface.xdg.popup.xdg_positioner, 0, 0, parent->internal->current.logical_width, parent->internal->current.logical_width); - xdg_positioner_set_constraint_adjustment(data->shell_surface.xdg.popup.xdg_positioner, - XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_SLIDE_X | XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_SLIDE_Y); - xdg_positioner_set_gravity(data->shell_surface.xdg.popup.xdg_positioner, XDG_POSITIONER_GRAVITY_BOTTOM_RIGHT); - xdg_positioner_set_size(data->shell_surface.xdg.popup.xdg_positioner, data->current.logical_width, data->current.logical_height); - - // Set the popup initial position - position_x = window->last_position_pending ? window->pending.x : window->x; - position_y = window->last_position_pending ? window->pending.y : window->y; - EnsurePopupPositionIsValid(window, &position_x, &position_y); - if (data->scale_to_display) { - position_x = PixelToPoint(window->parent, position_x); - position_y = PixelToPoint(window->parent, position_y); - } - AdjustPopupOffset(window, &position_x, &position_y); - xdg_positioner_set_offset(data->shell_surface.xdg.popup.xdg_positioner, position_x, position_y); - - // Assign the popup role - data->shell_surface.xdg.popup.xdg_popup = xdg_surface_get_popup(data->shell_surface.xdg.surface, - parent_xdg_surface, - data->shell_surface.xdg.popup.xdg_positioner); - xdg_popup_add_listener(data->shell_surface.xdg.popup.xdg_popup, &popup_listener_xdg, data); - - if (window->flags & SDL_WINDOW_TOOLTIP) { - struct wl_region *region; - - // Tooltips can't be interacted with, so turn off the input region to avoid blocking anything behind them - region = wl_compositor_create_region(c->compositor); - wl_region_add(region, 0, 0, 0, 0); - wl_surface_set_input_region(data->surface, region); - wl_region_destroy(region); - } else if (window->flags & SDL_WINDOW_POPUP_MENU) { - SetKeyboardFocus(window, window->parent == SDL_GetKeyboardFocus()); - } - - SDL_SetPointerProperty(props, SDL_PROP_WINDOW_WAYLAND_XDG_POPUP_POINTER, data->shell_surface.xdg.popup.xdg_popup); - SDL_SetPointerProperty(props, SDL_PROP_WINDOW_WAYLAND_XDG_POSITIONER_POINTER, data->shell_surface.xdg.popup.xdg_positioner); - } else { - data->shell_surface.xdg.toplevel.xdg_toplevel = xdg_surface_get_toplevel(data->shell_surface.xdg.surface); - xdg_toplevel_set_app_id(data->shell_surface.xdg.toplevel.xdg_toplevel, data->app_id); - xdg_toplevel_add_listener(data->shell_surface.xdg.toplevel.xdg_toplevel, &toplevel_listener_xdg, data); - - // Create the window decorations - if (c->decoration_manager) { - data->server_decoration = zxdg_decoration_manager_v1_get_toplevel_decoration(c->decoration_manager, data->shell_surface.xdg.toplevel.xdg_toplevel); - zxdg_toplevel_decoration_v1_add_listener(data->server_decoration, &decoration_listener, window); - 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; - zxdg_toplevel_decoration_v1_set_mode(data->server_decoration, mode); - } - - if (c->zxdg_exporter_v2) { - data->exported = zxdg_exporter_v2_export_toplevel(c->zxdg_exporter_v2, data->surface); - zxdg_exported_v2_add_listener(data->exported, &exported_v2_listener, data); - } - - if (c->xdg_toplevel_icon_manager_v1 && data->xdg_toplevel_icon_v1) { - xdg_toplevel_icon_manager_v1_set_icon(_this->internal->xdg_toplevel_icon_manager_v1, - data->shell_surface.xdg.toplevel.xdg_toplevel, - data->xdg_toplevel_icon_v1); - } - - SDL_SetPointerProperty(props, SDL_PROP_WINDOW_WAYLAND_XDG_TOPLEVEL_POINTER, data->shell_surface.xdg.toplevel.xdg_toplevel); - } - } - - // Restore state that was set prior to this call - Wayland_SetWindowParent(_this, window, window->parent); - - if (window->flags & SDL_WINDOW_MODAL) { - Wayland_SetWindowModal(_this, window, true); - } - - Wayland_SetWindowTitle(_this, window); - - /* We have to wait until the surface gets a "configure" event, or use of - * this surface will fail. This is a new rule for xdg_shell. - */ -#ifdef HAVE_LIBDECOR_H - if (data->shell_surface_type == WAYLAND_SHELL_SURFACE_TYPE_LIBDECOR) { - if (data->shell_surface.libdecor.frame) { - while (!data->shell_surface.libdecor.initial_configure_seen) { - WAYLAND_wl_display_flush(c->display); - WAYLAND_wl_display_dispatch(c->display); - } - } - } else -#endif - if (data->shell_surface_type == WAYLAND_SHELL_SURFACE_TYPE_XDG_POPUP || data->shell_surface_type == WAYLAND_SHELL_SURFACE_TYPE_XDG_TOPLEVEL) { - /* Unlike libdecor we need to call this explicitly to prevent a deadlock. - * libdecor will call this as part of their configure event! - * -flibit - */ - wl_surface_commit(data->surface); - if (data->shell_surface.xdg.surface) { - while (!data->shell_surface.xdg.initial_configure_seen) { - WAYLAND_wl_display_flush(c->display); - WAYLAND_wl_display_dispatch(c->display); - } - } - } else { - // Nothing to see here, just commit. - wl_surface_commit(data->surface); - } - - // Make sure the window can't be resized to 0 or it can be spuriously closed by the window manager. - data->system_limits.min_width = SDL_max(data->system_limits.min_width, 1); - data->system_limits.min_height = SDL_max(data->system_limits.min_height, 1); - - /* Unlike the rest of window state we have to set this _after_ flushing the - * display, because we need to create the decorations before possibly hiding - * them immediately afterward. - */ -#ifdef HAVE_LIBDECOR_H - if (data->shell_surface_type == WAYLAND_SHELL_SURFACE_TYPE_LIBDECOR) { - // Libdecor plugins can enforce minimum window sizes, so adjust if the initial window size is too small. - if (window->windowed.w < data->system_limits.min_width || - window->windowed.h < data->system_limits.min_height) { - - // Warn if the window frame will be larger than the content surface. - SDL_LogWarn(SDL_LOG_CATEGORY_VIDEO, - "Window dimensions (%i, %i) are smaller than the system enforced minimum (%i, %i); window borders will be larger than the content surface.", - window->windowed.w, window->windowed.h, data->system_limits.min_width, data->system_limits.min_height); - - data->current.logical_width = SDL_max(window->windowed.w, data->system_limits.min_width); - data->current.logical_height = SDL_max(window->windowed.h, data->system_limits.min_height); - CommitLibdecorFrame(window); - } - } -#endif - Wayland_SetWindowResizable(_this, window, !!(window->flags & SDL_WINDOW_RESIZABLE)); - - // We're finally done putting the window together, raise if possible - if (c->activation_manager) { - /* Note that we don't check for empty strings, as that is still - * considered a valid activation token! - */ - const char *activation_token = SDL_getenv("XDG_ACTIVATION_TOKEN"); - if (activation_token) { - xdg_activation_v1_activate(c->activation_manager, - activation_token, - data->surface); - - // Clear this variable, per the protocol's request - SDL_unsetenv_unsafe("XDG_ACTIVATION_TOKEN"); - } - } - - data->show_hide_sync_required = true; - struct wl_callback *cb = wl_display_sync(_this->internal->display); - wl_callback_add_listener(cb, &show_hide_sync_listener, (void*)((uintptr_t)window->id)); - - // Send an exposure event to signal that the client should draw. - if (data->shell_surface_status == WAYLAND_SHELL_SURFACE_STATUS_WAITING_FOR_FRAME) { - SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_EXPOSED, 0, 0); - } -} - -static void Wayland_ReleasePopup(SDL_VideoDevice *_this, SDL_Window *popup) -{ - SDL_WindowData *popupdata; - - // Basic sanity checks to weed out the weird popup closures - if (!SDL_ObjectValid(popup, SDL_OBJECT_TYPE_WINDOW)) { - return; - } - popupdata = popup->internal; - if (!popupdata) { - return; - } - - // This may already be freed by a parent popup! - if (popupdata->shell_surface.xdg.popup.xdg_popup == NULL) { - return; - } - - if (popup->flags & SDL_WINDOW_POPUP_MENU) { - SDL_Window *new_focus = popup->parent; - bool set_focus = popup == SDL_GetKeyboardFocus(); - - // Find the highest level window, up to the toplevel parent, that isn't being hidden or destroyed. - while (SDL_WINDOW_IS_POPUP(new_focus) && (new_focus->is_hiding || new_focus->is_destroying)) { - new_focus = new_focus->parent; - - // If some window in the chain currently had focus, set it to the new lowest-level window. - if (!set_focus) { - set_focus = new_focus == SDL_GetKeyboardFocus(); - } - } - - SetKeyboardFocus(new_focus, set_focus); - } - - xdg_popup_destroy(popupdata->shell_surface.xdg.popup.xdg_popup); - xdg_positioner_destroy(popupdata->shell_surface.xdg.popup.xdg_positioner); - popupdata->shell_surface.xdg.popup.xdg_popup = NULL; - popupdata->shell_surface.xdg.popup.xdg_positioner = NULL; - - SDL_PropertiesID props = SDL_GetWindowProperties(popup); - SDL_SetPointerProperty(props, SDL_PROP_WINDOW_WAYLAND_XDG_POPUP_POINTER, NULL); - SDL_SetPointerProperty(props, SDL_PROP_WINDOW_WAYLAND_XDG_POSITIONER_POINTER, NULL); -} - -void Wayland_HideWindow(SDL_VideoDevice *_this, SDL_Window *window) -{ - SDL_VideoData *data = _this->internal; - SDL_WindowData *wind = window->internal; - SDL_PropertiesID props = SDL_GetWindowProperties(window); - - // Custom surfaces have nothing to destroy and are always considered to be 'shown'; nothing to do here. - if (wind->shell_surface_type == WAYLAND_SHELL_SURFACE_TYPE_CUSTOM) { - return; - } - - /* The window was shown, but the sync point hasn't yet been reached. - * Pump events to avoid a possible protocol violation. - */ - if (wind->show_hide_sync_required) { - WAYLAND_wl_display_roundtrip(data->display); - } - - wind->shell_surface_status = WAYLAND_SHELL_SURFACE_STATUS_HIDDEN; - - if (wind->server_decoration) { - zxdg_toplevel_decoration_v1_destroy(wind->server_decoration); - wind->server_decoration = NULL; - } - - // Be sure to detach after this is done, otherwise ShowWindow crashes! - if (wind->shell_surface_type != WAYLAND_SHELL_SURFACE_TYPE_XDG_POPUP) { - wl_surface_attach(wind->surface, NULL, 0, 0); - wl_surface_commit(wind->surface); - } - - // Clean up the export handle. - if (wind->exported) { - zxdg_exported_v2_destroy(wind->exported); - wind->exported = NULL; - - SDL_SetStringProperty(props, SDL_PROP_WINDOW_WAYLAND_XDG_TOPLEVEL_EXPORT_HANDLE_STRING, NULL); - } - - if (wind->xdg_dialog_v1) { - xdg_dialog_v1_destroy(wind->xdg_dialog_v1); - wind->xdg_dialog_v1 = NULL; - } - -#ifdef HAVE_LIBDECOR_H - if (wind->shell_surface_type == WAYLAND_SHELL_SURFACE_TYPE_LIBDECOR) { - if (wind->shell_surface.libdecor.frame) { - libdecor_frame_unref(wind->shell_surface.libdecor.frame); - wind->shell_surface.libdecor.frame = NULL; - - SDL_SetPointerProperty(props, SDL_PROP_WINDOW_WAYLAND_XDG_SURFACE_POINTER, NULL); - SDL_SetPointerProperty(props, SDL_PROP_WINDOW_WAYLAND_XDG_TOPLEVEL_POINTER, NULL); - } - } else -#endif - if (wind->shell_surface_type == WAYLAND_SHELL_SURFACE_TYPE_XDG_POPUP) { - Wayland_ReleasePopup(_this, window); - } else if (wind->shell_surface.xdg.toplevel.xdg_toplevel) { - xdg_toplevel_destroy(wind->shell_surface.xdg.toplevel.xdg_toplevel); - wind->shell_surface.xdg.toplevel.xdg_toplevel = NULL; - SDL_SetPointerProperty(props, SDL_PROP_WINDOW_WAYLAND_XDG_TOPLEVEL_POINTER, NULL); - } - if (wind->shell_surface.xdg.surface) { - xdg_surface_destroy(wind->shell_surface.xdg.surface); - wind->shell_surface.xdg.surface = NULL; - SDL_SetPointerProperty(props, SDL_PROP_WINDOW_WAYLAND_XDG_SURFACE_POINTER, NULL); - } - - wind->show_hide_sync_required = true; - struct wl_callback *cb = wl_display_sync(_this->internal->display); - wl_callback_add_listener(cb, &show_hide_sync_listener, (void*)((uintptr_t)window->id)); -} - -static void handle_xdg_activation_done(void *data, - struct xdg_activation_token_v1 *xdg_activation_token_v1, - const char *token) -{ - SDL_WindowData *window = data; - if (xdg_activation_token_v1 == window->activation_token) { - xdg_activation_v1_activate(window->waylandData->activation_manager, - token, - window->surface); - xdg_activation_token_v1_destroy(window->activation_token); - window->activation_token = NULL; - } -} - -static const struct xdg_activation_token_v1_listener activation_listener_xdg = { - handle_xdg_activation_done -}; - -/* The xdg-activation protocol considers "activation" to be one of two things: - * - * 1: Raising a window to the top and flashing the titlebar - * 2: Flashing the titlebar while keeping the window where it is - * - * As you might expect from Wayland, the general policy is to go with #2 unless - * the client can prove to the compositor beyond a reasonable doubt that raising - * the window will not be malicuous behavior. - * - * For SDL this means RaiseWindow and FlashWindow both use the same protocol, - * but in different ways: RaiseWindow will provide as _much_ information as - * possible while FlashWindow will provide as _little_ information as possible, - * to nudge the compositor into doing what we want. - * - * This isn't _strictly_ what the protocol says will happen, but this is what - * current implementations are doing (as of writing, YMMV in the far distant - * future). - * - * -flibit - */ -static void Wayland_activate_window(SDL_VideoData *data, SDL_WindowData *target_wind, bool set_serial) -{ - struct SDL_WaylandInput * input = data->input; - SDL_Window *focus = SDL_GetKeyboardFocus(); - struct wl_surface *requesting_surface = focus ? focus->internal->surface : NULL; - - if (data->activation_manager) { - if (target_wind->activation_token) { - // We're about to overwrite this with a new request - xdg_activation_token_v1_destroy(target_wind->activation_token); - } - - target_wind->activation_token = xdg_activation_v1_get_activation_token(data->activation_manager); - xdg_activation_token_v1_add_listener(target_wind->activation_token, - &activation_listener_xdg, - target_wind); - - /* Note that we are not setting the app_id here. - * - * Hypothetically we could set the app_id from data->classname, but - * that part of the API is for _external_ programs, not ourselves. - * - * -flibit - */ - if (requesting_surface) { - // This specifies the surface from which the activation request is originating, not the activation target surface. - xdg_activation_token_v1_set_surface(target_wind->activation_token, requesting_surface); - } - if (set_serial && input && input->seat) { - xdg_activation_token_v1_set_serial(target_wind->activation_token, input->last_implicit_grab_serial, input->seat); - } - xdg_activation_token_v1_commit(target_wind->activation_token); - } -} - -void Wayland_RaiseWindow(SDL_VideoDevice *_this, SDL_Window *window) -{ - Wayland_activate_window(_this->internal, window->internal, true); -} - -bool Wayland_FlashWindow(SDL_VideoDevice *_this, SDL_Window *window, SDL_FlashOperation operation) -{ - /* Not setting the serial will specify 'urgency' without switching focus as per - * https://gitlab.freedesktop.org/wayland/wayland-protocols/-/merge_requests/9#note_854977 - */ - Wayland_activate_window(_this->internal, window->internal, false); - return true; -} - -SDL_FullscreenResult Wayland_SetWindowFullscreen(SDL_VideoDevice *_this, SDL_Window *window, - SDL_VideoDisplay *display, SDL_FullscreenOp fullscreen) -{ - SDL_WindowData *wind = window->internal; - struct wl_output *output = display->internal->output; - - // Custom surfaces have no toplevel to make fullscreen. - if (wind->shell_surface_type == WAYLAND_SHELL_SURFACE_TYPE_CUSTOM) { - return SDL_FULLSCREEN_FAILED; - } - - if (wind->show_hide_sync_required) { - WAYLAND_wl_display_roundtrip(_this->internal->display); - } - - // Flushing old events pending a new one, ignore this request. - if (wind->drop_fullscreen_requests) { - return SDL_FULLSCREEN_SUCCEEDED; - } - - wind->drop_fullscreen_requests = true; - FlushPendingEvents(window); - wind->drop_fullscreen_requests = false; - - // Nothing to do if the window is not fullscreen, and this isn't an explicit enter request. - if (!wind->is_fullscreen) { - if (fullscreen == SDL_FULLSCREEN_OP_UPDATE) { - // Request was out of date; signal the video core not to update any state. - return SDL_FULLSCREEN_PENDING; - } else if (fullscreen == SDL_FULLSCREEN_OP_LEAVE) { - // Already not fullscreen; nothing to do. - return SDL_FULLSCREEN_SUCCEEDED; - } - } - - // Don't send redundant fullscreen set/unset events. - if (fullscreen != wind->is_fullscreen) { - wind->fullscreen_was_positioned = fullscreen; - SetFullscreen(window, fullscreen ? output : NULL); - } else if (wind->is_fullscreen) { - /* - * If the window is already fullscreen, this is likely a request to switch between - * fullscreen and fullscreen desktop, change outputs, or change the video mode. - * - * If the window is already positioned on the target output, just update the - * window geometry. - */ - if (wind->last_displayID != display->id) { - wind->fullscreen_was_positioned = true; - SetFullscreen(window, output); - } else { - ConfigureWindowGeometry(window); - CommitLibdecorFrame(window); - - return SDL_FULLSCREEN_SUCCEEDED; - } - } - - return SDL_FULLSCREEN_PENDING; -} - -void Wayland_RestoreWindow(SDL_VideoDevice *_this, SDL_Window *window) -{ - SDL_WindowData *wind = window->internal; - - // Not currently fullscreen or maximized, and no state pending; nothing to do. - if (!(window->flags & (SDL_WINDOW_FULLSCREEN | SDL_WINDOW_MAXIMIZED)) && - !wind->fullscreen_deadline_count && !wind->maximized_restored_deadline_count) { - return; - } - -#ifdef HAVE_LIBDECOR_H - if (wind->shell_surface_type == WAYLAND_SHELL_SURFACE_TYPE_LIBDECOR) { - if (!wind->shell_surface.libdecor.frame) { - return; // Can't do anything yet, wait for ShowWindow - } - libdecor_frame_unset_maximized(wind->shell_surface.libdecor.frame); - - ++wind->maximized_restored_deadline_count; - struct wl_callback *cb = wl_display_sync(_this->internal->display); - wl_callback_add_listener(cb, &maximized_restored_deadline_listener, (void *)((uintptr_t)window->id)); - } else -#endif - // Note that xdg-shell does NOT provide a way to unset minimize! - if (wind->shell_surface_type == WAYLAND_SHELL_SURFACE_TYPE_XDG_TOPLEVEL) { - if (wind->shell_surface.xdg.toplevel.xdg_toplevel == NULL) { - return; // Can't do anything yet, wait for ShowWindow - } - xdg_toplevel_unset_maximized(wind->shell_surface.xdg.toplevel.xdg_toplevel); - - ++wind->maximized_restored_deadline_count; - struct wl_callback *cb = wl_display_sync(_this->internal->display); - wl_callback_add_listener(cb, &maximized_restored_deadline_listener, (void *)((uintptr_t)window->id)); - } -} - -void Wayland_SetWindowBordered(SDL_VideoDevice *_this, SDL_Window *window, bool bordered) -{ - SDL_WindowData *wind = window->internal; - const SDL_VideoData *viddata = (const SDL_VideoData *)_this->internal; - -#ifdef HAVE_LIBDECOR_H - if (wind->shell_surface_type == WAYLAND_SHELL_SURFACE_TYPE_LIBDECOR) { - if (wind->shell_surface.libdecor.frame) { - libdecor_frame_set_visibility(wind->shell_surface.libdecor.frame, bordered); - } - } else -#endif - if (wind->shell_surface_type == WAYLAND_SHELL_SURFACE_TYPE_XDG_TOPLEVEL) { - if ((viddata->decoration_manager) && (wind->server_decoration)) { - const enum zxdg_toplevel_decoration_v1_mode mode = bordered ? ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE : ZXDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE; - zxdg_toplevel_decoration_v1_set_mode(wind->server_decoration, mode); - } - } -} - -void Wayland_SetWindowResizable(SDL_VideoDevice *_this, SDL_Window *window, bool resizable) -{ -#ifdef HAVE_LIBDECOR_H - const SDL_WindowData *wind = window->internal; - - if (wind->shell_surface_type == WAYLAND_SHELL_SURFACE_TYPE_LIBDECOR) { - if (!wind->shell_surface.libdecor.frame) { - return; // Can't do anything yet, wait for ShowWindow - } - if (libdecor_frame_has_capability(wind->shell_surface.libdecor.frame, LIBDECOR_ACTION_RESIZE)) { - if (!resizable) { - libdecor_frame_unset_capabilities(wind->shell_surface.libdecor.frame, LIBDECOR_ACTION_RESIZE); - } - } else if (resizable) { - libdecor_frame_set_capabilities(wind->shell_surface.libdecor.frame, LIBDECOR_ACTION_RESIZE); - } - } -#endif - - /* When changing the resize capability on libdecor windows, the limits must always - * be reapplied, as when libdecor changes states, it overwrites the values internally. - */ - SetMinMaxDimensions(window); - CommitLibdecorFrame(window); -} - -void Wayland_MaximizeWindow(SDL_VideoDevice *_this, SDL_Window *window) -{ - SDL_VideoData *viddata = _this->internal; - SDL_WindowData *wind = window->internal; - - if (wind->show_hide_sync_required) { - WAYLAND_wl_display_roundtrip(_this->internal->display); - } - - // Not fullscreen, already maximized, and no state pending; nothing to do. - if (!(window->flags & SDL_WINDOW_FULLSCREEN) && (window->flags & SDL_WINDOW_MAXIMIZED) && - !wind->fullscreen_deadline_count && !wind->maximized_restored_deadline_count) { - return; - } - -#ifdef HAVE_LIBDECOR_H - if (wind->shell_surface_type == WAYLAND_SHELL_SURFACE_TYPE_LIBDECOR) { - if (!wind->shell_surface.libdecor.frame) { - return; // Can't do anything yet, wait for ShowWindow - } - - // Commit to preserve any pending size data. - wl_surface_commit(wind->surface); - libdecor_frame_set_maximized(wind->shell_surface.libdecor.frame); - - ++wind->maximized_restored_deadline_count; - struct wl_callback *cb = wl_display_sync(viddata->display); - wl_callback_add_listener(cb, &maximized_restored_deadline_listener, (void *)((uintptr_t)window->id)); - } else -#endif - if (wind->shell_surface_type == WAYLAND_SHELL_SURFACE_TYPE_XDG_TOPLEVEL) { - if (wind->shell_surface.xdg.toplevel.xdg_toplevel == NULL) { - return; // Can't do anything yet, wait for ShowWindow - } - - // Commit to preserve any pending size data. - wl_surface_commit(wind->surface); - xdg_toplevel_set_maximized(wind->shell_surface.xdg.toplevel.xdg_toplevel); - - ++wind->maximized_restored_deadline_count; - struct wl_callback *cb = wl_display_sync(viddata->display); - wl_callback_add_listener(cb, &maximized_restored_deadline_listener, (void *)((uintptr_t)window->id)); - } -} - -void Wayland_MinimizeWindow(SDL_VideoDevice *_this, SDL_Window *window) -{ - SDL_WindowData *wind = window->internal; - - if (!(wind->wm_caps & WAYLAND_WM_CAPS_MINIMIZE)) { - return; - } - -#ifdef HAVE_LIBDECOR_H - if (wind->shell_surface_type == WAYLAND_SHELL_SURFACE_TYPE_LIBDECOR) { - if (!wind->shell_surface.libdecor.frame) { - return; // Can't do anything yet, wait for ShowWindow - } - libdecor_frame_set_minimized(wind->shell_surface.libdecor.frame); - SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_MINIMIZED, 0, 0); - } else -#endif - if (wind->shell_surface_type == WAYLAND_SHELL_SURFACE_TYPE_XDG_TOPLEVEL) { - if (wind->shell_surface.xdg.toplevel.xdg_toplevel == NULL) { - return; // Can't do anything yet, wait for ShowWindow - } - xdg_toplevel_set_minimized(wind->shell_surface.xdg.toplevel.xdg_toplevel); - SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_MINIMIZED, 0, 0); - } -} - -bool Wayland_SetWindowMouseRect(SDL_VideoDevice *_this, SDL_Window *window) -{ - SDL_VideoData *data = _this->internal; - - /* This may look suspiciously like SetWindowGrab, despite SetMouseRect not - * implicitly doing a grab. And you're right! Wayland doesn't let us mess - * around with mouse focus whatsoever, so it just happens to be that the - * work that we can do in these two functions ends up being the same. - * - * Just know that this call lets you confine with a rect, SetWindowGrab - * lets you confine without a rect. - */ - if (SDL_RectEmpty(&window->mouse_rect) && !(window->flags & SDL_WINDOW_MOUSE_GRABBED)) { - return Wayland_input_unconfine_pointer(data->input, window); - } else { - return Wayland_input_confine_pointer(data->input, window); - } -} - -bool Wayland_SetWindowMouseGrab(SDL_VideoDevice *_this, SDL_Window *window, bool grabbed) -{ - SDL_VideoData *data = _this->internal; - - if (grabbed) { - return Wayland_input_confine_pointer(data->input, window); - } else if (SDL_RectEmpty(&window->mouse_rect)) { - return Wayland_input_unconfine_pointer(data->input, window); - } - - return true; -} - -bool Wayland_SetWindowKeyboardGrab(SDL_VideoDevice *_this, SDL_Window *window, bool grabbed) -{ - SDL_VideoData *data = _this->internal; - - if (grabbed) { - return Wayland_input_grab_keyboard(window, data->input); - } else { - return Wayland_input_ungrab_keyboard(window); - } -} - -bool Wayland_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window, SDL_PropertiesID create_props) -{ - SDL_WindowData *data; - SDL_VideoData *c = _this->internal; - struct wl_surface *external_surface = (struct wl_surface *)SDL_GetPointerProperty(create_props, SDL_PROP_WINDOW_CREATE_WAYLAND_WL_SURFACE_POINTER, - (struct wl_surface *)SDL_GetPointerProperty(create_props, "sdl2-compat.external_window", NULL)); - const bool custom_surface_role = (external_surface != NULL) || SDL_GetBooleanProperty(create_props, SDL_PROP_WINDOW_CREATE_WAYLAND_SURFACE_ROLE_CUSTOM_BOOLEAN, false); - const bool create_egl_window = !!(window->flags & SDL_WINDOW_OPENGL) || - SDL_GetBooleanProperty(create_props, SDL_PROP_WINDOW_CREATE_WAYLAND_CREATE_EGL_WINDOW_BOOLEAN, false); - - data = SDL_calloc(1, sizeof(*data)); - if (!data) { - return false; - } - - window->internal = data; - - if (window->x == SDL_WINDOWPOS_UNDEFINED) { - window->x = 0; - } - if (window->y == SDL_WINDOWPOS_UNDEFINED) { - window->y = 0; - } - - data->waylandData = c; - data->sdlwindow = window; - - // Default to all capabilities - data->wm_caps = WAYLAND_WM_CAPS_ALL; - - data->scale_factor = 1.0; - - if (SDL_WINDOW_IS_POPUP(window)) { - data->scale_to_display = window->parent->internal->scale_to_display; - data->scale_factor = window->parent->internal->scale_factor; - EnsurePopupPositionIsValid(window, &window->x, &window->y); - } else { - for (int i = 0; i < _this->num_displays; i++) { - data->scale_factor = SDL_max(data->scale_factor, _this->displays[i]->internal->scale_factor); - } - } - - data->outputs = NULL; - data->num_outputs = 0; - data->scale_to_display = c->scale_to_display_enabled; - - // Cache the app_id at creation time, as it may change before the window is mapped. - data->app_id = SDL_strdup(SDL_GetAppID()); - - if (!data->scale_to_display) { - data->requested.logical_width = window->floating.w; - data->requested.logical_height = window->floating.h; - } else { - data->requested.logical_width = PixelToPoint(window, window->floating.w); - data->requested.logical_height = PixelToPoint(window, window->floating.h); - data->requested.pixel_width = window->floating.w; - data->requested.pixel_height = window->floating.h; - } - - if (!external_surface) { - data->surface = wl_compositor_create_surface(c->compositor); - wl_surface_add_listener(data->surface, &surface_listener, data); - wl_surface_set_user_data(data->surface, data); - SDL_WAYLAND_register_surface(data->surface); - } else { - window->flags |= SDL_WINDOW_EXTERNAL; - data->surface = external_surface; - - /* External surfaces are registered by being put in a list, as changing tags or userdata - * can cause problems with external toolkits. - */ - Wayland_AddWindowDataToExternalList(data); - } - - /* Always attach a viewport and fractional scale manager if available and the surface is not custom/external, - * or the custom/external surface was explicitly flagged as high pixel density aware, which signals that the - * application wants SDL to handle scaling. - */ - if (!custom_surface_role || (window->flags & SDL_WINDOW_HIGH_PIXEL_DENSITY)) { - if (c->viewporter) { - data->viewport = wp_viewporter_get_viewport(c->viewporter, data->surface); - - // The viewport always uses the entire buffer. - wp_viewport_set_source(data->viewport, - wl_fixed_from_int(-1), wl_fixed_from_int(-1), - wl_fixed_from_int(-1), wl_fixed_from_int(-1)); - } - if (c->fractional_scale_manager) { - data->fractional_scale = wp_fractional_scale_manager_v1_get_fractional_scale(c->fractional_scale_manager, data->surface); - wp_fractional_scale_v1_add_listener(data->fractional_scale, &fractional_scale_listener, data); - } - } - - if (!custom_surface_role) { - if (c->wp_color_manager_v1) { - data->wp_color_management_surface_feedback = wp_color_manager_v1_get_surface_feedback(c->wp_color_manager_v1, data->surface); - wp_color_management_surface_feedback_v1_add_listener(data->wp_color_management_surface_feedback, &color_management_surface_feedback_listener, data); - Wayland_GetColorInfoForWindow(data, true); - } else if (c->frog_color_management_factory_v1) { - data->frog_color_managed_surface = frog_color_management_factory_v1_get_color_managed_surface(c->frog_color_management_factory_v1, data->surface); - frog_color_managed_surface_add_listener(data->frog_color_managed_surface, &frog_surface_listener, data); - } - - if (c->wp_alpha_modifier_v1) { - data->wp_alpha_modifier_surface_v1 = wp_alpha_modifier_v1_get_surface(c->wp_alpha_modifier_v1, data->surface); - wp_alpha_modifier_surface_v1_set_multiplier(data->wp_alpha_modifier_surface_v1, SDL_MAX_UINT32); - } - } - - // Must be called before EGL configuration to set the drawable backbuffer size. - ConfigureWindowGeometry(window); - - /* Fire a callback when the compositor wants a new frame rendered. - * Right now this only matters for OpenGL; we use this callback to add a - * wait timeout that avoids getting deadlocked by the compositor when the - * window isn't visible. - */ - if (window->flags & SDL_WINDOW_OPENGL) { - data->gles_swap_frame_event_queue = WAYLAND_wl_display_create_queue(data->waylandData->display); - data->gles_swap_frame_surface_wrapper = WAYLAND_wl_proxy_create_wrapper(data->surface); - WAYLAND_wl_proxy_set_queue((struct wl_proxy *)data->gles_swap_frame_surface_wrapper, data->gles_swap_frame_event_queue); - data->gles_swap_frame_callback = wl_surface_frame(data->gles_swap_frame_surface_wrapper); - wl_callback_add_listener(data->gles_swap_frame_callback, &gles_swap_frame_listener, data); - } - - // No frame callback on external surfaces as it may already have one attached. - if (!external_surface) { - // Fire a callback when the compositor wants a new frame to set the surface damage region. - data->surface_frame_callback = wl_surface_frame(data->surface); - wl_callback_add_listener(data->surface_frame_callback, &surface_frame_listener, data); - } - - if (window->flags & SDL_WINDOW_TRANSPARENT) { - if (_this->gl_config.alpha_size == 0) { - _this->gl_config.alpha_size = 8; - } - } - - if (create_egl_window) { - data->egl_window = WAYLAND_wl_egl_window_create(data->surface, data->current.pixel_width, data->current.pixel_height); - } - -#ifdef SDL_VIDEO_OPENGL_EGL - if (window->flags & SDL_WINDOW_OPENGL) { - // Create the GLES window surface - data->egl_surface = SDL_EGL_CreateSurface(_this, window, (NativeWindowType)data->egl_window); - - if (data->egl_surface == EGL_NO_SURFACE) { - return false; // SDL_EGL_CreateSurface should have set error - } - } -#endif - - if (c->relative_mouse_mode) { - Wayland_input_enable_relative_pointer(c->input); - } - - // We may need to create an idle inhibitor for this new window - Wayland_SuspendScreenSaver(_this); - - if (!custom_surface_role) { -#ifdef HAVE_LIBDECOR_H - if (c->shell.libdecor && !SDL_WINDOW_IS_POPUP(window)) { - data->shell_surface_type = WAYLAND_SHELL_SURFACE_TYPE_LIBDECOR; - } else -#endif - if (c->shell.xdg) { - if (SDL_WINDOW_IS_POPUP(window)) { - data->shell_surface_type = WAYLAND_SHELL_SURFACE_TYPE_XDG_POPUP; - } else { - data->shell_surface_type = WAYLAND_SHELL_SURFACE_TYPE_XDG_TOPLEVEL; - } - } // All other cases will be WAYLAND_SURFACE_UNKNOWN - } else { - // Roleless and external surfaces are always considered to be in the shown state by the backend. - data->shell_surface_type = WAYLAND_SHELL_SURFACE_TYPE_CUSTOM; - data->shell_surface_status = WAYLAND_SHELL_SURFACE_STATUS_SHOWN; - } - - if (SDL_GetHintBoolean(SDL_HINT_VIDEO_DOUBLE_BUFFER, false)) { - data->double_buffer = true; - } - - SDL_PropertiesID props = SDL_GetWindowProperties(window); - SDL_SetPointerProperty(props, SDL_PROP_WINDOW_WAYLAND_DISPLAY_POINTER, data->waylandData->display); - SDL_SetPointerProperty(props, SDL_PROP_WINDOW_WAYLAND_SURFACE_POINTER, data->surface); - SDL_SetPointerProperty(props, SDL_PROP_WINDOW_WAYLAND_VIEWPORT_POINTER, data->viewport); - SDL_SetPointerProperty(props, SDL_PROP_WINDOW_WAYLAND_EGL_WINDOW_POINTER, data->egl_window); - - data->hit_test_result = SDL_HITTEST_NORMAL; - - return true; -} - -void Wayland_SetWindowMinimumSize(SDL_VideoDevice *_this, SDL_Window *window) -{ - // Will be committed when Wayland_SetWindowSize() is called by the video core. - SetMinMaxDimensions(window); -} - -void Wayland_SetWindowMaximumSize(SDL_VideoDevice *_this, SDL_Window *window) -{ - // Will be committed when Wayland_SetWindowSize() is called by the video core. - SetMinMaxDimensions(window); -} - -bool Wayland_SetWindowPosition(SDL_VideoDevice *_this, SDL_Window *window) -{ - SDL_WindowData *wind = window->internal; - - // Only popup windows can be positioned relative to the parent. - if (wind->shell_surface_type == WAYLAND_SHELL_SURFACE_TYPE_XDG_POPUP) { - if (wind->shell_surface.xdg.popup.xdg_popup && - xdg_popup_get_version(wind->shell_surface.xdg.popup.xdg_popup) < XDG_POPUP_REPOSITION_SINCE_VERSION) { - return SDL_Unsupported(); - } - - RepositionPopup(window, false); - return true; - } else if (wind->shell_surface_type == WAYLAND_SHELL_SURFACE_TYPE_LIBDECOR || wind->shell_surface_type == WAYLAND_SHELL_SURFACE_TYPE_XDG_TOPLEVEL) { - /* Catch up on any pending state before attempting to change the fullscreen window - * display via a set fullscreen call to make sure the window doesn't have a pending - * leave fullscreen event that it might override. - */ - FlushPendingEvents(window); - - if (wind->is_fullscreen) { - SDL_VideoDisplay *display = SDL_GetVideoDisplayForFullscreenWindow(window); - if (display && wind->last_displayID != display->id) { - struct wl_output *output = display->internal->output; - SetFullscreen(window, output); - - return true; - } - } - } - return SDL_SetError("wayland cannot position non-popup windows"); -} - -void Wayland_SetWindowSize(SDL_VideoDevice *_this, SDL_Window *window) -{ - SDL_WindowData *wind = window->internal; - - /* Flush any pending state operations, as fullscreen windows do not get - * explicitly resized, not strictly obeying the size of a maximized window - * is a protocol violation, and pending restore events might result in a - * configure event overwriting the requested size. - * - * Calling this on a custom surface is informative, so the size must - * always be passed through. - */ - FlushPendingEvents(window); - - // Maximized and fullscreen windows don't get resized. - if (!(window->flags & (SDL_WINDOW_FULLSCREEN | SDL_WINDOW_MAXIMIZED)) || - wind->shell_surface_type == WAYLAND_SHELL_SURFACE_TYPE_CUSTOM) { - if (!wind->scale_to_display) { - wind->requested.logical_width = window->pending.w; - wind->requested.logical_height = window->pending.h; - } else { - wind->requested.logical_width = PixelToPoint(window, window->pending.w); - wind->requested.logical_height = PixelToPoint(window, window->pending.h); - wind->requested.pixel_width = window->pending.w; - wind->requested.pixel_height = window->pending.h; - } - - ConfigureWindowGeometry(window); - } else { - // Can't resize the window. - window->last_size_pending = false; - } - - // Always commit, as this may be in response to a min/max limit change. - CommitLibdecorFrame(window); -} - -void Wayland_GetWindowSizeInPixels(SDL_VideoDevice *_this, SDL_Window *window, int *w, int *h) -{ - SDL_WindowData *data = window->internal; - - *w = data->current.pixel_width; - *h = data->current.pixel_height; -} - -float Wayland_GetWindowContentScale(SDL_VideoDevice *_this, SDL_Window *window) -{ - SDL_WindowData *wind = window->internal; - - if (window->flags & SDL_WINDOW_HIGH_PIXEL_DENSITY || wind->scale_to_display || wind->fullscreen_exclusive) { - return (float)wind->scale_factor; - } - - return 1.0f; -} - -SDL_DisplayID Wayland_GetDisplayForWindow(SDL_VideoDevice *_this, SDL_Window *window) -{ - SDL_WindowData *wind = window->internal; - - if (wind) { - return wind->last_displayID; - } - - return 0; -} - -bool Wayland_SetWindowOpacity(SDL_VideoDevice *_this, SDL_Window *window, float opacity) -{ - SDL_WindowData *wind = window->internal; - - if (wind->wp_alpha_modifier_surface_v1) { - SetSurfaceOpaqueRegion(wind, !(window->flags & SDL_WINDOW_TRANSPARENT) && opacity == 1.0f); - wp_alpha_modifier_surface_v1_set_multiplier(wind->wp_alpha_modifier_surface_v1, (Uint32)((double)SDL_MAX_UINT32 * (double)opacity)); - - return true; - } - - return SDL_SetError("wayland: set window opacity failed; compositor lacks support for the required wp_alpha_modifier_v1 protocol"); -} - -void Wayland_SetWindowTitle(SDL_VideoDevice *_this, SDL_Window *window) -{ - SDL_WindowData *wind = window->internal; - const char *title = window->title ? window->title : ""; - -#ifdef HAVE_LIBDECOR_H - if (wind->shell_surface_type == WAYLAND_SHELL_SURFACE_TYPE_LIBDECOR && wind->shell_surface.libdecor.frame) { - libdecor_frame_set_title(wind->shell_surface.libdecor.frame, title); - } else -#endif - if (wind->shell_surface_type == WAYLAND_SHELL_SURFACE_TYPE_XDG_TOPLEVEL && wind->shell_surface.xdg.toplevel.xdg_toplevel) { - xdg_toplevel_set_title(wind->shell_surface.xdg.toplevel.xdg_toplevel, title); - } -} - -bool Wayland_SetWindowIcon(SDL_VideoDevice *_this, SDL_Window *window, SDL_Surface *icon) -{ - SDL_WindowData *wind = window->internal; - struct xdg_toplevel *toplevel = NULL; - - if (!_this->internal->xdg_toplevel_icon_manager_v1) { - return SDL_SetError("wayland: cannot set icon; required xdg_toplevel_icon_v1 protocol not supported"); - } - - if (icon->w != icon->h) { - return SDL_SetError("wayland: icon width and height must be equal, got %ix%i", icon->w, icon->h); - } - - int image_count = 0; - SDL_Surface **images = SDL_GetSurfaceImages(icon, &image_count); - if (!images || !image_count) { - return false; - } - - // Release the old icon resources. - if (wind->xdg_toplevel_icon_v1) { - xdg_toplevel_icon_v1_destroy(wind->xdg_toplevel_icon_v1); - wind->xdg_toplevel_icon_v1 = NULL; - } - - for (int i = 0; i < wind->icon_buffer_count; ++i) { - Wayland_ReleaseSHMBuffer(&wind->icon_buffers[i]); - } - SDL_free(wind->icon_buffers); - wind->icon_buffer_count = 0; - - wind->xdg_toplevel_icon_v1 = xdg_toplevel_icon_manager_v1_create_icon(_this->internal->xdg_toplevel_icon_manager_v1); - wind->icon_buffers = SDL_calloc(image_count, sizeof(struct Wayland_SHMBuffer)); - if (!wind->icon_buffers) { - goto failure_cleanup; - } - - for (int i = 0; i < image_count; ++i) { - if (images[i]->w == images[i]->h) { - struct Wayland_SHMBuffer *buffer = &wind->icon_buffers[wind->icon_buffer_count]; - - if (!Wayland_AllocSHMBuffer(images[i]->w, images[i]->h, buffer)) { - SDL_SetError("wayland: failed to allocate SHM buffer for the icon"); - goto failure_cleanup; - } - - SDL_PremultiplyAlpha(images[i]->w, images[i]->h, images[i]->format, images[i]->pixels, images[i]->pitch, SDL_PIXELFORMAT_ARGB8888, buffer->shm_data, images[i]->w * 4, true); - const int scale = (int)SDL_ceil((double)images[i]->w / (double)icon->w); - xdg_toplevel_icon_v1_add_buffer(wind->xdg_toplevel_icon_v1, buffer->wl_buffer, scale); - wind->icon_buffer_count++; - } else { - SDL_LogWarn(SDL_LOG_CATEGORY_VIDEO, "wayland: icon width and height must be equal, got %ix%i for image level %i; skipping", images[i]->w, images[i]->h, i); - } - } - - SDL_free(images); - -#ifdef HAVE_LIBDECOR_H - if (wind->shell_surface_type == WAYLAND_SHELL_SURFACE_TYPE_LIBDECOR && wind->shell_surface.libdecor.frame) { - toplevel = libdecor_frame_get_xdg_toplevel(wind->shell_surface.libdecor.frame); - } else -#endif - if (wind->shell_surface_type == WAYLAND_SHELL_SURFACE_TYPE_XDG_TOPLEVEL && wind->shell_surface.xdg.toplevel.xdg_toplevel) { - toplevel = wind->shell_surface.xdg.toplevel.xdg_toplevel; - } - - if (toplevel) { - xdg_toplevel_icon_manager_v1_set_icon(_this->internal->xdg_toplevel_icon_manager_v1, toplevel, wind->xdg_toplevel_icon_v1); - } - - return true; - -failure_cleanup: - - if (wind->xdg_toplevel_icon_v1) { - xdg_toplevel_icon_v1_destroy(wind->xdg_toplevel_icon_v1); - wind->xdg_toplevel_icon_v1 = NULL; - } - - for (int i = 0; i < wind->icon_buffer_count; ++i) { - Wayland_ReleaseSHMBuffer(&wind->icon_buffers[i]); - } - SDL_free(wind->icon_buffers); - wind->icon_buffers = NULL; - wind->icon_buffer_count = 0; - - return false; -} - -void *Wayland_GetWindowICCProfile(SDL_VideoDevice *_this, SDL_Window *window, size_t *size) -{ - SDL_WindowData *wind = window->internal; - void *ret = NULL; - - if (wind->icc_size > 0) { - void *icc_map = mmap(NULL, wind->icc_size, PROT_READ, MAP_PRIVATE, wind->icc_fd, 0); - if (icc_map != MAP_FAILED) { - ret = SDL_malloc(wind->icc_size); - if (ret) { - *size = wind->icc_size; - SDL_memcpy(ret, icc_map, *size); - } - munmap(icc_map, wind->icc_size); - } - } - - return ret; -} - -bool Wayland_SyncWindow(SDL_VideoDevice *_this, SDL_Window *window) -{ - SDL_WindowData *wind = window->internal; - - do { - WAYLAND_wl_display_roundtrip(_this->internal->display); - } while (wind->fullscreen_deadline_count || wind->maximized_restored_deadline_count); - - return true; -} - -void Wayland_ShowWindowSystemMenu(SDL_Window *window, int x, int y) -{ - SDL_WindowData *wind = window->internal; - - if (wind->scale_to_display) { - x = PixelToPoint(window, x); - y = PixelToPoint(window, y); - } - -#ifdef HAVE_LIBDECOR_H - if (wind->shell_surface_type == WAYLAND_SHELL_SURFACE_TYPE_LIBDECOR) { - if (wind->shell_surface.libdecor.frame) { - libdecor_frame_show_window_menu(wind->shell_surface.libdecor.frame, wind->waylandData->input->seat, wind->waylandData->input->last_implicit_grab_serial, x, y); - } - } else -#endif - if (wind->shell_surface_type == WAYLAND_SHELL_SURFACE_TYPE_XDG_TOPLEVEL) { - if (wind->shell_surface.xdg.toplevel.xdg_toplevel) { - xdg_toplevel_show_window_menu(wind->shell_surface.xdg.toplevel.xdg_toplevel, wind->waylandData->input->seat, wind->waylandData->input->last_implicit_grab_serial, x, y); - } - } -} - -bool Wayland_SuspendScreenSaver(SDL_VideoDevice *_this) -{ - SDL_VideoData *data = _this->internal; - -#ifdef SDL_USE_LIBDBUS - if (SDL_DBus_ScreensaverInhibit(_this->suspend_screensaver)) { - return true; - } -#endif - - /* The idle_inhibit_unstable_v1 protocol suspends the screensaver - on a per wl_surface basis, but SDL assumes that suspending - the screensaver can be done independently of any window. - - To reconcile these differences, we propagate the idle inhibit - state to each window. If there is no window active, we will - be able to inhibit idle once the first window is created. - */ - if (data->idle_inhibit_manager) { - SDL_Window *window = _this->windows; - while (window) { - SDL_WindowData *win_data = window->internal; - - if (_this->suspend_screensaver && !win_data->idle_inhibitor) { - win_data->idle_inhibitor = - zwp_idle_inhibit_manager_v1_create_inhibitor(data->idle_inhibit_manager, - win_data->surface); - } else if (!_this->suspend_screensaver && win_data->idle_inhibitor) { - zwp_idle_inhibitor_v1_destroy(win_data->idle_inhibitor); - win_data->idle_inhibitor = NULL; - } - - window = window->next; - } - } - - return true; -} - -void Wayland_DestroyWindow(SDL_VideoDevice *_this, SDL_Window *window) -{ - SDL_VideoData *data = _this->internal; - SDL_WindowData *wind = window->internal; - - if (data && wind) { - /* Roundtrip before destroying the window to make sure that it has received input leave events, so that - * no internal structures are left pointing to the destroyed window. - */ - if (wind->show_hide_sync_required) { - WAYLAND_wl_display_roundtrip(data->display); - } - -#ifdef SDL_VIDEO_OPENGL_EGL - if (wind->egl_surface) { - SDL_EGL_DestroySurface(_this, wind->egl_surface); - } -#endif - if (wind->egl_window) { - WAYLAND_wl_egl_window_destroy(wind->egl_window); - } - - if (wind->idle_inhibitor) { - zwp_idle_inhibitor_v1_destroy(wind->idle_inhibitor); - } - - if (wind->activation_token) { - xdg_activation_token_v1_destroy(wind->activation_token); - } - - if (wind->viewport) { - wp_viewport_destroy(wind->viewport); - } - - if (wind->fractional_scale) { - wp_fractional_scale_v1_destroy(wind->fractional_scale); - } - - if (wind->wp_alpha_modifier_surface_v1) { - wp_alpha_modifier_surface_v1_destroy(wind->wp_alpha_modifier_surface_v1); - } - - if (wind->frog_color_managed_surface) { - frog_color_managed_surface_destroy(wind->frog_color_managed_surface); - } - - if (wind->wp_color_management_surface_feedback) { - Wayland_FreeColorInfoState(wind->color_info_state); - wp_color_management_surface_feedback_v1_destroy(wind->wp_color_management_surface_feedback); - } - - SDL_free(wind->outputs); - SDL_free(wind->app_id); - - if (wind->gles_swap_frame_callback) { - wl_callback_destroy(wind->gles_swap_frame_callback); - WAYLAND_wl_proxy_wrapper_destroy(wind->gles_swap_frame_surface_wrapper); - WAYLAND_wl_event_queue_destroy(wind->gles_swap_frame_event_queue); - } - - if (wind->surface_frame_callback) { - wl_callback_destroy(wind->surface_frame_callback); - } - - if (!(window->flags & SDL_WINDOW_EXTERNAL)) { - wl_surface_destroy(wind->surface); - } else { - Wayland_RemoveWindowDataFromExternalList(wind); - } - - if (wind->xdg_toplevel_icon_v1) { - xdg_toplevel_icon_v1_destroy(wind->xdg_toplevel_icon_v1); - } - - for (int i = 0; i < wind->icon_buffer_count; ++i) { - Wayland_ReleaseSHMBuffer(&wind->icon_buffers[i]); - } - SDL_free(wind->icon_buffers); - wind->icon_buffer_count = 0; - - SDL_free(wind); - WAYLAND_wl_display_flush(data->display); - } - window->internal = NULL; -} - -#endif // SDL_VIDEO_DRIVER_WAYLAND -- cgit v1.2.3