From c6a70d68989cc08e1d0785627bcced5e59818aeb Mon Sep 17 00:00:00 2001 From: Semphris Date: Wed, 3 Apr 2024 16:44:03 -0400 Subject: [PATCH] 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. --- include/SDL3/SDL_video.h | 17 ++- src/video/SDL_video.c | 104 ++++++++++++---- src/video/cocoa/SDL_cocoaevents.m | 8 ++ src/video/cocoa/SDL_cocoavideo.m | 1 + src/video/cocoa/SDL_cocoawindow.h | 2 + src/video/cocoa/SDL_cocoawindow.m | 27 ++++ src/video/haiku/SDL_bwindow.cc | 23 +++- src/video/wayland/SDL_waylandvideo.c | 8 ++ src/video/wayland/SDL_waylandvideo.h | 1 + src/video/wayland/SDL_waylandwindow.c | 74 +++++++---- src/video/wayland/SDL_waylandwindow.h | 2 + src/video/windows/SDL_windowsvideo.c | 1 + src/video/windows/SDL_windowswindow.c | 44 +++++++ src/video/windows/SDL_windowswindow.h | 1 + src/video/x11/SDL_x11video.c | 2 + src/video/x11/SDL_x11video.h | 2 + src/video/x11/SDL_x11window.c | 43 ++++++- test/CMakeLists.txt | 1 + test/testmodal.c | 172 ++++++++++++++++++++++++++ wayland-protocols/xdg-dialog-v1.xml | 110 ++++++++++++++++ 20 files changed, 592 insertions(+), 51 deletions(-) create mode 100644 test/testmodal.c create mode 100644 wayland-protocols/xdg-dialog-v1.xml diff --git a/include/SDL3/SDL_video.h b/include/SDL3/SDL_video.h index 0bf26e496..38d76f296 100644 --- a/include/SDL3/SDL_video.h +++ b/include/SDL3/SDL_video.h @@ -144,6 +144,7 @@ typedef Uint32 SDL_WindowFlags; #define SDL_WINDOW_INPUT_FOCUS 0x00000200U /**< window has input focus */ #define SDL_WINDOW_MOUSE_FOCUS 0x00000400U /**< window has mouse focus */ #define SDL_WINDOW_EXTERNAL 0x00000800U /**< window not created by SDL */ +#define SDL_WINDOW_MODAL 0x00001000U /**< window is modal */ #define SDL_WINDOW_HIGH_PIXEL_DENSITY 0x00002000U /**< window uses high pixel density back buffer if possible */ #define SDL_WINDOW_MOUSE_CAPTURE 0x00004000U /**< window has mouse captured (unrelated to MOUSE_GRABBED) */ #define SDL_WINDOW_ALWAYS_ON_TOP 0x00008000U /**< window should always be above others */ @@ -907,13 +908,15 @@ extern DECLSPEC SDL_Window *SDLCALL SDL_CreatePopupWindow(SDL_Window *parent, in * with Metal rendering * - `SDL_PROP_WINDOW_CREATE_MINIMIZED_BOOLEAN`: true if the window should * start minimized + * - `SDL_PROP_WINDOW_CREATE_MODAL_BOOLEAN`: true if the window is modal to its + * parent * - `SDL_PROP_WINDOW_CREATE_MOUSE_GRABBED_BOOLEAN`: true if the window starts * with grabbed mouse focus * - `SDL_PROP_WINDOW_CREATE_OPENGL_BOOLEAN`: true if the window will be used * with OpenGL rendering * - `SDL_PROP_WINDOW_CREATE_PARENT_POINTER`: an SDL_Window that will be the - * parent of this window, required for windows with the "toolip" and "menu" - * properties + * parent of this window, required for windows with the "toolip", "menu", and + * "modal" properties * - `SDL_PROP_WINDOW_CREATE_RESIZABLE_BOOLEAN`: true if the window should be * resizable * - `SDL_PROP_WINDOW_CREATE_TITLE_STRING`: the title of the window, in UTF-8 @@ -1008,6 +1011,7 @@ extern DECLSPEC SDL_Window *SDLCALL SDL_CreateWindowWithProperties(SDL_Propertie #define SDL_PROP_WINDOW_CREATE_MENU_BOOLEAN "menu" #define SDL_PROP_WINDOW_CREATE_METAL_BOOLEAN "metal" #define SDL_PROP_WINDOW_CREATE_MINIMIZED_BOOLEAN "minimized" +#define SDL_PROP_WINDOW_CREATE_MODAL_BOOLEAN "modal" #define SDL_PROP_WINDOW_CREATE_MOUSE_GRABBED_BOOLEAN "mouse_grabbed" #define SDL_PROP_WINDOW_CREATE_OPENGL_BOOLEAN "opengl" #define SDL_PROP_WINDOW_CREATE_PARENT_POINTER "parent" @@ -2000,7 +2004,12 @@ extern DECLSPEC int SDLCALL SDL_SetWindowOpacity(SDL_Window *window, float opaci extern DECLSPEC int SDLCALL SDL_GetWindowOpacity(SDL_Window *window, float *out_opacity); /** - * Set the window as a modal for another window. + * Set the window as a modal to 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. + * + * Setting a window as modal to a parent that is a descendent of the modal 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 @@ -2181,6 +2190,8 @@ extern DECLSPEC int SDLCALL SDL_FlashWindow(SDL_Window *window, SDL_FlashOperati /** * Destroy a window. * + * Any popups or modal windows owned by the window will be recursively destroyed as well. + * * If `window` is NULL, this function will return immediately after setting * the SDL error message to "Invalid window". See SDL_GetError(). * diff --git a/src/video/SDL_video.c b/src/video/SDL_video.c index 347266bc0..18e4d255a 100644 --- a/src/video/SDL_video.c +++ b/src/video/SDL_video.c @@ -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; diff --git a/src/video/cocoa/SDL_cocoaevents.m b/src/video/cocoa/SDL_cocoaevents.m index 81f086cea..7bd0a54ae 100644 --- a/src/video/cocoa/SDL_cocoaevents.m +++ b/src/video/cocoa/SDL_cocoaevents.m @@ -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) { diff --git a/src/video/cocoa/SDL_cocoavideo.m b/src/video/cocoa/SDL_cocoavideo.m index ae4bbcfbf..385d0eb97 100644 --- a/src/video/cocoa/SDL_cocoavideo.m +++ b/src/video/cocoa/SDL_cocoavideo.m @@ -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 diff --git a/src/video/cocoa/SDL_cocoawindow.h b/src/video/cocoa/SDL_cocoawindow.h index 4f4d9dacc..b4d4cec13 100644 --- a/src/video/cocoa/SDL_cocoawindow.h +++ b/src/video/cocoa/SDL_cocoawindow.h @@ -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_ */ diff --git a/src/video/cocoa/SDL_cocoawindow.m b/src/video/cocoa/SDL_cocoawindow.m index 13f52ddc6..9b6e6ce48 100644 --- a/src/video/cocoa/SDL_cocoawindow.m +++ b/src/video/cocoa/SDL_cocoawindow.m @@ -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 { diff --git a/src/video/haiku/SDL_bwindow.cc b/src/video/haiku/SDL_bwindow.cc index 86436a0db..b4deb70d0 100644 --- a/src/video/haiku/SDL_bwindow.cc +++ b/src/video/haiku/SDL_bwindow.cc @@ -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)); diff --git a/src/video/wayland/SDL_waylandvideo.c b/src/video/wayland/SDL_waylandvideo.c index 7cfb5a25e..173fc7f7c 100644 --- a/src/video/wayland/SDL_waylandvideo.c +++ b/src/video/wayland/SDL_waylandvideo.c @@ -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); diff --git a/src/video/wayland/SDL_waylandvideo.h b/src/video/wayland/SDL_waylandvideo.h index c6ee45788..7228636ed 100644 --- a/src/video/wayland/SDL_waylandvideo.h +++ b/src/video/wayland/SDL_waylandvideo.h @@ -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; diff --git a/src/video/wayland/SDL_waylandwindow.c b/src/video/wayland/SDL_waylandwindow.c index 28d7b2cbb..83dc22a25 100644 --- a/src/video/wayland/SDL_waylandwindow.c +++ b/src/video/wayland/SDL_waylandwindow.c @@ -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 @@ -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); diff --git a/src/video/wayland/SDL_waylandwindow.h b/src/video/wayland/SDL_waylandwindow.h index 23f880668..24245cff5 100644 --- a/src/video/wayland/SDL_waylandwindow.h +++ b/src/video/wayland/SDL_waylandwindow.h @@ -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; diff --git a/src/video/windows/SDL_windowsvideo.c b/src/video/windows/SDL_windowsvideo.c index 32b42ce72..5dc929d87 100644 --- a/src/video/windows/SDL_windowsvideo.c +++ b/src/video/windows/SDL_windowsvideo.c @@ -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; diff --git a/src/video/windows/SDL_windowswindow.c b/src/video/windows/SDL_windowswindow.c index b2dd17b18..605a4e7bd 100644 --- a/src/video/windows/SDL_windowswindow.c +++ b/src/video/windows/SDL_windowswindow.c @@ -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 */ diff --git a/src/video/windows/SDL_windowswindow.h b/src/video/windows/SDL_windowswindow.h index 760c1a7a9..65f87daf4 100644 --- a/src/video/windows/SDL_windowswindow.h +++ b/src/video/windows/SDL_windowswindow.h @@ -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 diff --git a/src/video/x11/SDL_x11video.c b/src/video/x11/SDL_x11video.c index d1736ecf3..7a50119d2 100644 --- a/src/video/x11/SDL_x11video.c +++ b/src/video/x11/SDL_x11video.c @@ -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); diff --git a/src/video/x11/SDL_x11video.h b/src/video/x11/SDL_x11video.h index cafa035a0..b10b3b30f 100644 --- a/src/video/x11/SDL_x11video.h +++ b/src/video/x11/SDL_x11video.h @@ -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; diff --git a/src/video/x11/SDL_x11window.c b/src/video/x11/SDL_x11window.c index fdd0ba3ab..fedad99d5 100644 --- a/src/video/x11/SDL_x11window.c +++ b/src/video/x11/SDL_x11window.c @@ -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; } diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 890b880bd..ae6f5b342 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -414,6 +414,7 @@ add_sdl_test_executable(testpopup SOURCES testpopup.c) add_sdl_test_executable(testdialog SOURCES testdialog.c) add_sdl_test_executable(testtime SOURCES testtime.c) add_sdl_test_executable(testmanymouse SOURCES testmanymouse.c) +add_sdl_test_executable(testmodal SOURCES testmodal.c) if (HAVE_WAYLAND) # Set the GENERATED property on the protocol file, since it is first created at build time diff --git a/test/testmodal.c b/test/testmodal.c new file mode 100644 index 000000000..0313df170 --- /dev/null +++ b/test/testmodal.c @@ -0,0 +1,172 @@ +/* + Copyright (C) 1997-2024 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely. +*/ +/* Sample program: Create a parent window and a modal child window. */ + +#include +#include +#include +#include + +int main(int argc, char *argv[]) +{ + SDL_Window *w1 = NULL, *w2 = NULL; + SDL_Renderer *r1 = NULL, *r2 = NULL; + SDLTest_CommonState *state = NULL; + Uint64 show_deadline = 0; + int i; + int exit_code = 0; + + /* Initialize test framework */ + state = SDLTest_CommonCreateState(argv, 0); + if (state == NULL) { + return 1; + } + + /* Enable standard application logging */ + SDL_LogSetPriority(SDL_LOG_CATEGORY_APPLICATION, SDL_LOG_PRIORITY_INFO); + + /* Parse commandline */ + for (i = 1; i < argc;) { + int consumed; + + consumed = SDLTest_CommonArg(state, i); + + if (consumed <= 0) { + static const char *options[] = { NULL }; + SDLTest_CommonLogUsage(state, argv[0], options); + return 1; + } + + i += consumed; + } + + if (SDL_Init(SDL_INIT_VIDEO) < 0) { + SDL_Log("SDL_Init failed (%s)", SDL_GetError()); + return 1; + } + + if (SDL_CreateWindowAndRenderer("Parent Window", 640, 480, 0, &w1, &r1)) { + SDL_Log("Failed to create parent window and/or renderer: %s\n", SDL_GetError()); + exit_code = 1; + goto sdl_quit; + } + + SDL_CreateWindowAndRenderer("Non-Modal Window", 320, 200, 0, &w2, &r2); + if (!w2) { + SDL_Log("Failed to create parent window and/or renderer: %s\n", SDL_GetError()); + exit_code = 1; + goto sdl_quit; + } + + if (!SDL_SetWindowModalFor(w2, w1)) { + SDL_SetWindowTitle(w2, "Modal Window"); + } + + while (1) { + int quit = 0; + SDL_Event e; + while (SDL_PollEvent(&e)) { + if (e.type == SDL_EVENT_QUIT) { + quit = 1; + break; + } else if (e.type == SDL_EVENT_WINDOW_CLOSE_REQUESTED) { + if (e.window.windowID == SDL_GetWindowID(w2)) { + SDL_DestroyRenderer(r2); + SDL_DestroyWindow(w2); + r2 = NULL; + w2 = NULL; + } else if (e.window.windowID == SDL_GetWindowID(w1)) { + SDL_DestroyRenderer(r1); + SDL_DestroyWindow(w1); + r1 = NULL; + w1 = NULL; + } + } else if (e.type == SDL_EVENT_KEY_DOWN) { + if ((e.key.keysym.sym == SDLK_m || e.key.keysym.sym == SDLK_n) && !w2) { + if (SDL_CreateWindowAndRenderer("Non-Modal Window", 320, 200, SDL_WINDOW_HIDDEN, &w2, &r2) < 0) { + SDL_Log("Failed to create modal window and/or renderer: %s\n", SDL_GetError()); + exit_code = 1; + goto sdl_quit; + } + + if (e.key.keysym.sym == SDLK_m) { + if (!SDL_SetWindowModalFor(w2, w1)) { + SDL_SetWindowTitle(w2, "Modal Window"); + } + } + SDL_ShowWindow(w2); + } else if (e.key.keysym.sym == SDLK_ESCAPE && w2) { + SDL_DestroyWindow(w2); + r2 = NULL; + w2 = NULL; + } else if (e.key.keysym.sym == SDLK_h) { + if (e.key.keysym.mod & SDL_KMOD_CTRL) { + /* Hide the parent, which should hide the modal too. */ + show_deadline = SDL_GetTicksNS() + SDL_SECONDS_TO_NS(3); + SDL_HideWindow(w1); + } else if (w2) { + /* Show/hide the modal window */ + if (SDL_GetWindowFlags(w2) & SDL_WINDOW_HIDDEN) { + SDL_ShowWindow(w2); + } else { + SDL_HideWindow(w2); + } + } + } else if (e.key.keysym.sym == SDLK_p && w2) { + if (SDL_GetWindowFlags(w2) & SDL_WINDOW_MODAL) { + /* Unparent the window */ + if (!SDL_SetWindowModalFor(w2, NULL)) { + SDL_SetWindowTitle(w2, "Non-Modal Window"); + } + } else { + /* Reparent the window */ + if (!SDL_SetWindowModalFor(w2, w1)) { + SDL_SetWindowTitle(w2, "Modal Window"); + } + } + } + } + } + if (quit) { + break; + } + SDL_Delay(100); + + if (show_deadline && show_deadline <= SDL_GetTicksNS()) { + SDL_ShowWindow(w1); + } + + /* Parent window is red */ + if (r1) { + SDL_SetRenderDrawColor(r1, 224, 48, 12, SDL_ALPHA_OPAQUE); + SDL_RenderClear(r1); + SDL_RenderPresent(r1); + } + + /* Child window is blue */ + if (r2) { + SDL_SetRenderDrawColor(r2, 6, 76, 255, SDL_ALPHA_OPAQUE); + SDL_RenderClear(r2); + SDL_RenderPresent(r2); + } + } + +sdl_quit: + if (w1) { + /* The child window and renderer will be cleaned up automatically. */ + SDL_DestroyWindow(w1); + } + + SDL_Quit(); + SDLTest_CommonDestroyState(state); + return exit_code; +} diff --git a/wayland-protocols/xdg-dialog-v1.xml b/wayland-protocols/xdg-dialog-v1.xml new file mode 100644 index 000000000..fb3fc14e6 --- /dev/null +++ b/wayland-protocols/xdg-dialog-v1.xml @@ -0,0 +1,110 @@ + + + + Copyright © 2023 Carlos Garnacho + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice (including the next + paragraph) shall be included in all copies or substantial portions of the + Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. + + + + + The xdg_wm_dialog_v1 interface is exposed as a global object allowing + to register surfaces with a xdg_toplevel role as "dialogs" relative to + another toplevel. + + The compositor may let this relation influence how the surface is + placed, displayed or interacted with. + + Warning! The protocol described in this file is currently in the testing + phase. Backward compatible changes may be added together with the + corresponding interface version bump. Backward incompatible changes can + only be done by creating a new major version of the extension. + + + + + + + + + Destroys the xdg_wm_dialog_v1 object. This does not affect + the xdg_dialog_v1 objects generated through it. + + + + + + Creates a xdg_dialog_v1 object for the given toplevel. See the interface + description for more details. + + Compositors must raise an already_used error if clients attempt to + create multiple xdg_dialog_v1 objects for the same xdg_toplevel. + + + + + + + + + A xdg_dialog_v1 object is an ancillary object tied to a xdg_toplevel. Its + purpose is hinting the compositor that the toplevel is a "dialog" (e.g. a + temporary window) relative to another toplevel (see + xdg_toplevel.set_parent). If the xdg_toplevel is destroyed, the xdg_dialog_v1 + becomes inert. + + Through this object, the client may provide additional hints about + the purpose of the secondary toplevel. This interface has no effect + on toplevels that are not attached to a parent toplevel. + + + + + Destroys the xdg_dialog_v1 object. If this object is destroyed + before the related xdg_toplevel, the compositor should unapply its + effects. + + + + + + Hints that the dialog has "modal" behavior. Modal dialogs typically + require to be fully addressed by the user (i.e. closed) before resuming + interaction with the parent toplevel, and may require a distinct + presentation. + + Clients must implement the logic to filter events in the parent + toplevel on their own. + + Compositors may choose any policy in event delivery to the parent + toplevel, from delivering all events unfiltered to using them for + internal consumption. + + + + + + Drops the hint that this dialog has "modal" behavior. See + xdg_dialog_v1.set_modal for more details. + + + +