mirror of
https://github.com/libsdl-org/SDL.git
synced 2026-08-28 09:31:36 +00:00
Add support for modal windows to more platforms
- Adds support for modal windows to Win32, Mac, and Haiku, and enhances functionality on Wayland and X11, which previous set only the parent window, but not the modal state. - Windows can be declared modal at creation time, and the modal state can be toggled at any time via SDL_SetWindowModalFor() (tested with UE5 through sdl2-compat). - Allows dynamic unparenting/reparenting of windows. - Includes a modal window test.
This commit is contained in:
@@ -200,6 +200,33 @@ static void SDL_SyncIfRequired(SDL_Window *window)
|
||||
}
|
||||
}
|
||||
|
||||
static void SDL_SetWindowParent(SDL_Window *window, SDL_Window *parent)
|
||||
{
|
||||
/* Unlink the window from the existing parent. */
|
||||
if (window->parent) {
|
||||
if (window->next_sibling) {
|
||||
window->next_sibling->prev_sibling = window->prev_sibling;
|
||||
}
|
||||
if (window->prev_sibling) {
|
||||
window->prev_sibling->next_sibling = window->next_sibling;
|
||||
} else {
|
||||
window->parent->first_child = window->next_sibling;
|
||||
}
|
||||
|
||||
window->parent = NULL;
|
||||
}
|
||||
|
||||
if (parent) {
|
||||
window->parent = parent;
|
||||
|
||||
window->next_sibling = parent->first_child;
|
||||
if (parent->first_child) {
|
||||
parent->first_child->prev_sibling = window;
|
||||
}
|
||||
parent->first_child = window;
|
||||
}
|
||||
}
|
||||
|
||||
/* Support for framebuffer emulation using an accelerated renderer */
|
||||
|
||||
#define SDL_PROP_WINDOW_TEXTUREDATA_POINTER "SDL.internal.window.texturedata"
|
||||
@@ -2002,6 +2029,7 @@ static struct {
|
||||
{ SDL_PROP_WINDOW_CREATE_MENU_BOOLEAN, SDL_WINDOW_POPUP_MENU, SDL_FALSE },
|
||||
{ SDL_PROP_WINDOW_CREATE_METAL_BOOLEAN, SDL_WINDOW_METAL, SDL_FALSE },
|
||||
{ SDL_PROP_WINDOW_CREATE_MINIMIZED_BOOLEAN, SDL_WINDOW_MINIMIZED, SDL_FALSE },
|
||||
{ SDL_PROP_WINDOW_CREATE_MODAL_BOOLEAN, SDL_WINDOW_MODAL, SDL_FALSE },
|
||||
{ SDL_PROP_WINDOW_CREATE_MOUSE_GRABBED_BOOLEAN, SDL_WINDOW_MOUSE_GRABBED, SDL_FALSE },
|
||||
{ SDL_PROP_WINDOW_CREATE_OPENGL_BOOLEAN, SDL_WINDOW_OPENGL, SDL_FALSE },
|
||||
{ SDL_PROP_WINDOW_CREATE_RESIZABLE_BOOLEAN, SDL_WINDOW_RESIZABLE, SDL_FALSE },
|
||||
@@ -2057,6 +2085,11 @@ SDL_Window *SDL_CreateWindowWithProperties(SDL_PropertiesID props)
|
||||
}
|
||||
}
|
||||
|
||||
if ((flags & SDL_WINDOW_MODAL) && (!parent || parent->magic != &_this->window_magic)) {
|
||||
SDL_SetError("Modal windows must specify a parent window");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if ((flags & (SDL_WINDOW_TOOLTIP | SDL_WINDOW_POPUP_MENU)) != 0) {
|
||||
if (!(_this->device_caps & VIDEO_DEVICE_CAPS_HAS_POPUP_WINDOW_SUPPORT)) {
|
||||
SDL_Unsupported();
|
||||
@@ -2074,7 +2107,7 @@ SDL_Window *SDL_CreateWindowWithProperties(SDL_PropertiesID props)
|
||||
}
|
||||
|
||||
/* Ensure no more than one of these flags is set */
|
||||
type_flags = flags & (SDL_WINDOW_UTILITY | SDL_WINDOW_TOOLTIP | SDL_WINDOW_POPUP_MENU);
|
||||
type_flags = flags & (SDL_WINDOW_UTILITY | SDL_WINDOW_TOOLTIP | SDL_WINDOW_POPUP_MENU | SDL_WINDOW_MODAL);
|
||||
if (type_flags & (type_flags - 1)) {
|
||||
SDL_SetError("Conflicting window type flags specified: 0x%.8x", (unsigned int)type_flags);
|
||||
return NULL;
|
||||
@@ -2200,14 +2233,9 @@ SDL_Window *SDL_CreateWindowWithProperties(SDL_PropertiesID props)
|
||||
}
|
||||
_this->windows = window;
|
||||
|
||||
if (parent) {
|
||||
window->parent = parent;
|
||||
|
||||
window->next_sibling = parent->first_child;
|
||||
if (parent->first_child) {
|
||||
parent->first_child->prev_sibling = window;
|
||||
}
|
||||
parent->first_child = window;
|
||||
/* Set the parent before creation if this is non-modal, otherwise it will be set later. */
|
||||
if (!(flags & SDL_WINDOW_MODAL)) {
|
||||
SDL_SetWindowParent(window, parent);
|
||||
}
|
||||
|
||||
if (_this->CreateSDLWindow && _this->CreateSDLWindow(_this, window, props) < 0) {
|
||||
@@ -2236,6 +2264,9 @@ SDL_Window *SDL_CreateWindowWithProperties(SDL_PropertiesID props)
|
||||
flags = window->flags;
|
||||
#endif
|
||||
|
||||
if (flags & SDL_WINDOW_MODAL) {
|
||||
SDL_SetWindowModalFor(window, parent);
|
||||
}
|
||||
if (title) {
|
||||
SDL_SetWindowTitle(window, title);
|
||||
}
|
||||
@@ -2293,6 +2324,7 @@ int SDL_RecreateWindow(SDL_Window *window, SDL_WindowFlags flags)
|
||||
SDL_bool need_vulkan_unload = SDL_FALSE;
|
||||
SDL_bool need_vulkan_load = SDL_FALSE;
|
||||
SDL_WindowFlags graphics_flags;
|
||||
SDL_Window *parent = window->parent;
|
||||
|
||||
/* ensure no more than one of these flags is set */
|
||||
graphics_flags = flags & (SDL_WINDOW_OPENGL | SDL_WINDOW_METAL | SDL_WINDOW_VULKAN);
|
||||
@@ -2317,6 +2349,11 @@ int SDL_RecreateWindow(SDL_Window *window, SDL_WindowFlags flags)
|
||||
flags &= ~SDL_WINDOW_EXTERNAL;
|
||||
}
|
||||
|
||||
/* If this is a modal dialog, clear the modal status. */
|
||||
if (window->flags & SDL_WINDOW_MODAL) {
|
||||
SDL_SetWindowModalFor(window, NULL);
|
||||
}
|
||||
|
||||
/* Restore video mode, etc. */
|
||||
if (!(window->flags & SDL_WINDOW_EXTERNAL)) {
|
||||
const SDL_bool restore_on_show = window->restore_on_show;
|
||||
@@ -2410,6 +2447,10 @@ int SDL_RecreateWindow(SDL_Window *window, SDL_WindowFlags flags)
|
||||
window->flags |= SDL_WINDOW_EXTERNAL;
|
||||
}
|
||||
|
||||
if (flags & SDL_WINDOW_MODAL) {
|
||||
SDL_SetWindowModalFor(window, parent);
|
||||
}
|
||||
|
||||
if (_this->SetWindowTitle && window->title) {
|
||||
_this->SetWindowTitle(_this, window);
|
||||
}
|
||||
@@ -3259,15 +3300,35 @@ int SDL_GetWindowOpacity(SDL_Window *window, float *out_opacity)
|
||||
int SDL_SetWindowModalFor(SDL_Window *modal_window, SDL_Window *parent_window)
|
||||
{
|
||||
CHECK_WINDOW_MAGIC(modal_window, -1);
|
||||
CHECK_WINDOW_MAGIC(parent_window, -1);
|
||||
CHECK_WINDOW_NOT_POPUP(modal_window, -1);
|
||||
CHECK_WINDOW_NOT_POPUP(parent_window, -1);
|
||||
|
||||
if (parent_window) {
|
||||
CHECK_WINDOW_MAGIC(parent_window, -1);
|
||||
CHECK_WINDOW_NOT_POPUP(parent_window, -1);
|
||||
}
|
||||
|
||||
if (!_this->SetWindowModalFor) {
|
||||
return SDL_Unsupported();
|
||||
}
|
||||
|
||||
return _this->SetWindowModalFor(_this, modal_window, parent_window);
|
||||
if (parent_window) {
|
||||
modal_window->flags |= SDL_WINDOW_MODAL;
|
||||
} else if (modal_window->flags & SDL_WINDOW_MODAL) {
|
||||
modal_window->flags &= ~SDL_WINDOW_MODAL;
|
||||
} else {
|
||||
return 0; /* Not modal; nothing to do. */
|
||||
}
|
||||
|
||||
const int ret = _this->SetWindowModalFor(_this, modal_window, parent_window);
|
||||
|
||||
/* The existing parent might be needed when changing the modal status,
|
||||
* so don't change the heirarchy until after setting the new modal state.
|
||||
*/
|
||||
if (!ret) {
|
||||
SDL_SetWindowParent(modal_window, !ret ? parent_window : NULL);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int SDL_SetWindowInputFocus(SDL_Window *window)
|
||||
@@ -3686,16 +3747,12 @@ void SDL_DestroyWindow(SDL_Window *window)
|
||||
|
||||
SDL_DestroyProperties(window->props);
|
||||
|
||||
/* If this is a child window, unlink it from its siblings */
|
||||
if (window->parent) {
|
||||
if (window->next_sibling) {
|
||||
window->next_sibling->prev_sibling = window->prev_sibling;
|
||||
}
|
||||
if (window->prev_sibling) {
|
||||
window->prev_sibling->next_sibling = window->next_sibling;
|
||||
} else {
|
||||
window->parent->first_child = window->next_sibling;
|
||||
}
|
||||
/* Clear the modal status, but don't unset the parent, as it may be
|
||||
* needed later in the destruction process if a backend needs to
|
||||
* update the input focus.
|
||||
*/
|
||||
if (_this->SetWindowModalFor && (window->flags & SDL_WINDOW_MODAL)) {
|
||||
_this->SetWindowModalFor(_this, window, NULL);
|
||||
}
|
||||
|
||||
/* Restore video mode, etc. */
|
||||
@@ -3765,6 +3822,9 @@ void SDL_DestroyWindow(SDL_Window *window)
|
||||
SDL_free(window->title);
|
||||
SDL_DestroySurface(window->icon);
|
||||
|
||||
/* Unlink the window from its siblings. */
|
||||
SDL_SetWindowParent(window, NULL);
|
||||
|
||||
/* Unlink the window from the list */
|
||||
if (window->next) {
|
||||
window->next->prev = window->prev;
|
||||
|
||||
@@ -563,6 +563,14 @@ Uint64 Cocoa_GetEventTimestamp(NSTimeInterval nsTimestamp)
|
||||
|
||||
int Cocoa_PumpEventsUntilDate(SDL_VideoDevice *_this, NSDate *expiration, bool accumulate)
|
||||
{
|
||||
/* Run any existing modal sessions. */
|
||||
for (SDL_Window *w = _this->windows; w; w = w->next) {
|
||||
SDL_CocoaWindowData *data = (__bridge SDL_CocoaWindowData *)w->driverdata;
|
||||
if (data.modal_session) {
|
||||
[NSApp runModalSession:data.modal_session];
|
||||
}
|
||||
}
|
||||
|
||||
for (;;) {
|
||||
NSEvent *event = [NSApp nextEventMatchingMask:NSEventMaskAny untilDate:expiration inMode:NSDefaultRunLoopMode dequeue:YES];
|
||||
if (event == nil) {
|
||||
|
||||
@@ -122,6 +122,7 @@ static SDL_VideoDevice *Cocoa_CreateDevice(void)
|
||||
device->UpdateWindowShape = Cocoa_UpdateWindowShape;
|
||||
device->FlashWindow = Cocoa_FlashWindow;
|
||||
device->SetWindowFocusable = Cocoa_SetWindowFocusable;
|
||||
device->SetWindowModalFor = Cocoa_SetWindowModalFor;
|
||||
device->SyncWindow = Cocoa_SyncWindow;
|
||||
|
||||
#ifdef SDL_VIDEO_OPENGL_CGL
|
||||
|
||||
@@ -138,6 +138,7 @@ typedef enum
|
||||
@property(nonatomic) NSInteger flash_request;
|
||||
@property(nonatomic) SDL_Window *keyboard_focus;
|
||||
@property(nonatomic) Cocoa_WindowListener *listener;
|
||||
@property(nonatomic) NSModalSession modal_session;
|
||||
@property(nonatomic) SDL_CocoaVideoData *videodata;
|
||||
@property(nonatomic) SDL_bool send_floating_size;
|
||||
@property(nonatomic) SDL_bool send_floating_position;
|
||||
@@ -178,6 +179,7 @@ extern int Cocoa_SetWindowHitTest(SDL_Window *window, SDL_bool enabled);
|
||||
extern void Cocoa_AcceptDragAndDrop(SDL_Window *window, SDL_bool accept);
|
||||
extern int Cocoa_FlashWindow(SDL_VideoDevice *_this, SDL_Window *window, SDL_FlashOperation operation);
|
||||
extern int Cocoa_SetWindowFocusable(SDL_VideoDevice *_this, SDL_Window *window, SDL_bool focusable);
|
||||
extern int Cocoa_SetWindowModalFor(SDL_VideoDevice *_this, SDL_Window *modal_window, SDL_Window *parent_window);
|
||||
extern int Cocoa_SyncWindow(SDL_VideoDevice *_this, SDL_Window *window);
|
||||
|
||||
#endif /* SDL_cocoawindow_h_ */
|
||||
|
||||
@@ -2369,6 +2369,10 @@ void Cocoa_ShowWindow(SDL_VideoDevice *_this, SDL_Window *window)
|
||||
NSWindow *nsparent = ((__bridge SDL_CocoaWindowData *)window->parent->driverdata).nswindow;
|
||||
[nsparent addChildWindow:nswindow ordered:NSWindowAbove];
|
||||
} else {
|
||||
if ((window->flags & SDL_WINDOW_MODAL) && window->parent) {
|
||||
Cocoa_SetWindowModalFor(_this, window, window->parent);
|
||||
}
|
||||
|
||||
if (bActivate) {
|
||||
[nswindow makeKeyAndOrderFront:nil];
|
||||
} else {
|
||||
@@ -2402,6 +2406,11 @@ void Cocoa_HideWindow(SDL_VideoDevice *_this, SDL_Window *window)
|
||||
[nswindow close];
|
||||
}
|
||||
|
||||
/* If this window is the source of a modal session, end it when
|
||||
* hidden, or other windows will be prevented from closing.
|
||||
*/
|
||||
Cocoa_SetWindowModalFor(_this, window, NULL);
|
||||
|
||||
/* Transfer keyboard focus back to the parent */
|
||||
if (window->flags & SDL_WINDOW_POPUP_MENU) {
|
||||
if (window == SDL_GetKeyboardFocus()) {
|
||||
@@ -2928,6 +2937,24 @@ void Cocoa_AcceptDragAndDrop(SDL_Window *window, SDL_bool accept)
|
||||
}
|
||||
}
|
||||
|
||||
int Cocoa_SetWindowModalFor(SDL_VideoDevice *_this, SDL_Window *modal_window, SDL_Window *parent_window)
|
||||
{
|
||||
@autoreleasepool {
|
||||
SDL_CocoaWindowData *modal_data = (__bridge SDL_CocoaWindowData *)modal_window->driverdata;
|
||||
|
||||
if (modal_data.modal_session) {
|
||||
[NSApp endModalSession:modal_data.modal_session];
|
||||
modal_data.modal_session = nil;
|
||||
}
|
||||
|
||||
if (parent_window) {
|
||||
modal_data.modal_session = [NSApp beginModalSessionForWindow:modal_data.nswindow];
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int Cocoa_FlashWindow(SDL_VideoDevice *_this, SDL_Window *window, SDL_FlashOperation operation)
|
||||
{
|
||||
@autoreleasepool {
|
||||
|
||||
@@ -39,7 +39,7 @@ static SDL_INLINE SDL_BLooper *_GetBeLooper() {
|
||||
return SDL_Looper;
|
||||
}
|
||||
|
||||
static int _InitWindow(SDL_VideoDevice *_this, SDL_Window *window) {
|
||||
static int _InitWindow(SDL_VideoDevice *_this, SDL_Window *window, SDL_PropertiesID create_props) {
|
||||
uint32 flags = 0;
|
||||
window_look look = B_TITLED_WINDOW_LOOK;
|
||||
|
||||
@@ -77,7 +77,7 @@ static int _InitWindow(SDL_VideoDevice *_this, SDL_Window *window) {
|
||||
}
|
||||
|
||||
int HAIKU_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window, SDL_PropertiesID create_props) {
|
||||
if (_InitWindow(_this, window) < 0) {
|
||||
if (_InitWindow(_this, window, create_props) < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -171,6 +171,25 @@ int HAIKU_SetWindowMouseGrab(SDL_VideoDevice *_this, SDL_Window * window, SDL_bo
|
||||
return SDL_Unsupported();
|
||||
}
|
||||
|
||||
int HAIKU_SetWindowModalFor(SDL_VideoDevice *_this, SDL_Window *modal_window, SDL_Window *parent_window) {
|
||||
if (modal_window->parent && modal_window->parent != parent_window) {
|
||||
/* Remove from the subset of a previous parent. */
|
||||
_ToBeWin(modal_window)->RemoveFromSubset(_ToBeWin(modal_window->parent));
|
||||
}
|
||||
|
||||
if (parent_window) {
|
||||
_ToBeWin(modal_window)->SetLook(B_MODAL_WINDOW_LOOK);
|
||||
_ToBeWin(modal_window)->SetFeel(B_MODAL_SUBSET_WINDOW_FEEL);
|
||||
_ToBeWin(modal_window)->AddToSubset(_ToBeWin(parent_window));
|
||||
} else {
|
||||
window_look look = (modal_window->flags & SDL_WINDOW_BORDERLESS) ? B_NO_BORDER_WINDOW_LOOK : B_TITLED_WINDOW_LOOK;
|
||||
_ToBeWin(modal_window)->SetLook(look);
|
||||
_ToBeWin(modal_window)->SetFeel(B_NORMAL_WINDOW_FEEL);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void HAIKU_DestroyWindow(SDL_VideoDevice *_this, SDL_Window * window) {
|
||||
_ToBeWin(window)->LockLooper(); /* This MUST be locked */
|
||||
_GetBeLooper()->ClearID(_ToBeWin(window));
|
||||
|
||||
@@ -57,6 +57,7 @@
|
||||
#include "viewporter-client-protocol.h"
|
||||
#include "xdg-activation-v1-client-protocol.h"
|
||||
#include "xdg-decoration-unstable-v1-client-protocol.h"
|
||||
#include "xdg-dialog-v1-client-protocol.h"
|
||||
#include "xdg-foreign-unstable-v2-client-protocol.h"
|
||||
#include "xdg-output-unstable-v1-client-protocol.h"
|
||||
#include "xdg-shell-client-protocol.h"
|
||||
@@ -1088,6 +1089,8 @@ static void display_handle_global(void *data, struct wl_registry *registry, uint
|
||||
}
|
||||
} else if (SDL_strcmp(interface, "zxdg_exporter_v2") == 0) {
|
||||
d->zxdg_exporter_v2 = wl_registry_bind(d->registry, id, &zxdg_exporter_v2_interface, 1);
|
||||
} else if (SDL_strcmp(interface, "xdg_wm_dialog_v1") == 0) {
|
||||
d->xdg_wm_dialog_v1 = wl_registry_bind(d->registry, id, &xdg_wm_dialog_v1_interface, 1);
|
||||
} else if (SDL_strcmp(interface, "kde_output_order_v1") == 0) {
|
||||
d->kde_output_order = wl_registry_bind(d->registry, id, &kde_output_order_v1_interface, 1);
|
||||
kde_output_order_v1_add_listener(d->kde_output_order, &kde_output_order_listener, d);
|
||||
@@ -1346,6 +1349,11 @@ static void Wayland_VideoCleanup(SDL_VideoDevice *_this)
|
||||
data->zxdg_exporter_v2 = NULL;
|
||||
}
|
||||
|
||||
if (data->xdg_wm_dialog_v1) {
|
||||
xdg_wm_dialog_v1_destroy(data->xdg_wm_dialog_v1);
|
||||
data->xdg_wm_dialog_v1 = NULL;
|
||||
}
|
||||
|
||||
if (data->kde_output_order) {
|
||||
Wayland_FlushOutputOrder(data);
|
||||
kde_output_order_v1_destroy(data->kde_output_order);
|
||||
|
||||
@@ -80,6 +80,7 @@ struct SDL_VideoData
|
||||
struct wp_fractional_scale_manager_v1 *fractional_scale_manager;
|
||||
struct zwp_input_timestamps_manager_v1 *input_timestamps_manager;
|
||||
struct zxdg_exporter_v2 *zxdg_exporter_v2;
|
||||
struct xdg_wm_dialog_v1 *xdg_wm_dialog_v1;
|
||||
struct kde_output_order_v1 *kde_output_order;
|
||||
|
||||
struct xkb_context *xkb_context;
|
||||
|
||||
@@ -39,6 +39,7 @@
|
||||
#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"
|
||||
|
||||
#ifdef HAVE_LIBDECOR_H
|
||||
#include <libdecor.h>
|
||||
@@ -654,6 +655,8 @@ static void surface_frame_done(void *data, struct wl_callback *cb, uint32_t time
|
||||
for (SDL_Window *w = wind->sdlwindow->first_child; w; w = w->next_sibling) {
|
||||
if (w->driverdata->surface_status == WAYLAND_SURFACE_STATUS_SHOW_PENDING) {
|
||||
Wayland_ShowWindow(SDL_GetVideoDevice(), w);
|
||||
} else if ((w->flags & SDL_WINDOW_MODAL) && w->driverdata->modal_reparenting_required) {
|
||||
Wayland_SetWindowModalFor(SDL_GetVideoDevice(), w, w->parent);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1434,35 +1437,56 @@ int Wayland_SetWindowModalFor(SDL_VideoDevice *_this, SDL_Window *modal_window,
|
||||
{
|
||||
SDL_VideoData *viddata = _this->driverdata;
|
||||
SDL_WindowData *modal_data = modal_window->driverdata;
|
||||
SDL_WindowData *parent_data = parent_window->driverdata;
|
||||
SDL_WindowData *parent_data = parent_window ? parent_window->driverdata : NULL;
|
||||
struct xdg_toplevel *modal_toplevel = NULL;
|
||||
struct xdg_toplevel *parent_toplevel = NULL;
|
||||
|
||||
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");
|
||||
modal_data->modal_reparenting_required = SDL_FALSE;
|
||||
|
||||
if (parent_data && parent_data->surface_status != WAYLAND_SURFACE_STATUS_SHOWN) {
|
||||
/* Need to wait for the parent to become mapped, or it's the same as setting a null parent. */
|
||||
modal_data->modal_reparenting_required = SDL_TRUE;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* 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 (viddata->shell.libdecor) {
|
||||
if (!modal_data->shell_surface.libdecor.frame) {
|
||||
return SDL_SetError("Modal window was hidden");
|
||||
}
|
||||
if (!parent_data->shell_surface.libdecor.frame) {
|
||||
return SDL_SetError("Parent window was hidden");
|
||||
}
|
||||
libdecor_frame_set_parent(modal_data->shell_surface.libdecor.frame,
|
||||
parent_data->shell_surface.libdecor.frame);
|
||||
if (modal_data->shell_surface_type == WAYLAND_SURFACE_LIBDECOR && modal_data->shell_surface.libdecor.frame) {
|
||||
modal_toplevel = libdecor_frame_get_xdg_toplevel(modal_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 (modal_data->shell_surface_type == WAYLAND_SURFACE_XDG_TOPLEVEL && modal_data->shell_surface.xdg.roleobj.toplevel) {
|
||||
modal_toplevel = modal_data->shell_surface.xdg.roleobj.toplevel;
|
||||
}
|
||||
|
||||
if (parent_data) {
|
||||
#ifdef HAVE_LIBDECOR_H
|
||||
if (parent_data->shell_surface_type == WAYLAND_SURFACE_LIBDECOR && parent_data->shell_surface.libdecor.frame) {
|
||||
parent_toplevel = libdecor_frame_get_xdg_toplevel(parent_data->shell_surface.libdecor.frame);
|
||||
} else
|
||||
#endif
|
||||
if (parent_data->shell_surface_type == WAYLAND_SURFACE_XDG_TOPLEVEL && parent_data->shell_surface.xdg.roleobj.toplevel) {
|
||||
parent_toplevel = parent_data->shell_surface.xdg.roleobj.toplevel;
|
||||
}
|
||||
if (parent_data->shell_surface.xdg.roleobj.toplevel == NULL) {
|
||||
return SDL_SetError("Parent window was hidden");
|
||||
}
|
||||
|
||||
if (modal_toplevel) {
|
||||
xdg_toplevel_set_parent(modal_toplevel, parent_toplevel);
|
||||
|
||||
if (viddata->xdg_wm_dialog_v1) {
|
||||
if (parent_toplevel) {
|
||||
if (!modal_data->xdg_dialog_v1) {
|
||||
modal_data->xdg_dialog_v1 = xdg_wm_dialog_v1_get_xdg_dialog(viddata->xdg_wm_dialog_v1, modal_toplevel);
|
||||
}
|
||||
|
||||
xdg_dialog_v1_set_modal(modal_data->xdg_dialog_v1);
|
||||
} else if (modal_data->xdg_dialog_v1) {
|
||||
xdg_dialog_v1_unset_modal(modal_data->xdg_dialog_v1);
|
||||
}
|
||||
}
|
||||
xdg_toplevel_set_parent(modal_data->shell_surface.xdg.roleobj.toplevel,
|
||||
parent_data->shell_surface.xdg.roleobj.toplevel);
|
||||
} else {
|
||||
return SDL_Unsupported();
|
||||
}
|
||||
|
||||
return 0;
|
||||
@@ -1653,6 +1677,10 @@ void Wayland_ShowWindow(SDL_VideoDevice *_this, SDL_Window *window)
|
||||
}
|
||||
|
||||
/* Restore state that was set prior to this call */
|
||||
if (window->flags & SDL_WINDOW_MODAL) {
|
||||
Wayland_SetWindowModalFor(_this, window, window->parent);
|
||||
}
|
||||
|
||||
Wayland_SetWindowTitle(_this, window);
|
||||
|
||||
/* We have to wait until the surface gets a "configure" event, or use of
|
||||
@@ -2590,6 +2618,10 @@ void Wayland_DestroyWindow(SDL_VideoDevice *_this, SDL_Window *window)
|
||||
wp_fractional_scale_v1_destroy(wind->fractional_scale);
|
||||
}
|
||||
|
||||
if (wind->xdg_dialog_v1) {
|
||||
xdg_dialog_v1_destroy(wind->xdg_dialog_v1);
|
||||
}
|
||||
|
||||
SDL_free(wind->outputs);
|
||||
SDL_free(wind->app_id);
|
||||
|
||||
|
||||
@@ -96,6 +96,7 @@ struct SDL_WindowData
|
||||
struct wp_viewport *viewport;
|
||||
struct wp_fractional_scale_v1 *fractional_scale;
|
||||
struct zxdg_exported_v2 *exported;
|
||||
struct xdg_dialog_v1 *xdg_dialog_v1;
|
||||
|
||||
SDL_AtomicInt swap_interval_ready;
|
||||
|
||||
@@ -172,6 +173,7 @@ struct SDL_WindowData
|
||||
SDL_bool fullscreen_was_positioned;
|
||||
SDL_bool show_hide_sync_required;
|
||||
SDL_bool scale_to_display;
|
||||
SDL_bool modal_reparenting_required;
|
||||
|
||||
SDL_HitTestResult hit_test_result;
|
||||
|
||||
|
||||
@@ -202,6 +202,7 @@ static SDL_VideoDevice *WIN_CreateDevice(void)
|
||||
device->SetWindowResizable = WIN_SetWindowResizable;
|
||||
device->SetWindowAlwaysOnTop = WIN_SetWindowAlwaysOnTop;
|
||||
device->SetWindowFullscreen = WIN_SetWindowFullscreen;
|
||||
device->SetWindowModalFor = WIN_SetWindowModalFor;
|
||||
#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
|
||||
device->GetWindowICCProfile = WIN_GetWindowICCProfile;
|
||||
device->SetWindowMouseRect = WIN_SetWindowMouseRect;
|
||||
|
||||
@@ -984,6 +984,10 @@ void WIN_ShowWindow(SDL_VideoDevice *_this, SDL_Window *window)
|
||||
WIN_SetWindowPosition(_this, window);
|
||||
}
|
||||
|
||||
if (window->flags & SDL_WINDOW_MODAL) {
|
||||
EnableWindow(window->parent->driverdata->hwnd, FALSE);
|
||||
}
|
||||
|
||||
hwnd = window->driverdata->hwnd;
|
||||
style = GetWindowLong(hwnd, GWL_EXSTYLE);
|
||||
if (style & WS_EX_NOACTIVATE) {
|
||||
@@ -1006,6 +1010,11 @@ void WIN_ShowWindow(SDL_VideoDevice *_this, SDL_Window *window)
|
||||
void WIN_HideWindow(SDL_VideoDevice *_this, SDL_Window *window)
|
||||
{
|
||||
HWND hwnd = window->driverdata->hwnd;
|
||||
|
||||
if (window->flags & SDL_WINDOW_MODAL) {
|
||||
EnableWindow(window->parent->driverdata->hwnd, TRUE);
|
||||
}
|
||||
|
||||
ShowWindow(hwnd, SW_HIDE);
|
||||
|
||||
/* Transfer keyboard focus back to the parent */
|
||||
@@ -1720,4 +1729,39 @@ void WIN_UpdateDarkModeForHWND(HWND hwnd)
|
||||
}
|
||||
}
|
||||
|
||||
int WIN_SetWindowModalFor(SDL_VideoDevice *_this, SDL_Window *modal_window, SDL_Window *parent_window)
|
||||
{
|
||||
SDL_WindowData *modal_data = modal_window->driverdata;
|
||||
const LONG_PTR parent_hwnd = (LONG_PTR)(parent_window ? parent_window->driverdata->hwnd : NULL);
|
||||
const LONG_PTR old_ptr = GetWindowLongPtr(modal_data->hwnd, GWLP_HWNDPARENT);
|
||||
const DWORD style = GetWindowLong(modal_data->hwnd, GWL_STYLE);
|
||||
|
||||
if (old_ptr == parent_hwnd) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Reenable the old parent window. */
|
||||
if (old_ptr) {
|
||||
EnableWindow((HWND)old_ptr, TRUE);
|
||||
}
|
||||
|
||||
if (!(style & WS_CHILD)) {
|
||||
/* Despite the name, this changes the *owner* of a toplevel window, not
|
||||
* the parent of a child window.
|
||||
*
|
||||
* https://devblogs.microsoft.com/oldnewthing/20100315-00/?p=14613
|
||||
*/
|
||||
SetWindowLongPtr(modal_data->hwnd, GWLP_HWNDPARENT, parent_hwnd);
|
||||
} else {
|
||||
SetParent(modal_data->hwnd, (HWND)parent_hwnd);
|
||||
}
|
||||
|
||||
/* Disable the new parent window if the modal window is visible. */
|
||||
if (!(modal_window->flags & SDL_WINDOW_HIDDEN) && parent_hwnd) {
|
||||
EnableWindow((HWND)parent_hwnd, FALSE);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif /* SDL_VIDEO_DRIVER_WINDOWS */
|
||||
|
||||
@@ -118,6 +118,7 @@ extern void WIN_ShowWindowSystemMenu(SDL_Window *window, int x, int y);
|
||||
extern int WIN_SetWindowFocusable(SDL_VideoDevice *_this, SDL_Window *window, SDL_bool focusable);
|
||||
extern int WIN_AdjustWindowRect(SDL_Window *window, int *x, int *y, int *width, int *height, SDL_WindowRect rect_type);
|
||||
extern int WIN_AdjustWindowRectForHWND(HWND hwnd, LPRECT lpRect, UINT frame_dpi);
|
||||
extern int WIN_SetWindowModalFor(SDL_VideoDevice *_this, SDL_Window *modal_window, SDL_Window *parent_window);
|
||||
|
||||
/* Ends C function definitions when using C++ */
|
||||
#ifdef __cplusplus
|
||||
|
||||
@@ -389,6 +389,7 @@ int X11_VideoInit(SDL_VideoDevice *_this)
|
||||
GET_ATOM(WM_DELETE_WINDOW);
|
||||
GET_ATOM(WM_TAKE_FOCUS);
|
||||
GET_ATOM(WM_NAME);
|
||||
GET_ATOM(WM_TRANSIENT_FOR);
|
||||
GET_ATOM(_NET_WM_STATE);
|
||||
GET_ATOM(_NET_WM_STATE_HIDDEN);
|
||||
GET_ATOM(_NET_WM_STATE_FOCUSED);
|
||||
@@ -398,6 +399,7 @@ int X11_VideoInit(SDL_VideoDevice *_this)
|
||||
GET_ATOM(_NET_WM_STATE_ABOVE);
|
||||
GET_ATOM(_NET_WM_STATE_SKIP_TASKBAR);
|
||||
GET_ATOM(_NET_WM_STATE_SKIP_PAGER);
|
||||
GET_ATOM(_NET_WM_STATE_MODAL);
|
||||
GET_ATOM(_NET_WM_ALLOWED_ACTIONS);
|
||||
GET_ATOM(_NET_WM_ACTION_FULLSCREEN);
|
||||
GET_ATOM(_NET_WM_NAME);
|
||||
|
||||
@@ -67,6 +67,7 @@ struct SDL_VideoData
|
||||
Atom WM_DELETE_WINDOW;
|
||||
Atom WM_TAKE_FOCUS;
|
||||
Atom WM_NAME;
|
||||
Atom WM_TRANSIENT_FOR;
|
||||
Atom _NET_WM_STATE;
|
||||
Atom _NET_WM_STATE_HIDDEN;
|
||||
Atom _NET_WM_STATE_FOCUSED;
|
||||
@@ -76,6 +77,7 @@ struct SDL_VideoData
|
||||
Atom _NET_WM_STATE_ABOVE;
|
||||
Atom _NET_WM_STATE_SKIP_TASKBAR;
|
||||
Atom _NET_WM_STATE_SKIP_PAGER;
|
||||
Atom _NET_WM_STATE_MODAL;
|
||||
Atom _NET_WM_ALLOWED_ACTIONS;
|
||||
Atom _NET_WM_ACTION_FULLSCREEN;
|
||||
Atom _NET_WM_NAME;
|
||||
|
||||
@@ -138,6 +138,7 @@ void X11_SetNetWMState(SDL_VideoDevice *_this, Window xwindow, SDL_WindowFlags f
|
||||
Atom _NET_WM_STATE_ABOVE = videodata->_NET_WM_STATE_ABOVE;
|
||||
Atom _NET_WM_STATE_SKIP_TASKBAR = videodata->_NET_WM_STATE_SKIP_TASKBAR;
|
||||
Atom _NET_WM_STATE_SKIP_PAGER = videodata->_NET_WM_STATE_SKIP_PAGER;
|
||||
Atom _NET_WM_STATE_MODAL = videodata->_NET_WM_STATE_MODAL;
|
||||
Atom atoms[16];
|
||||
int count = 0;
|
||||
|
||||
@@ -167,6 +168,9 @@ void X11_SetNetWMState(SDL_VideoDevice *_this, Window xwindow, SDL_WindowFlags f
|
||||
if (flags & SDL_WINDOW_FULLSCREEN) {
|
||||
atoms[count++] = _NET_WM_STATE_FULLSCREEN;
|
||||
}
|
||||
if (flags & SDL_WINDOW_MODAL) {
|
||||
atoms[count++] = _NET_WM_STATE_MODAL;
|
||||
}
|
||||
|
||||
SDL_assert(count <= SDL_arraysize(atoms));
|
||||
|
||||
@@ -1204,10 +1208,43 @@ int X11_SetWindowOpacity(SDL_VideoDevice *_this, SDL_Window *window, float opaci
|
||||
int X11_SetWindowModalFor(SDL_VideoDevice *_this, SDL_Window *modal_window, SDL_Window *parent_window)
|
||||
{
|
||||
SDL_WindowData *data = modal_window->driverdata;
|
||||
SDL_WindowData *parent_data = parent_window->driverdata;
|
||||
Display *display = data->videodata->display;
|
||||
SDL_WindowData *parent_data = parent_window ? parent_window->driverdata : NULL;
|
||||
SDL_VideoData *video_data = _this->driverdata;
|
||||
SDL_DisplayData *displaydata = SDL_GetDisplayDriverDataForWindow(modal_window);
|
||||
Display *display = video_data->display;
|
||||
Uint32 flags = modal_window->flags;
|
||||
Atom _NET_WM_STATE = data->videodata->_NET_WM_STATE;
|
||||
Atom _NET_WM_STATE_MODAL = data->videodata->_NET_WM_STATE_MODAL;
|
||||
|
||||
if (parent_data) {
|
||||
flags |= SDL_WINDOW_MODAL;
|
||||
X11_XSetTransientForHint(display, data->xwindow, parent_data->xwindow);
|
||||
} else {
|
||||
flags &= ~SDL_WINDOW_MODAL;
|
||||
X11_XDeleteProperty(display, data->xwindow, video_data->WM_TRANSIENT_FOR);
|
||||
}
|
||||
|
||||
if (X11_IsWindowMapped(_this, modal_window)) {
|
||||
XEvent e;
|
||||
|
||||
SDL_zero(e);
|
||||
e.xany.type = ClientMessage;
|
||||
e.xclient.message_type = _NET_WM_STATE;
|
||||
e.xclient.format = 32;
|
||||
e.xclient.window = data->xwindow;
|
||||
e.xclient.data.l[0] =
|
||||
parent_data ? _NET_WM_STATE_ADD : _NET_WM_STATE_REMOVE;
|
||||
e.xclient.data.l[1] = _NET_WM_STATE_MODAL;
|
||||
e.xclient.data.l[3] = 0l;
|
||||
|
||||
X11_XSendEvent(display, RootWindow(display, displaydata->screen), 0,
|
||||
SubstructureNotifyMask | SubstructureRedirectMask, &e);
|
||||
} else {
|
||||
X11_SetNetWMState(_this, data->xwindow, flags);
|
||||
}
|
||||
|
||||
X11_XFlush(display);
|
||||
|
||||
X11_XSetTransientForHint(display, data->xwindow, parent_data->xwindow);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user