From a46e7027ce7191f2e3ffb6a633e077bb78f7ea68 Mon Sep 17 00:00:00 2001 From: Frank Praznik Date: Sat, 29 Jun 2024 17:07:53 -0400 Subject: [PATCH] video: Allow setting the parents of toplevel windows Allow setting a parent/child relationship on toplevel windows, which allows raising sets of windows together, and allows child windows to always float above their parents. Modal windows are now set by setting the parent, then toggling modal status, as the previous interface duplicated functionality now handled by SDL_SetWindowParent(). --- include/SDL3/SDL_video.h | 41 +++++++--- src/dynapi/SDL_dynapi.sym | 3 +- src/dynapi/SDL_dynapi_overrides.h | 3 +- src/dynapi/SDL_dynapi_procs.h | 3 +- src/video/SDL_sysvideo.h | 3 +- src/video/SDL_video.c | 100 +++++++++++++---------- src/video/cocoa/SDL_cocoavideo.m | 3 +- src/video/cocoa/SDL_cocoawindow.h | 3 +- src/video/cocoa/SDL_cocoawindow.m | 112 +++++++++++++++++--------- src/video/haiku/SDL_bvideo.cc | 2 + src/video/haiku/SDL_bwindow.cc | 25 +++--- src/video/haiku/SDL_bwindow.h | 2 + src/video/wayland/SDL_waylandvideo.c | 3 +- src/video/wayland/SDL_waylandwindow.c | 100 ++++++++++++++--------- src/video/wayland/SDL_waylandwindow.h | 5 +- src/video/windows/SDL_windowsvideo.c | 3 +- src/video/windows/SDL_windowswindow.c | 59 +++++++------- src/video/windows/SDL_windowswindow.h | 3 +- src/video/x11/SDL_x11video.c | 3 +- src/video/x11/SDL_x11window.c | 38 ++++++--- src/video/x11/SDL_x11window.h | 3 +- test/testmodal.c | 24 ++++-- 22 files changed, 339 insertions(+), 202 deletions(-) diff --git a/include/SDL3/SDL_video.h b/include/SDL3/SDL_video.h index 7cce828aa..fd80ef1ed 100644 --- a/include/SDL3/SDL_video.h +++ b/include/SDL3/SDL_video.h @@ -2223,24 +2223,47 @@ extern SDL_DECLSPEC SDL_bool SDLCALL SDL_SetWindowOpacity(SDL_Window *window, fl */ extern SDL_DECLSPEC float SDLCALL SDL_GetWindowOpacity(SDL_Window *window); -/** - * Set the window as a modal to a parent window. + /** + * Set the window as a child of a parent window. * - * If the window is already modal to an existing window, it will be reparented - * to the new owner. Setting the parent window to null unparents the modal - * window and removes modal status. + * If the window is already the child of an existing window, it will be reparented + * to the new owner. Setting the parent window to null unparents the window and + * removes child window status. * - * Setting a window as modal to a parent that is a descendent of the modal + * Attempting to set the parent of a window that is currently in the modal state will fail. + * Use SDL_SetWindowModalFor() to cancel the modal status before attempting to change + * the parent. + * + * Setting a parent window that is currently the sibling or descendent of the child * window results in undefined behavior. * - * \param modal_window the window that should be set modal. - * \param parent_window the parent window for the modal window. + * \param window the window that should become the child of a parent. + * \param parent the new parent window for the child window. + * \returns SDL_TRUE on success or SDL_FALSE on failure; call + * SDL_GetError() for more information. + * + * \since This function is available since SDL 3.0.0. + * + * \sa SDL_SetWindowModal + */ + extern SDL_DECLSPEC SDL_bool SDLCALL SDL_SetWindowParent(SDL_Window *window, SDL_Window *parent); + +/** + * Toggle the state of the window as modal. + * + * To enable modal status on a window, the window must currently be the child window of a parent, + * or toggling modal status on will fail. + * + * \param window the window on which to set the modal state. + * \param modal SDL_TRUE to toggle modal status on, SDL_FALSE to toggle it off. * \returns SDL_TRUE on success or SDL_FALSE on failure; call SDL_GetError() * for more information. * * \since This function is available since SDL 3.0.0. + * + * \sa SDL_SetWindowParent */ -extern SDL_DECLSPEC SDL_bool SDLCALL SDL_SetWindowModalFor(SDL_Window *modal_window, SDL_Window *parent_window); +extern SDL_DECLSPEC SDL_bool SDLCALL SDL_SetWindowModal(SDL_Window *window, SDL_bool modal); /** * Set whether the window may have input focus. diff --git a/src/dynapi/SDL_dynapi.sym b/src/dynapi/SDL_dynapi.sym index 9ce6598ee..f3b8de3c4 100644 --- a/src/dynapi/SDL_dynapi.sym +++ b/src/dynapi/SDL_dynapi.sym @@ -854,10 +854,11 @@ SDL3_0.0.0 { SDL_SetWindowKeyboardGrab; SDL_SetWindowMaximumSize; SDL_SetWindowMinimumSize; - SDL_SetWindowModalFor; + SDL_SetWindowModal; SDL_SetWindowMouseGrab; SDL_SetWindowMouseRect; SDL_SetWindowOpacity; + SDL_SetWindowParent; SDL_SetWindowPosition; SDL_SetWindowRelativeMouseMode; SDL_SetWindowResizable; diff --git a/src/dynapi/SDL_dynapi_overrides.h b/src/dynapi/SDL_dynapi_overrides.h index a4b486adb..66e618b77 100644 --- a/src/dynapi/SDL_dynapi_overrides.h +++ b/src/dynapi/SDL_dynapi_overrides.h @@ -879,10 +879,11 @@ #define SDL_SetWindowKeyboardGrab SDL_SetWindowKeyboardGrab_REAL #define SDL_SetWindowMaximumSize SDL_SetWindowMaximumSize_REAL #define SDL_SetWindowMinimumSize SDL_SetWindowMinimumSize_REAL -#define SDL_SetWindowModalFor SDL_SetWindowModalFor_REAL +#define SDL_SetWindowModal SDL_SetWindowModal_REAL #define SDL_SetWindowMouseGrab SDL_SetWindowMouseGrab_REAL #define SDL_SetWindowMouseRect SDL_SetWindowMouseRect_REAL #define SDL_SetWindowOpacity SDL_SetWindowOpacity_REAL +#define SDL_SetWindowParent SDL_SetWindowParent_REAL #define SDL_SetWindowPosition SDL_SetWindowPosition_REAL #define SDL_SetWindowRelativeMouseMode SDL_SetWindowRelativeMouseMode_REAL #define SDL_SetWindowResizable SDL_SetWindowResizable_REAL diff --git a/src/dynapi/SDL_dynapi_procs.h b/src/dynapi/SDL_dynapi_procs.h index 7292c9c8d..54f542f37 100644 --- a/src/dynapi/SDL_dynapi_procs.h +++ b/src/dynapi/SDL_dynapi_procs.h @@ -889,10 +889,11 @@ SDL_DYNAPI_PROC(SDL_bool,SDL_SetWindowIcon,(SDL_Window *a, SDL_Surface *b),(a,b) SDL_DYNAPI_PROC(SDL_bool,SDL_SetWindowKeyboardGrab,(SDL_Window *a, SDL_bool b),(a,b),return) SDL_DYNAPI_PROC(SDL_bool,SDL_SetWindowMaximumSize,(SDL_Window *a, int b, int c),(a,b,c),return) SDL_DYNAPI_PROC(SDL_bool,SDL_SetWindowMinimumSize,(SDL_Window *a, int b, int c),(a,b,c),return) -SDL_DYNAPI_PROC(SDL_bool,SDL_SetWindowModalFor,(SDL_Window *a, SDL_Window *b),(a,b),return) +SDL_DYNAPI_PROC(SDL_bool,SDL_SetWindowModal,(SDL_Window *a, SDL_bool b),(a,b),return) SDL_DYNAPI_PROC(SDL_bool,SDL_SetWindowMouseGrab,(SDL_Window *a, SDL_bool b),(a,b),return) SDL_DYNAPI_PROC(SDL_bool,SDL_SetWindowMouseRect,(SDL_Window *a, const SDL_Rect *b),(a,b),return) SDL_DYNAPI_PROC(SDL_bool,SDL_SetWindowOpacity,(SDL_Window *a, float b),(a,b),return) +SDL_DYNAPI_PROC(SDL_bool,SDL_SetWindowParent,(SDL_Window *a, SDL_Window *b),(a,b),return) SDL_DYNAPI_PROC(SDL_bool,SDL_SetWindowPosition,(SDL_Window *a, int b, int c),(a,b,c),return) SDL_DYNAPI_PROC(SDL_bool,SDL_SetWindowRelativeMouseMode,(SDL_Window *a, SDL_bool b),(a,b),return) SDL_DYNAPI_PROC(SDL_bool,SDL_SetWindowResizable,(SDL_Window *a, SDL_bool b),(a,b),return) diff --git a/src/video/SDL_sysvideo.h b/src/video/SDL_sysvideo.h index 862405e13..56d0d18ce 100644 --- a/src/video/SDL_sysvideo.h +++ b/src/video/SDL_sysvideo.h @@ -264,7 +264,8 @@ struct SDL_VideoDevice bool (*GetWindowBordersSize)(SDL_VideoDevice *_this, SDL_Window *window, int *top, int *left, int *bottom, int *right); void (*GetWindowSizeInPixels)(SDL_VideoDevice *_this, SDL_Window *window, int *w, int *h); bool (*SetWindowOpacity)(SDL_VideoDevice *_this, SDL_Window *window, float opacity); - bool (*SetWindowModalFor)(SDL_VideoDevice *_this, SDL_Window *modal_window, SDL_Window *parent_window); + bool (*SetWindowParent)(SDL_VideoDevice *_this, SDL_Window *window, SDL_Window *parent); + bool (*SetWindowModal)(SDL_VideoDevice *_this, SDL_Window *window, bool modal); void (*ShowWindow)(SDL_VideoDevice *_this, SDL_Window *window); void (*HideWindow)(SDL_VideoDevice *_this, SDL_Window *window); void (*RaiseWindow)(SDL_VideoDevice *_this, SDL_Window *window); diff --git a/src/video/SDL_video.c b/src/video/SDL_video.c index f28abf80b..b192e18e5 100644 --- a/src/video/SDL_video.c +++ b/src/video/SDL_video.c @@ -219,7 +219,7 @@ static void SDL_SyncIfRequired(SDL_Window *window) } } -static void SDL_SetWindowParent(SDL_Window *window, SDL_Window *parent) +static void SDL_UpdateWindowHierarchy(SDL_Window *window, SDL_Window *parent) { // Unlink the window from the existing parent. if (window->parent) { @@ -2169,6 +2169,10 @@ static void ApplyWindowFlags(SDL_Window *window, SDL_WindowFlags flags) SDL_MinimizeWindow(window); } + if (flags & SDL_WINDOW_MODAL) { + SDL_SetWindowModal(window, true); + } + if (flags & SDL_WINDOW_MOUSE_GRABBED) { SDL_SetWindowMouseGrab(window, true); } @@ -2432,10 +2436,8 @@ SDL_Window *SDL_CreateWindowWithProperties(SDL_PropertiesID props) } _this->windows = 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); - } + // Set the parent before creation. + SDL_UpdateWindowHierarchy(window, parent); if (_this->CreateSDLWindow && !_this->CreateSDLWindow(_this, window, props)) { SDL_DestroyWindow(window); @@ -2462,10 +2464,6 @@ 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); } @@ -2525,7 +2523,6 @@ bool SDL_RecreateWindow(SDL_Window *window, SDL_WindowFlags flags) bool need_vulkan_unload = false; bool need_vulkan_load = 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); @@ -2552,7 +2549,7 @@ bool SDL_RecreateWindow(SDL_Window *window, SDL_WindowFlags flags) // If this is a modal dialog, clear the modal status. if (window->flags & SDL_WINDOW_MODAL) { - SDL_SetWindowModalFor(window, NULL); + SDL_SetWindowModal(window, false); } // Restore video mode, etc. @@ -2642,10 +2639,6 @@ bool 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); } @@ -3579,40 +3572,59 @@ float SDL_GetWindowOpacity(SDL_Window *window) return window->opacity; } -SDL_bool SDL_SetWindowModalFor(SDL_Window *modal_window, SDL_Window *parent_window) +SDL_bool SDL_SetWindowParent(SDL_Window *window, SDL_Window *parent) { - bool result; + CHECK_WINDOW_MAGIC(window, false); + CHECK_WINDOW_NOT_POPUP(window, false); - CHECK_WINDOW_MAGIC(modal_window, false); - CHECK_WINDOW_NOT_POPUP(modal_window, false); - - if (parent_window) { - CHECK_WINDOW_MAGIC(parent_window, false); - CHECK_WINDOW_NOT_POPUP(parent_window, false); + if (parent) { + CHECK_WINDOW_MAGIC(parent, false); + CHECK_WINDOW_NOT_POPUP(parent, false); } - if (!_this->SetWindowModalFor) { + if (!_this->SetWindowParent) { return SDL_Unsupported(); } - if (parent_window) { - modal_window->flags |= SDL_WINDOW_MODAL; - } else if (modal_window->flags & SDL_WINDOW_MODAL) { - modal_window->flags &= ~SDL_WINDOW_MODAL; + if (window->flags & SDL_WINDOW_MODAL) { + return SDL_SetError("Modal windows cannot change parents; call SDL_SetWindowModal() to clear modal status first."); + } + + if (window->parent == parent) { + return true; + } + + const SDL_bool ret = _this->SetWindowParent(_this, window, parent); + SDL_UpdateWindowHierarchy(window, ret ? parent : NULL); + + return ret; +} + +SDL_bool SDL_SetWindowModal(SDL_Window *window, SDL_bool modal) +{ + CHECK_WINDOW_MAGIC(window, false); + CHECK_WINDOW_NOT_POPUP(window, false); + + if (!_this->SetWindowModal) { + return SDL_Unsupported(); + } + + if (modal) { + if (!window->parent) { + return SDL_SetError("Window must have a parent to enable the modal state; use SDL_SetWindowParent() to set the parent first."); + } + window->flags |= SDL_WINDOW_MODAL; + } else if (window->flags & SDL_WINDOW_MODAL) { + window->flags &= ~SDL_WINDOW_MODAL; } else { - return true; // Not modal; nothing to do. + return true; // Already not modal, so nothing to do. } - result = _this->SetWindowModalFor(_this, modal_window, parent_window); - - /* The existing parent might be needed when changing the modal status, - * so don't change the hierarchy until after setting the new modal state. - */ - if (result) { - SDL_SetWindowParent(modal_window, parent_window); + if (window->flags & SDL_WINDOW_HIDDEN) { + return true; } - return result; + return _this->SetWindowModal(_this, window, modal); } SDL_bool SDL_SetWindowFocusable(SDL_Window *window, SDL_bool focusable) @@ -4101,12 +4113,12 @@ void SDL_DestroyWindow(SDL_Window *window) SDL_DestroyProperties(window->text_input_props); SDL_DestroyProperties(window->props); - /* 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. + /* Clear the modal status, but don't unset the parent just yet, 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); + if (_this->SetWindowModal && (window->flags & SDL_WINDOW_MODAL)) { + _this->SetWindowModal(_this, window, false); } // Make sure the destroyed window isn't referenced by any display as a fullscreen window. @@ -4168,9 +4180,9 @@ void SDL_DestroyWindow(SDL_Window *window) SDL_DestroySurface(window->icon); // Unlink the window from its siblings. - SDL_SetWindowParent(window, NULL); + SDL_UpdateWindowHierarchy(window, NULL); - // Unlink the window from the list + // Unlink the window from the global window list if (window->next) { window->next->prev = window->prev; } diff --git a/src/video/cocoa/SDL_cocoavideo.m b/src/video/cocoa/SDL_cocoavideo.m index 85795e141..93b915317 100644 --- a/src/video/cocoa/SDL_cocoavideo.m +++ b/src/video/cocoa/SDL_cocoavideo.m @@ -122,7 +122,8 @@ static SDL_VideoDevice *Cocoa_CreateDevice(void) device->UpdateWindowShape = Cocoa_UpdateWindowShape; device->FlashWindow = Cocoa_FlashWindow; device->SetWindowFocusable = Cocoa_SetWindowFocusable; - device->SetWindowModalFor = Cocoa_SetWindowModalFor; + device->SetWindowParent = Cocoa_SetWindowParent; + device->SetWindowModal = Cocoa_SetWindowModal; device->SyncWindow = Cocoa_SyncWindow; #ifdef SDL_VIDEO_OPENGL_CGL diff --git a/src/video/cocoa/SDL_cocoawindow.h b/src/video/cocoa/SDL_cocoawindow.h index 3e0413871..dd30c200a 100644 --- a/src/video/cocoa/SDL_cocoawindow.h +++ b/src/video/cocoa/SDL_cocoawindow.h @@ -183,7 +183,8 @@ extern bool Cocoa_SetWindowHitTest(SDL_Window *window, bool enabled); extern void Cocoa_AcceptDragAndDrop(SDL_Window *window, bool accept); extern bool Cocoa_FlashWindow(SDL_VideoDevice *_this, SDL_Window *window, SDL_FlashOperation operation); extern bool Cocoa_SetWindowFocusable(SDL_VideoDevice *_this, SDL_Window *window, bool focusable); -extern bool Cocoa_SetWindowModalFor(SDL_VideoDevice *_this, SDL_Window *modal_window, SDL_Window *parent_window); +extern bool Cocoa_SetWindowModal(SDL_VideoDevice *_this, SDL_Window *window, bool modal); +extern bool Cocoa_SetWindowParent(SDL_VideoDevice *_this, SDL_Window *window, SDL_Window *parent); extern bool Cocoa_SyncWindow(SDL_VideoDevice *_this, SDL_Window *window); #endif // SDL_cocoawindow_h_ diff --git a/src/video/cocoa/SDL_cocoawindow.m b/src/video/cocoa/SDL_cocoawindow.m index ba12e02f6..21e437f5c 100644 --- a/src/video/cocoa/SDL_cocoawindow.m +++ b/src/video/cocoa/SDL_cocoawindow.m @@ -2075,23 +2075,10 @@ static bool SetupWindowData(SDL_VideoDevice *_this, SDL_Window *window, NSWindow window->flags &= ~SDL_WINDOW_MINIMIZED; } - if (!SDL_WINDOW_IS_POPUP(window)) { - if ([nswindow isKeyWindow]) { - window->flags |= SDL_WINDOW_INPUT_FOCUS; - Cocoa_SetKeyboardFocus(data.window); - } - } else { + if (window->parent) { NSWindow *nsparent = ((__bridge SDL_CocoaWindowData *)window->parent->internal).nswindow; [nsparent addChildWindow:nswindow ordered:NSWindowAbove]; - if (window->flags & SDL_WINDOW_TOOLTIP) { - [nswindow setIgnoresMouseEvents:YES]; - } else if (window->flags & SDL_WINDOW_POPUP_MENU) { - if (window->parent == SDL_GetKeyboardFocus()) { - Cocoa_SetKeyboardFocus(window); - } - } - /* FIXME: Should not need to call addChildWindow then orderOut. Attaching a hidden child window to a hidden parent window will cause the child window to show when the parent does. We therefore shouldn't attach the child window here as we're @@ -2103,6 +2090,21 @@ static bool SetupWindowData(SDL_VideoDevice *_this, SDL_Window *window, NSWindow } } + if (!SDL_WINDOW_IS_POPUP(window)) { + if ([nswindow isKeyWindow]) { + window->flags |= SDL_WINDOW_INPUT_FOCUS; + Cocoa_SetKeyboardFocus(data.window); + } + } else { + if (window->flags & SDL_WINDOW_TOOLTIP) { + [nswindow setIgnoresMouseEvents:YES]; + } else if (window->flags & SDL_WINDOW_POPUP_MENU) { + if (window->parent == SDL_GetKeyboardFocus()) { + Cocoa_SetKeyboardFocus(window); + } + } + } + if (nswindow.isOpaque) { window->flags &= ~SDL_WINDOW_TRANSPARENT; } else { @@ -2465,14 +2467,15 @@ void Cocoa_ShowWindow(SDL_VideoDevice *_this, SDL_Window *window) if (![nswindow isMiniaturized]) { [windowData.listener pauseVisibleObservation]; - if (SDL_WINDOW_IS_POPUP(window)) { + if (window->parent) { NSWindow *nsparent = ((__bridge SDL_CocoaWindowData *)window->parent->internal).nswindow; [nsparent addChildWindow:nswindow ordered:NSWindowAbove]; - } else { - if ((window->flags & SDL_WINDOW_MODAL) && window->parent) { - Cocoa_SetWindowModalFor(_this, window, window->parent); - } + if (window->flags & SDL_WINDOW_MODAL) { + Cocoa_SetWindowModal(_this, window, true); + } + } + if (!SDL_WINDOW_IS_POPUP(window)) { if (bActivate) { [nswindow makeKeyAndOrderFront:nil]; } else { @@ -2482,9 +2485,9 @@ void Cocoa_ShowWindow(SDL_VideoDevice *_this, SDL_Window *window) } } } - [nswindow setIsVisible:YES]; - [windowData.listener resumeVisibleObservation]; } + [nswindow setIsVisible:YES]; + [windowData.listener resumeVisibleObservation]; } } @@ -2492,6 +2495,7 @@ void Cocoa_HideWindow(SDL_VideoDevice *_this, SDL_Window *window) { @autoreleasepool { NSWindow *nswindow = ((__bridge SDL_CocoaWindowData *)window->internal).nswindow; + const BOOL waskey = [nswindow isKeyWindow]; /* orderOut has no effect on miniaturized windows, so close must be used to remove * the window from the desktop and window list in this case. @@ -2509,9 +2513,9 @@ void Cocoa_HideWindow(SDL_VideoDevice *_this, SDL_Window *window) /* 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); + Cocoa_SetWindowModal(_this, window, false); - // Transfer keyboard focus back to the parent + // Transfer keyboard focus back to the parent when closing a popup menu if (window->flags & SDL_WINDOW_POPUP_MENU) { if (window == SDL_GetKeyboardFocus()) { SDL_Window *new_focus = window->parent; @@ -2523,6 +2527,20 @@ void Cocoa_HideWindow(SDL_VideoDevice *_this, SDL_Window *window) Cocoa_SetKeyboardFocus(new_focus); } + } else if (window->parent && waskey) { + /* Key status is not automatically set on the parent when a child is hidden. Check if the + * child window was key, and set the first visible parent to be key if so. + */ + SDL_Window *new_focus = window->parent; + + while (new_focus->parent != NULL && (new_focus->is_hiding || new_focus->is_destroying)) { + new_focus = new_focus->parent; + } + + if (new_focus) { + NSWindow *newkey = ((__bridge SDL_CocoaWindowData *)window->internal).nswindow; + [newkey makeKeyAndOrderFront:nil]; + } } } } @@ -2539,19 +2557,21 @@ void Cocoa_RaiseWindow(SDL_VideoDevice *_this, SDL_Window *window) */ [windowData.listener pauseVisibleObservation]; if (![nswindow isMiniaturized] && [nswindow isVisible]) { - if (SDL_WINDOW_IS_POPUP(window)) { + if (window->parent) { NSWindow *nsparent = ((__bridge SDL_CocoaWindowData *)window->parent->internal).nswindow; [nsparent addChildWindow:nswindow ordered:NSWindowAbove]; - if (bActivate) { - [nswindow makeKeyWindow]; - } - } else { + } + if (!SDL_WINDOW_IS_POPUP(window)) { if (bActivate) { [NSApp activateIgnoringOtherApps:YES]; [nswindow makeKeyAndOrderFront:nil]; } else { [nswindow orderFront:nil]; } + } else { + if (bActivate) { + [nswindow makeKeyWindow]; + } } } [windowData.listener resumeVisibleObservation]; @@ -2943,7 +2963,7 @@ void Cocoa_DestroyWindow(SDL_VideoDevice *_this, SDL_Window *window) */ if (topmost_data.keyboard_focus == window) { SDL_Window *new_focus = window; - while(new_focus->parent && (new_focus->is_hiding || new_focus->is_destroying)) { + while (new_focus->parent && (new_focus->is_hiding || new_focus->is_destroying)) { new_focus = new_focus->parent; } @@ -3054,18 +3074,38 @@ void Cocoa_AcceptDragAndDrop(SDL_Window *window, bool accept) } } -bool Cocoa_SetWindowModalFor(SDL_VideoDevice *_this, SDL_Window *modal_window, SDL_Window *parent_window) +bool Cocoa_SetWindowParent(SDL_VideoDevice *_this, SDL_Window *window, SDL_Window *parent) { @autoreleasepool { - SDL_CocoaWindowData *modal_data = (__bridge SDL_CocoaWindowData *)modal_window->internal; + SDL_CocoaWindowData *child_data = (__bridge SDL_CocoaWindowData *)window->internal; - if (modal_data.modal_session) { - [NSApp endModalSession:modal_data.modal_session]; - modal_data.modal_session = nil; + // Remove an existing parent. + if (child_data.nswindow.parentWindow) { + NSWindow *nsparent = ((__bridge SDL_CocoaWindowData *)window->parent->internal).nswindow; + [nsparent removeChildWindow:child_data.nswindow]; } - if (parent_window) { - modal_data.modal_session = [NSApp beginModalSessionForWindow:modal_data.nswindow]; + if (parent) { + SDL_CocoaWindowData *parent_data = (__bridge SDL_CocoaWindowData *)parent->internal; + [parent_data.nswindow addChildWindow:child_data.nswindow ordered:NSWindowAbove]; + } + } + + return true; +} + +bool Cocoa_SetWindowModal(SDL_VideoDevice *_this, SDL_Window *window, bool modal) +{ + @autoreleasepool { + SDL_CocoaWindowData *data = (__bridge SDL_CocoaWindowData *)window->internal; + + if (data.modal_session) { + [NSApp endModalSession:data.modal_session]; + data.modal_session = nil; + } + + if (modal) { + data.modal_session = [NSApp beginModalSessionForWindow:data.nswindow]; } } diff --git a/src/video/haiku/SDL_bvideo.cc b/src/video/haiku/SDL_bvideo.cc index 942fc966c..23521f73f 100644 --- a/src/video/haiku/SDL_bvideo.cc +++ b/src/video/haiku/SDL_bvideo.cc @@ -82,6 +82,8 @@ static SDL_VideoDevice * HAIKU_CreateDevice(void) device->SetWindowFullscreen = HAIKU_SetWindowFullscreen; device->SetWindowMouseGrab = HAIKU_SetWindowMouseGrab; device->SetWindowMinimumSize = HAIKU_SetWindowMinimumSize; + device->SetWindowParent = HAIKU_SetWindowParent; + device->SetWindowModal = HAIKU_SetWindowModal; device->DestroyWindow = HAIKU_DestroyWindow; device->CreateWindowFramebuffer = HAIKU_CreateWindowFramebuffer; device->UpdateWindowFramebuffer = HAIKU_UpdateWindowFramebuffer; diff --git a/src/video/haiku/SDL_bwindow.cc b/src/video/haiku/SDL_bwindow.cc index b6eca9db3..de9bc2484 100644 --- a/src/video/haiku/SDL_bwindow.cc +++ b/src/video/haiku/SDL_bwindow.cc @@ -188,21 +188,22 @@ bool HAIKU_SetWindowMouseGrab(SDL_VideoDevice *_this, SDL_Window * window, bool return SDL_Unsupported(); } -bool HAIKU_SetWindowModalFor(SDL_VideoDevice *_this, SDL_Window *modal_window, SDL_Window *parent_window) +bool HAIKU_SetWindowParent(SDL_VideoDevice *_this, SDL_Window * window, SDL_Window *parent) { - 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)); - } + return true; +} - 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)); +bool HAIKU_SetWindowModal(SDL_VideoDevice *_this, SDL_Window *window, bool modal) +{ + if (modal) { + _ToBeWin(window)->SetLook(B_MODAL_WINDOW_LOOK); + _ToBeWin(window)->SetFeel(B_MODAL_SUBSET_WINDOW_FEEL); + _ToBeWin(window)->AddToSubset(_ToBeWin(window->parent)); } 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); + window_look look = (window->flags & SDL_WINDOW_BORDERLESS) ? B_NO_BORDER_WINDOW_LOOK : B_TITLED_WINDOW_LOOK; + _ToBeWin(window)->RemoveFromSubset(_ToBeWin(window->parent)); + _ToBeWin(window)->SetLook(look); + _ToBeWin(window)->SetFeel(B_NORMAL_WINDOW_FEEL); } return true; diff --git a/src/video/haiku/SDL_bwindow.h b/src/video/haiku/SDL_bwindow.h index 98c88d77f..1bc711d14 100644 --- a/src/video/haiku/SDL_bwindow.h +++ b/src/video/haiku/SDL_bwindow.h @@ -39,6 +39,8 @@ extern void HAIKU_SetWindowBordered(SDL_VideoDevice *_this, SDL_Window *window, extern void HAIKU_SetWindowResizable(SDL_VideoDevice *_this, SDL_Window *window, bool resizable); extern SDL_FullscreenResult HAIKU_SetWindowFullscreen(SDL_VideoDevice *_this, SDL_Window *window, SDL_VideoDisplay *display, SDL_FullscreenOp fullscreen); extern bool HAIKU_SetWindowMouseGrab(SDL_VideoDevice *_this, SDL_Window *window, bool grabbed); +extern bool HAIKU_SetWindowParent(SDL_VideoDevice *_this, SDL_Window *window, SDL_Window *parent); +extern bool HAIKU_SetWindowModal(SDL_VideoDevice *_this, SDL_Window *window, bool modal); extern void HAIKU_DestroyWindow(SDL_VideoDevice *_this, SDL_Window *window); #endif diff --git a/src/video/wayland/SDL_waylandvideo.c b/src/video/wayland/SDL_waylandvideo.c index a3898c4da..32cc022e7 100644 --- a/src/video/wayland/SDL_waylandvideo.c +++ b/src/video/wayland/SDL_waylandvideo.c @@ -547,7 +547,8 @@ static SDL_VideoDevice *Wayland_CreateDevice(bool require_preferred_protocols) device->SetWindowSize = Wayland_SetWindowSize; device->SetWindowMinimumSize = Wayland_SetWindowMinimumSize; device->SetWindowMaximumSize = Wayland_SetWindowMaximumSize; - device->SetWindowModalFor = Wayland_SetWindowModalFor; + device->SetWindowParent = Wayland_SetWindowParent; + device->SetWindowModal = Wayland_SetWindowModal; device->SetWindowOpacity = Wayland_SetWindowOpacity; device->SetWindowTitle = Wayland_SetWindowTitle; device->SetWindowIcon = Wayland_SetWindowIcon; diff --git a/src/video/wayland/SDL_waylandwindow.c b/src/video/wayland/SDL_waylandwindow.c index 4596a3078..24ee3eefd 100644 --- a/src/video/wayland/SDL_waylandwindow.c +++ b/src/video/wayland/SDL_waylandwindow.c @@ -672,8 +672,11 @@ 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->internal->surface_status == WAYLAND_SURFACE_STATUS_SHOW_PENDING) { Wayland_ShowWindow(SDL_GetVideoDevice(), w); - } else if ((w->flags & SDL_WINDOW_MODAL) && w->internal->modal_reparenting_required) { - Wayland_SetWindowModalFor(SDL_GetVideoDevice(), w, w->parent); + } else if (w->internal->reparenting_required) { + Wayland_SetWindowParent(SDL_GetVideoDevice(), w, w->parent); + if (w->flags & SDL_WINDOW_MODAL) { + Wayland_SetWindowModal(SDL_GetVideoDevice(), w, true); + } } } @@ -1564,58 +1567,75 @@ bool Wayland_SetWindowHitTest(SDL_Window *window, bool enabled) return true; // just succeed, the real work is done elsewhere. } -bool Wayland_SetWindowModalFor(SDL_VideoDevice *_this, SDL_Window *modal_window, SDL_Window *parent_window) +static struct xdg_toplevel *GetToplevelForWindow(SDL_WindowData *wind) { - SDL_VideoData *viddata = _this->internal; - SDL_WindowData *modal_data = modal_window->internal; - SDL_WindowData *parent_data = parent_window ? parent_window->internal : NULL; - struct xdg_toplevel *modal_toplevel = NULL; - struct xdg_toplevel *parent_toplevel = NULL; + if (wind) { + /* Libdecor crashes on attempts to unset the parent by passing null, which is allowed by the + * toplevel spec, so just use the raw xdg-toplevel instead (that's what libdecor does + * internally anyways). + */ +#ifdef HAVE_LIBDECOR_H + if (wind->shell_surface_type == WAYLAND_SURFACE_LIBDECOR && wind->shell_surface.libdecor.frame) { + return libdecor_frame_get_xdg_toplevel(wind->shell_surface.libdecor.frame); + } else +#endif + if (wind->shell_surface_type == WAYLAND_SURFACE_XDG_TOPLEVEL && wind->shell_surface.xdg.roleobj.toplevel) { + return wind->shell_surface.xdg.roleobj.toplevel; + } + } - modal_data->modal_reparenting_required = false; + return NULL; +} + +bool Wayland_SetWindowParent(SDL_VideoDevice *_this, SDL_Window *window, SDL_Window *parent_window) +{ + SDL_WindowData *child_data = window->internal; + SDL_WindowData *parent_data = parent_window ? parent_window->internal : NULL; + + child_data->reparenting_required = 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 = true; + child_data->reparenting_required = SDL_TRUE; return true; } - /* 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 (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 (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; + struct xdg_toplevel *child_toplevel = GetToplevelForWindow(child_data); + struct xdg_toplevel *parent_toplevel = GetToplevelForWindow(parent_data); + + if (child_toplevel) { + xdg_toplevel_set_parent(child_toplevel, parent_toplevel); } - 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; - } + return true; +} + +bool Wayland_SetWindowModal(SDL_VideoDevice *_this, SDL_Window *window, bool modal) +{ + SDL_VideoData *viddata = _this->internal; + SDL_WindowData *data = window->internal; + SDL_WindowData *parent_data = window->parent->internal; + + if (parent_data->surface_status != WAYLAND_SURFACE_STATUS_SHOWN) { + // Need to wait for the parent to become mapped before changing modal status. + data->reparenting_required = true; + return true; + } else { + data->reparenting_required = false; } - if (modal_toplevel) { - xdg_toplevel_set_parent(modal_toplevel, parent_toplevel); + struct xdg_toplevel *toplevel = GetToplevelForWindow(data); + if (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); + if (modal) { + if (!data->xdg_dialog_v1) { + data->xdg_dialog_v1 = xdg_wm_dialog_v1_get_xdg_dialog(viddata->xdg_wm_dialog_v1, toplevel); } - xdg_dialog_v1_set_modal(modal_data->xdg_dialog_v1); - } else if (modal_data->xdg_dialog_v1) { - xdg_dialog_v1_unset_modal(modal_data->xdg_dialog_v1); + xdg_dialog_v1_set_modal(data->xdg_dialog_v1); + } else if (data->xdg_dialog_v1) { + xdg_dialog_v1_unset_modal(data->xdg_dialog_v1); } } } @@ -1820,8 +1840,10 @@ void Wayland_ShowWindow(SDL_VideoDevice *_this, SDL_Window *window) } // Restore state that was set prior to this call + Wayland_SetWindowParent(_this, window, window->parent); + if (window->flags & SDL_WINDOW_MODAL) { - Wayland_SetWindowModalFor(_this, window, window->parent); + Wayland_SetWindowModal(_this, window, true); } Wayland_SetWindowTitle(_this, window); diff --git a/src/video/wayland/SDL_waylandwindow.h b/src/video/wayland/SDL_waylandwindow.h index 278884f2d..344ac583f 100644 --- a/src/video/wayland/SDL_waylandwindow.h +++ b/src/video/wayland/SDL_waylandwindow.h @@ -172,7 +172,7 @@ struct SDL_WindowData bool fullscreen_was_positioned; bool show_hide_sync_required; bool scale_to_display; - bool modal_reparenting_required; + bool reparenting_required; bool pending_restored_size; bool double_buffer; @@ -200,7 +200,8 @@ extern void Wayland_SetWindowMinimumSize(SDL_VideoDevice *_this, SDL_Window *win extern void Wayland_SetWindowMaximumSize(SDL_VideoDevice *_this, SDL_Window *window); extern void Wayland_GetWindowSizeInPixels(SDL_VideoDevice *_this, SDL_Window *window, int *w, int *h); extern SDL_DisplayID Wayland_GetDisplayForWindow(SDL_VideoDevice *_this, SDL_Window *window); -extern bool Wayland_SetWindowModalFor(SDL_VideoDevice *_this, SDL_Window *modal_window, SDL_Window *parent_window); +extern bool Wayland_SetWindowParent(SDL_VideoDevice *_this, SDL_Window *window, SDL_Window *parent_window); +extern bool Wayland_SetWindowModal(SDL_VideoDevice *_this, SDL_Window *window, bool modal); extern bool Wayland_SetWindowOpacity(SDL_VideoDevice *_this, SDL_Window *window, float opacity); extern void Wayland_SetWindowTitle(SDL_VideoDevice *_this, SDL_Window *window); extern void Wayland_ShowWindowSystemMenu(SDL_Window *window, int x, int y); diff --git a/src/video/windows/SDL_windowsvideo.c b/src/video/windows/SDL_windowsvideo.c index 05b45bc93..b9e3a9961 100644 --- a/src/video/windows/SDL_windowsvideo.c +++ b/src/video/windows/SDL_windowsvideo.c @@ -229,7 +229,8 @@ static SDL_VideoDevice *WIN_CreateDevice(void) device->SetWindowResizable = WIN_SetWindowResizable; device->SetWindowAlwaysOnTop = WIN_SetWindowAlwaysOnTop; device->SetWindowFullscreen = WIN_SetWindowFullscreen; - device->SetWindowModalFor = WIN_SetWindowModalFor; + device->SetWindowParent = WIN_SetWindowParent; + device->SetWindowModal = WIN_SetWindowModal; #if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES) device->GetWindowICCProfile = WIN_GetWindowICCProfile; device->SetWindowMouseRect = WIN_SetWindowMouseRect; diff --git a/src/video/windows/SDL_windowswindow.c b/src/video/windows/SDL_windowswindow.c index bbe8aa8b8..9d1d820c4 100644 --- a/src/video/windows/SDL_windowswindow.c +++ b/src/video/windows/SDL_windowswindow.c @@ -693,10 +693,10 @@ bool WIN_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window, SDL_Properties int x, y; int w, h; - if (SDL_WINDOW_IS_POPUP(window)) { - parent = window->parent->internal->hwnd; - } else if (window->flags & SDL_WINDOW_UTILITY) { + if (window->flags & SDL_WINDOW_UTILITY) { parent = CreateWindow(SDL_Appname, TEXT(""), STYLE_BASIC, 0, 0, 32, 32, NULL, NULL, SDL_Instance, NULL); + } else if (window->parent) { + parent = window->parent->internal->hwnd; } style |= GetWindowStyle(window); @@ -1017,12 +1017,6 @@ void WIN_ShowWindow(SDL_VideoDevice *_this, SDL_Window *window) WIN_SetWindowPosition(_this, window); } -#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES) - if (window->flags & SDL_WINDOW_MODAL) { - EnableWindow(window->parent->internal->hwnd, FALSE); - } -#endif // !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES) - hwnd = window->internal->hwnd; style = GetWindowLong(hwnd, GWL_EXSTYLE); if (style & WS_EX_NOACTIVATE) { @@ -1040,17 +1034,18 @@ void WIN_ShowWindow(SDL_VideoDevice *_this, SDL_Window *window) WIN_SetKeyboardFocus(window); } } + if (window->flags & SDL_WINDOW_MODAL) { + WIN_SetWindowModal(_this, window, true); + } } void WIN_HideWindow(SDL_VideoDevice *_this, SDL_Window *window) { HWND hwnd = window->internal->hwnd; -#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES) if (window->flags & SDL_WINDOW_MODAL) { - EnableWindow(window->parent->internal->hwnd, TRUE); + WIN_SetWindowModal(_this, window, false); } -#endif // !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES) ShowWindow(hwnd, SW_HIDE); @@ -2240,22 +2235,12 @@ void WIN_UpdateDarkModeForHWND(HWND hwnd) } } -bool WIN_SetWindowModalFor(SDL_VideoDevice *_this, SDL_Window *modal_window, SDL_Window *parent_window) +bool WIN_SetWindowParent(SDL_VideoDevice *_this, SDL_Window *window, SDL_Window *parent) { #if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES) - SDL_WindowData *modal_data = modal_window->internal; - const LONG_PTR parent_hwnd = (LONG_PTR)(parent_window ? parent_window->internal->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 true; - } - - // Reenable the old parent window. - if (old_ptr) { - EnableWindow((HWND)old_ptr, TRUE); - } + SDL_WindowData *child_data = window->internal; + const LONG_PTR parent_hwnd = (LONG_PTR)(parent ? parent->internal->hwnd : NULL); + const DWORD style = GetWindowLong(child_data->hwnd, GWL_STYLE); if (!(style & WS_CHILD)) { /* Despite the name, this changes the *owner* of a toplevel window, not @@ -2263,14 +2248,26 @@ bool WIN_SetWindowModalFor(SDL_VideoDevice *_this, SDL_Window *modal_window, SDL * * https://devblogs.microsoft.com/oldnewthing/20100315-00/?p=14613 */ - SetWindowLongPtr(modal_data->hwnd, GWLP_HWNDPARENT, parent_hwnd); + SetWindowLongPtr(child_data->hwnd, GWLP_HWNDPARENT, parent_hwnd); } else { - SetParent(modal_data->hwnd, (HWND)parent_hwnd); + SetParent(child_data->hwnd, (HWND)parent_hwnd); } +#endif /*!defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)*/ - // 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 true; +} + +bool WIN_SetWindowModal(SDL_VideoDevice *_this, SDL_Window *window, bool modal) +{ +#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES) + const HWND parent_hwnd = window->parent->internal->hwnd; + + if (modal) { + // Disable the parent window. + EnableWindow(parent_hwnd, FALSE); + } else if (!(window->flags & SDL_WINDOW_HIDDEN)) { + // Re-enable the parent window + EnableWindow(parent_hwnd, TRUE); } #endif // !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES) diff --git a/src/video/windows/SDL_windowswindow.h b/src/video/windows/SDL_windowswindow.h index 84b57dc9d..9a448a797 100644 --- a/src/video/windows/SDL_windowswindow.h +++ b/src/video/windows/SDL_windowswindow.h @@ -137,7 +137,8 @@ extern void WIN_ShowWindowSystemMenu(SDL_Window *window, int x, int y); extern bool WIN_SetWindowFocusable(SDL_VideoDevice *_this, SDL_Window *window, bool focusable); extern bool WIN_AdjustWindowRect(SDL_Window *window, int *x, int *y, int *width, int *height, SDL_WindowRect rect_type); extern bool WIN_AdjustWindowRectForHWND(HWND hwnd, LPRECT lpRect, UINT frame_dpi); -extern bool WIN_SetWindowModalFor(SDL_VideoDevice *_this, SDL_Window *modal_window, SDL_Window *parent_window); +extern bool WIN_SetWindowParent(SDL_VideoDevice *_this, SDL_Window *window, SDL_Window *parent); +extern bool WIN_SetWindowModal(SDL_VideoDevice *_this, SDL_Window *window, bool modal); // Ends C function definitions when using C++ #ifdef __cplusplus diff --git a/src/video/x11/SDL_x11video.c b/src/video/x11/SDL_x11video.c index 39eba0ff9..04a811de7 100644 --- a/src/video/x11/SDL_x11video.c +++ b/src/video/x11/SDL_x11video.c @@ -197,7 +197,8 @@ static SDL_VideoDevice *X11_CreateDevice(void) device->SetWindowAspectRatio = X11_SetWindowAspectRatio; device->GetWindowBordersSize = X11_GetWindowBordersSize; device->SetWindowOpacity = X11_SetWindowOpacity; - device->SetWindowModalFor = X11_SetWindowModalFor; + device->SetWindowParent = X11_SetWindowParent; + device->SetWindowModal = X11_SetWindowModal; device->ShowWindow = X11_ShowWindow; device->HideWindow = X11_HideWindow; device->RaiseWindow = X11_RaiseWindow; diff --git a/src/video/x11/SDL_x11window.c b/src/video/x11/SDL_x11window.c index 198914492..509fa08c0 100644 --- a/src/video/x11/SDL_x11window.c +++ b/src/video/x11/SDL_x11window.c @@ -792,6 +792,11 @@ bool X11_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window, SDL_Properties } windowdata = window->internal; + // Set the parent if this is a non-popup window. + if (!SDL_WINDOW_IS_POPUP(window) && window->parent) { + X11_XSetTransientForHint(display, w, window->parent->internal->xwindow); + } + // Set the flag if the borders were forced on when creating a fullscreen window for later removal. windowdata->fullscreen_borders_forced_on = !!(window->pending_flags & SDL_WINDOW_FULLSCREEN) && !!(window->flags & SDL_WINDOW_BORDERLESS); @@ -1235,26 +1240,40 @@ bool X11_SetWindowOpacity(SDL_VideoDevice *_this, SDL_Window *window, float opac return true; } -bool X11_SetWindowModalFor(SDL_VideoDevice *_this, SDL_Window *modal_window, SDL_Window *parent_window) +bool X11_SetWindowParent(SDL_VideoDevice *_this, SDL_Window *window, SDL_Window *parent) { - SDL_WindowData *data = modal_window->internal; - SDL_WindowData *parent_data = parent_window ? parent_window->internal : NULL; + SDL_WindowData *data = window->internal; + SDL_WindowData *parent_data = parent ? parent->internal : NULL; SDL_VideoData *video_data = _this->internal; - SDL_DisplayData *displaydata = SDL_GetDisplayDriverDataForWindow(modal_window); Display *display = video_data->display; - Uint32 flags = modal_window->flags; + + if (parent_data) { + X11_XSetTransientForHint(display, data->xwindow, parent_data->xwindow); + } else { + X11_XDeleteProperty(display, data->xwindow, video_data->WM_TRANSIENT_FOR); + } + + return true; +} + +bool X11_SetWindowModal(SDL_VideoDevice *_this, SDL_Window *window, bool modal) +{ + SDL_WindowData *data = window->internal; + SDL_VideoData *video_data = _this->internal; + SDL_DisplayData *displaydata = SDL_GetDisplayDriverDataForWindow(window); + Display *display = video_data->display; + Uint32 flags = 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) { + if (modal) { 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)) { + if (X11_IsWindowMapped(_this, window)) { XEvent e; SDL_zero(e); @@ -1262,8 +1281,7 @@ bool X11_SetWindowModalFor(SDL_VideoDevice *_this, SDL_Window *modal_window, SDL 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[0] = modal ? _NET_WM_STATE_ADD : _NET_WM_STATE_REMOVE; e.xclient.data.l[1] = _NET_WM_STATE_MODAL; e.xclient.data.l[3] = 0l; diff --git a/src/video/x11/SDL_x11window.h b/src/video/x11/SDL_x11window.h index 4bf7e8a20..d867255ba 100644 --- a/src/video/x11/SDL_x11window.h +++ b/src/video/x11/SDL_x11window.h @@ -118,7 +118,8 @@ extern void X11_SetWindowMaximumSize(SDL_VideoDevice *_this, SDL_Window *window) extern void X11_SetWindowAspectRatio(SDL_VideoDevice *_this, SDL_Window *window); extern bool X11_GetWindowBordersSize(SDL_VideoDevice *_this, SDL_Window *window, int *top, int *left, int *bottom, int *right); extern bool X11_SetWindowOpacity(SDL_VideoDevice *_this, SDL_Window *window, float opacity); -extern bool X11_SetWindowModalFor(SDL_VideoDevice *_this, SDL_Window *modal_window, SDL_Window *parent_window); +extern bool X11_SetWindowParent(SDL_VideoDevice *_this, SDL_Window *window, SDL_Window *parent); +extern bool X11_SetWindowModal(SDL_VideoDevice *_this, SDL_Window *window, bool modal); extern void X11_SetWindowSize(SDL_VideoDevice *_this, SDL_Window *window); extern void X11_ShowWindow(SDL_VideoDevice *_this, SDL_Window *window); extern void X11_HideWindow(SDL_VideoDevice *_this, SDL_Window *window); diff --git a/test/testmodal.c b/test/testmodal.c index 608d9176e..4fa9c1abd 100644 --- a/test/testmodal.c +++ b/test/testmodal.c @@ -66,8 +66,10 @@ int main(int argc, char *argv[]) goto sdl_quit; } - if (!SDL_SetWindowModalFor(w2, w1)) { - SDL_SetWindowTitle(w2, "Modal Window"); + if (SDL_SetWindowParent(w2, w1)) { + if (SDL_SetWindowModal(w2, true)) { + SDL_SetWindowTitle(w2, "Modal Window"); + } } while (1) { @@ -98,8 +100,10 @@ int main(int argc, char *argv[]) } if (e.key.key == SDLK_M) { - if (!SDL_SetWindowModalFor(w2, w1)) { - SDL_SetWindowTitle(w2, "Modal Window"); + if (SDL_SetWindowParent(w2, w2)) { + if (SDL_SetWindowModal(w2, true)) { + SDL_SetWindowTitle(w2, "Modal Window"); + } } } SDL_ShowWindow(w2); @@ -123,13 +127,17 @@ int main(int argc, char *argv[]) } else if (e.key.key == SDLK_P && w2) { if (SDL_GetWindowFlags(w2) & SDL_WINDOW_MODAL) { /* Unparent the window */ - if (!SDL_SetWindowModalFor(w2, NULL)) { - SDL_SetWindowTitle(w2, "Non-Modal Window"); + if (SDL_SetWindowModal(w2, false)) { + if (SDL_SetWindowParent(w2, NULL)) { + SDL_SetWindowTitle(w2, "Non-Modal Window"); + } } } else { /* Reparent the window */ - if (!SDL_SetWindowModalFor(w2, w1)) { - SDL_SetWindowTitle(w2, "Modal Window"); + if (SDL_SetWindowParent(w2, w1)) { + if (SDL_SetWindowModal(w2, true)) { + SDL_SetWindowTitle(w2, "Modal Window"); + } } } }