[PATCH v3 0/6] MR11248: winewayland: Use xdg-popup instead of subsurfaces for owned or equivalent windows.
Same as https://gitlab.winehq.org/wine/wine/-/merge_requests/11178 with changes to allow resizing and sharing the logic between toplevel and popups. -- v3: winewayland: Implement xdg-popup for unmanaged windows. winewayland: Pass the wayland surface to wayland_surface_config_is_compatible. winewayland: Use absolute coordinates in wayland_surface_config rect. winewayland: Convert wayland_surface_config from surface coordinates earlier. winewayland: Introduce is_rect_bigger / is_rect_smaller helpers. winewayland: Don't request any surface geometry x,y. https://gitlab.winehq.org/wine/wine/-/merge_requests/11248
From: Etaash Mathamsetty <etaash.mathamsetty@gmail.com> --- dlls/winewayland.drv/wayland_surface.c | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/dlls/winewayland.drv/wayland_surface.c b/dlls/winewayland.drv/wayland_surface.c index 0dae0922179..015777ee9ea 100644 --- a/dlls/winewayland.drv/wayland_surface.c +++ b/dlls/winewayland.drv/wayland_surface.c @@ -640,19 +640,15 @@ static void wayland_surface_reconfigure_geometry(struct wayland_surface *surface } TRACE("Window is too large for Wayland state, using subregion\n"); } - else - { - OffsetRect(&rect, -rect.left, -rect.top); - } TRACE("hwnd=%p geometry=%s\n", surface->hwnd, wine_dbgstr_rect(&rect)); if (!IsRectEmpty(&rect)) { int width = rect.right - rect.left, height = rect.bottom - rect.top; - xdg_surface_set_window_geometry(surface->xdg_surface, - rect.left, rect.top, - width, height); + + xdg_surface_set_window_geometry(surface->xdg_surface, 0, 0, width, height); + if (surface->window.resizeable) { xdg_toplevel_set_min_size(surface->xdg_toplevel, 0, 0); -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/11248
From: Rémi Bernon <rbernon@codeweavers.com> --- dlls/winewayland.drv/wayland_surface.c | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/dlls/winewayland.drv/wayland_surface.c b/dlls/winewayland.drv/wayland_surface.c index 015777ee9ea..c4adadd7f94 100644 --- a/dlls/winewayland.drv/wayland_surface.c +++ b/dlls/winewayland.drv/wayland_surface.c @@ -545,6 +545,16 @@ void wayland_surface_attach_shm(struct wayland_surface *surface, surface->content_height = win_height; } +static BOOL is_rect_bigger(RECT a, RECT b) +{ + return a.right - a.left > b.right - b.left || a.bottom - a.top > b.bottom - b.top; +} + +static BOOL is_rect_smaller(RECT a, RECT b) +{ + return a.right - a.left < b.right - b.left || a.bottom - a.top < b.bottom - b.top; +} + /********************************************************************** * wayland_surface_config_is_compatible * @@ -571,12 +581,7 @@ BOOL wayland_surface_config_is_compatible(struct wayland_surface_config *conf, R /* The maximized state requires the configured size. During surface * reconfiguration we can use surface geometry to provide smaller areas * from larger sizes, so only smaller sizes are incompatible. */ - if ((conf->state & WAYLAND_SURFACE_CONFIG_STATE_MAXIMIZED) && - (rect.right - rect.left < conf->rect.right - conf->rect.left || - rect.bottom - rect.top < conf->rect.bottom - conf->rect.top)) - { - return FALSE; - } + if ((conf->state & WAYLAND_SURFACE_CONFIG_STATE_MAXIMIZED) && is_rect_smaller(rect, conf->rect)) return FALSE; return TRUE; } @@ -616,8 +621,7 @@ static void wayland_surface_reconfigure_geometry(struct wayland_surface *surface * largest visible (from Windows' perspective) subregion of the window. */ if ((surface->current.state & (WAYLAND_SURFACE_CONFIG_STATE_MAXIMIZED | WAYLAND_SURFACE_CONFIG_STATE_FULLSCREEN)) && - (rect.right - rect.left > surface->current.rect.right - surface->current.rect.left || - rect.bottom - rect.top > surface->current.rect.bottom - surface->current.rect.top)) + is_rect_bigger(rect, surface->current.rect)) { wayland_surface_get_rect_in_monitor(surface, &rect); @@ -627,8 +631,7 @@ static void wayland_surface_reconfigure_geometry(struct wayland_surface *surface * fall back to an appropriately sized rect at the top-left. */ if ((surface->current.state & WAYLAND_SURFACE_CONFIG_STATE_MAXIMIZED) && !(surface->current.state & WAYLAND_SURFACE_CONFIG_STATE_FULLSCREEN) && - (rect.right - rect.left < surface->current.rect.right - surface->current.rect.left || - rect.bottom - rect.top < surface->current.rect.bottom - surface->current.rect.top)) + is_rect_smaller(rect, surface->current.rect)) { SetRect(&rect, 0, 0, surface->current.rect.right - surface->current.rect.left, surface->current.rect.bottom - surface->current.rect.top); -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/11248
From: Rémi Bernon <rbernon@codeweavers.com> --- dlls/winewayland.drv/wayland_surface.c | 12 +++++++----- dlls/winewayland.drv/window.c | 7 ++----- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/dlls/winewayland.drv/wayland_surface.c b/dlls/winewayland.drv/wayland_surface.c index c4adadd7f94..267b45a1085 100644 --- a/dlls/winewayland.drv/wayland_surface.c +++ b/dlls/winewayland.drv/wayland_surface.c @@ -87,6 +87,7 @@ static void xdg_toplevel_handle_configure(void *private, HWND hwnd = private; uint32_t *state; enum wayland_surface_config_state config_state = 0; + RECT rect = { 0, 0, width, height }; struct wayland_win_data *data; wl_array_for_each(state, states) @@ -113,13 +114,13 @@ static void xdg_toplevel_handle_configure(void *private, } } - TRACE("hwnd=%p %dx%d,%#x\n", hwnd, width, height, config_state); + TRACE("hwnd=%p rect=%s state=%#x\n", hwnd, wine_dbgstr_rect(&rect), config_state); if (!(data = wayland_win_data_get(hwnd))) return; if ((surface = data->wayland_surface) && wayland_surface_is_toplevel(surface)) { - SetRect(&surface->pending.rect, 0, 0, width, height); + surface->pending.rect = map_rect_from_surface(surface, rect); surface->pending.state = config_state; } @@ -625,8 +626,6 @@ static void wayland_surface_reconfigure_geometry(struct wayland_surface *surface { wayland_surface_get_rect_in_monitor(surface, &rect); - rect = map_rect_to_surface(surface, rect); - /* If the window rect in the monitor is smaller than required, * fall back to an appropriately sized rect at the top-left. */ if ((surface->current.state & WAYLAND_SURFACE_CONFIG_STATE_MAXIMIZED) && @@ -644,6 +643,8 @@ static void wayland_surface_reconfigure_geometry(struct wayland_surface *surface TRACE("Window is too large for Wayland state, using subregion\n"); } + rect = map_rect_to_surface(surface, rect); + TRACE("hwnd=%p geometry=%s\n", surface->hwnd, wine_dbgstr_rect(&rect)); if (!IsRectEmpty(&rect)) @@ -796,7 +797,7 @@ static void wayland_surface_reconfigure_subsurface(struct wayland_surface *surfa BOOL wayland_surface_reconfigure(struct wayland_surface *surface) { struct wayland_window_config *window = &surface->window; - RECT rect = map_rect_to_surface(surface, surface->window.rect); + RECT rect = window->rect; TRACE("hwnd=%p window=%s,%#x processing=%s,%#x current=%s,%#x\n", surface->hwnd, wine_dbgstr_rect(&rect), window->state, @@ -817,6 +818,7 @@ BOOL wayland_surface_reconfigure(struct wayland_surface *surface) break; } + rect = map_rect_to_surface(surface, rect); wayland_surface_reconfigure_size(surface, rect.right - rect.left, rect.bottom - rect.top); return TRUE; diff --git a/dlls/winewayland.drv/window.c b/dlls/winewayland.drv/window.c index afef743755b..c53211bd38a 100644 --- a/dlls/winewayland.drv/window.c +++ b/dlls/winewayland.drv/window.c @@ -503,7 +503,7 @@ static void wayland_configure_window(HWND hwnd) BOOL needs_exit_size_move = FALSE; BOOL restoring_from_minimize = FALSE; struct wayland_win_data *data; - RECT rect, surface_rect; + RECT rect; if (!(data = wayland_win_data_get(hwnd))) return; if (!(surface = data->wayland_surface)) @@ -562,14 +562,12 @@ static void wayland_configure_window(HWND hwnd) flags |= SWP_FRAMECHANGED; } - surface_rect = map_rect_to_surface(surface, surface->window.rect); - /* If the window is already fullscreen and its size is compatible with what * the compositor is requesting, don't force a resize, since some applications * are very insistent on a particular fullscreen size (which may not match * the monitor size). */ if ((surface->window.state & WAYLAND_SURFACE_CONFIG_STATE_FULLSCREEN) && - wayland_surface_config_is_compatible(&surface->processing, surface_rect, + wayland_surface_config_is_compatible(&surface->processing, surface->window.rect, surface->window.state)) { flags |= SWP_NOSIZE; @@ -598,7 +596,6 @@ static void wayland_configure_window(HWND hwnd) } SetRect(&rect, 0, 0, width, height); - rect = map_rect_from_surface(surface, rect); OffsetRect(&rect, data->rects.window.left, data->rects.window.top); wayland_win_data_release(data); -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/11248
From: Rémi Bernon <rbernon@codeweavers.com> --- dlls/winewayland.drv/wayland_surface.c | 1 + dlls/winewayland.drv/window.c | 17 +++-------------- 2 files changed, 4 insertions(+), 14 deletions(-) diff --git a/dlls/winewayland.drv/wayland_surface.c b/dlls/winewayland.drv/wayland_surface.c index 267b45a1085..04e49d7444b 100644 --- a/dlls/winewayland.drv/wayland_surface.c +++ b/dlls/winewayland.drv/wayland_surface.c @@ -122,6 +122,7 @@ static void xdg_toplevel_handle_configure(void *private, { surface->pending.rect = map_rect_from_surface(surface, rect); surface->pending.state = config_state; + OffsetRect(&surface->pending.rect, surface->window.rect.left, surface->window.rect.top); } wayland_win_data_release(data); diff --git a/dlls/winewayland.drv/window.c b/dlls/winewayland.drv/window.c index c53211bd38a..6a341a4e8a8 100644 --- a/dlls/winewayland.drv/window.c +++ b/dlls/winewayland.drv/window.c @@ -495,7 +495,6 @@ void WAYLAND_WindowPosChanged(HWND hwnd, HWND insert_after, HWND owner_hint, UIN static void wayland_configure_window(HWND hwnd) { struct wayland_surface *surface; - INT width, height; UINT flags = 0; uint32_t state; DWORD style; @@ -529,18 +528,11 @@ static void wayland_configure_window(HWND hwnd) surface->processing = surface->requested; memset(&surface->requested, 0, sizeof(surface->requested)); + rect = surface->processing.rect; state = surface->processing.state; /* Ignore size hints if we don't have a state that requires strict * size adherence, in order to avoid spurious resizes. */ - if (state) - { - width = surface->processing.rect.right - surface->processing.rect.left; - height = surface->processing.rect.bottom - surface->processing.rect.top; - } - else - { - width = height = 0; - } + if (!state) SetRect(&rect, rect.left, rect.top, rect.left, rect.top); if ((state & WAYLAND_SURFACE_CONFIG_STATE_RESIZING) && !surface->resizing) { @@ -595,12 +587,9 @@ static void wayland_configure_window(HWND hwnd) return; } - SetRect(&rect, 0, 0, width, height); - OffsetRect(&rect, data->rects.window.left, data->rects.window.top); - wayland_win_data_release(data); - TRACE("processing=%dx%d,%#x\n", width, height, state); + TRACE("processing rect=%s state=%#x\n", wine_dbgstr_rect(&rect), state); if (needs_enter_size_move) send_message(hwnd, WM_ENTERSIZEMOVE, 0, 0); if (needs_exit_size_move) send_message(hwnd, WM_EXITSIZEMOVE, 0, 0); -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/11248
From: Rémi Bernon <rbernon@codeweavers.com> --- dlls/winewayland.drv/wayland_surface.c | 45 +++++++++++--------------- dlls/winewayland.drv/waylanddrv.h | 3 +- dlls/winewayland.drv/window.c | 3 +- 3 files changed, 21 insertions(+), 30 deletions(-) diff --git a/dlls/winewayland.drv/wayland_surface.c b/dlls/winewayland.drv/wayland_surface.c index 04e49d7444b..376013325ef 100644 --- a/dlls/winewayland.drv/wayland_surface.c +++ b/dlls/winewayland.drv/wayland_surface.c @@ -563,11 +563,9 @@ static BOOL is_rect_smaller(RECT a, RECT b) * Checks whether a wayland_surface_config object is compatible with the * the provided arguments. */ -BOOL wayland_surface_config_is_compatible(struct wayland_surface_config *conf, RECT rect, - enum wayland_surface_config_state state) +BOOL wayland_surface_config_is_compatible(struct wayland_surface *surface, struct wayland_surface_config *conf) { - static enum wayland_surface_config_state mask = - WAYLAND_SURFACE_CONFIG_STATE_MAXIMIZED; + RECT rect = surface->window.rect; /* The fullscreen state requires a size smaller or equal to the configured * size. If we have a larger size, we can use surface geometry during @@ -578,7 +576,7 @@ BOOL wayland_surface_config_is_compatible(struct wayland_surface_config *conf, R return TRUE; /* We require the same state. */ - if ((state & mask) != (conf->state & mask)) return FALSE; + if ((surface->window.state ^ conf->state) & WAYLAND_SURFACE_CONFIG_STATE_MAXIMIZED) return FALSE; /* The maximized state requires the configured size. During surface * reconfiguration we can use surface geometry to provide smaller areas @@ -617,8 +615,10 @@ static void wayland_surface_get_rect_in_monitor(struct wayland_surface *surface, * * Sets the xdg_surface geometry */ -static void wayland_surface_reconfigure_geometry(struct wayland_surface *surface, RECT rect) +static void wayland_surface_reconfigure_geometry(struct wayland_surface *surface) { + RECT rect = surface->window.rect; + /* If the window size is bigger than the current state accepts, use the * largest visible (from Windows' perspective) subregion of the window. */ if ((surface->current.state & (WAYLAND_SURFACE_CONFIG_STATE_MAXIMIZED | @@ -672,10 +672,12 @@ static void wayland_surface_reconfigure_geometry(struct wayland_surface *surface * * Sets the surface size with viewporter */ -static void wayland_surface_reconfigure_size(struct wayland_surface *surface, - int width, int height) +static void wayland_surface_reconfigure_size(struct wayland_surface *surface) { - TRACE("hwnd=%p size=%dx%d\n", surface->hwnd, width, height); + RECT rect = map_rect_to_surface(surface, surface->window.rect); + int width = rect.right - rect.left, height = rect.bottom - rect.top; + + TRACE("hwnd=%p rect=%s\n", surface->hwnd, wine_dbgstr_rect(&rect)); if (width != 0 && height != 0) wp_viewport_set_destination(surface->wp_viewport, width, height); @@ -720,14 +722,11 @@ static void wayland_surface_reconfigure_client(struct wayland_surface *surface, * Reconfigures the xdg surface as needed to match the latest requested * state. */ -static BOOL wayland_surface_reconfigure_xdg(struct wayland_surface *surface, RECT rect) +static BOOL wayland_surface_reconfigure_xdg(struct wayland_surface *surface) { - struct wayland_window_config *window = &surface->window; - /* Acknowledge any compatible processed config. */ if (surface->processing.serial && surface->processing.processed && - wayland_surface_config_is_compatible(&surface->processing, rect, - window->state)) + wayland_surface_config_is_compatible(surface, &surface->processing)) { surface->current = surface->processing; memset(&surface->processing, 0, sizeof(surface->processing)); @@ -737,21 +736,19 @@ static BOOL wayland_surface_reconfigure_xdg(struct wayland_surface *surface, REC * config, use that, in order to draw windows that don't go through the * message loop (e.g., some splash screens). */ else if (!surface->current.serial && surface->requested.serial && - wayland_surface_config_is_compatible(&surface->requested, rect, - window->state)) + wayland_surface_config_is_compatible(surface, &surface->requested)) { surface->current = surface->requested; memset(&surface->requested, 0, sizeof(surface->requested)); xdg_surface_ack_configure(surface->xdg_surface, surface->current.serial); } else if (!surface->current.serial || - !wayland_surface_config_is_compatible(&surface->current, rect, - window->state)) + !wayland_surface_config_is_compatible(surface, &surface->current)) { return FALSE; } - wayland_surface_reconfigure_geometry(surface, rect); + wayland_surface_reconfigure_geometry(surface); return TRUE; } @@ -797,11 +794,8 @@ static void wayland_surface_reconfigure_subsurface(struct wayland_surface *surfa */ BOOL wayland_surface_reconfigure(struct wayland_surface *surface) { - struct wayland_window_config *window = &surface->window; - RECT rect = window->rect; - TRACE("hwnd=%p window=%s,%#x processing=%s,%#x current=%s,%#x\n", - surface->hwnd, wine_dbgstr_rect(&rect), window->state, + surface->hwnd, wine_dbgstr_rect(&surface->window.rect), surface->window.state, wine_dbgstr_rect(&surface->processing.rect), surface->processing.state, wine_dbgstr_rect(&surface->current.rect), surface->current.state); @@ -811,7 +805,7 @@ BOOL wayland_surface_reconfigure(struct wayland_surface *surface) break; case WAYLAND_SURFACE_ROLE_TOPLEVEL: if (!surface->xdg_surface) break; /* surface role has been cleared */ - if (!wayland_surface_reconfigure_xdg(surface, rect)) return FALSE; + if (!wayland_surface_reconfigure_xdg(surface)) return FALSE; break; case WAYLAND_SURFACE_ROLE_SUBSURFACE: if (!surface->wl_subsurface) break; /* surface role has been cleared */ @@ -819,8 +813,7 @@ BOOL wayland_surface_reconfigure(struct wayland_surface *surface) break; } - rect = map_rect_to_surface(surface, rect); - wayland_surface_reconfigure_size(surface, rect.right - rect.left, rect.bottom - rect.top); + wayland_surface_reconfigure_size(surface); return TRUE; } diff --git a/dlls/winewayland.drv/waylanddrv.h b/dlls/winewayland.drv/waylanddrv.h index e0df8e56bb5..aba013f80ad 100644 --- a/dlls/winewayland.drv/waylanddrv.h +++ b/dlls/winewayland.drv/waylanddrv.h @@ -331,8 +331,7 @@ void wayland_surface_attach_shm(struct wayland_surface *surface, struct wayland_shm_buffer *shm_buffer, HRGN surface_damage_region); BOOL wayland_surface_reconfigure(struct wayland_surface *surface); -BOOL wayland_surface_config_is_compatible(struct wayland_surface_config *conf, RECT rect, - enum wayland_surface_config_state state); +BOOL wayland_surface_config_is_compatible(struct wayland_surface *surface, struct wayland_surface_config *conf); RECT map_rect_to_surface(struct wayland_surface *surface, RECT rect); POINT map_point_to_surface(struct wayland_surface *surface, POINT point); RECT map_rect_from_surface(struct wayland_surface *surface, RECT rect); diff --git a/dlls/winewayland.drv/window.c b/dlls/winewayland.drv/window.c index 6a341a4e8a8..91d74da61a2 100644 --- a/dlls/winewayland.drv/window.c +++ b/dlls/winewayland.drv/window.c @@ -559,8 +559,7 @@ static void wayland_configure_window(HWND hwnd) * are very insistent on a particular fullscreen size (which may not match * the monitor size). */ if ((surface->window.state & WAYLAND_SURFACE_CONFIG_STATE_FULLSCREEN) && - wayland_surface_config_is_compatible(&surface->processing, surface->window.rect, - surface->window.state)) + wayland_surface_config_is_compatible(surface, &surface->processing)) { flags |= SWP_NOSIZE; } -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/11248
From: Etaash Mathamsetty <etaash.mathamsetty@gmail.com> --- dlls/winewayland.drv/wayland.c | 8 +- dlls/winewayland.drv/wayland_surface.c | 254 ++++++++++++++++--------- dlls/winewayland.drv/waylanddrv.h | 14 +- dlls/winewayland.drv/window.c | 57 ++---- 4 files changed, 199 insertions(+), 134 deletions(-) diff --git a/dlls/winewayland.drv/wayland.c b/dlls/winewayland.drv/wayland.c index 5b2b0c5b25b..7f56e1cfd20 100644 --- a/dlls/winewayland.drv/wayland.c +++ b/dlls/winewayland.drv/wayland.c @@ -120,11 +120,9 @@ static void registry_handle_global(void *data, struct wl_registry *registry, } else if (strcmp(interface, "xdg_wm_base") == 0) { - /* Bind version 2 so that compositors (e.g., sway) can properly send tiled - * states, instead of falling back to (ab)using the maximized state. */ - process_wayland.xdg_wm_base = - wl_registry_bind(registry, id, &xdg_wm_base_interface, - version < 2 ? version : 2); + /* version 3 is required for xdg_popup::reposition */ + if (version < 3) return; + process_wayland.xdg_wm_base = wl_registry_bind(registry, id, &xdg_wm_base_interface, 3); xdg_wm_base_add_listener(process_wayland.xdg_wm_base, &xdg_wm_base_listener, NULL); } else if (strcmp(interface, "wl_shm") == 0) diff --git a/dlls/winewayland.drv/wayland_surface.c b/dlls/winewayland.drv/wayland_surface.c index 376013325ef..799330ed105 100644 --- a/dlls/winewayland.drv/wayland_surface.c +++ b/dlls/winewayland.drv/wayland_surface.c @@ -34,6 +34,16 @@ WINE_DEFAULT_DEBUG_CHANNEL(waylanddrv); +/* rountrip through WindowPosChanged to refresh the host window state */ +static void update_window_state(HWND hwnd) +{ + static const UINT swp_flags = SWP_NOSIZE | SWP_NOMOVE | SWP_NOCLIENTSIZE | SWP_NOCLIENTMOVE | + SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOREDRAW; + static const RECT rect; + + NtUserSetRawWindowPos(hwnd, rect, swp_flags, FALSE); +} + static void xdg_surface_handle_configure(void *private, struct xdg_surface *xdg_surface, uint32_t serial) { @@ -48,19 +58,21 @@ static void xdg_surface_handle_configure(void *private, struct xdg_surface *xdg_ /* Handle this event only if wayland_surface is still associated with * the target xdg_surface. */ - if ((surface = data->wayland_surface) && wayland_surface_is_toplevel(surface) && - surface->xdg_surface == xdg_surface) + if (!(surface = data->wayland_surface) || surface->xdg_surface != xdg_surface) { - /* If we have a previously requested config, we have already sent a - * WM_WAYLAND_CONFIGURE which hasn't been handled yet. In that case, - * avoid sending another message to reduce message queue traffic. */ - should_post = surface->requested.serial == 0; - initial_configure = surface->current.serial == 0; - surface->pending.serial = serial; - surface->requested = surface->pending; - memset(&surface->pending, 0, sizeof(surface->pending)); + wayland_win_data_release(data); + return; } + /* If we have a previously requested config, we have already sent a + * WM_WAYLAND_CONFIGURE which hasn't been handled yet. In that case, + * avoid sending another message to reduce message queue traffic. */ + should_post = surface->requested.serial == 0; + initial_configure = surface->current.serial == 0; + surface->pending.serial = serial; + surface->requested = surface->pending; + memset(&surface->pending, 0, sizeof(surface->pending)); + wayland_win_data_release(data); if (should_post) NtUserPostMessage(hwnd, WM_WAYLAND_CONFIGURE, 0, 0); @@ -139,6 +151,68 @@ static const struct xdg_toplevel_listener xdg_toplevel_listener = xdg_toplevel_handle_close }; +static void xdg_popup_handle_configure(void *private, struct xdg_popup *xdg_popup, + int32_t x, int32_t y, int32_t width, int32_t height) +{ + HWND hwnd = private; + struct wayland_win_data *data, *owner_data; + struct wayland_surface *surface, *owner; + RECT rect = { x, y, x + width, y + height }; + + TRACE("hwnd=%p rect=%s\n", hwnd, wine_dbgstr_rect(&rect)); + + if (!(data = wayland_win_data_get(hwnd))) return; + + if ((surface = data->wayland_surface) && wayland_surface_is_popup(surface)) + { + surface->pending.rect = map_rect_from_surface(surface, rect); + surface->pending.state = 0; + + if (!(owner_data = wayland_win_data_get_nolock(surface->owner_hwnd))) WARN("Lost surface %p owner window\n", surface); + else if (!(owner = owner_data->wayland_surface)) WARN("Lost surface %p owner surface\n", surface); + else OffsetRect(&surface->pending.rect, owner->window.rect.left, owner->window.rect.top); + } + + wayland_win_data_release(data); +} + +static void xdg_popup_handle_done(void *private, struct xdg_popup *xdg_popup) +{ + struct wayland_surface *surface; + struct wayland_win_data *data; + HWND hwnd = private; + + /* Recreate the popup if the compositor dismissed it for some reason. + * The protocol does not explicitly prohibit this from occuring on ungrabbed popups. */ + WARN("Compositor dismissed popup hwnd=%p\n", hwnd); + + /* the protocol requires us to destroy the xdg_popup */ + xdg_popup_destroy(xdg_popup); + + if (!(data = wayland_win_data_get(hwnd))) return; + if ((surface = data->wayland_surface) && surface->xdg_popup == xdg_popup) + { + surface->xdg_popup = NULL; + wayland_surface_clear_role(surface); + } + wayland_win_data_release(data); + + update_window_state(hwnd); +} + +static void xdg_popup_handle_reposition(void *private, struct xdg_popup *xdg_popup, uint32_t token) +{ + /* we also get a configure event in this case */ + TRACE("hwnd=%p\n", private); +} + +static const struct xdg_popup_listener xdg_popup_listener = +{ + xdg_popup_handle_configure, + xdg_popup_handle_done, + xdg_popup_handle_reposition, +}; + void wp_fractional_scale_handle_scale(void* user_data, struct wp_fractional_scale_v1 *fractional_scale_v1, uint32_t scale_fixed) @@ -164,16 +238,10 @@ void wp_fractional_scale_handle_scale(void* user_data, if ((client = data->client_surface)) wayland_client_surface_attach(client, client->toplevel); - /* the subsurface rect has changed */ - if (surface->role == WAYLAND_SURFACE_ROLE_SUBSURFACE) - { - surface->processing.serial = 1; - surface->processing.processed = TRUE; - } - wayland_win_data_release(data); NtUserExposeWindowSurface(hwnd, 0, NULL, 0); + update_window_state(hwnd); } static const struct wp_fractional_scale_v1_listener wp_fractional_scale_listener = @@ -324,6 +392,28 @@ static void wayland_surface_init_fractional_scale(struct wayland_surface *surfac surface->hwnd); } +/* helper to intialize the positioner using a given surface rect */ +static struct xdg_positioner *xdg_positioner_create(RECT rect) +{ + struct xdg_positioner *xdg_positioner; + + if (!(xdg_positioner = xdg_wm_base_create_positioner(process_wayland.xdg_wm_base))) return NULL; + + if (rect.right == rect.left) rect.right = rect.left + 1; + if (rect.bottom == rect.top) rect.bottom = rect.top + 1; + + /* this anchor rect is always valid */ + xdg_positioner_set_anchor_rect(xdg_positioner, 0, 0, 1, 1); + xdg_positioner_set_anchor(xdg_positioner, XDG_POSITIONER_ANCHOR_TOP_LEFT); + xdg_positioner_set_gravity(xdg_positioner, XDG_POSITIONER_GRAVITY_BOTTOM_RIGHT); + + /* then we offset by the requested amount */ + xdg_positioner_set_offset(xdg_positioner, rect.left, rect.top); + xdg_positioner_set_size(xdg_positioner, rect.right - rect.left, rect.bottom - rect.top); + + return xdg_positioner; +} + /********************************************************************** * wayland_surface_make_toplevel * @@ -372,46 +462,66 @@ err: } /********************************************************************** - * wayland_surface_make_subsurface + * wayland_surface_make_popup * - * Gives the subsurface role to a plain wayland surface. + * Gives the popup role to a plain wayland surface. */ -void wayland_surface_make_subsurface(struct wayland_surface *surface, - struct wayland_surface *owner) +void wayland_surface_make_popup(struct wayland_surface *surface, + struct wayland_surface *owner) { - assert(!surface->role || surface->role == WAYLAND_SURFACE_ROLE_SUBSURFACE); - if (surface->wl_subsurface && surface->owner_hwnd == owner->hwnd) return; - - wayland_surface_clear_role(surface); - surface->role = WAYLAND_SURFACE_ROLE_SUBSURFACE; + struct xdg_positioner *xdg_positioner; + RECT rect = surface->window.rect; - TRACE("surface=%p owner=%p\n", surface, owner); + assert(!surface->role || surface->role == WAYLAND_SURFACE_ROLE_POPUP); - surface->wl_subsurface = - wl_subcompositor_get_subsurface(process_wayland.wl_subcompositor, - surface->wl_surface, - owner->wl_surface); - if (!surface->wl_subsurface) + if (surface->xdg_surface && surface->xdg_popup && surface->owner_hwnd == owner->hwnd) { - ERR("Failed to create client wl_subsurface\n"); - goto err; + struct wayland_surface_config *config = surface->processing.serial ? &surface->processing : &surface->current; + if (config->rect.left == rect.left && config->rect.top == rect.top) return; + + TRACE("surface=%p owner=%p rect=%s, repositioning\n", surface, owner, wine_dbgstr_rect(&rect)); + + OffsetRect(&rect, -owner->window.rect.left, -owner->window.rect.top); + if (!(xdg_positioner = xdg_positioner_create(map_rect_to_surface(surface, rect)))) goto err; + xdg_popup_reposition(surface->xdg_popup, xdg_positioner, 0); + xdg_positioner_destroy(xdg_positioner); + + wl_surface_commit(surface->wl_surface); + wl_display_flush(process_wayland.wl_display); + return; } + wayland_surface_clear_role(surface); + surface->role = WAYLAND_SURFACE_ROLE_POPUP; + + TRACE("surface=%p owner=%p rect=%s\n", surface, owner, wine_dbgstr_rect(&rect)); + + surface->xdg_surface = xdg_wm_base_get_xdg_surface(process_wayland.xdg_wm_base, + surface->wl_surface); + if (!surface->xdg_surface) goto err; + xdg_surface_add_listener(surface->xdg_surface, &xdg_surface_listener, surface->hwnd); + + OffsetRect(&rect, -owner->window.rect.left, -owner->window.rect.top); + if (!(xdg_positioner = xdg_positioner_create(map_rect_to_surface(surface, rect)))) goto err; + surface->xdg_popup = xdg_surface_get_popup(surface->xdg_surface, owner->xdg_surface, + xdg_positioner); + xdg_positioner_destroy(xdg_positioner); + + if (!surface->xdg_popup) goto err; + xdg_popup_add_listener(surface->xdg_popup, &xdg_popup_listener, surface->hwnd); + wayland_surface_init_fractional_scale(surface, owner->window.scale); - surface->role = WAYLAND_SURFACE_ROLE_SUBSURFACE; surface->owner_hwnd = owner->hwnd; - /* Present contents independently of the owner surface. */ - wl_subsurface_set_desync(surface->wl_subsurface); - + wl_surface_commit(surface->wl_surface); wl_display_flush(process_wayland.wl_display); return; err: wayland_surface_clear_role(surface); - ERR("Failed to assign subsurface role to wayland surface\n"); + ERR("Failed to assign popup role to wayland surface\n"); } /********************************************************************** @@ -453,25 +563,25 @@ void wayland_surface_clear_role(struct wayland_surface *surface) xdg_toplevel_destroy(surface->xdg_toplevel); surface->xdg_toplevel = NULL; } - - if (surface->xdg_surface) - { - xdg_surface_destroy(surface->xdg_surface); - surface->xdg_surface = NULL; - } break; - case WAYLAND_SURFACE_ROLE_SUBSURFACE: - if (surface->wl_subsurface) + case WAYLAND_SURFACE_ROLE_POPUP: + if (surface->xdg_popup) { - wl_subsurface_destroy(surface->wl_subsurface); - surface->wl_subsurface = NULL; + xdg_popup_destroy(surface->xdg_popup); + surface->xdg_popup = NULL; } surface->owner_hwnd = NULL; break; } + if (surface->xdg_surface) + { + xdg_surface_destroy(surface->xdg_surface); + surface->xdg_surface = NULL; + } + memset(&surface->pending, 0, sizeof(surface->pending)); memset(&surface->requested, 0, sizeof(surface->requested)); memset(&surface->processing, 0, sizeof(surface->processing)); @@ -583,6 +693,9 @@ BOOL wayland_surface_config_is_compatible(struct wayland_surface *surface, struc * from larger sizes, so only smaller sizes are incompatible. */ if ((conf->state & WAYLAND_SURFACE_CONFIG_STATE_MAXIMIZED) && is_rect_smaller(rect, conf->rect)) return FALSE; + /* If the compositor requested popup position doesn't match the win32 position, the window needs to be moved */ + if (wayland_surface_is_popup(surface) && (rect.left != conf->rect.left || rect.top != conf->rect.top)) return FALSE; + return TRUE; } @@ -653,6 +766,11 @@ static void wayland_surface_reconfigure_geometry(struct wayland_surface *surface int width = rect.right - rect.left, height = rect.bottom - rect.top; xdg_surface_set_window_geometry(surface->xdg_surface, 0, 0, width, height); + } + + if (wayland_surface_is_toplevel(surface)) + { + int width = rect.right - rect.left, height = rect.bottom - rect.top; if (surface->window.resizeable) { @@ -753,39 +871,6 @@ static BOOL wayland_surface_reconfigure_xdg(struct wayland_surface *surface) return TRUE; } -/********************************************************************** - * wayland_surface_reconfigure_subsurface - * - * Reconfigures the subsurface as needed to match the latest requested - * state. - */ -static void wayland_surface_reconfigure_subsurface(struct wayland_surface *surface) -{ - struct wayland_win_data *owner_data; - struct wayland_surface *owner_surface; - - if (surface->processing.serial && surface->processing.processed && - (owner_data = wayland_win_data_get_nolock(surface->owner_hwnd)) && - (owner_surface = owner_data->wayland_surface)) - { - RECT rect = surface->window.rect; - - OffsetRect(&rect, -owner_surface->window.rect.left, -owner_surface->window.rect.top); - rect = map_rect_to_surface(surface, rect); - - TRACE("hwnd=%p rect=%s\n", surface->hwnd, wine_dbgstr_rect(&rect)); - - wl_subsurface_set_position(surface->wl_subsurface, rect.left, rect.top); - if (owner_data->client_surface && owner_data->client_surface->wl_subsurface) - wl_subsurface_place_above(surface->wl_subsurface, owner_data->client_surface->wl_surface); - else - wl_subsurface_place_above(surface->wl_subsurface, owner_surface->wl_surface); - wl_surface_commit(owner_surface->wl_surface); - - memset(&surface->processing, 0, sizeof(surface->processing)); - } -} - /********************************************************************** * wayland_surface_reconfigure * @@ -804,13 +889,10 @@ BOOL wayland_surface_reconfigure(struct wayland_surface *surface) case WAYLAND_SURFACE_ROLE_NONE: break; case WAYLAND_SURFACE_ROLE_TOPLEVEL: + case WAYLAND_SURFACE_ROLE_POPUP: if (!surface->xdg_surface) break; /* surface role has been cleared */ if (!wayland_surface_reconfigure_xdg(surface)) return FALSE; break; - case WAYLAND_SURFACE_ROLE_SUBSURFACE: - if (!surface->wl_subsurface) break; /* surface role has been cleared */ - wayland_surface_reconfigure_subsurface(surface); - break; } wayland_surface_reconfigure_size(surface); diff --git a/dlls/winewayland.drv/waylanddrv.h b/dlls/winewayland.drv/waylanddrv.h index aba013f80ad..bd75afedd7f 100644 --- a/dlls/winewayland.drv/waylanddrv.h +++ b/dlls/winewayland.drv/waylanddrv.h @@ -84,7 +84,7 @@ enum wayland_surface_role { WAYLAND_SURFACE_ROLE_NONE, WAYLAND_SURFACE_ROLE_TOPLEVEL, - WAYLAND_SURFACE_ROLE_SUBSURFACE, + WAYLAND_SURFACE_ROLE_POPUP, }; struct wayland_keyboard @@ -280,17 +280,18 @@ struct wayland_surface struct wayland_shm_buffer *big_icon_buffer; enum wayland_surface_role role; + + struct xdg_surface *xdg_surface; union { struct { - struct xdg_surface *xdg_surface; struct xdg_toplevel *xdg_toplevel; struct xdg_toplevel_icon_v1 *xdg_toplevel_icon; }; struct { - struct wl_subsurface *wl_subsurface; + struct xdg_popup *xdg_popup; HWND owner_hwnd; }; }; @@ -326,6 +327,8 @@ void wayland_surface_destroy(struct wayland_surface *surface); void wayland_surface_make_toplevel(struct wayland_surface *surface); void wayland_surface_make_subsurface(struct wayland_surface *surface, struct wayland_surface *parent); +void wayland_surface_make_popup(struct wayland_surface *surface, + struct wayland_surface *owner); void wayland_surface_clear_role(struct wayland_surface *surface); void wayland_surface_attach_shm(struct wayland_surface *surface, struct wayland_shm_buffer *shm_buffer, @@ -348,6 +351,11 @@ static inline BOOL wayland_surface_is_toplevel(struct wayland_surface *surface) return surface->role == WAYLAND_SURFACE_ROLE_TOPLEVEL && surface->xdg_toplevel; } +static inline BOOL wayland_surface_is_popup(struct wayland_surface *surface) +{ + return surface->role == WAYLAND_SURFACE_ROLE_POPUP && surface->xdg_popup; +} + /********************************************************************** * Wayland SHM buffer */ diff --git a/dlls/winewayland.drv/window.c b/dlls/winewayland.drv/window.c index 91d74da61a2..b1957a2f246 100644 --- a/dlls/winewayland.drv/window.c +++ b/dlls/winewayland.drv/window.c @@ -203,7 +203,7 @@ static BOOL wayland_win_data_create_wayland_surface(struct wayland_win_data *dat (!(exstyle & WS_EX_LAYERED) || data->layered_attribs_set); if (!visible) role = WAYLAND_SURFACE_ROLE_NONE; - else if (owner_surface) role = WAYLAND_SURFACE_ROLE_SUBSURFACE; + else if (owner_surface) role = WAYLAND_SURFACE_ROLE_POPUP; else role = WAYLAND_SURFACE_ROLE_TOPLEVEL; /* we can temporarily clear the role of a surface but cannot assign a different one after it's set */ @@ -224,6 +224,8 @@ static BOOL wayland_win_data_create_wayland_surface(struct wayland_win_data *dat wl_surface_set_input_region(surface->wl_surface, input_region); if (input_region) wl_region_destroy(input_region); + wayland_win_data_get_config(data, &surface->window); + /* If the window is a visible toplevel make it a wayland * xdg_toplevel. Otherwise keep it role-less to avoid polluting the * compositor with empty xdg_toplevels. */ @@ -235,13 +237,12 @@ static BOOL wayland_win_data_create_wayland_surface(struct wayland_win_data *dat case WAYLAND_SURFACE_ROLE_TOPLEVEL: wayland_surface_make_toplevel(surface); break; - case WAYLAND_SURFACE_ROLE_SUBSURFACE: - wayland_surface_make_subsurface(surface, owner_surface); + case WAYLAND_SURFACE_ROLE_POPUP: + wayland_surface_make_popup(surface, owner_surface); break; } if (visible && client) wayland_client_surface_attach(client, data->hwnd); - wayland_win_data_get_config(data, &surface->window); /* Size/position changes affect the effective pointer constraint, so update * it as needed. */ @@ -252,8 +253,9 @@ static BOOL wayland_win_data_create_wayland_surface(struct wayland_win_data *dat return TRUE; } -static void wayland_surface_update_state_toplevel(struct wayland_surface *surface) +static void wayland_win_data_update_wayland_state(struct wayland_win_data *data) { + struct wayland_surface *surface = data->wayland_surface; BOOL processing_config = surface->processing.serial && !surface->processing.processed; @@ -264,7 +266,11 @@ static void wayland_surface_update_state_toplevel(struct wayland_surface *surfac /* If we are not processing a compositor requested config, use the * window state to determine and update the Wayland state. */ - if (!processing_config) + if (processing_config) + { + surface->processing.processed = TRUE; + } + else if (wayland_surface_is_toplevel(surface)) { /* First do all state unsettings, before setting new state. Some * Wayland compositors misbehave if the order is reversed. */ @@ -296,32 +302,6 @@ static void wayland_surface_update_state_toplevel(struct wayland_surface *surfac xdg_toplevel_set_minimized(surface->xdg_toplevel); } } - else - { - surface->processing.processed = TRUE; - } -} - -static void wayland_win_data_update_wayland_state(struct wayland_win_data *data) -{ - struct wayland_surface *surface = data->wayland_surface; - - switch (surface->role) - { - case WAYLAND_SURFACE_ROLE_NONE: - break; - case WAYLAND_SURFACE_ROLE_TOPLEVEL: - if (!surface->xdg_surface) break; /* surface role has been cleared */ - wayland_surface_update_state_toplevel(surface); - break; - case WAYLAND_SURFACE_ROLE_SUBSURFACE: - TRACE("hwnd=%p subsurface owner=%p\n", surface->hwnd, surface->owner_hwnd); - /* Although subsurfaces don't have a dedicated surface config mechanism, - * we use the config fields to mark them as updated. */ - surface->processing.serial = 1; - surface->processing.processed = TRUE; - break; - } wl_display_flush(process_wayland.wl_display); } @@ -462,6 +442,8 @@ void WAYLAND_WindowPosChanged(HWND hwnd, HWND insert_after, HWND owner_hint, UIN if (!(data = wayland_win_data_get(hwnd))) return; owner_data = owner && owner != hwnd ? wayland_win_data_get_nolock(owner) : NULL; owner_surface = owner_data ? owner_data->wayland_surface : NULL; + /* for it to be a popup, we need a valid xdg surface. */ + if (owner_surface && !owner_surface->xdg_surface) owner_surface = NULL; data->rects = *new_rects; data->is_fullscreen = fullscreen; @@ -511,13 +493,6 @@ static void wayland_configure_window(HWND hwnd) return; } - if (!wayland_surface_is_toplevel(surface)) - { - TRACE("missing xdg_toplevel, returning\n"); - wayland_win_data_release(data); - return; - } - if (!surface->requested.serial) { TRACE("requested configure event already handled, returning\n"); @@ -586,6 +561,8 @@ static void wayland_configure_window(HWND hwnd) return; } + if (wayland_surface_is_toplevel(surface)) flags |= SWP_NOMOVE; + wayland_win_data_release(data); TRACE("processing rect=%s state=%#x\n", wine_dbgstr_rect(&rect), state); @@ -593,7 +570,7 @@ static void wayland_configure_window(HWND hwnd) if (needs_enter_size_move) send_message(hwnd, WM_ENTERSIZEMOVE, 0, 0); if (needs_exit_size_move) send_message(hwnd, WM_EXITSIZEMOVE, 0, 0); - flags |= SWP_NOACTIVATE | SWP_NOZORDER | SWP_NOOWNERZORDER | SWP_NOMOVE; + flags |= SWP_NOACTIVATE | SWP_NOZORDER | SWP_NOOWNERZORDER; if (rect.left == rect.right || rect.bottom == rect.top) flags |= SWP_NOSIZE; style = NtUserGetWindowLongW(hwnd, GWL_STYLE); -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/11248
On Thu Jun 25 06:50:07 2026 +0000, Etaash Mathamsetty wrote:
to avoid any feedback loop from NtUserSetRawWindowPos I think we should check for if a config is being processed. `if (surface->processing.serial && !surface->processing.processed) return;` I've changed this to conditionally check against the processing config if we're reconfiguring the window.
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/11248#note_144199
v2: Add more changes to simplify the code by converting wayland surface config rects to absolute window coordinate space earlier, making all the windowing code work in the same (Wine raw DPI) coordinate space. Also fixed some missing popup repositioning support here and there. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/11248#note_144200
Etaash Mathamsetty (@etaash.mathamsetty) commented about dlls/winewayland.drv/wayland_surface.c:
+static void xdg_popup_handle_configure(void *private, struct xdg_popup *xdg_popup, + int32_t x, int32_t y, int32_t width, int32_t height) +{ + HWND hwnd = private; + struct wayland_win_data *data, *owner_data; + struct wayland_surface *surface, *owner; + RECT rect = { x, y, x + width, y + height }; + + TRACE("hwnd=%p rect=%s\n", hwnd, wine_dbgstr_rect(&rect)); + + if (!(data = wayland_win_data_get(hwnd))) return; + + if ((surface = data->wayland_surface) && wayland_surface_is_popup(surface)) + { + surface->pending.rect = map_rect_from_surface(surface, rect);
fractional scale events are not guaranteed to be sent with configure events, so you could have a case where surface-\>pending.rect is converted using a stale scale and then the incorrect rect is used in wayland_configure_window. Also, there could be another scale event between PostMessage and wayland_configure_window. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/11248#note_144289
Etaash Mathamsetty (@etaash.mathamsetty) commented about dlls/winewayland.drv/wayland_surface.c:
- if ((state & mask) != (conf->state & mask)) return FALSE; + if ((surface->window.state ^ conf->state) & WAYLAND_SURFACE_CONFIG_STATE_MAXIMIZED) return FALSE;
/* The maximized state requires the configured size. During surface * reconfiguration we can use surface geometry to provide smaller areas * from larger sizes, so only smaller sizes are incompatible. */ - if ((conf->state & WAYLAND_SURFACE_CONFIG_STATE_MAXIMIZED) && - (rect.right - rect.left < conf->rect.right - conf->rect.left || - rect.bottom - rect.top < conf->rect.bottom - conf->rect.top)) - { - return FALSE; - } + if ((conf->state & WAYLAND_SURFACE_CONFIG_STATE_MAXIMIZED) && is_rect_smaller(rect, conf->rect)) return FALSE; + + /* If the compositor requested popup position doesn't match the win32 position, the window needs to be moved */ + if (wayland_surface_is_popup(surface) && (rect.left != conf->rect.left || rect.top != conf->rect.top)) return FALSE; I don't think this is right, if the compositor decides to move our popup, then we will not be able to reposition the popup (since returning false here would would prevent wayland_surface_reconfigure_geometry from getting called). This would also prevent it from getting mapped on the first frame if the compositor decided to move the popup.
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/11248#note_144290
On Fri Jun 26 06:02:22 2026 +0000, Etaash Mathamsetty wrote:
I don't think this is right, if the compositor decides to move our popup from where we want it, then we will not be able to reposition the popup (since returning false here would would prevent wayland_surface_reconfigure_geometry from getting called). This would also prevent it from getting mapped on the first frame if the compositor decided to move the popup. The positioning is now done in `wayland_surface_make_popup`, not `wayland_surface_reconfigure_geometry`. Returning FALSE here makes sure `wayland_configure_window` will need to process it and update the Win32 state with the new popup position.
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/11248#note_144292
participants (4)
-
Etaash Mathamsetty -
Etaash Mathamsetty (@etaash.mathamsetty) -
Rémi Bernon -
Rémi Bernon (@rbernon)