mirror of
https://github.com/Ryujinx/SDL.git
synced 2025-03-30 23:06:53 +00:00
Fixes the formatting on some function signatures that clang-format missed. (cherry picked from commit 378b1c286a9c3206e827a47f0147384e92c4c2b2)
2299 lines
86 KiB
C
2299 lines
86 KiB
C
/*
|
|
Simple DirectMedia Layer
|
|
Copyright (C) 1997-2022 Sam Lantinga <slouken@libsdl.org>
|
|
|
|
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"
|
|
|
|
#if SDL_VIDEO_DRIVER_WAYLAND
|
|
|
|
#include "../SDL_sysvideo.h"
|
|
#include "../../events/SDL_windowevents_c.h"
|
|
#include "../../events/SDL_mouse_c.h"
|
|
#include "../SDL_egl_c.h"
|
|
#include "SDL_waylandevents_c.h"
|
|
#include "SDL_waylandwindow.h"
|
|
#include "SDL_waylandvideo.h"
|
|
#include "SDL_waylandtouch.h"
|
|
#include "SDL_hints.h"
|
|
#include "../../SDL_hints_c.h"
|
|
#include "SDL_events.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"
|
|
|
|
#ifdef HAVE_LIBDECOR_H
|
|
#include <libdecor.h>
|
|
#endif
|
|
|
|
#define FULLSCREEN_MASK (SDL_WINDOW_FULLSCREEN | SDL_WINDOW_FULLSCREEN_DESKTOP)
|
|
|
|
SDL_FORCE_INLINE SDL_bool FloatEqual(float a, float b)
|
|
{
|
|
const float diff = SDL_fabsf(a - b);
|
|
const float largest = SDL_max(SDL_fabsf(a), SDL_fabsf(b));
|
|
|
|
return diff <= largest * SDL_FLT_EPSILON;
|
|
}
|
|
|
|
static void GetFullScreenDimensions(SDL_Window *window, int *width, int *height, int *drawable_width, int *drawable_height)
|
|
{
|
|
SDL_WindowData *wind = (SDL_WindowData *)window->driverdata;
|
|
SDL_WaylandOutputData *output = (SDL_WaylandOutputData *)SDL_GetDisplayForWindow(window)->driverdata;
|
|
|
|
int fs_width, fs_height;
|
|
int buf_width, buf_height;
|
|
const int output_width = wind->fs_output_width ? wind->fs_output_width : output->width;
|
|
const int output_height = wind->fs_output_height ? wind->fs_output_height : output->height;
|
|
|
|
/*
|
|
* Fullscreen desktop mandates a desktop sized window, so that's what applications will get.
|
|
* If the application is DPI aware, it will need to handle the transformations between the
|
|
* differently sized window and backbuffer spaces on its own.
|
|
*/
|
|
if ((window->flags & SDL_WINDOW_FULLSCREEN_DESKTOP) == SDL_WINDOW_FULLSCREEN_DESKTOP) {
|
|
fs_width = output_width;
|
|
fs_height = output_height;
|
|
|
|
/* If the application is DPI aware, we can expose the true backbuffer size */
|
|
if (window->flags & SDL_WINDOW_ALLOW_HIGHDPI) {
|
|
buf_width = output->native_width;
|
|
buf_height = output->native_height;
|
|
} else {
|
|
buf_width = fs_width;
|
|
buf_height = fs_height;
|
|
}
|
|
} else {
|
|
/*
|
|
* If a mode was set, use it, otherwise use the native resolution
|
|
* for DPI aware apps and the desktop size for legacy apps.
|
|
*/
|
|
if (window->fullscreen_mode.w != 0 && window->fullscreen_mode.h != 0) {
|
|
fs_width = window->fullscreen_mode.w;
|
|
fs_height = window->fullscreen_mode.h;
|
|
} else if (window->flags & SDL_WINDOW_ALLOW_HIGHDPI) {
|
|
fs_width = output->native_width;
|
|
fs_height = output->native_height;
|
|
} else {
|
|
fs_width = output_width;
|
|
fs_height = output_height;
|
|
}
|
|
|
|
buf_width = fs_width;
|
|
buf_height = fs_height;
|
|
}
|
|
|
|
if (width) {
|
|
*width = fs_width;
|
|
}
|
|
if (height) {
|
|
*height = fs_height;
|
|
}
|
|
if (drawable_width) {
|
|
*drawable_width = buf_width;
|
|
}
|
|
if (drawable_height) {
|
|
*drawable_height = buf_height;
|
|
}
|
|
}
|
|
|
|
SDL_FORCE_INLINE SDL_bool SurfaceScaleIsFractional(SDL_Window *window)
|
|
{
|
|
SDL_WindowData *data = window->driverdata;
|
|
return !FloatEqual(SDL_roundf(data->scale_factor), data->scale_factor);
|
|
}
|
|
|
|
SDL_FORCE_INLINE SDL_bool FullscreenModeEmulation(SDL_Window *window)
|
|
{
|
|
return (window->flags & SDL_WINDOW_FULLSCREEN) &&
|
|
((window->flags & SDL_WINDOW_FULLSCREEN_DESKTOP) != SDL_WINDOW_FULLSCREEN_DESKTOP);
|
|
}
|
|
|
|
static SDL_bool NeedViewport(SDL_Window *window)
|
|
{
|
|
SDL_WindowData *wind = window->driverdata;
|
|
SDL_VideoData *video = wind->waylandData;
|
|
SDL_WaylandOutputData *output = ((SDL_WaylandOutputData *)SDL_GetDisplayForWindow(window)->driverdata);
|
|
int fs_width, fs_height;
|
|
|
|
/*
|
|
* A viewport is only required when scaling is enabled and:
|
|
* - A fullscreen mode is being emulated and the mode does not match the logical desktop dimensions.
|
|
* - The desktop uses fractional scaling and the high-DPI flag is set.
|
|
*/
|
|
if (video->viewporter != NULL) {
|
|
if (FullscreenModeEmulation(window)) {
|
|
GetFullScreenDimensions(window, &fs_width, &fs_height, NULL, NULL);
|
|
if (fs_width != output->width || fs_height != output->height) {
|
|
return SDL_TRUE;
|
|
}
|
|
} else if (SurfaceScaleIsFractional(window) && (window->flags & SDL_WINDOW_ALLOW_HIGHDPI)) {
|
|
return SDL_TRUE;
|
|
}
|
|
}
|
|
|
|
return SDL_FALSE;
|
|
}
|
|
|
|
static void GetBufferSize(SDL_Window *window, int *width, int *height)
|
|
{
|
|
SDL_WindowData *data = window->driverdata;
|
|
int buf_width;
|
|
int buf_height;
|
|
|
|
if (FullscreenModeEmulation(window)) {
|
|
GetFullScreenDimensions(window, NULL, NULL, &buf_width, &buf_height);
|
|
} else if (NeedViewport(window)) {
|
|
/* Round fractional backbuffer sizes halfway away from zero. */
|
|
buf_width = (int)SDL_lroundf(window->w * data->scale_factor);
|
|
buf_height = (int)SDL_lroundf(window->h * data->scale_factor);
|
|
} else {
|
|
/*
|
|
* Integer scaled windowed or fullscreen with no viewport
|
|
*
|
|
* Round the scale factor up in the unlikely scenario of a compositor
|
|
* that supports fractional scaling, but not viewports.
|
|
*/
|
|
int scale_factor = (int)SDL_ceilf(data->scale_factor);
|
|
|
|
buf_width = window->w * scale_factor;
|
|
buf_height = window->h * scale_factor;
|
|
}
|
|
|
|
if (width) {
|
|
*width = buf_width;
|
|
}
|
|
if (height) {
|
|
*height = buf_height;
|
|
}
|
|
}
|
|
|
|
static void SetDrawSurfaceViewport(SDL_Window *window, int src_width, int src_height, int dst_width, int dst_height)
|
|
{
|
|
SDL_WindowData *wind = window->driverdata;
|
|
SDL_VideoData *video = wind->waylandData;
|
|
|
|
if (video->viewporter) {
|
|
if (wind->draw_viewport == NULL) {
|
|
wind->draw_viewport = wp_viewporter_get_viewport(video->viewporter, wind->surface);
|
|
}
|
|
|
|
wp_viewport_set_source(wind->draw_viewport, wl_fixed_from_int(0), wl_fixed_from_int(0), wl_fixed_from_int(src_width), wl_fixed_from_int(src_height));
|
|
wp_viewport_set_destination(wind->draw_viewport, dst_width, dst_height);
|
|
}
|
|
}
|
|
|
|
static void UnsetDrawSurfaceViewport(SDL_Window *window)
|
|
{
|
|
SDL_WindowData *wind = window->driverdata;
|
|
|
|
if (wind->draw_viewport) {
|
|
wp_viewport_destroy(wind->draw_viewport);
|
|
wind->draw_viewport = NULL;
|
|
}
|
|
}
|
|
|
|
static void ConfigureWindowGeometry(SDL_Window *window)
|
|
{
|
|
SDL_WindowData *data = window->driverdata;
|
|
SDL_VideoData *viddata = data->waylandData;
|
|
SDL_WaylandOutputData *output = (SDL_WaylandOutputData *)SDL_GetDisplayForWindow(window)->driverdata;
|
|
struct wl_region *region;
|
|
const int old_dw = data->drawable_width;
|
|
const int old_dh = data->drawable_height;
|
|
SDL_bool window_size_changed;
|
|
SDL_bool drawable_size_changed;
|
|
|
|
/* Set the drawable backbuffer size. */
|
|
GetBufferSize(window, &data->drawable_width, &data->drawable_height);
|
|
drawable_size_changed = data->drawable_width != old_dw || data->drawable_height != old_dh;
|
|
|
|
if (data->egl_window && drawable_size_changed) {
|
|
WAYLAND_wl_egl_window_resize(data->egl_window,
|
|
data->drawable_width,
|
|
data->drawable_height,
|
|
0, 0);
|
|
}
|
|
|
|
if (FullscreenModeEmulation(window) && NeedViewport(window)) {
|
|
int fs_width, fs_height;
|
|
const int output_width = data->fs_output_width ? data->fs_output_width : output->width;
|
|
const int output_height = data->fs_output_height ? data->fs_output_height : output->height;
|
|
|
|
window_size_changed = data->window_width != output_width || data->window_height != output_height;
|
|
|
|
if (window_size_changed || drawable_size_changed) {
|
|
GetFullScreenDimensions(window, &fs_width, &fs_height, NULL, NULL);
|
|
|
|
/* Set the buffer scale to 1 since a viewport will be used. */
|
|
wl_surface_set_buffer_scale(data->surface, 1);
|
|
SetDrawSurfaceViewport(window, data->drawable_width, data->drawable_height,
|
|
output_width, output_height);
|
|
|
|
data->window_width = output_width;
|
|
data->window_height = output_height;
|
|
|
|
data->pointer_scale_x = (float)fs_width / (float)output_width;
|
|
data->pointer_scale_y = (float)fs_height / (float)output_height;
|
|
}
|
|
} else {
|
|
window_size_changed = data->window_width != window->w || data->window_height != window->h;
|
|
|
|
if (window_size_changed || drawable_size_changed) {
|
|
if (NeedViewport(window)) {
|
|
wl_surface_set_buffer_scale(data->surface, 1);
|
|
SetDrawSurfaceViewport(window, data->drawable_width, data->drawable_height, window->w, window->h);
|
|
} else {
|
|
UnsetDrawSurfaceViewport(window);
|
|
|
|
/* Round to the next integer in case of a fractional value. */
|
|
wl_surface_set_buffer_scale(data->surface, (int32_t)SDL_ceilf(data->scale_factor));
|
|
}
|
|
|
|
data->window_width = window->w;
|
|
data->window_height = window->h;
|
|
|
|
data->pointer_scale_x = 1.0f;
|
|
data->pointer_scale_y = 1.0f;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* The opaque and pointer confinement regions only need to be recalculated
|
|
* if the output size has changed.
|
|
*/
|
|
if (window_size_changed) {
|
|
if (!viddata->egl_transparency_enabled) {
|
|
region = wl_compositor_create_region(viddata->compositor);
|
|
wl_region_add(region, 0, 0,
|
|
data->window_width, data->window_height);
|
|
wl_surface_set_opaque_region(data->surface, region);
|
|
wl_region_destroy(region);
|
|
}
|
|
|
|
if (data->confined_pointer) {
|
|
Wayland_input_confine_pointer(viddata->input, window);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void CommitLibdecorFrame(SDL_Window *window)
|
|
{
|
|
#ifdef HAVE_LIBDECOR_H
|
|
SDL_WindowData *wind = (SDL_WindowData *)window->driverdata;
|
|
|
|
if (wind->shell_surface_type == WAYLAND_SURFACE_LIBDECOR && wind->shell_surface.libdecor.frame) {
|
|
struct libdecor_state *state = libdecor_state_new(wind->window_width, wind->window_height);
|
|
libdecor_frame_commit(wind->shell_surface.libdecor.frame, state, NULL);
|
|
libdecor_state_free(state);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
static void SetMinMaxDimensions(SDL_Window *window, SDL_bool commit)
|
|
{
|
|
SDL_WindowData *wind = window->driverdata;
|
|
SDL_VideoData *viddata = wind->waylandData;
|
|
int min_width, min_height, max_width, max_height;
|
|
|
|
/* Pop-ups don't get to change size */
|
|
if (wind->shell_surface_type == WAYLAND_SURFACE_XDG_POPUP) {
|
|
/* ... but we still want to commit, particularly for ShowWindow */
|
|
if (commit) {
|
|
wl_surface_commit(wind->surface);
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (window->flags & SDL_WINDOW_FULLSCREEN) {
|
|
min_width = 0;
|
|
min_height = 0;
|
|
max_width = 0;
|
|
max_height = 0;
|
|
} else if (window->flags & SDL_WINDOW_RESIZABLE) {
|
|
min_width = window->min_w;
|
|
min_height = window->min_h;
|
|
max_width = window->max_w;
|
|
max_height = window->max_h;
|
|
} else {
|
|
min_width = window->windowed.w;
|
|
min_height = window->windowed.h;
|
|
max_width = window->windowed.w;
|
|
max_height = window->windowed.h;
|
|
}
|
|
|
|
#ifdef HAVE_LIBDECOR_H
|
|
if (wind->shell_surface_type == WAYLAND_SURFACE_LIBDECOR) {
|
|
if (wind->shell_surface.libdecor.frame == NULL) {
|
|
return; /* Can't do anything yet, wait for ShowWindow */
|
|
}
|
|
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);
|
|
|
|
if (commit) {
|
|
CommitLibdecorFrame(window);
|
|
wl_surface_commit(wind->surface);
|
|
}
|
|
} else
|
|
#endif
|
|
if (viddata->shell.xdg) {
|
|
if (wind->shell_surface.xdg.roleobj.toplevel == NULL) {
|
|
return; /* Can't do anything yet, wait for ShowWindow */
|
|
}
|
|
xdg_toplevel_set_min_size(wind->shell_surface.xdg.roleobj.toplevel,
|
|
min_width,
|
|
min_height);
|
|
xdg_toplevel_set_max_size(wind->shell_surface.xdg.roleobj.toplevel,
|
|
max_width,
|
|
max_height);
|
|
if (commit) {
|
|
wl_surface_commit(wind->surface);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void SetFullscreen(SDL_Window *window, struct wl_output *output)
|
|
{
|
|
SDL_WindowData *wind = window->driverdata;
|
|
SDL_VideoData *viddata = wind->waylandData;
|
|
|
|
/* The desktop may try to enforce min/max sizes here, so turn them off for
|
|
* fullscreen and on (if applicable) for windowed
|
|
*/
|
|
SetMinMaxDimensions(window, SDL_FALSE);
|
|
|
|
#ifdef HAVE_LIBDECOR_H
|
|
if (wind->shell_surface_type == WAYLAND_SURFACE_LIBDECOR) {
|
|
if (wind->shell_surface.libdecor.frame == NULL) {
|
|
return; /* Can't do anything yet, wait for ShowWindow */
|
|
}
|
|
if (output) {
|
|
if (!(window->flags & SDL_WINDOW_RESIZABLE)) {
|
|
/* Ensure that window is resizable before going into fullscreen.
|
|
* This triggers a frame commit internally, so a separate one is not necessary.
|
|
*/
|
|
libdecor_frame_set_capabilities(wind->shell_surface.libdecor.frame, LIBDECOR_ACTION_RESIZE);
|
|
wl_surface_commit(wind->surface);
|
|
} else {
|
|
CommitLibdecorFrame(window);
|
|
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);
|
|
|
|
if (!(window->flags & SDL_WINDOW_RESIZABLE)) {
|
|
/* restore previous RESIZE capability */
|
|
libdecor_frame_unset_capabilities(wind->shell_surface.libdecor.frame, LIBDECOR_ACTION_RESIZE);
|
|
wl_surface_commit(wind->surface);
|
|
} else {
|
|
CommitLibdecorFrame(window);
|
|
wl_surface_commit(wind->surface);
|
|
}
|
|
}
|
|
} else
|
|
#endif
|
|
if (viddata->shell.xdg) {
|
|
if (wind->shell_surface.xdg.roleobj.toplevel == NULL) {
|
|
return; /* Can't do anything yet, wait for ShowWindow */
|
|
}
|
|
|
|
wl_surface_commit(wind->surface);
|
|
|
|
if (output) {
|
|
xdg_toplevel_set_fullscreen(wind->shell_surface.xdg.roleobj.toplevel, output);
|
|
} else {
|
|
xdg_toplevel_unset_fullscreen(wind->shell_surface.xdg.roleobj.toplevel);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void UpdateWindowFullscreen(SDL_Window *window, SDL_bool fullscreen)
|
|
{
|
|
SDL_WindowData *wind = (SDL_WindowData *)window->driverdata;
|
|
|
|
if (fullscreen) {
|
|
if (!(window->flags & SDL_WINDOW_FULLSCREEN)) {
|
|
/*
|
|
* If the window was never previously made full screen, check if a particular
|
|
* fullscreen mode has been set for the window. If one is found, use SDL_WINDOW_FULLSCREEN,
|
|
* otherwise, use SDL_WINDOW_FULLSCREEN_DESKTOP.
|
|
*
|
|
* If the previous flag was SDL_WINDOW_FULLSCREEN, make sure a mode is still set,
|
|
* otherwise, fall back to SDL_WINDOW_FULLSCREEN_DESKTOP.
|
|
*/
|
|
if (!wind->fullscreen_flags) {
|
|
if (window->fullscreen_mode.w && window->fullscreen_mode.h) {
|
|
wind->fullscreen_flags = SDL_WINDOW_FULLSCREEN;
|
|
} else {
|
|
wind->fullscreen_flags = SDL_WINDOW_FULLSCREEN_DESKTOP;
|
|
}
|
|
} else if (wind->fullscreen_flags != SDL_WINDOW_FULLSCREEN_DESKTOP &&
|
|
(!window->fullscreen_mode.w || !window->fullscreen_mode.h)) {
|
|
wind->fullscreen_flags = SDL_WINDOW_FULLSCREEN_DESKTOP;
|
|
}
|
|
|
|
wind->is_fullscreen = SDL_TRUE;
|
|
|
|
wind->in_fullscreen_transition = SDL_TRUE;
|
|
SDL_SetWindowFullscreen(window, wind->fullscreen_flags);
|
|
wind->in_fullscreen_transition = SDL_FALSE;
|
|
}
|
|
} else {
|
|
/* Don't change the fullscreen flags if the window is hidden or being hidden. */
|
|
if (!window->is_hiding && !(window->flags & SDL_WINDOW_HIDDEN)) {
|
|
if (window->flags & SDL_WINDOW_FULLSCREEN) {
|
|
wind->is_fullscreen = SDL_FALSE;
|
|
|
|
wind->in_fullscreen_transition = SDL_TRUE;
|
|
SDL_SetWindowFullscreen(window, 0);
|
|
wind->in_fullscreen_transition = SDL_FALSE;
|
|
SetMinMaxDimensions(window, SDL_FALSE);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static const struct wl_callback_listener surface_damage_frame_listener;
|
|
|
|
static void surface_damage_frame_done(void *data, struct wl_callback *cb, uint32_t time)
|
|
{
|
|
SDL_WindowData *wind = (SDL_WindowData *)data;
|
|
|
|
/*
|
|
* wl_surface.damage_buffer is the preferred method of setting the damage region
|
|
* on compositor version 4 and above.
|
|
*/
|
|
if (wl_compositor_get_version(wind->waylandData->compositor) >= 4) {
|
|
wl_surface_damage_buffer(wind->surface, 0, 0,
|
|
wind->drawable_width, wind->drawable_height);
|
|
} else {
|
|
wl_surface_damage(wind->surface, 0, 0,
|
|
wind->window_width, wind->window_height);
|
|
}
|
|
|
|
wl_callback_destroy(cb);
|
|
wind->surface_damage_frame_callback = wl_surface_frame(wind->surface);
|
|
wl_callback_add_listener(wind->surface_damage_frame_callback, &surface_damage_frame_listener, data);
|
|
}
|
|
|
|
static const struct wl_callback_listener surface_damage_frame_listener = {
|
|
surface_damage_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_AtomicSet(&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 Wayland_HandleResize(SDL_Window *window, int width, int height, float scale);
|
|
|
|
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;
|
|
|
|
Wayland_HandleResize(window, window->w, window->h, wind->scale_factor);
|
|
xdg_surface_ack_configure(xdg, serial);
|
|
|
|
wind->shell_surface.xdg.initial_configure_seen = SDL_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;
|
|
SDL_WaylandOutputData *driverdata;
|
|
|
|
enum xdg_toplevel_state *state;
|
|
SDL_bool fullscreen = SDL_FALSE;
|
|
SDL_bool maximized = SDL_FALSE;
|
|
SDL_bool floating = SDL_TRUE;
|
|
wl_array_for_each (state, states) {
|
|
switch (*state) {
|
|
case XDG_TOPLEVEL_STATE_FULLSCREEN:
|
|
fullscreen = SDL_TRUE;
|
|
floating = SDL_FALSE;
|
|
break;
|
|
case XDG_TOPLEVEL_STATE_MAXIMIZED:
|
|
maximized = SDL_TRUE;
|
|
floating = SDL_FALSE;
|
|
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:
|
|
floating = SDL_FALSE;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
driverdata = (SDL_WaylandOutputData *)SDL_GetDisplayForWindow(window)->driverdata;
|
|
|
|
UpdateWindowFullscreen(window, fullscreen);
|
|
|
|
if (!fullscreen) {
|
|
if (width == 0 || height == 0) {
|
|
/* This usually happens when we're being restored from a
|
|
* non-floating state, so use the cached floating size here.
|
|
*/
|
|
width = wind->floating_width;
|
|
height = wind->floating_height;
|
|
}
|
|
|
|
/* 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 (window->max_w > 0) {
|
|
width = SDL_min(width, window->max_w);
|
|
}
|
|
width = SDL_max(width, window->min_w);
|
|
|
|
if (window->max_h > 0) {
|
|
height = SDL_min(height, window->max_h);
|
|
}
|
|
height = SDL_max(height, window->min_h);
|
|
} else if (floating) {
|
|
/* If we're a fixed-size window, we know our size for sure.
|
|
* Always assume the configure is wrong.
|
|
*/
|
|
width = window->windowed.w;
|
|
height = window->windowed.h;
|
|
}
|
|
|
|
/* 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.
|
|
*/
|
|
SDL_SendWindowEvent(window,
|
|
maximized ? SDL_WINDOWEVENT_MAXIMIZED : SDL_WINDOWEVENT_RESTORED,
|
|
0, 0);
|
|
|
|
/* Store current floating dimensions for restoring */
|
|
if (floating) {
|
|
wind->floating_width = width;
|
|
wind->floating_height = height;
|
|
}
|
|
|
|
/* Store this now so the xdg_surface configure knows what to resize to */
|
|
if (window->w != width || window->h != height) {
|
|
window->w = width;
|
|
window->h = height;
|
|
wind->needs_resize_event = SDL_TRUE;
|
|
}
|
|
} else {
|
|
/* For fullscreen, foolishly do what the compositor says. If it's wrong,
|
|
* don't blame us, we were explicitly instructed to do this.
|
|
*
|
|
* UPDATE: Nope, sure enough a compositor sends 0,0. This is a known bug:
|
|
* https://bugs.kde.org/show_bug.cgi?id=444962
|
|
*/
|
|
if (width && height) {
|
|
wind->fs_output_width = width;
|
|
wind->fs_output_height = height;
|
|
} else {
|
|
wind->fs_output_width = 0;
|
|
wind->fs_output_height = 0;
|
|
}
|
|
|
|
if (FullscreenModeEmulation(window)) {
|
|
GetFullScreenDimensions(window, &width, &height, NULL, NULL);
|
|
}
|
|
if (width != 0 && height != 0 && (window->w != width || window->h != height)) {
|
|
window->w = width;
|
|
window->h = height;
|
|
wind->needs_resize_event = SDL_TRUE;
|
|
}
|
|
|
|
/* This part is good though. */
|
|
if ((window->flags & SDL_WINDOW_ALLOW_HIGHDPI) && !FloatEqual(wind->scale_factor, driverdata->scale_factor)) {
|
|
wind->scale_factor = driverdata->scale_factor;
|
|
wind->needs_resize_event = SDL_TRUE;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void handle_close_xdg_toplevel(void *data, struct xdg_toplevel *xdg_toplevel)
|
|
{
|
|
SDL_WindowData *window = (SDL_WindowData *)data;
|
|
SDL_SendWindowEvent(window->sdlwindow, SDL_WINDOWEVENT_CLOSE, 0, 0);
|
|
}
|
|
|
|
static const struct xdg_toplevel_listener toplevel_listener_xdg = {
|
|
handle_configure_xdg_toplevel,
|
|
handle_close_xdg_toplevel
|
|
};
|
|
|
|
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)
|
|
{
|
|
/* No-op, we don't use x/y and width/height are fixed-size */
|
|
}
|
|
|
|
static void handle_done_xdg_popup(void *data, struct xdg_popup *xdg_popup)
|
|
{
|
|
SDL_WindowData *window = (SDL_WindowData *)data;
|
|
SDL_SendWindowEvent(window->sdlwindow, SDL_WINDOWEVENT_CLOSE, 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
|
|
};
|
|
|
|
#define TOOLTIP_CURSOR_OFFSET 8 /* FIXME: Arbitrary, eyeballed from X tooltip */
|
|
|
|
static int Wayland_PopupWatch(void *data, SDL_Event *event)
|
|
{
|
|
if (event->type == SDL_MOUSEMOTION) {
|
|
SDL_Window *window = (SDL_Window *)data;
|
|
SDL_WindowData *wind = window->driverdata;
|
|
|
|
/* Coordinates might be relative to the popup, which we don't want */
|
|
if (event->motion.windowID == wind->shell_surface.xdg.roleobj.popup.parentID) {
|
|
xdg_positioner_set_offset(wind->shell_surface.xdg.roleobj.popup.positioner,
|
|
event->motion.x + TOOLTIP_CURSOR_OFFSET,
|
|
event->motion.y + TOOLTIP_CURSOR_OFFSET);
|
|
xdg_popup_reposition(wind->shell_surface.xdg.roleobj.popup.popup,
|
|
wind->shell_surface.xdg.roleobj.popup.positioner,
|
|
0);
|
|
}
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
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 *driverdata = (SDL_WindowData *)window->driverdata;
|
|
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(driverdata->waylandData, SDL_TRUE)) {
|
|
/* libdecor isn't available, so no borders for you... oh well */
|
|
return;
|
|
}
|
|
WAYLAND_wl_display_roundtrip(driverdata->waylandData->display);
|
|
|
|
Wayland_HideWindow(device, window);
|
|
driverdata->shell_surface_type = WAYLAND_SURFACE_LIBDECOR;
|
|
|
|
if (!window->is_hiding && !(window->flags & SDL_WINDOW_HIDDEN)) {
|
|
Wayland_ShowWindow(device, window);
|
|
}
|
|
}
|
|
}
|
|
|
|
static const struct zxdg_toplevel_decoration_v1_listener decoration_listener = {
|
|
handle_configure_zxdg_decoration
|
|
};
|
|
|
|
#ifdef HAVE_LIBDECOR_H
|
|
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;
|
|
SDL_WaylandOutputData *driverdata;
|
|
struct libdecor_state *state;
|
|
|
|
enum libdecor_window_state window_state;
|
|
int width, height;
|
|
float scale_factor = wind->scale_factor;
|
|
|
|
SDL_bool focused = SDL_FALSE;
|
|
SDL_bool fullscreen = SDL_FALSE;
|
|
SDL_bool maximized = SDL_FALSE;
|
|
SDL_bool tiled = SDL_FALSE;
|
|
SDL_bool floating;
|
|
|
|
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;
|
|
focused = (window_state & LIBDECOR_WINDOW_STATE_ACTIVE) != 0;
|
|
tiled = (window_state & tiled_states) != 0;
|
|
}
|
|
floating = !(fullscreen || maximized || tiled);
|
|
|
|
driverdata = (SDL_WaylandOutputData *)SDL_GetDisplayForWindow(window)->driverdata;
|
|
|
|
UpdateWindowFullscreen(window, fullscreen);
|
|
|
|
if (!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.
|
|
*/
|
|
SDL_SendWindowEvent(window,
|
|
maximized ? SDL_WINDOWEVENT_MAXIMIZED : SDL_WINDOWEVENT_RESTORED,
|
|
0, 0);
|
|
}
|
|
|
|
/* Similar to maximized/restore events above, send focus events too! */
|
|
SDL_SendWindowEvent(window,
|
|
focused ? SDL_WINDOWEVENT_FOCUS_GAINED : SDL_WINDOWEVENT_FOCUS_LOST,
|
|
0, 0);
|
|
|
|
/* For fullscreen or fixed-size windows we know our size.
|
|
* Always assume the configure is wrong.
|
|
*/
|
|
if (fullscreen) {
|
|
/* FIXME: We have been explicitly told to respect the fullscreen size
|
|
* parameters here, even though they are known to be wrong on GNOME at
|
|
* bare minimum. If this is wrong, don't blame us, we were explicitly
|
|
* told to do this.
|
|
*/
|
|
if (libdecor_configuration_get_content_size(configuration, frame,
|
|
&width, &height)) {
|
|
wind->fs_output_width = width;
|
|
wind->fs_output_height = height;
|
|
} else {
|
|
width = window->w;
|
|
height = window->h;
|
|
wind->fs_output_width = 0;
|
|
wind->fs_output_height = 0;
|
|
}
|
|
|
|
if (FullscreenModeEmulation(window)) {
|
|
GetFullScreenDimensions(window, &width, &height, NULL, NULL);
|
|
}
|
|
|
|
/* This part is good though. */
|
|
if (window->flags & SDL_WINDOW_ALLOW_HIGHDPI) {
|
|
scale_factor = driverdata->scale_factor;
|
|
}
|
|
} else if (!(window->flags & SDL_WINDOW_RESIZABLE) || (floating && wind->floating_resize_pending)) {
|
|
width = window->windowed.w;
|
|
height = window->windowed.h;
|
|
wind->floating_resize_pending = SDL_FALSE;
|
|
} else {
|
|
/*
|
|
* XXX: libdecor can send bogus content sizes that are +/- the height
|
|
* of the title bar when hiding a window or transitioning from
|
|
* non-floating to floating state, which distorts the window size.
|
|
*
|
|
* Ignore any size values from libdecor in these scenarios in
|
|
* favor of the cached window size.
|
|
*
|
|
* https://gitlab.gnome.org/jadahl/libdecor/-/issues/40
|
|
*/
|
|
const SDL_bool use_cached_size = (floating && !wind->was_floating) ||
|
|
(window->is_hiding || !!(window->flags & SDL_WINDOW_HIDDEN));
|
|
|
|
/* This will never set 0 for width/height unless the function returns false */
|
|
if (use_cached_size || !libdecor_configuration_get_content_size(configuration, frame, &width, &height)) {
|
|
if (floating) {
|
|
/* This usually happens when we're being restored from a
|
|
* non-floating state, so use the cached floating size here.
|
|
*/
|
|
width = wind->floating_width;
|
|
height = wind->floating_height;
|
|
} else {
|
|
width = window->w;
|
|
height = window->h;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Store current floating dimensions for restoring */
|
|
if (floating) {
|
|
wind->floating_width = width;
|
|
wind->floating_height = height;
|
|
}
|
|
|
|
wind->was_floating = floating;
|
|
|
|
/* Do the resize on the SDL side (this will set window->w/h)... */
|
|
Wayland_HandleResize(window, width, height, scale_factor);
|
|
wind->shell_surface.libdecor.initial_configure_seen = SDL_TRUE;
|
|
|
|
/* ... then commit the changes on the libdecor side. */
|
|
state = libdecor_state_new(wind->window_width, wind->window_height);
|
|
libdecor_frame_commit(frame, state, configuration);
|
|
libdecor_state_free(state);
|
|
|
|
/* Update the resize capability. 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.
|
|
*/
|
|
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_WINDOWEVENT_CLOSE, 0, 0);
|
|
}
|
|
|
|
static void decoration_frame_commit(struct libdecor_frame *frame, void *user_data)
|
|
{
|
|
SDL_WindowData *wind = user_data;
|
|
|
|
SDL_SendWindowEvent(wind->sdlwindow, SDL_WINDOWEVENT_EXPOSED, 0, 0);
|
|
}
|
|
|
|
static struct libdecor_frame_interface libdecor_frame_interface = {
|
|
decoration_frame_configure,
|
|
decoration_frame_close,
|
|
decoration_frame_commit,
|
|
};
|
|
#endif
|
|
|
|
#ifdef SDL_VIDEO_DRIVER_WAYLAND_QT_TOUCH
|
|
static void handle_onscreen_visibility(void *data,
|
|
struct qt_extended_surface *qt_extended_surface, int32_t visible)
|
|
{
|
|
}
|
|
|
|
static void handle_set_generic_property(void *data,
|
|
struct qt_extended_surface *qt_extended_surface, const char *name,
|
|
struct wl_array *value)
|
|
{
|
|
}
|
|
|
|
static void handle_close(void *data, struct qt_extended_surface *qt_extended_surface)
|
|
{
|
|
SDL_WindowData *window = (SDL_WindowData *)data;
|
|
SDL_SendWindowEvent(window->sdlwindow, SDL_WINDOWEVENT_CLOSE, 0, 0);
|
|
}
|
|
|
|
static const struct qt_extended_surface_listener extended_surface_listener = {
|
|
handle_onscreen_visibility,
|
|
handle_set_generic_property,
|
|
handle_close,
|
|
};
|
|
#endif /* SDL_VIDEO_DRIVER_WAYLAND_QT_TOUCH */
|
|
|
|
static void update_scale_factor(SDL_WindowData *window)
|
|
{
|
|
float old_factor = window->scale_factor;
|
|
float new_factor;
|
|
int i;
|
|
|
|
if (!(window->sdlwindow->flags & SDL_WINDOW_ALLOW_HIGHDPI)) {
|
|
/* Scale will always be 1, just ignore this */
|
|
return;
|
|
}
|
|
|
|
if (FULLSCREEN_VISIBLE(window->sdlwindow)) {
|
|
/* For fullscreen, use the active display's scale factor */
|
|
SDL_VideoDisplay *display = SDL_GetDisplayForWindow(window->sdlwindow);
|
|
SDL_WaylandOutputData *driverdata = display->driverdata;
|
|
new_factor = driverdata->scale_factor;
|
|
} else if (window->num_outputs == 0) {
|
|
/* No monitor (somehow)? Just fall back. */
|
|
new_factor = old_factor;
|
|
} else {
|
|
/* Check every display's factor, use the highest */
|
|
new_factor = 0.0f;
|
|
for (i = 0; i < window->num_outputs; i++) {
|
|
SDL_WaylandOutputData *driverdata = window->outputs[i];
|
|
new_factor = SDL_max(new_factor, driverdata->scale_factor);
|
|
}
|
|
}
|
|
|
|
if (!FloatEqual(new_factor, old_factor)) {
|
|
Wayland_HandleResize(window->sdlwindow, window->sdlwindow->w, window->sdlwindow->h, new_factor);
|
|
}
|
|
}
|
|
|
|
/* 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_WaylandOutputData *driverdata)
|
|
{
|
|
SDL_WindowData *wind = (SDL_WindowData *)window->driverdata;
|
|
SDL_VideoDisplay *display;
|
|
SDL_bool fs_display_changed = SDL_FALSE;
|
|
int i, j;
|
|
const int numdisplays = SDL_GetNumVideoDisplays();
|
|
for (i = 0; i < numdisplays; i += 1) {
|
|
display = SDL_GetDisplay(i);
|
|
if (display->driverdata == driverdata) {
|
|
SDL_Rect bounds;
|
|
|
|
/* If the window is fullscreen and not on the target display, move it. */
|
|
if ((window->flags & SDL_WINDOW_FULLSCREEN) && display->fullscreen_window != window) {
|
|
/* If the target display already has a fullscreen window, minimize it. */
|
|
if (display->fullscreen_window) {
|
|
SDL_MinimizeWindow(display->fullscreen_window);
|
|
}
|
|
|
|
/* Find the window and move it to the target display. */
|
|
for (j = 0; j < numdisplays; ++j) {
|
|
SDL_VideoDisplay *v = SDL_GetDisplay(j);
|
|
|
|
if (v->fullscreen_window == window) {
|
|
v->fullscreen_window = NULL;
|
|
}
|
|
}
|
|
|
|
display->fullscreen_window = window;
|
|
fs_display_changed = SDL_TRUE;
|
|
}
|
|
|
|
/* 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
|
|
*/
|
|
SDL_GetDisplayBounds(i, &bounds);
|
|
SDL_SendWindowEvent(window, SDL_WINDOWEVENT_MOVED, bounds.x, bounds.y);
|
|
|
|
/*
|
|
* If the fullscreen output was changed, and we have bad dimensions from
|
|
* the compositor, commit with the dimensions of the new display.
|
|
*/
|
|
if (fs_display_changed &&
|
|
(!wind->fs_output_width || !wind->fs_output_height)) {
|
|
ConfigureWindowGeometry(window);
|
|
CommitLibdecorFrame(window);
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void handle_surface_enter(void *data, struct wl_surface *surface,
|
|
struct wl_output *output)
|
|
{
|
|
SDL_WindowData *window = data;
|
|
SDL_WaylandOutputData *driverdata = wl_output_get_user_data(output);
|
|
|
|
if (!SDL_WAYLAND_own_output(output) || !SDL_WAYLAND_own_surface(surface)) {
|
|
return;
|
|
}
|
|
|
|
window->outputs = SDL_realloc(window->outputs,
|
|
sizeof(SDL_WaylandOutputData *) * (window->num_outputs + 1));
|
|
window->outputs[window->num_outputs++] = driverdata;
|
|
|
|
/* Update the scale factor after the move so that fullscreen outputs are updated. */
|
|
Wayland_move_window(window->sdlwindow, driverdata);
|
|
|
|
if (!window->fractional_scale) {
|
|
update_scale_factor(window);
|
|
}
|
|
}
|
|
|
|
static void handle_surface_leave(void *data, struct wl_surface *surface,
|
|
struct wl_output *output)
|
|
{
|
|
SDL_WindowData *window = data;
|
|
int i, send_move_event = 0;
|
|
SDL_WaylandOutputData *driverdata = wl_output_get_user_data(output);
|
|
|
|
if (!SDL_WAYLAND_own_output(output) || !SDL_WAYLAND_own_surface(surface)) {
|
|
return;
|
|
}
|
|
|
|
for (i = 0; i < window->num_outputs; i++) {
|
|
if (window->outputs[i] == driverdata) { /* remove this one */
|
|
if (i == (window->num_outputs - 1)) {
|
|
window->outputs[i] = NULL;
|
|
send_move_event = 1;
|
|
} else {
|
|
SDL_memmove(&window->outputs[i],
|
|
&window->outputs[i + 1],
|
|
sizeof(SDL_WaylandOutputData *) * ((window->num_outputs - i) - 1));
|
|
}
|
|
window->num_outputs--;
|
|
i--;
|
|
}
|
|
}
|
|
|
|
if (window->num_outputs == 0) {
|
|
SDL_free(window->outputs);
|
|
window->outputs = NULL;
|
|
} else if (send_move_event) {
|
|
Wayland_move_window(window->sdlwindow,
|
|
window->outputs[window->num_outputs - 1]);
|
|
}
|
|
|
|
if (!window->fractional_scale) {
|
|
update_scale_factor(window);
|
|
}
|
|
}
|
|
|
|
static const struct wl_surface_listener surface_listener = {
|
|
handle_surface_enter,
|
|
handle_surface_leave
|
|
};
|
|
|
|
static void
|
|
Wayland_FillEmptyShellInfo(SDL_SysWMinfo * info, const Uint32 version)
|
|
{
|
|
info->info.wl.xdg_surface = NULL;
|
|
if (version >= SDL_VERSIONNUM(2, 0, 17)) {
|
|
info->info.wl.xdg_toplevel = NULL;
|
|
if (version >= SDL_VERSIONNUM(2, 0, 22)) {
|
|
info->info.wl.xdg_popup = NULL;
|
|
info->info.wl.xdg_positioner = NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
SDL_bool
|
|
Wayland_GetWindowWMInfo(_THIS, SDL_Window * window, SDL_SysWMinfo * info)
|
|
{
|
|
SDL_VideoData *viddata = (SDL_VideoData *) _this->driverdata;
|
|
SDL_WindowData *data = (SDL_WindowData *) window->driverdata;
|
|
const Uint32 version = SDL_VERSIONNUM((Uint32)info->version.major,
|
|
(Uint32)info->version.minor,
|
|
(Uint32)info->version.patch);
|
|
|
|
/* Before 2.0.6, it was possible to build an SDL with Wayland support
|
|
(SDL_SysWMinfo will be large enough to hold Wayland info), but build
|
|
your app against SDL headers that didn't have Wayland support
|
|
(SDL_SysWMinfo could be smaller than Wayland needs. This would lead
|
|
to an app properly using SDL_GetWindowWMInfo() but we'd accidentally
|
|
overflow memory on the stack or heap. To protect against this, we've
|
|
padded out the struct unconditionally in the headers and Wayland will
|
|
just return an error for older apps using this function. Those apps
|
|
will need to be recompiled against newer headers or not use Wayland,
|
|
maybe by forcing SDL_VIDEODRIVER=x11. */
|
|
if (version < SDL_VERSIONNUM(2, 0, 6)) {
|
|
info->subsystem = SDL_SYSWM_UNKNOWN;
|
|
SDL_SetError("Version must be 2.0.6 or newer");
|
|
return SDL_FALSE;
|
|
}
|
|
|
|
info->info.wl.display = data->waylandData->display;
|
|
info->info.wl.surface = data->surface;
|
|
|
|
if (version >= SDL_VERSIONNUM(2, 0, 15)) {
|
|
info->info.wl.egl_window = data->egl_window;
|
|
|
|
#ifdef HAVE_LIBDECOR_H
|
|
if (data->shell_surface_type == WAYLAND_SURFACE_LIBDECOR) {
|
|
if (data->shell_surface.libdecor.frame != NULL) {
|
|
info->info.wl.xdg_surface = libdecor_frame_get_xdg_surface(data->shell_surface.libdecor.frame);
|
|
if (version >= SDL_VERSIONNUM(2, 0, 17)) {
|
|
info->info.wl.xdg_toplevel = libdecor_frame_get_xdg_toplevel(data->shell_surface.libdecor.frame);
|
|
if (version >= SDL_VERSIONNUM(2, 0, 22)) {
|
|
info->info.wl.xdg_popup = NULL;
|
|
info->info.wl.xdg_positioner = NULL;
|
|
}
|
|
}
|
|
} else {
|
|
/* Not mapped yet */
|
|
Wayland_FillEmptyShellInfo(info, version);
|
|
}
|
|
} else
|
|
#endif
|
|
if (viddata->shell.xdg && data->shell_surface.xdg.surface != NULL) {
|
|
info->info.wl.xdg_surface = data->shell_surface.xdg.surface;
|
|
if (version >= SDL_VERSIONNUM(2, 0, 17)) {
|
|
SDL_bool popup = data->shell_surface_type == WAYLAND_SURFACE_XDG_POPUP;
|
|
info->info.wl.xdg_toplevel = popup ? NULL : data->shell_surface.xdg.roleobj.toplevel;
|
|
if (version >= SDL_VERSIONNUM(2, 0, 22)) {
|
|
if (popup) {
|
|
info->info.wl.xdg_popup = data->shell_surface.xdg.roleobj.popup.popup;
|
|
info->info.wl.xdg_positioner = data->shell_surface.xdg.roleobj.popup.positioner;
|
|
} else {
|
|
info->info.wl.xdg_popup = NULL;
|
|
info->info.wl.xdg_positioner = NULL;
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
/* Either it's not mapped yet or we don't have a shell protocol */
|
|
Wayland_FillEmptyShellInfo(info, version);
|
|
}
|
|
}
|
|
|
|
/* Deprecated in 2.0.16 */
|
|
info->info.wl.shell_surface = NULL;
|
|
|
|
info->subsystem = SDL_SYSWM_WAYLAND;
|
|
|
|
return SDL_TRUE;
|
|
}
|
|
|
|
int Wayland_SetWindowHitTest(SDL_Window *window, SDL_bool enabled)
|
|
{
|
|
return 0; /* just succeed, the real work is done elsewhere. */
|
|
}
|
|
|
|
int Wayland_SetWindowModalFor(_THIS, SDL_Window *modal_window, SDL_Window *parent_window)
|
|
{
|
|
SDL_VideoData *viddata = (SDL_VideoData *)_this->driverdata;
|
|
SDL_WindowData *modal_data = modal_window->driverdata;
|
|
SDL_WindowData *parent_data = parent_window->driverdata;
|
|
|
|
if (modal_data->shell_surface_type == WAYLAND_SURFACE_XDG_POPUP || parent_data->shell_surface_type == WAYLAND_SURFACE_XDG_POPUP) {
|
|
return SDL_SetError("Modal/Parent was a popup, not a toplevel");
|
|
}
|
|
|
|
#ifdef HAVE_LIBDECOR_H
|
|
if (viddata->shell.libdecor) {
|
|
if (modal_data->shell_surface.libdecor.frame == NULL) {
|
|
return SDL_SetError("Modal window was hidden");
|
|
}
|
|
if (parent_data->shell_surface.libdecor.frame == NULL) {
|
|
return SDL_SetError("Parent window was hidden");
|
|
}
|
|
libdecor_frame_set_parent(modal_data->shell_surface.libdecor.frame,
|
|
parent_data->shell_surface.libdecor.frame);
|
|
} else
|
|
#endif
|
|
if (viddata->shell.xdg) {
|
|
if (modal_data->shell_surface.xdg.roleobj.toplevel == NULL) {
|
|
return SDL_SetError("Modal window was hidden");
|
|
}
|
|
if (parent_data->shell_surface.xdg.roleobj.toplevel == NULL) {
|
|
return SDL_SetError("Parent window was hidden");
|
|
}
|
|
xdg_toplevel_set_parent(modal_data->shell_surface.xdg.roleobj.toplevel,
|
|
parent_data->shell_surface.xdg.roleobj.toplevel);
|
|
} else {
|
|
return SDL_Unsupported();
|
|
}
|
|
|
|
WAYLAND_wl_display_flush(viddata->display);
|
|
return 0;
|
|
}
|
|
|
|
void Wayland_ShowWindow(_THIS, SDL_Window *window)
|
|
{
|
|
SDL_VideoData *c = _this->driverdata;
|
|
SDL_WindowData *data = window->driverdata;
|
|
|
|
/* 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_SURFACE_LIBDECOR) {
|
|
if (data->shell_surface.libdecor.frame) {
|
|
/* If the frame already exists, just set the visibility. */
|
|
libdecor_frame_set_visibility(data->shell_surface.libdecor.frame, true);
|
|
libdecor_frame_set_app_id(data->shell_surface.libdecor.frame, c->classname);
|
|
} else {
|
|
data->shell_surface.libdecor.frame = libdecor_decorate(c->shell.libdecor,
|
|
data->surface,
|
|
&libdecor_frame_interface,
|
|
data);
|
|
if (data->shell_surface.libdecor.frame == NULL) {
|
|
SDL_LogError(SDL_LOG_CATEGORY_VIDEO, "Failed to create libdecor frame!");
|
|
} else {
|
|
libdecor_frame_set_app_id(data->shell_surface.libdecor.frame, c->classname);
|
|
libdecor_frame_map(data->shell_surface.libdecor.frame);
|
|
}
|
|
}
|
|
} else
|
|
#endif
|
|
if (c->shell.xdg) {
|
|
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);
|
|
|
|
if (data->shell_surface_type == WAYLAND_SURFACE_XDG_POPUP) {
|
|
SDL_Mouse *mouse = SDL_GetMouse();
|
|
SDL_Window *focused = SDL_GetMouseFocus();
|
|
SDL_WindowData *focuseddata = focused->driverdata;
|
|
|
|
/* This popup may be a child of another popup! */
|
|
data->shell_surface.xdg.roleobj.popup.parentID = SDL_GetWindowID(focused);
|
|
data->shell_surface.xdg.roleobj.popup.child = NULL;
|
|
if (focuseddata->shell_surface_type == WAYLAND_SURFACE_XDG_POPUP) {
|
|
SDL_assert(focuseddata->shell_surface.xdg.roleobj.popup.child == NULL);
|
|
focuseddata->shell_surface.xdg.roleobj.popup.child = window;
|
|
}
|
|
|
|
/* Set up the positioner for the popup */
|
|
data->shell_surface.xdg.roleobj.popup.positioner = xdg_wm_base_create_positioner(c->shell.xdg);
|
|
xdg_positioner_set_offset(data->shell_surface.xdg.roleobj.popup.positioner,
|
|
mouse->x + TOOLTIP_CURSOR_OFFSET,
|
|
mouse->y + TOOLTIP_CURSOR_OFFSET);
|
|
|
|
/* Assign the popup role */
|
|
data->shell_surface.xdg.roleobj.popup.popup = xdg_surface_get_popup(data->shell_surface.xdg.surface,
|
|
focuseddata->shell_surface.xdg.surface,
|
|
data->shell_surface.xdg.roleobj.popup.positioner);
|
|
xdg_popup_add_listener(data->shell_surface.xdg.roleobj.popup.popup, &popup_listener_xdg, data);
|
|
|
|
/* For tooltips, track mouse motion so it follows the cursor */
|
|
if (window->flags & SDL_WINDOW_TOOLTIP) {
|
|
if (xdg_popup_get_version(data->shell_surface.xdg.roleobj.popup.popup) >= 3) {
|
|
SDL_AddEventWatch(Wayland_PopupWatch, window);
|
|
}
|
|
}
|
|
} else {
|
|
data->shell_surface.xdg.roleobj.toplevel = xdg_surface_get_toplevel(data->shell_surface.xdg.surface);
|
|
xdg_toplevel_set_app_id(data->shell_surface.xdg.roleobj.toplevel, c->classname);
|
|
xdg_toplevel_add_listener(data->shell_surface.xdg.roleobj.toplevel, &toplevel_listener_xdg, data);
|
|
}
|
|
}
|
|
|
|
/* Restore state that was set prior to this call */
|
|
Wayland_SetWindowTitle(_this, window);
|
|
if (window->flags & SDL_WINDOW_MAXIMIZED) {
|
|
Wayland_MaximizeWindow(_this, window);
|
|
}
|
|
if (window->flags & SDL_WINDOW_MINIMIZED) {
|
|
Wayland_MinimizeWindow(_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_SURFACE_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 (c->shell.xdg) {
|
|
/* 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);
|
|
}
|
|
}
|
|
|
|
/* Create the window decorations */
|
|
if (data->shell_surface_type != WAYLAND_SURFACE_XDG_POPUP && data->shell_surface.xdg.roleobj.toplevel && c->decoration_manager) {
|
|
data->server_decoration = zxdg_decoration_manager_v1_get_toplevel_decoration(c->decoration_manager, data->shell_surface.xdg.roleobj.toplevel);
|
|
zxdg_toplevel_decoration_v1_add_listener(data->server_decoration,
|
|
&decoration_listener,
|
|
window);
|
|
}
|
|
} else {
|
|
/* Nothing to see here, just commit. */
|
|
wl_surface_commit(data->surface);
|
|
}
|
|
|
|
/* 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_SURFACE_LIBDECOR) {
|
|
/* ... but don't call it redundantly for libdecor, the decorator
|
|
* may not interpret a redundant call nicely and cause weird stuff to happen
|
|
*/
|
|
if (data->shell_surface.libdecor.frame && window->flags & SDL_WINDOW_BORDERLESS) {
|
|
Wayland_SetWindowBordered(_this, window, SDL_FALSE);
|
|
}
|
|
} else
|
|
#endif
|
|
{
|
|
Wayland_SetWindowBordered(_this, window, !(window->flags & SDL_WINDOW_BORDERLESS));
|
|
}
|
|
|
|
/* 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 */
|
|
unsetenv("XDG_ACTIVATION_TOKEN");
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Roundtrip required to avoid a possible protocol violation when
|
|
* HideWindow was called immediately before ShowWindow.
|
|
*/
|
|
WAYLAND_wl_display_roundtrip(c->display);
|
|
}
|
|
|
|
static void Wayland_ReleasePopup(_THIS, SDL_Window *popup)
|
|
{
|
|
SDL_WindowData *popupdata;
|
|
|
|
/* Basic sanity checks to weed out the weird popup closures */
|
|
if (popup == NULL || popup->magic != &_this->window_magic) {
|
|
return;
|
|
}
|
|
popupdata = popup->driverdata;
|
|
if (popupdata == NULL) {
|
|
return;
|
|
}
|
|
|
|
/* This may already be freed by a parent popup! */
|
|
if (popupdata->shell_surface.xdg.roleobj.popup.popup == NULL) {
|
|
return;
|
|
}
|
|
|
|
/* Release the child _first_, otherwise a protocol error triggers */
|
|
if (popupdata->shell_surface.xdg.roleobj.popup.child != NULL) {
|
|
Wayland_ReleasePopup(_this, popupdata->shell_surface.xdg.roleobj.popup.child);
|
|
popupdata->shell_surface.xdg.roleobj.popup.child = NULL;
|
|
}
|
|
|
|
if (popup->flags & SDL_WINDOW_TOOLTIP) {
|
|
if (xdg_popup_get_version(popupdata->shell_surface.xdg.roleobj.popup.popup) >= 3) {
|
|
SDL_DelEventWatch(Wayland_PopupWatch, popup);
|
|
}
|
|
}
|
|
xdg_popup_destroy(popupdata->shell_surface.xdg.roleobj.popup.popup);
|
|
xdg_positioner_destroy(popupdata->shell_surface.xdg.roleobj.popup.positioner);
|
|
popupdata->shell_surface.xdg.roleobj.popup.popup = NULL;
|
|
popupdata->shell_surface.xdg.roleobj.popup.positioner = NULL;
|
|
}
|
|
|
|
void Wayland_HideWindow(_THIS, SDL_Window *window)
|
|
{
|
|
SDL_VideoData *data = _this->driverdata;
|
|
SDL_WindowData *wind = window->driverdata;
|
|
|
|
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! */
|
|
wl_surface_attach(wind->surface, NULL, 0, 0);
|
|
wl_surface_commit(wind->surface);
|
|
|
|
#ifdef HAVE_LIBDECOR_H
|
|
if (wind->shell_surface_type == WAYLAND_SURFACE_LIBDECOR) {
|
|
if (wind->shell_surface.libdecor.frame) {
|
|
libdecor_frame_set_visibility(wind->shell_surface.libdecor.frame, false);
|
|
libdecor_frame_set_app_id(wind->shell_surface.libdecor.frame, data->classname);
|
|
}
|
|
} else
|
|
#endif
|
|
if (data->shell.xdg) {
|
|
if (wind->shell_surface_type == WAYLAND_SURFACE_XDG_POPUP) {
|
|
Wayland_ReleasePopup(_this, window);
|
|
} else if (wind->shell_surface.xdg.roleobj.toplevel) {
|
|
xdg_toplevel_destroy(wind->shell_surface.xdg.roleobj.toplevel);
|
|
wind->shell_surface.xdg.roleobj.toplevel = NULL;
|
|
}
|
|
if (wind->shell_surface.xdg.surface) {
|
|
xdg_surface_destroy(wind->shell_surface.xdg.surface);
|
|
wind->shell_surface.xdg.surface = NULL;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Roundtrip required to avoid a possible protocol violation when
|
|
* ShowWindow is called immediately after HideWindow.
|
|
*/
|
|
WAYLAND_wl_display_roundtrip(data->display);
|
|
}
|
|
|
|
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 *wind,
|
|
struct wl_surface *surface,
|
|
uint32_t serial, struct wl_seat *seat)
|
|
{
|
|
if (data->activation_manager) {
|
|
if (wind->activation_token != NULL) {
|
|
/* We're about to overwrite this with a new request */
|
|
xdg_activation_token_v1_destroy(wind->activation_token);
|
|
}
|
|
|
|
wind->activation_token = xdg_activation_v1_get_activation_token(data->activation_manager);
|
|
xdg_activation_token_v1_add_listener(wind->activation_token,
|
|
&activation_listener_xdg,
|
|
wind);
|
|
|
|
/* Note that we are not setting the app_id or serial 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 (surface != NULL) {
|
|
xdg_activation_token_v1_set_surface(wind->activation_token, surface);
|
|
}
|
|
if (seat != NULL) {
|
|
xdg_activation_token_v1_set_serial(wind->activation_token, serial, seat);
|
|
}
|
|
xdg_activation_token_v1_commit(wind->activation_token);
|
|
}
|
|
}
|
|
|
|
void Wayland_RaiseWindow(_THIS, SDL_Window *window)
|
|
{
|
|
SDL_WindowData *wind = window->driverdata;
|
|
|
|
/* FIXME: This Raise event is arbitrary and doesn't come from an event, so
|
|
* it's actually very likely that this token will be ignored! Maybe add
|
|
* support for passing serials (and the associated seat) so this can have
|
|
* a better chance of actually raising the window.
|
|
* -flibit
|
|
*/
|
|
Wayland_activate_window(_this->driverdata,
|
|
wind,
|
|
wind->surface,
|
|
0,
|
|
NULL);
|
|
}
|
|
|
|
int Wayland_FlashWindow(_THIS, SDL_Window *window, SDL_FlashOperation operation)
|
|
{
|
|
Wayland_activate_window(_this->driverdata,
|
|
window->driverdata,
|
|
NULL,
|
|
0,
|
|
NULL);
|
|
return 0;
|
|
}
|
|
|
|
void handle_preferred_scale_changed(void *data,
|
|
struct wp_fractional_scale_v1 *wp_fractional_scale_v1,
|
|
uint preferred_scale)
|
|
{
|
|
SDL_WindowData *window = data;
|
|
float old_factor = window->scale_factor;
|
|
float new_factor = preferred_scale / 120.; /* 120 is a magic number defined in the spec as a common denominator*/
|
|
|
|
if (!(window->sdlwindow->flags & SDL_WINDOW_ALLOW_HIGHDPI)) {
|
|
/* Scale will always be 1, just ignore this */
|
|
return;
|
|
}
|
|
|
|
if (!FloatEqual(new_factor, old_factor)) {
|
|
Wayland_HandleResize(window->sdlwindow, window->sdlwindow->w, window->sdlwindow->h, new_factor);
|
|
}
|
|
}
|
|
|
|
static const struct wp_fractional_scale_v1_listener fractional_scale_listener = {
|
|
handle_preferred_scale_changed
|
|
};
|
|
|
|
#ifdef SDL_VIDEO_DRIVER_WAYLAND_QT_TOUCH
|
|
static void SDLCALL QtExtendedSurface_OnHintChanged(void *userdata, const char *name,
|
|
const char *oldValue, const char *newValue)
|
|
{
|
|
struct qt_extended_surface *qt_extended_surface = userdata;
|
|
int i;
|
|
|
|
static struct
|
|
{
|
|
const char *name;
|
|
int32_t value;
|
|
} orientations[] = {
|
|
{ "portrait", QT_EXTENDED_SURFACE_ORIENTATION_PRIMARYORIENTATION },
|
|
{ "landscape", QT_EXTENDED_SURFACE_ORIENTATION_LANDSCAPEORIENTATION },
|
|
{ "inverted-portrait", QT_EXTENDED_SURFACE_ORIENTATION_INVERTEDPORTRAITORIENTATION },
|
|
{ "inverted-landscape", QT_EXTENDED_SURFACE_ORIENTATION_INVERTEDLANDSCAPEORIENTATION }
|
|
};
|
|
|
|
if (name == NULL) {
|
|
return;
|
|
}
|
|
|
|
if (SDL_strcmp(name, SDL_HINT_QTWAYLAND_CONTENT_ORIENTATION) == 0) {
|
|
int32_t orientation = QT_EXTENDED_SURFACE_ORIENTATION_PRIMARYORIENTATION;
|
|
|
|
if (newValue != NULL) {
|
|
const char *value_attempt = newValue;
|
|
while (value_attempt != NULL && *value_attempt != 0) {
|
|
const char *value_attempt_end = SDL_strchr(value_attempt, ',');
|
|
size_t value_attempt_len = (value_attempt_end != NULL) ? (value_attempt_end - value_attempt)
|
|
: SDL_strlen(value_attempt);
|
|
|
|
for (i = 0; i < SDL_arraysize(orientations); i += 1) {
|
|
if ((value_attempt_len == SDL_strlen(orientations[i].name)) &&
|
|
(SDL_strncasecmp(orientations[i].name, value_attempt, value_attempt_len) == 0)) {
|
|
orientation |= orientations[i].value;
|
|
break;
|
|
}
|
|
}
|
|
|
|
value_attempt = (value_attempt_end != NULL) ? (value_attempt_end + 1) : NULL;
|
|
}
|
|
}
|
|
|
|
qt_extended_surface_set_content_orientation(qt_extended_surface, orientation);
|
|
} else if (SDL_strcmp(name, SDL_HINT_QTWAYLAND_WINDOW_FLAGS) == 0) {
|
|
uint32_t flags = 0;
|
|
|
|
if (newValue != NULL) {
|
|
char *tmp = SDL_strdup(newValue);
|
|
char *saveptr = NULL;
|
|
|
|
char *flag = SDL_strtokr(tmp, " ", &saveptr);
|
|
while (flag) {
|
|
if (SDL_strcmp(flag, "OverridesSystemGestures") == 0) {
|
|
flags |= QT_EXTENDED_SURFACE_WINDOWFLAG_OVERRIDESSYSTEMGESTURES;
|
|
} else if (SDL_strcmp(flag, "StaysOnTop") == 0) {
|
|
flags |= QT_EXTENDED_SURFACE_WINDOWFLAG_STAYSONTOP;
|
|
} else if (SDL_strcmp(flag, "BypassWindowManager") == 0) {
|
|
// See https://github.com/qtproject/qtwayland/commit/fb4267103d
|
|
flags |= 4 /* QT_EXTENDED_SURFACE_WINDOWFLAG_BYPASSWINDOWMANAGER */;
|
|
}
|
|
|
|
flag = SDL_strtokr(NULL, " ", &saveptr);
|
|
}
|
|
|
|
SDL_free(tmp);
|
|
}
|
|
|
|
qt_extended_surface_set_window_flags(qt_extended_surface, flags);
|
|
}
|
|
}
|
|
|
|
static void QtExtendedSurface_Subscribe(struct qt_extended_surface *surface, const char *name)
|
|
{
|
|
SDL_AddHintCallback(name, QtExtendedSurface_OnHintChanged, surface);
|
|
}
|
|
|
|
static void QtExtendedSurface_Unsubscribe(struct qt_extended_surface *surface, const char *name)
|
|
{
|
|
SDL_DelHintCallback(name, QtExtendedSurface_OnHintChanged, surface);
|
|
}
|
|
#endif /* SDL_VIDEO_DRIVER_WAYLAND_QT_TOUCH */
|
|
|
|
void Wayland_SetWindowFullscreen(_THIS, SDL_Window *window,
|
|
SDL_VideoDisplay *_display, SDL_bool fullscreen)
|
|
{
|
|
SDL_WindowData *wind = (SDL_WindowData *)window->driverdata;
|
|
struct wl_output *output = ((SDL_WaylandOutputData *)_display->driverdata)->output;
|
|
SDL_VideoData *viddata = (SDL_VideoData *)_this->driverdata;
|
|
|
|
/* Called from within a configure event or the window is a popup, drop it. */
|
|
if (wind->in_fullscreen_transition || wind->shell_surface_type == WAYLAND_SURFACE_XDG_POPUP) {
|
|
return;
|
|
}
|
|
|
|
/* Save the last fullscreen flags for future requests by the compositor. */
|
|
if (fullscreen) {
|
|
wind->fullscreen_flags = window->flags & FULLSCREEN_MASK;
|
|
}
|
|
|
|
/* Don't send redundant fullscreen set/unset events. */
|
|
if (wind->is_fullscreen != fullscreen) {
|
|
wind->is_fullscreen = fullscreen;
|
|
SetFullscreen(window, fullscreen ? output : NULL);
|
|
|
|
/* Roundtrip required to receive the updated window dimensions */
|
|
WAYLAND_wl_display_roundtrip(viddata->display);
|
|
} else if (wind->is_fullscreen) {
|
|
/*
|
|
* If the window is already fullscreen, this is likely a request to switch between
|
|
* fullscreen and fullscreen desktop, or to change the video mode. Update the
|
|
* geometry and trigger a commit.
|
|
*/
|
|
ConfigureWindowGeometry(window);
|
|
CommitLibdecorFrame(window);
|
|
|
|
/* Roundtrip required to receive the updated window dimensions */
|
|
WAYLAND_wl_display_roundtrip(viddata->display);
|
|
}
|
|
}
|
|
|
|
void Wayland_RestoreWindow(_THIS, SDL_Window *window)
|
|
{
|
|
SDL_WindowData *wind = window->driverdata;
|
|
SDL_VideoData *viddata = (SDL_VideoData *)_this->driverdata;
|
|
|
|
if (wind->shell_surface_type == WAYLAND_SURFACE_XDG_POPUP) {
|
|
return;
|
|
}
|
|
|
|
/* Set this flag now even if we never actually maximized, eventually
|
|
* ShowWindow will take care of it along with the other window state.
|
|
*/
|
|
window->flags &= ~SDL_WINDOW_MAXIMIZED;
|
|
|
|
#ifdef HAVE_LIBDECOR_H
|
|
if (wind->shell_surface_type == WAYLAND_SURFACE_LIBDECOR) {
|
|
if (wind->shell_surface.libdecor.frame == NULL) {
|
|
return; /* Can't do anything yet, wait for ShowWindow */
|
|
}
|
|
libdecor_frame_unset_maximized(wind->shell_surface.libdecor.frame);
|
|
} else
|
|
#endif
|
|
/* Note that xdg-shell does NOT provide a way to unset minimize! */
|
|
if (viddata->shell.xdg) {
|
|
if (wind->shell_surface.xdg.roleobj.toplevel == NULL) {
|
|
return; /* Can't do anything yet, wait for ShowWindow */
|
|
}
|
|
xdg_toplevel_unset_maximized(wind->shell_surface.xdg.roleobj.toplevel);
|
|
}
|
|
|
|
WAYLAND_wl_display_flush(viddata->display);
|
|
}
|
|
|
|
void Wayland_SetWindowBordered(_THIS, SDL_Window *window, SDL_bool bordered)
|
|
{
|
|
SDL_WindowData *wind = window->driverdata;
|
|
const SDL_VideoData *viddata = (const SDL_VideoData *)_this->driverdata;
|
|
|
|
if (wind->shell_surface_type == WAYLAND_SURFACE_XDG_POPUP) {
|
|
return;
|
|
}
|
|
|
|
#ifdef HAVE_LIBDECOR_H
|
|
if (wind->shell_surface_type == WAYLAND_SURFACE_LIBDECOR) {
|
|
if (wind->shell_surface.libdecor.frame) {
|
|
libdecor_frame_set_visibility(wind->shell_surface.libdecor.frame, bordered);
|
|
}
|
|
} else
|
|
#endif
|
|
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(_THIS, SDL_Window *window, SDL_bool resizable)
|
|
{
|
|
#ifdef HAVE_LIBDECOR_H
|
|
const SDL_WindowData *wind = window->driverdata;
|
|
|
|
if (wind->shell_surface_type == WAYLAND_SURFACE_LIBDECOR) {
|
|
if (wind->shell_surface.libdecor.frame == NULL) {
|
|
return; /* Can't do anything yet, wait for ShowWindow */
|
|
}
|
|
if (resizable) {
|
|
libdecor_frame_set_capabilities(wind->shell_surface.libdecor.frame, LIBDECOR_ACTION_RESIZE);
|
|
} else {
|
|
libdecor_frame_unset_capabilities(wind->shell_surface.libdecor.frame, LIBDECOR_ACTION_RESIZE);
|
|
}
|
|
} else
|
|
#endif
|
|
{
|
|
SetMinMaxDimensions(window, SDL_TRUE);
|
|
}
|
|
}
|
|
|
|
void Wayland_MaximizeWindow(_THIS, SDL_Window *window)
|
|
{
|
|
SDL_WindowData *wind = window->driverdata;
|
|
SDL_VideoData *viddata = (SDL_VideoData *)_this->driverdata;
|
|
|
|
if (wind->shell_surface_type == WAYLAND_SURFACE_XDG_POPUP) {
|
|
return;
|
|
}
|
|
|
|
if (!(window->flags & SDL_WINDOW_RESIZABLE)) {
|
|
return;
|
|
}
|
|
|
|
/* Set this flag now even if we don't actually maximize yet, eventually
|
|
* ShowWindow will take care of it along with the other window state.
|
|
*/
|
|
window->flags |= SDL_WINDOW_MAXIMIZED;
|
|
|
|
#ifdef HAVE_LIBDECOR_H
|
|
if (wind->shell_surface_type == WAYLAND_SURFACE_LIBDECOR) {
|
|
if (wind->shell_surface.libdecor.frame == NULL) {
|
|
return; /* Can't do anything yet, wait for ShowWindow */
|
|
}
|
|
libdecor_frame_set_maximized(wind->shell_surface.libdecor.frame);
|
|
} else
|
|
#endif
|
|
if (viddata->shell.xdg) {
|
|
if (wind->shell_surface.xdg.roleobj.toplevel == NULL) {
|
|
return; /* Can't do anything yet, wait for ShowWindow */
|
|
}
|
|
xdg_toplevel_set_maximized(wind->shell_surface.xdg.roleobj.toplevel);
|
|
}
|
|
|
|
WAYLAND_wl_display_flush(viddata->display);
|
|
}
|
|
|
|
void Wayland_MinimizeWindow(_THIS, SDL_Window *window)
|
|
{
|
|
SDL_WindowData *wind = window->driverdata;
|
|
SDL_VideoData *viddata = (SDL_VideoData *)_this->driverdata;
|
|
|
|
if (wind->shell_surface_type == WAYLAND_SURFACE_XDG_POPUP) {
|
|
return;
|
|
}
|
|
|
|
#ifdef HAVE_LIBDECOR_H
|
|
if (wind->shell_surface_type == WAYLAND_SURFACE_LIBDECOR) {
|
|
if (wind->shell_surface.libdecor.frame == NULL) {
|
|
return; /* Can't do anything yet, wait for ShowWindow */
|
|
}
|
|
libdecor_frame_set_minimized(wind->shell_surface.libdecor.frame);
|
|
} else
|
|
#endif
|
|
if (viddata->shell.xdg) {
|
|
if (wind->shell_surface.xdg.roleobj.toplevel == NULL) {
|
|
return; /* Can't do anything yet, wait for ShowWindow */
|
|
}
|
|
xdg_toplevel_set_minimized(wind->shell_surface.xdg.roleobj.toplevel);
|
|
}
|
|
|
|
WAYLAND_wl_display_flush(viddata->display);
|
|
}
|
|
|
|
void Wayland_SetWindowMouseRect(_THIS, SDL_Window *window)
|
|
{
|
|
SDL_VideoData *data = (SDL_VideoData *)_this->driverdata;
|
|
|
|
/* 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)) {
|
|
Wayland_input_unconfine_pointer(data->input, window);
|
|
} else {
|
|
Wayland_input_confine_pointer(data->input, window);
|
|
}
|
|
}
|
|
|
|
void Wayland_SetWindowMouseGrab(_THIS, SDL_Window *window, SDL_bool grabbed)
|
|
{
|
|
SDL_VideoData *data = (SDL_VideoData *)_this->driverdata;
|
|
|
|
if (grabbed) {
|
|
Wayland_input_confine_pointer(data->input, window);
|
|
} else if (SDL_RectEmpty(&window->mouse_rect)) {
|
|
Wayland_input_unconfine_pointer(data->input, window);
|
|
}
|
|
}
|
|
|
|
void Wayland_SetWindowKeyboardGrab(_THIS, SDL_Window *window, SDL_bool grabbed)
|
|
{
|
|
SDL_VideoData *data = (SDL_VideoData *)_this->driverdata;
|
|
|
|
if (grabbed) {
|
|
Wayland_input_grab_keyboard(window, data->input);
|
|
} else {
|
|
Wayland_input_ungrab_keyboard(window);
|
|
}
|
|
}
|
|
|
|
int Wayland_CreateWindow(_THIS, SDL_Window *window)
|
|
{
|
|
SDL_WindowData *data;
|
|
SDL_VideoData *c;
|
|
|
|
data = SDL_calloc(1, sizeof *data);
|
|
if (data == NULL) {
|
|
return SDL_OutOfMemory();
|
|
}
|
|
|
|
c = _this->driverdata;
|
|
window->driverdata = data;
|
|
|
|
if (!(window->flags & SDL_WINDOW_VULKAN)) {
|
|
if (!(window->flags & SDL_WINDOW_OPENGL)) {
|
|
SDL_GL_LoadLibrary(NULL);
|
|
window->flags |= SDL_WINDOW_OPENGL;
|
|
}
|
|
}
|
|
|
|
if (window->x == SDL_WINDOWPOS_UNDEFINED) {
|
|
window->x = 0;
|
|
}
|
|
if (window->y == SDL_WINDOWPOS_UNDEFINED) {
|
|
window->y = 0;
|
|
}
|
|
|
|
data->waylandData = c;
|
|
data->sdlwindow = window;
|
|
|
|
data->scale_factor = 1.0f;
|
|
|
|
if (window->flags & SDL_WINDOW_ALLOW_HIGHDPI) {
|
|
int i;
|
|
for (i = 0; i < SDL_GetVideoDevice()->num_displays; i++) {
|
|
float scale = ((SDL_WaylandOutputData *)SDL_GetVideoDevice()->displays[i].driverdata)->scale_factor;
|
|
data->scale_factor = SDL_max(data->scale_factor, scale);
|
|
}
|
|
}
|
|
|
|
data->outputs = NULL;
|
|
data->num_outputs = 0;
|
|
|
|
data->floating_width = window->windowed.w;
|
|
data->floating_height = window->windowed.h;
|
|
|
|
data->surface =
|
|
wl_compositor_create_surface(c->compositor);
|
|
wl_surface_add_listener(data->surface, &surface_listener, data);
|
|
|
|
SDL_WAYLAND_register_surface(data->surface);
|
|
|
|
/* 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);
|
|
}
|
|
|
|
/* Fire a callback when the compositor wants a new frame to set the surface damage region. */
|
|
data->surface_damage_frame_callback = wl_surface_frame(data->surface);
|
|
wl_callback_add_listener(data->surface_damage_frame_callback, &surface_damage_frame_listener, data);
|
|
|
|
#ifdef SDL_VIDEO_DRIVER_WAYLAND_QT_TOUCH
|
|
if (c->surface_extension) {
|
|
data->extended_surface = qt_surface_extension_get_extended_surface(
|
|
c->surface_extension, data->surface);
|
|
|
|
QtExtendedSurface_Subscribe(data->extended_surface, SDL_HINT_QTWAYLAND_CONTENT_ORIENTATION);
|
|
QtExtendedSurface_Subscribe(data->extended_surface, SDL_HINT_QTWAYLAND_WINDOW_FLAGS);
|
|
}
|
|
#endif /* SDL_VIDEO_DRIVER_WAYLAND_QT_TOUCH */
|
|
|
|
if (window->flags & SDL_WINDOW_OPENGL) {
|
|
data->egl_window = WAYLAND_wl_egl_window_create(data->surface, data->drawable_width, data->drawable_height);
|
|
|
|
#if SDL_VIDEO_OPENGL_EGL
|
|
/* Create the GLES window surface */
|
|
data->egl_surface = SDL_EGL_CreateSurface(_this, (NativeWindowType)data->egl_window);
|
|
|
|
if (data->egl_surface == EGL_NO_SURFACE) {
|
|
return -1; /* SDL_EGL_CreateSurface should have set error */
|
|
}
|
|
#endif
|
|
}
|
|
|
|
#ifdef SDL_VIDEO_DRIVER_WAYLAND_QT_TOUCH
|
|
if (data->extended_surface) {
|
|
qt_extended_surface_set_user_data(data->extended_surface, data);
|
|
qt_extended_surface_add_listener(data->extended_surface,
|
|
&extended_surface_listener, data);
|
|
}
|
|
#endif /* SDL_VIDEO_DRIVER_WAYLAND_QT_TOUCH */
|
|
|
|
if (c->relative_mouse_mode) {
|
|
Wayland_input_lock_pointer(c->input);
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
/* Moved this call to ShowWindow: wl_surface_commit(data->surface); */
|
|
WAYLAND_wl_display_flush(c->display);
|
|
|
|
/* We may need to create an idle inhibitor for this new window */
|
|
Wayland_SuspendScreenSaver(_this);
|
|
|
|
#define IS_POPUP(window) \
|
|
(window->flags & (SDL_WINDOW_TOOLTIP | SDL_WINDOW_POPUP_MENU))
|
|
#ifdef HAVE_LIBDECOR_H
|
|
if (c->shell.libdecor && !IS_POPUP(window)) {
|
|
data->shell_surface_type = WAYLAND_SURFACE_LIBDECOR;
|
|
} else
|
|
#endif
|
|
if (c->shell.xdg) {
|
|
if (IS_POPUP(window)) {
|
|
data->shell_surface_type = WAYLAND_SURFACE_XDG_POPUP;
|
|
} else {
|
|
data->shell_surface_type = WAYLAND_SURFACE_XDG_TOPLEVEL;
|
|
}
|
|
} /* All other cases will be WAYLAND_SURFACE_UNKNOWN */
|
|
#undef IS_POPUP
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void Wayland_HandleResize(SDL_Window *window, int width, int height, float scale)
|
|
{
|
|
SDL_WindowData *data = (SDL_WindowData *)window->driverdata;
|
|
const int old_w = window->w, old_h = window->h;
|
|
const int old_drawable_width = data->drawable_width;
|
|
const int old_drawable_height = data->drawable_height;
|
|
|
|
/* Update the window geometry. */
|
|
window->w = width;
|
|
window->h = height;
|
|
data->scale_factor = scale;
|
|
ConfigureWindowGeometry(window);
|
|
|
|
if (data->needs_resize_event ||
|
|
old_w != width || old_h != height ||
|
|
old_drawable_width != data->drawable_width || old_drawable_height != data->drawable_height) {
|
|
/* We may have already updated window w/h (or only adjusted scale factor),
|
|
* so we must override the deduplication logic in the video core */
|
|
window->w = 0;
|
|
window->h = 0;
|
|
SDL_SendWindowEvent(window, SDL_WINDOWEVENT_RESIZED, width, height);
|
|
window->w = width;
|
|
window->h = height;
|
|
data->needs_resize_event = SDL_FALSE;
|
|
}
|
|
}
|
|
|
|
void Wayland_SetWindowMinimumSize(_THIS, SDL_Window *window)
|
|
{
|
|
SetMinMaxDimensions(window, SDL_TRUE);
|
|
}
|
|
|
|
void Wayland_SetWindowMaximumSize(_THIS, SDL_Window *window)
|
|
{
|
|
SetMinMaxDimensions(window, SDL_TRUE);
|
|
}
|
|
|
|
void Wayland_SetWindowSize(_THIS, SDL_Window *window)
|
|
{
|
|
SDL_WindowData *wind = window->driverdata;
|
|
|
|
#ifdef HAVE_LIBDECOR_H
|
|
/* we must not resize the window while we have a static (non-floating) size */
|
|
if (wind->shell_surface_type == WAYLAND_SURFACE_LIBDECOR &&
|
|
wind->shell_surface.libdecor.frame &&
|
|
!libdecor_frame_is_floating(wind->shell_surface.libdecor.frame)) {
|
|
/* Commit the resize when we re-enter floating state */
|
|
wind->floating_resize_pending = SDL_TRUE;
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
/* Update the window geometry. */
|
|
ConfigureWindowGeometry(window);
|
|
CommitLibdecorFrame(window);
|
|
|
|
/* windowed is unconditionally set, so we can trust it here */
|
|
wind->floating_width = window->windowed.w;
|
|
wind->floating_height = window->windowed.h;
|
|
}
|
|
|
|
void Wayland_GetWindowSizeInPixels(_THIS, SDL_Window *window, int *w, int *h)
|
|
{
|
|
SDL_WindowData *data;
|
|
if (window->driverdata) {
|
|
data = (SDL_WindowData *)window->driverdata;
|
|
*w = data->drawable_width;
|
|
*h = data->drawable_height;
|
|
}
|
|
}
|
|
|
|
void Wayland_SetWindowTitle(_THIS, SDL_Window *window)
|
|
{
|
|
SDL_WindowData *wind = window->driverdata;
|
|
SDL_VideoData *viddata = _this->driverdata;
|
|
const char *title = window->title ? window->title : "";
|
|
|
|
if (wind->shell_surface_type == WAYLAND_SURFACE_XDG_POPUP) {
|
|
return;
|
|
}
|
|
|
|
#ifdef HAVE_LIBDECOR_H
|
|
if (wind->shell_surface_type == WAYLAND_SURFACE_LIBDECOR) {
|
|
if (wind->shell_surface.libdecor.frame == NULL) {
|
|
return; /* Can't do anything yet, wait for ShowWindow */
|
|
}
|
|
libdecor_frame_set_title(wind->shell_surface.libdecor.frame, title);
|
|
} else
|
|
#endif
|
|
if (viddata->shell.xdg) {
|
|
if (wind->shell_surface.xdg.roleobj.toplevel == NULL) {
|
|
return; /* Can't do anything yet, wait for ShowWindow */
|
|
}
|
|
xdg_toplevel_set_title(wind->shell_surface.xdg.roleobj.toplevel, title);
|
|
}
|
|
|
|
WAYLAND_wl_display_flush(viddata->display);
|
|
}
|
|
|
|
void Wayland_SuspendScreenSaver(_THIS)
|
|
{
|
|
SDL_VideoData *data = (SDL_VideoData *)_this->driverdata;
|
|
|
|
#if SDL_USE_LIBDBUS
|
|
if (SDL_DBus_ScreensaverInhibit(_this->suspend_screensaver)) {
|
|
return;
|
|
}
|
|
#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->driverdata;
|
|
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
|
|
void Wayland_DestroyWindow(_THIS, SDL_Window *window)
|
|
{
|
|
SDL_VideoData *data = _this->driverdata;
|
|
SDL_WindowData *wind = window->driverdata;
|
|
|
|
if (data) {
|
|
#if 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->draw_viewport) {
|
|
wp_viewport_destroy(wind->draw_viewport);
|
|
}
|
|
|
|
if (wind->fractional_scale) {
|
|
wp_fractional_scale_v1_destroy(wind->fractional_scale);
|
|
}
|
|
|
|
SDL_free(wind->outputs);
|
|
|
|
if (wind->gles_swap_frame_callback) {
|
|
WAYLAND_wl_event_queue_destroy(wind->gles_swap_frame_event_queue);
|
|
WAYLAND_wl_proxy_wrapper_destroy(wind->gles_swap_frame_surface_wrapper);
|
|
wl_callback_destroy(wind->gles_swap_frame_callback);
|
|
}
|
|
|
|
if (wind->surface_damage_frame_callback) {
|
|
wl_callback_destroy(wind->surface_damage_frame_callback);
|
|
}
|
|
|
|
#ifdef SDL_VIDEO_DRIVER_WAYLAND_QT_TOUCH
|
|
if (wind->extended_surface) {
|
|
QtExtendedSurface_Unsubscribe(wind->extended_surface, SDL_HINT_QTWAYLAND_CONTENT_ORIENTATION);
|
|
QtExtendedSurface_Unsubscribe(wind->extended_surface, SDL_HINT_QTWAYLAND_WINDOW_FLAGS);
|
|
qt_extended_surface_destroy(wind->extended_surface);
|
|
}
|
|
#endif /* SDL_VIDEO_DRIVER_WAYLAND_QT_TOUCH */
|
|
wl_surface_destroy(wind->surface);
|
|
|
|
SDL_free(wind);
|
|
WAYLAND_wl_display_flush(data->display);
|
|
}
|
|
window->driverdata = NULL;
|
|
}
|
|
|
|
static void EGLTransparencyChangedCallback(void *userdata, const char *name, const char *oldValue, const char *newValue)
|
|
{
|
|
const SDL_bool oldval = SDL_GetStringBoolean(oldValue, SDL_FALSE);
|
|
const SDL_bool newval = SDL_GetStringBoolean(newValue, SDL_FALSE);
|
|
|
|
if (oldval != newval) {
|
|
SDL_Window *window;
|
|
SDL_VideoData *viddata = (SDL_VideoData *)userdata;
|
|
SDL_VideoDevice *dev = SDL_GetVideoDevice();
|
|
|
|
viddata->egl_transparency_enabled = newval;
|
|
|
|
/* Iterate over all windows and update the surface opaque regions */
|
|
for (window = dev->windows; window != NULL; window = window->next) {
|
|
SDL_WindowData *wind = (SDL_WindowData *)window->driverdata;
|
|
|
|
if (!newval) {
|
|
struct wl_region *region = wl_compositor_create_region(wind->waylandData->compositor);
|
|
wl_region_add(region, 0, 0, wind->window_width, wind->window_height);
|
|
wl_surface_set_opaque_region(wind->surface, region);
|
|
wl_region_destroy(region);
|
|
} else {
|
|
wl_surface_set_opaque_region(wind->surface, NULL);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void Wayland_InitWin(SDL_VideoData *data)
|
|
{
|
|
data->egl_transparency_enabled = SDL_GetHintBoolean(SDL_HINT_VIDEO_EGL_ALLOW_TRANSPARENCY, SDL_FALSE);
|
|
SDL_AddHintCallback(SDL_HINT_VIDEO_EGL_ALLOW_TRANSPARENCY, EGLTransparencyChangedCallback, data);
|
|
}
|
|
|
|
void Wayland_QuitWin(SDL_VideoData *data)
|
|
{
|
|
SDL_DelHintCallback(SDL_HINT_VIDEO_EGL_ALLOW_TRANSPARENCY, EGLTransparencyChangedCallback, data);
|
|
}
|
|
|
|
#endif /* SDL_VIDEO_DRIVER_WAYLAND */
|
|
|
|
/* vi: set ts=4 sw=4 expandtab: */
|