wayland: Implement popup windows

This commit is contained in:
Frank Praznik
2023-02-28 13:47:40 -05:00
committed by Sam Lantinga
parent e987c4a463
commit 68d2d9f76d
7 changed files with 228 additions and 78 deletions

View File

@@ -160,6 +160,7 @@ void SDL_WAYLAND_UnloadSymbols(void);
#define libdecor_frame_set_parent (*WAYLAND_libdecor_frame_set_parent) #define libdecor_frame_set_parent (*WAYLAND_libdecor_frame_set_parent)
#define libdecor_frame_get_xdg_surface (*WAYLAND_libdecor_frame_get_xdg_surface) #define libdecor_frame_get_xdg_surface (*WAYLAND_libdecor_frame_get_xdg_surface)
#define libdecor_frame_get_xdg_toplevel (*WAYLAND_libdecor_frame_get_xdg_toplevel) #define libdecor_frame_get_xdg_toplevel (*WAYLAND_libdecor_frame_get_xdg_toplevel)
#define libdecor_frame_translate_coordinate (*WAYLAND_libdecor_frame_translate_coordinate)
#define libdecor_frame_map (*WAYLAND_libdecor_frame_map) #define libdecor_frame_map (*WAYLAND_libdecor_frame_map)
#define libdecor_state_new (*WAYLAND_libdecor_state_new) #define libdecor_state_new (*WAYLAND_libdecor_state_new)
#define libdecor_state_free (*WAYLAND_libdecor_state_free) #define libdecor_state_free (*WAYLAND_libdecor_state_free)

View File

@@ -1355,7 +1355,9 @@ static void keyboard_handle_enter(void *data, struct wl_keyboard *keyboard,
if (window) { if (window) {
input->keyboard_focus = window; input->keyboard_focus = window;
window->keyboard_device = input; window->keyboard_device = input;
SDL_SetKeyboardFocus(window->sdlwindow);
/* Restore the keyboard focus to the child popup that was holding it */
SDL_SetKeyboardFocus(window->keyboard_focus ? window->keyboard_focus : window->sdlwindow);
} }
#ifdef SDL_USE_IME #ifdef SDL_USE_IME
if (!input->text_input) { if (!input->text_input) {

View File

@@ -115,7 +115,8 @@ int Wayland_GLES_SwapWindow(_THIS, SDL_Window *window)
* FIXME: Request EGL_WAYLAND_swap_buffers_with_timeout. * FIXME: Request EGL_WAYLAND_swap_buffers_with_timeout.
* -flibit * -flibit
*/ */
if (window->flags & SDL_WINDOW_HIDDEN) { if (data->surface_status != WAYLAND_SURFACE_STATUS_SHOWN &&
data->surface_status != WAYLAND_SURFACE_STATUS_WAITING_FOR_FRAME) {
return 0; return 0;
} }

View File

@@ -206,6 +206,7 @@ SDL_WAYLAND_SYM(void, libdecor_frame_set_parent, (struct libdecor_frame *,\
struct libdecor_frame *)) struct libdecor_frame *))
SDL_WAYLAND_SYM(struct xdg_surface *, libdecor_frame_get_xdg_surface, (struct libdecor_frame *)) SDL_WAYLAND_SYM(struct xdg_surface *, libdecor_frame_get_xdg_surface, (struct libdecor_frame *))
SDL_WAYLAND_SYM(struct xdg_toplevel *, libdecor_frame_get_xdg_toplevel, (struct libdecor_frame *)) SDL_WAYLAND_SYM(struct xdg_toplevel *, libdecor_frame_get_xdg_toplevel, (struct libdecor_frame *))
SDL_WAYLAND_SYM(void, libdecor_frame_translate_coordinate, (struct libdecor_frame *, int, int, int *, int *))
SDL_WAYLAND_SYM(void, libdecor_frame_map, (struct libdecor_frame *)) SDL_WAYLAND_SYM(void, libdecor_frame_map, (struct libdecor_frame *))
SDL_WAYLAND_SYM(struct libdecor_state *, libdecor_state_new, (int, int)) SDL_WAYLAND_SYM(struct libdecor_state *, libdecor_state_new, (int, int))
SDL_WAYLAND_SYM(void, libdecor_state_free, (struct libdecor_state *)) SDL_WAYLAND_SYM(void, libdecor_state_free, (struct libdecor_state *))

View File

@@ -239,6 +239,7 @@ static SDL_VideoDevice *Wayland_CreateDevice(void)
device->RestoreWindow = Wayland_RestoreWindow; device->RestoreWindow = Wayland_RestoreWindow;
device->SetWindowBordered = Wayland_SetWindowBordered; device->SetWindowBordered = Wayland_SetWindowBordered;
device->SetWindowResizable = Wayland_SetWindowResizable; device->SetWindowResizable = Wayland_SetWindowResizable;
device->SetWindowPosition = Wayland_SetWindowPosition;
device->SetWindowSize = Wayland_SetWindowSize; device->SetWindowSize = Wayland_SetWindowSize;
device->SetWindowMinimumSize = Wayland_SetWindowMinimumSize; device->SetWindowMinimumSize = Wayland_SetWindowMinimumSize;
device->SetWindowMaximumSize = Wayland_SetWindowMaximumSize; device->SetWindowMaximumSize = Wayland_SetWindowMaximumSize;
@@ -275,7 +276,8 @@ static SDL_VideoDevice *Wayland_CreateDevice(void)
device->free = Wayland_DeleteDevice; device->free = Wayland_DeleteDevice;
device->quirk_flags = VIDEO_DEVICE_QUIRK_MODE_SWITCHING_EMULATED | device->quirk_flags = VIDEO_DEVICE_QUIRK_MODE_SWITCHING_EMULATED |
VIDEO_DEVICE_QUIRK_DISABLE_UNSET_FULLSCREEN_ON_MINIMIZE; VIDEO_DEVICE_QUIRK_DISABLE_UNSET_FULLSCREEN_ON_MINIMIZE |
VIDEO_DEVICE_QUIRK_HAS_POPUP_WINDOW_SUPPORT;
return device; return device;
} }

View File

@@ -24,8 +24,7 @@
#if SDL_VIDEO_DRIVER_WAYLAND #if SDL_VIDEO_DRIVER_WAYLAND
#include "../SDL_sysvideo.h" #include "../SDL_sysvideo.h"
#include "../../events/SDL_windowevents_c.h" #include "../../events/SDL_events_c.h"
#include "../../events/SDL_mouse_c.h"
#include "../SDL_egl_c.h" #include "../SDL_egl_c.h"
#include "SDL_waylandevents_c.h" #include "SDL_waylandevents_c.h"
#include "SDL_waylandwindow.h" #include "SDL_waylandwindow.h"
@@ -243,6 +242,27 @@ static void ConfigureWindowGeometry(SDL_Window *window)
SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED, data->drawable_width, data->drawable_height); SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED, data->drawable_width, data->drawable_height);
} }
static void EnsurePopupIsWithinParent(SDL_Window *window)
{
/* Per the spec, popup windows *must* overlap the parent window.
* Failure to do so on a compositor that enforces this restriction
* will result in the window being spuriously closed at best, and
* a protocol violation at worst.
*/
if (window->x + window->w < 0) {
window->x = -window->w;
}
if (window->y + window->h < 0) {
window->y = -window->h;
}
if (window->x > window->parent->w) {
window->x = window->parent->w;
}
if (window->y > window->parent->h) {
window->y = window->parent->h;
}
}
static void CommitLibdecorFrame(SDL_Window *window) static void CommitLibdecorFrame(SDL_Window *window)
{ {
#ifdef HAVE_LIBDECOR_H #ifdef HAVE_LIBDECOR_H
@@ -405,10 +425,26 @@ static void UpdateWindowFullscreen(SDL_Window *window, SDL_bool fullscreen)
} }
} }
static const struct wl_callback_listener surface_damage_frame_listener; static void GetPopupPosition(SDL_Window *popup, int x, int y, int *adj_x, int *adj_y)
static void surface_damage_frame_done(void *data, struct wl_callback *cb, uint32_t time)
{ {
/* Adjust the popup positioning, if necessary */
#ifdef HAVE_LIBDECOR_H
if (popup->parent->driverdata->shell_surface_type == WAYLAND_SURFACE_LIBDECOR) {
libdecor_frame_translate_coordinate(popup->parent->driverdata->shell_surface.libdecor.frame,
x, y, adj_x, adj_y);
} else
#endif
{
*adj_x = x;
*adj_y = y;
}
}
static const struct wl_callback_listener surface_frame_listener;
static void surface_frame_done(void *data, struct wl_callback *cb, uint32_t time)
{
SDL_Window *w;
SDL_WindowData *wind = (SDL_WindowData *)data; SDL_WindowData *wind = (SDL_WindowData *)data;
/* /*
@@ -423,13 +459,24 @@ static void surface_damage_frame_done(void *data, struct wl_callback *cb, uint32
wind->wl_window_width, wind->wl_window_height); wind->wl_window_width, wind->wl_window_height);
} }
if (wind->surface_status == WAYLAND_SURFACE_STATUS_WAITING_FOR_FRAME) {
wind->surface_status = WAYLAND_SURFACE_STATUS_SHOWN;
/* If any child windows are waiting on this window to be shown, show them now */
for (w = wind->sdlwindow->first_child; w != NULL; w = w->next_sibling) {
if (w->driverdata->surface_status == WAYLAND_SURFACE_STATUS_SHOW_PENDING) {
Wayland_ShowWindow(SDL_GetVideoDevice(), w);
}
}
}
wl_callback_destroy(cb); wl_callback_destroy(cb);
wind->surface_damage_frame_callback = wl_surface_frame(wind->surface); wind->surface_frame_callback = wl_surface_frame(wind->surface);
wl_callback_add_listener(wind->surface_damage_frame_callback, &surface_damage_frame_listener, data); wl_callback_add_listener(wind->surface_frame_callback, &surface_frame_listener, data);
} }
static const struct wl_callback_listener surface_damage_frame_listener = { static const struct wl_callback_listener surface_frame_listener = {
surface_damage_frame_done surface_frame_done
}; };
static const struct wl_callback_listener gles_swap_frame_listener; static const struct wl_callback_listener gles_swap_frame_listener;
@@ -570,6 +617,9 @@ static void handle_configure_xdg_toplevel(void *data,
wind->requested_window_width = width; wind->requested_window_width = width;
wind->requested_window_height = height; wind->requested_window_height = height;
wind->floating = floating; wind->floating = floating;
if (wind->surface_status == WAYLAND_SURFACE_STATUS_WAITING_FOR_CONFIGURE) {
wind->surface_status = WAYLAND_SURFACE_STATUS_WAITING_FOR_FRAME;
}
} }
static void handle_close_xdg_toplevel(void *data, struct xdg_toplevel *xdg_toplevel) static void handle_close_xdg_toplevel(void *data, struct xdg_toplevel *xdg_toplevel)
@@ -590,7 +640,21 @@ static void handle_configure_xdg_popup(void *data,
int32_t width, int32_t width,
int32_t height) int32_t height)
{ {
/* No-op, we don't use x/y and width/height are fixed-size */ /* Popups can't be resized, so only position data is sent. */
SDL_WindowData *wind = (SDL_WindowData *)data;
int offset_x, offset_y;
/* Adjust the position if it was offset for libdecor */
GetPopupPosition(wind->sdlwindow, 0, 0, &offset_x, &offset_y);
x -= offset_x;
y -= offset_y;
SDL_SendWindowEvent(wind->sdlwindow, SDL_EVENT_WINDOW_RESIZED, width, height);
SDL_SendWindowEvent(wind->sdlwindow, SDL_EVENT_WINDOW_MOVED, x, y);
if (wind->surface_status == WAYLAND_SURFACE_STATUS_WAITING_FOR_CONFIGURE) {
wind->surface_status = WAYLAND_SURFACE_STATUS_WAITING_FOR_FRAME;
}
} }
static void handle_done_xdg_popup(void *data, struct xdg_popup *xdg_popup) static void handle_done_xdg_popup(void *data, struct xdg_popup *xdg_popup)
@@ -612,27 +676,6 @@ static const struct xdg_popup_listener popup_listener_xdg = {
handle_repositioned_xdg_popup handle_repositioned_xdg_popup
}; };
#define TOOLTIP_CURSOR_OFFSET 8 /* FIXME: Arbitrary, eyeballed from X tooltip */
static int Wayland_PopupWatch(void *data, SDL_Event *event)
{
if (event->type == SDL_EVENT_MOUSE_MOTION) {
SDL_Window *window = (SDL_Window *)data;
SDL_WindowData *wind = window->driverdata;
/* Coordinates might be relative to the popup, which we don't want */
if (event->motion.windowID == wind->shell_surface.xdg.roleobj.popup.parentID) {
xdg_positioner_set_offset(wind->shell_surface.xdg.roleobj.popup.positioner,
event->motion.x + TOOLTIP_CURSOR_OFFSET,
event->motion.y + TOOLTIP_CURSOR_OFFSET);
xdg_popup_reposition(wind->shell_surface.xdg.roleobj.popup.popup,
wind->shell_surface.xdg.roleobj.popup.positioner,
0);
}
}
return 1;
}
static void handle_configure_zxdg_decoration(void *data, static void handle_configure_zxdg_decoration(void *data,
struct zxdg_toplevel_decoration_v1 *zxdg_toplevel_decoration_v1, struct zxdg_toplevel_decoration_v1 *zxdg_toplevel_decoration_v1,
uint32_t mode) uint32_t mode)
@@ -841,6 +884,9 @@ static void decoration_frame_configure(struct libdecor_frame *frame,
LibdecorGetMinContentSize(frame, &wind->system_min_required_width, &wind->system_min_required_height); LibdecorGetMinContentSize(frame, &wind->system_min_required_width, &wind->system_min_required_height);
wind->shell_surface.libdecor.initial_configure_seen = SDL_TRUE; wind->shell_surface.libdecor.initial_configure_seen = SDL_TRUE;
} }
if (wind->surface_status == WAYLAND_SURFACE_STATUS_WAITING_FOR_CONFIGURE) {
wind->surface_status = WAYLAND_SURFACE_STATUS_WAITING_FOR_FRAME;
}
/* Update the resize capability. Since this will change the capabilities and /* Update the resize capability. Since this will change the capabilities and
* commit a new frame state with the last known content dimension, this has * commit a new frame state with the last known content dimension, this has
@@ -930,6 +976,7 @@ static void update_scale_factor(SDL_WindowData *window)
*/ */
static void Wayland_move_window(SDL_Window *window, SDL_DisplayData *driverdata) static void Wayland_move_window(SDL_Window *window, SDL_DisplayData *driverdata)
{ {
SDL_WindowData *wind = window->driverdata;
SDL_DisplayID *displays; SDL_DisplayID *displays;
int i; int i;
@@ -959,8 +1006,10 @@ static void Wayland_move_window(SDL_Window *window, SDL_DisplayData *driverdata)
SDL_Rect bounds; SDL_Rect bounds;
SDL_GetDisplayBounds(displays[i], &bounds); SDL_GetDisplayBounds(displays[i], &bounds);
window->driverdata->last_displayID = displays[i]; wind->last_displayID = displays[i];
SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_MOVED, bounds.x, bounds.y); if (wind->shell_surface_type != WAYLAND_SURFACE_XDG_POPUP) {
SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_MOVED, bounds.x, bounds.y);
}
break; break;
} }
} }
@@ -1034,6 +1083,26 @@ static const struct wl_surface_listener surface_listener = {
handle_surface_leave handle_surface_leave
}; };
static void SetKeyboardFocus(SDL_Window *window)
{
SDL_Window *kb_focus = SDL_GetKeyboardFocus();
SDL_Window *topmost = window;
/* Find the topmost parent */
while (topmost->parent != NULL) {
topmost = topmost->parent;
}
topmost->driverdata->keyboard_focus = window;
/* Clear the mouse capture flags before changing keyboard focus */
if (kb_focus) {
kb_focus->flags &= ~SDL_WINDOW_MOUSE_CAPTURE;
}
window->flags &= ~SDL_WINDOW_MOUSE_CAPTURE;
SDL_SetKeyboardFocus(window);
}
int Wayland_GetWindowWMInfo(_THIS, SDL_Window *window, SDL_SysWMinfo *info) int Wayland_GetWindowWMInfo(_THIS, SDL_Window *window, SDL_SysWMinfo *info)
{ {
SDL_VideoData *viddata = _this->driverdata; SDL_VideoData *viddata = _this->driverdata;
@@ -1113,6 +1182,24 @@ void Wayland_ShowWindow(_THIS, SDL_Window *window)
{ {
SDL_VideoData *c = _this->driverdata; SDL_VideoData *c = _this->driverdata;
SDL_WindowData *data = window->driverdata; SDL_WindowData *data = window->driverdata;
const SDL_bool show_was_pending = data->surface_status == WAYLAND_SURFACE_STATUS_SHOW_PENDING;
/* If this is a child window, the parent *must* be in the final shown state,
* meaning that it has received a configure event, followed by a frame callback.
* If not, a race condition can result, with effects ranging from the child
* window to spuriously closing to protocol errors.
*
* If waiting on the parent window, set the pending status and the window will
* be shown when the parent is in the shown state.
*/
if (window->parent) {
if (window->parent->driverdata->surface_status != WAYLAND_SURFACE_STATUS_SHOWN) {
data->surface_status = WAYLAND_SURFACE_STATUS_SHOW_PENDING;
return;
}
}
data->surface_status = WAYLAND_SURFACE_STATUS_WAITING_FOR_CONFIGURE;
/* Detach any previous buffers before resetting everything, otherwise when /* Detach any previous buffers before resetting everything, otherwise when
* calling this a second time you'll get an annoying protocol error! * calling this a second time you'll get an annoying protocol error!
@@ -1152,40 +1239,58 @@ void Wayland_ShowWindow(_THIS, SDL_Window *window)
} }
} else } else
#endif #endif
if (c->shell.xdg) { if ((data->shell_surface_type == WAYLAND_SURFACE_XDG_TOPLEVEL || data->shell_surface_type == WAYLAND_SURFACE_XDG_POPUP) && c->shell.xdg) {
data->shell_surface.xdg.surface = xdg_wm_base_get_xdg_surface(c->shell.xdg, data->surface); data->shell_surface.xdg.surface = xdg_wm_base_get_xdg_surface(c->shell.xdg, data->surface);
xdg_surface_set_user_data(data->shell_surface.xdg.surface, data); xdg_surface_set_user_data(data->shell_surface.xdg.surface, data);
xdg_surface_add_listener(data->shell_surface.xdg.surface, &shell_surface_listener_xdg, data); xdg_surface_add_listener(data->shell_surface.xdg.surface, &shell_surface_listener_xdg, data);
if (data->shell_surface_type == WAYLAND_SURFACE_XDG_POPUP) { if (data->shell_surface_type == WAYLAND_SURFACE_XDG_POPUP) {
SDL_Mouse *mouse = SDL_GetMouse(); SDL_Window *parent = window->parent;
SDL_Window *focused = SDL_GetMouseFocus(); SDL_WindowData *parent_data = parent->driverdata;
SDL_WindowData *focuseddata = focused->driverdata; struct xdg_surface *parent_xdg_surface = NULL;
int position_x = 0, position_y = 0;
/* This popup may be a child of another popup! */ /* Configure the popup parameters */
data->shell_surface.xdg.roleobj.popup.parentID = SDL_GetWindowID(focused); #ifdef HAVE_LIBDECOR_H
data->shell_surface.xdg.roleobj.popup.child = NULL; if (parent_data->shell_surface_type == WAYLAND_SURFACE_LIBDECOR) {
if (focuseddata->shell_surface_type == WAYLAND_SURFACE_XDG_POPUP) { parent_xdg_surface = libdecor_frame_get_xdg_surface(parent_data->shell_surface.libdecor.frame);
SDL_assert(focuseddata->shell_surface.xdg.roleobj.popup.child == NULL); } else
focuseddata->shell_surface.xdg.roleobj.popup.child = window; #endif
if (parent_data->shell_surface_type == WAYLAND_SURFACE_XDG_TOPLEVEL ||
parent_data->shell_surface_type == WAYLAND_SURFACE_XDG_POPUP) {
parent_xdg_surface = parent_data->shell_surface.xdg.surface;
} }
/* Set up the positioner for the popup */ /* Set up the positioner for the popup and configure the constraints */
data->shell_surface.xdg.roleobj.popup.positioner = xdg_wm_base_create_positioner(c->shell.xdg); data->shell_surface.xdg.roleobj.popup.positioner = xdg_wm_base_create_positioner(c->shell.xdg);
xdg_positioner_set_offset(data->shell_surface.xdg.roleobj.popup.positioner, xdg_positioner_set_anchor(data->shell_surface.xdg.roleobj.popup.positioner, XDG_POSITIONER_ANCHOR_TOP_LEFT);
mouse->x + TOOLTIP_CURSOR_OFFSET, xdg_positioner_set_anchor_rect(data->shell_surface.xdg.roleobj.popup.positioner, 0, 0, parent->w, parent->h);
mouse->y + TOOLTIP_CURSOR_OFFSET); xdg_positioner_set_constraint_adjustment(data->shell_surface.xdg.roleobj.popup.positioner,
XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_SLIDE_X | XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_SLIDE_Y);
xdg_positioner_set_gravity(data->shell_surface.xdg.roleobj.popup.positioner, XDG_POSITIONER_GRAVITY_BOTTOM_RIGHT);
xdg_positioner_set_size(data->shell_surface.xdg.roleobj.popup.positioner, window->w, window->h);
/* Set the popup initial position */
GetPopupPosition(window, window->x, window->y, &position_x, &position_y);
xdg_positioner_set_offset(data->shell_surface.xdg.roleobj.popup.positioner, position_x, position_y);
/* Assign the popup role */ /* Assign the popup role */
data->shell_surface.xdg.roleobj.popup.popup = xdg_surface_get_popup(data->shell_surface.xdg.surface, data->shell_surface.xdg.roleobj.popup.popup = xdg_surface_get_popup(data->shell_surface.xdg.surface,
focuseddata->shell_surface.xdg.surface, parent_xdg_surface,
data->shell_surface.xdg.roleobj.popup.positioner); data->shell_surface.xdg.roleobj.popup.positioner);
xdg_popup_add_listener(data->shell_surface.xdg.roleobj.popup.popup, &popup_listener_xdg, data); xdg_popup_add_listener(data->shell_surface.xdg.roleobj.popup.popup, &popup_listener_xdg, data);
/* For tooltips, track mouse motion so it follows the cursor */
if (window->flags & SDL_WINDOW_TOOLTIP) { if (window->flags & SDL_WINDOW_TOOLTIP) {
if (xdg_popup_get_version(data->shell_surface.xdg.roleobj.popup.popup) >= 3) { struct wl_region *region;
SDL_AddEventWatch(Wayland_PopupWatch, window);
/* Tooltips can't be interacted with, so turn off the input region to avoid blocking anything behind them */
region = wl_compositor_create_region(c->compositor);
wl_region_add(region, 0, 0, 0, 0);
wl_surface_set_input_region(data->surface, region);
wl_region_destroy(region);
} else if (window->flags & SDL_WINDOW_POPUP_MENU) {
if (window->parent == SDL_GetKeyboardFocus()) {
SetKeyboardFocus(window);
} }
} }
} else { } else {
@@ -1297,7 +1402,11 @@ void Wayland_ShowWindow(_THIS, SDL_Window *window)
* Roundtrip required to avoid a possible protocol violation when * Roundtrip required to avoid a possible protocol violation when
* HideWindow was called immediately before ShowWindow. * HideWindow was called immediately before ShowWindow.
*/ */
WAYLAND_wl_display_roundtrip(c->display); if (!show_was_pending) {
while (data->surface_status == WAYLAND_SURFACE_STATUS_WAITING_FOR_CONFIGURE) {
WAYLAND_wl_display_roundtrip(c->display);
}
}
} }
static void Wayland_ReleasePopup(_THIS, SDL_Window *popup) static void Wayland_ReleasePopup(_THIS, SDL_Window *popup)
@@ -1318,17 +1427,19 @@ static void Wayland_ReleasePopup(_THIS, SDL_Window *popup)
return; return;
} }
/* Release the child _first_, otherwise a protocol error triggers */ if (popup->flags & SDL_WINDOW_POPUP_MENU) {
if (popupdata->shell_surface.xdg.roleobj.popup.child != NULL) { if (popup == SDL_GetKeyboardFocus()) {
Wayland_ReleasePopup(_this, popupdata->shell_surface.xdg.roleobj.popup.child); SDL_Window *new_focus = popup->parent;
popupdata->shell_surface.xdg.roleobj.popup.child = NULL;
}
if (popup->flags & SDL_WINDOW_TOOLTIP) { /* Find the highest level window that isn't being hidden or destroyed. */
if (xdg_popup_get_version(popupdata->shell_surface.xdg.roleobj.popup.popup) >= 3) { while (new_focus->parent != NULL && (new_focus->is_hiding || new_focus->is_destroying)) {
SDL_DelEventWatch(Wayland_PopupWatch, popup); new_focus = new_focus->parent;
}
SetKeyboardFocus(new_focus);
} }
} }
xdg_popup_destroy(popupdata->shell_surface.xdg.roleobj.popup.popup); xdg_popup_destroy(popupdata->shell_surface.xdg.roleobj.popup.popup);
xdg_positioner_destroy(popupdata->shell_surface.xdg.roleobj.popup.positioner); xdg_positioner_destroy(popupdata->shell_surface.xdg.roleobj.popup.positioner);
popupdata->shell_surface.xdg.roleobj.popup.popup = NULL; popupdata->shell_surface.xdg.roleobj.popup.popup = NULL;
@@ -1340,14 +1451,18 @@ void Wayland_HideWindow(_THIS, SDL_Window *window)
SDL_VideoData *data = _this->driverdata; SDL_VideoData *data = _this->driverdata;
SDL_WindowData *wind = window->driverdata; SDL_WindowData *wind = window->driverdata;
wind->surface_status = WAYLAND_SURFACE_STATUS_HIDDEN;
if (wind->server_decoration) { if (wind->server_decoration) {
zxdg_toplevel_decoration_v1_destroy(wind->server_decoration); zxdg_toplevel_decoration_v1_destroy(wind->server_decoration);
wind->server_decoration = NULL; wind->server_decoration = NULL;
} }
/* Be sure to detach after this is done, otherwise ShowWindow crashes! */ /* Be sure to detach after this is done, otherwise ShowWindow crashes! */
wl_surface_attach(wind->surface, NULL, 0, 0); if (wind->shell_surface_type != WAYLAND_SURFACE_XDG_POPUP) {
wl_surface_commit(wind->surface); wl_surface_attach(wind->surface, NULL, 0, 0);
wl_surface_commit(wind->surface);
}
#ifdef HAVE_LIBDECOR_H #ifdef HAVE_LIBDECOR_H
if (wind->shell_surface_type == WAYLAND_SURFACE_LIBDECOR) { if (wind->shell_surface_type == WAYLAND_SURFACE_LIBDECOR) {
@@ -1833,6 +1948,10 @@ int Wayland_CreateWindow(_THIS, SDL_Window *window)
window->y = 0; window->y = 0;
} }
if (SDL_WINDOW_IS_POPUP(window)) {
EnsurePopupIsWithinParent(window);
}
data->waylandData = c; data->waylandData = c;
data->sdlwindow = window; data->sdlwindow = window;
@@ -1877,8 +1996,8 @@ int Wayland_CreateWindow(_THIS, SDL_Window *window)
} }
/* Fire a callback when the compositor wants a new frame to set the surface damage region. */ /* Fire a callback when the compositor wants a new frame to set the surface damage region. */
data->surface_damage_frame_callback = wl_surface_frame(data->surface); data->surface_frame_callback = wl_surface_frame(data->surface);
wl_callback_add_listener(data->surface_damage_frame_callback, &surface_damage_frame_listener, data); wl_callback_add_listener(data->surface_frame_callback, &surface_frame_listener, data);
#ifdef SDL_VIDEO_DRIVER_WAYLAND_QT_TOUCH #ifdef SDL_VIDEO_DRIVER_WAYLAND_QT_TOUCH
if (c->surface_extension) { if (c->surface_extension) {
@@ -1927,21 +2046,18 @@ int Wayland_CreateWindow(_THIS, SDL_Window *window)
/* We may need to create an idle inhibitor for this new window */ /* We may need to create an idle inhibitor for this new window */
Wayland_SuspendScreenSaver(_this); Wayland_SuspendScreenSaver(_this);
#define IS_POPUP(window) \
(window->flags & (SDL_WINDOW_TOOLTIP | SDL_WINDOW_POPUP_MENU))
#ifdef HAVE_LIBDECOR_H #ifdef HAVE_LIBDECOR_H
if (c->shell.libdecor && !IS_POPUP(window)) { if (c->shell.libdecor && !SDL_WINDOW_IS_POPUP(window)) {
data->shell_surface_type = WAYLAND_SURFACE_LIBDECOR; data->shell_surface_type = WAYLAND_SURFACE_LIBDECOR;
} else } else
#endif #endif
if (c->shell.xdg) { if (c->shell.xdg) {
if (IS_POPUP(window)) { if (SDL_WINDOW_IS_POPUP(window)) {
data->shell_surface_type = WAYLAND_SURFACE_XDG_POPUP; data->shell_surface_type = WAYLAND_SURFACE_XDG_POPUP;
} else { } else {
data->shell_surface_type = WAYLAND_SURFACE_XDG_TOPLEVEL; data->shell_surface_type = WAYLAND_SURFACE_XDG_TOPLEVEL;
} }
} /* All other cases will be WAYLAND_SURFACE_UNKNOWN */ } /* All other cases will be WAYLAND_SURFACE_UNKNOWN */
#undef IS_POPUP
return 0; return 0;
} }
@@ -1956,6 +2072,24 @@ void Wayland_SetWindowMaximumSize(_THIS, SDL_Window *window)
SetMinMaxDimensions(window, SDL_TRUE); SetMinMaxDimensions(window, SDL_TRUE);
} }
void Wayland_SetWindowPosition(_THIS, SDL_Window *window)
{
SDL_WindowData *wind = window->driverdata;
/* Only popup windows can be positioned relative to the parent. */
if (wind->shell_surface_type == WAYLAND_SURFACE_XDG_POPUP && !(window->flags & SDL_WINDOW_HIDDEN) &&
xdg_popup_get_version(wind->shell_surface.xdg.roleobj.popup.popup) >= XDG_POPUP_REPOSITION_SINCE_VERSION) {
int x, y;
EnsurePopupIsWithinParent(window);
GetPopupPosition(window, window->x, window->y, &x, &y);
xdg_positioner_set_offset(wind->shell_surface.xdg.roleobj.popup.positioner, x, y);
xdg_popup_reposition(wind->shell_surface.xdg.roleobj.popup.popup,
wind->shell_surface.xdg.roleobj.popup.positioner,
0);
}
}
void Wayland_SetWindowSize(_THIS, SDL_Window *window) void Wayland_SetWindowSize(_THIS, SDL_Window *window)
{ {
SDL_WindowData *wind = window->driverdata; SDL_WindowData *wind = window->driverdata;
@@ -2059,7 +2193,7 @@ void Wayland_DestroyWindow(_THIS, SDL_Window *window)
SDL_VideoData *data = _this->driverdata; SDL_VideoData *data = _this->driverdata;
SDL_WindowData *wind = window->driverdata; SDL_WindowData *wind = window->driverdata;
if (data) { if (data && wind) {
#if SDL_VIDEO_OPENGL_EGL #if SDL_VIDEO_OPENGL_EGL
if (wind->egl_surface) { if (wind->egl_surface) {
SDL_EGL_DestroySurface(_this, wind->egl_surface); SDL_EGL_DestroySurface(_this, wind->egl_surface);
@@ -2093,8 +2227,8 @@ void Wayland_DestroyWindow(_THIS, SDL_Window *window)
wl_callback_destroy(wind->gles_swap_frame_callback); wl_callback_destroy(wind->gles_swap_frame_callback);
} }
if (wind->surface_damage_frame_callback) { if (wind->surface_frame_callback) {
wl_callback_destroy(wind->surface_damage_frame_callback); wl_callback_destroy(wind->surface_frame_callback);
} }
#ifdef SDL_VIDEO_DRIVER_WAYLAND_QT_TOUCH #ifdef SDL_VIDEO_DRIVER_WAYLAND_QT_TOUCH

View File

@@ -41,7 +41,7 @@ struct SDL_WindowData
struct wl_callback *gles_swap_frame_callback; struct wl_callback *gles_swap_frame_callback;
struct wl_event_queue *gles_swap_frame_event_queue; struct wl_event_queue *gles_swap_frame_event_queue;
struct wl_surface *gles_swap_frame_surface_wrapper; struct wl_surface *gles_swap_frame_surface_wrapper;
struct wl_callback *surface_damage_frame_callback; struct wl_callback *surface_frame_callback;
union union
{ {
@@ -62,8 +62,6 @@ struct SDL_WindowData
{ {
struct xdg_popup *popup; struct xdg_popup *popup;
struct xdg_positioner *positioner; struct xdg_positioner *positioner;
Uint32 parentID;
SDL_Window *child;
} popup; } popup;
} roleobj; } roleobj;
SDL_bool initial_configure_seen; SDL_bool initial_configure_seen;
@@ -76,6 +74,14 @@ struct SDL_WindowData
WAYLAND_SURFACE_XDG_POPUP, WAYLAND_SURFACE_XDG_POPUP,
WAYLAND_SURFACE_LIBDECOR WAYLAND_SURFACE_LIBDECOR
} shell_surface_type; } shell_surface_type;
enum
{
WAYLAND_SURFACE_STATUS_HIDDEN = 0,
WAYLAND_SURFACE_STATUS_WAITING_FOR_CONFIGURE,
WAYLAND_SURFACE_STATUS_WAITING_FOR_FRAME,
WAYLAND_SURFACE_STATUS_SHOW_PENDING,
WAYLAND_SURFACE_STATUS_SHOWN
} surface_status;
struct wl_egl_window *egl_window; struct wl_egl_window *egl_window;
struct SDL_WaylandInput *keyboard_device; struct SDL_WaylandInput *keyboard_device;
@@ -103,6 +109,8 @@ struct SDL_WindowData
SDL_DisplayData **outputs; SDL_DisplayData **outputs;
int num_outputs; int num_outputs;
SDL_Window *keyboard_focus;
float windowed_scale_factor; float windowed_scale_factor;
float pointer_scale_x; float pointer_scale_x;
float pointer_scale_y; float pointer_scale_y;
@@ -133,6 +141,7 @@ extern void Wayland_RestoreWindow(_THIS, SDL_Window *window);
extern void Wayland_SetWindowBordered(_THIS, SDL_Window *window, SDL_bool bordered); extern void Wayland_SetWindowBordered(_THIS, SDL_Window *window, SDL_bool bordered);
extern void Wayland_SetWindowResizable(_THIS, SDL_Window *window, SDL_bool resizable); extern void Wayland_SetWindowResizable(_THIS, SDL_Window *window, SDL_bool resizable);
extern int Wayland_CreateWindow(_THIS, SDL_Window *window); extern int Wayland_CreateWindow(_THIS, SDL_Window *window);
extern void Wayland_SetWindowPosition(_THIS, SDL_Window *window);
extern void Wayland_SetWindowSize(_THIS, SDL_Window *window); extern void Wayland_SetWindowSize(_THIS, SDL_Window *window);
extern void Wayland_SetWindowMinimumSize(_THIS, SDL_Window *window); extern void Wayland_SetWindowMinimumSize(_THIS, SDL_Window *window);
extern void Wayland_SetWindowMaximumSize(_THIS, SDL_Window *window); extern void Wayland_SetWindowMaximumSize(_THIS, SDL_Window *window);