[PATCH v4 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. -- v4: 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 | 23 +++++++++++++++++------ dlls/winewayland.drv/window.c | 7 ++----- 2 files changed, 19 insertions(+), 11 deletions(-) diff --git a/dlls/winewayland.drv/wayland_surface.c b/dlls/winewayland.drv/wayland_surface.c index c4adadd7f94..1207a3da5b1 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; } @@ -156,7 +157,16 @@ void wp_fractional_scale_handle_scale(void* user_data, return; } - surface->window.scale = scale; + /* adjust the surface config rects to the new surface scale, we won't receive a configure event */ + surface->pending.rect = map_rect_to_surface(surface, surface->pending.rect); + surface->requested.rect = map_rect_to_surface(surface, surface->requested.rect); + surface->processing.rect = map_rect_to_surface(surface, surface->processing.rect); + surface->current.rect = map_rect_to_surface(surface, surface->current.rect); + surface->window.scale = scale; + surface->pending.rect = map_rect_from_surface(surface, surface->pending.rect); + surface->requested.rect = map_rect_from_surface(surface, surface->requested.rect); + surface->processing.rect = map_rect_from_surface(surface, surface->processing.rect); + surface->current.rect = map_rect_from_surface(surface, surface->current.rect); /* reattach the client surface as its rect has changed */ if ((client = data->client_surface)) @@ -625,8 +635,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 +652,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 +806,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 +827,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 1207a3da5b1..ae8239dbd7e 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 ae8239dbd7e..d32ce5cd6f3 100644 --- a/dlls/winewayland.drv/wayland_surface.c +++ b/dlls/winewayland.drv/wayland_surface.c @@ -572,11 +572,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 @@ -587,7 +585,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 @@ -626,8 +624,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 | @@ -681,10 +681,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); @@ -729,14 +731,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)); @@ -746,21 +745,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; } @@ -806,11 +803,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); @@ -820,7 +814,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 */ @@ -828,8 +822,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 d32ce5cd6f3..0586da1e124 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) @@ -173,16 +247,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 = @@ -333,6 +401,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 * @@ -381,46 +471,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"); } /********************************************************************** @@ -462,25 +572,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)); @@ -592,6 +702,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; } @@ -662,6 +775,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) { @@ -762,39 +880,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 * @@ -813,13 +898,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 Fri Jun 26 05:15:52 2026 +0000, Etaash Mathamsetty wrote:
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. Right... I've added a change to fixup the rects when scale change. I'm not completely sure that this is the expected Wayland semantics, and makes me dislike that extra surface-specific scale a bit more, but it should more or less match what we were doing up until now.
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/11248#note_144294
On Fri Jun 26 06:21:52 2026 +0000, Rémi Bernon wrote:
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. Oh oops, I forgot we moved it. For my understanding, let's say the compositor decides to place the popup somewhere else. We update the win32u position to be where the compositor placed it. Since win32u commits new contents if the surface moves, this would ensure that the first frame would have contents if we return false here? I am just worried that if the compositor returns a different position in the xdg_popup::configure event, then we would return false here and the contents won't be committed.
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/11248#note_144295
On Fri Jun 26 06:26:37 2026 +0000, Rémi Bernon wrote:
Right... I've added a change to fixup the rects when scale change. I'm not completely sure that this is the expected Wayland semantics, and makes me dislike that extra surface-specific scale a bit more, but it should more or less match what we were doing up until now. This solution gets us closer, but `round(round(377 / 1.25) * 1.25) = 378` so it is not accurate. I'm not sure about the accuracy of the other way (product first then divide), ill check if there exists a counter example for that as well. But I think just this having an inaccuracy is already bad. There has to be some other way we can solve this problem
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/11248#note_144297
Etaash Mathamsetty (@etaash.mathamsetty) commented about dlls/winewayland.drv/wayland_surface.c:
- * 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; From my testing, we need some mechanism to ack configure events if the window was only repositioned with no new contents. Otherwise, the popup is unable to move with the window.
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/11248#note_144298
On Fri Jun 26 07:21:23 2026 +0000, Etaash Mathamsetty wrote:
From my testing, we need some mechanism to ack configure events if the window was only repositioned with no new contents. Otherwise, the popup is unable to move with the window. (it teleports the next time the contents are updated) Okay, maybe we should just not try to sync compositor position with Wine position for now, and do the same thing we do with toplevel windows. I'll change that.
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/11248#note_144299
participants (4)
-
Etaash Mathamsetty -
Etaash Mathamsetty (@etaash.mathamsetty) -
Rémi Bernon -
Rémi Bernon (@rbernon)