[PATCH v6 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. -- v6: winewayland: Document some wayland surface config members. winewayland: Implement xdg-popup for unmanaged windows. winewayland: Set SWP_NOSIZE flag rather than touching pending rect. winewayland: Pass the wayland surface to wayland_surface_config_is_compatible. 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 c2b4c4be891..f7f0f094645 100644 --- a/dlls/winewayland.drv/wayland_surface.c +++ b/dlls/winewayland.drv/wayland_surface.c @@ -638,19 +638,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 f7f0f094645..d64c21b30dd 100644 --- a/dlls/winewayland.drv/wayland_surface.c +++ b/dlls/winewayland.drv/wayland_surface.c @@ -543,6 +543,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 * @@ -569,12 +579,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; } @@ -614,8 +619,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); @@ -625,8 +629,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 | 44 +++++++++++--------------- dlls/winewayland.drv/waylanddrv.h | 3 +- dlls/winewayland.drv/window.c | 7 ++-- 3 files changed, 22 insertions(+), 32 deletions(-) diff --git a/dlls/winewayland.drv/wayland_surface.c b/dlls/winewayland.drv/wayland_surface.c index d64c21b30dd..f51fcd97142 100644 --- a/dlls/winewayland.drv/wayland_surface.c +++ b/dlls/winewayland.drv/wayland_surface.c @@ -559,11 +559,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 = map_rect_to_surface(surface, 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 @@ -574,7 +572,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 @@ -613,8 +611,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 = map_rect_to_surface(surface, 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 | @@ -668,10 +668,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); @@ -719,14 +721,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)); @@ -736,21 +735,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; } @@ -799,11 +796,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 = map_rect_to_surface(surface, surface->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); @@ -813,7 +807,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 */ @@ -821,7 +815,7 @@ BOOL wayland_surface_reconfigure(struct wayland_surface *surface) break; } - 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 da166c939d4..1aae646d2a9 100644 --- a/dlls/winewayland.drv/waylanddrv.h +++ b/dlls/winewayland.drv/waylanddrv.h @@ -332,8 +332,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 adff02e6c38..eba4c8856e4 100644 --- a/dlls/winewayland.drv/window.c +++ b/dlls/winewayland.drv/window.c @@ -481,7 +481,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)) @@ -540,15 +540,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, - 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: Rémi Bernon <rbernon@codeweavers.com> --- dlls/winewayland.drv/window.c | 24 +++++------------------- 1 file changed, 5 insertions(+), 19 deletions(-) diff --git a/dlls/winewayland.drv/window.c b/dlls/winewayland.drv/window.c index eba4c8856e4..71bc443593e 100644 --- a/dlls/winewayland.drv/window.c +++ b/dlls/winewayland.drv/window.c @@ -473,8 +473,7 @@ 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; + UINT flags = SWP_NOACTIVATE | SWP_NOZORDER | SWP_NOOWNERZORDER | SWP_NOMOVE; uint32_t state; DWORD style; BOOL needs_enter_size_move = FALSE; @@ -507,18 +506,12 @@ static void wayland_configure_window(HWND hwnd) surface->processing = surface->requested; memset(&surface->requested, 0, sizeof(surface->requested)); + rect = map_rect_from_surface(surface, 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) flags |= SWP_NOSIZE; if ((state & WAYLAND_SURFACE_CONFIG_STATE_RESIZING) && !surface->resizing) { @@ -572,20 +565,13 @@ static void wayland_configure_window(HWND hwnd) return; } - 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); - TRACE("processing=%dx%d,%#x\n", width, height, state); + TRACE("processing rect=%s state=%#x flags=%#x\n", wine_dbgstr_rect(&rect), state, flags); 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; - if (rect.left == rect.right || rect.bottom == rect.top) flags |= SWP_NOSIZE; - style = NtUserGetWindowLongW(hwnd, GWL_STYLE); if (!(state & WAYLAND_SURFACE_CONFIG_STATE_MAXIMIZED) != !(style & WS_MAXIMIZE) && !(state & WAYLAND_SURFACE_CONFIG_STATE_FULLSCREEN)) -- 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 | 279 ++++++++++++++++--------- dlls/winewayland.drv/waylanddrv.h | 14 +- dlls/winewayland.drv/window.c | 54 ++--- 4 files changed, 208 insertions(+), 147 deletions(-) diff --git a/dlls/winewayland.drv/wayland.c b/dlls/winewayland.drv/wayland.c index c5d04b81fe5..92b6fb7abfc 100644 --- a/dlls/winewayland.drv/wayland.c +++ b/dlls/winewayland.drv/wayland.c @@ -121,11 +121,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 f51fcd97142..a2d408d7a59 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); @@ -83,45 +95,44 @@ static void xdg_toplevel_handle_configure(void *private, int32_t width, int32_t height, struct wl_array *states) { + struct wayland_surface_config config = {0}; struct wayland_surface *surface; HWND hwnd = private; uint32_t *state; - enum wayland_surface_config_state config_state = 0; struct wayland_win_data *data; + SetRect(&config.rect, 0, 0, width, height); + wl_array_for_each(state, states) { switch(*state) { case XDG_TOPLEVEL_STATE_MAXIMIZED: - config_state |= WAYLAND_SURFACE_CONFIG_STATE_MAXIMIZED; + config.state |= WAYLAND_SURFACE_CONFIG_STATE_MAXIMIZED; break; case XDG_TOPLEVEL_STATE_RESIZING: - config_state |= WAYLAND_SURFACE_CONFIG_STATE_RESIZING; + config.state |= WAYLAND_SURFACE_CONFIG_STATE_RESIZING; break; case XDG_TOPLEVEL_STATE_TILED_LEFT: case XDG_TOPLEVEL_STATE_TILED_RIGHT: case XDG_TOPLEVEL_STATE_TILED_TOP: case XDG_TOPLEVEL_STATE_TILED_BOTTOM: - config_state |= WAYLAND_SURFACE_CONFIG_STATE_TILED; + config.state |= WAYLAND_SURFACE_CONFIG_STATE_TILED; break; case XDG_TOPLEVEL_STATE_FULLSCREEN: - config_state |= WAYLAND_SURFACE_CONFIG_STATE_FULLSCREEN; + config.state |= WAYLAND_SURFACE_CONFIG_STATE_FULLSCREEN; break; default: break; } } - TRACE("hwnd=%p %dx%d,%#x\n", hwnd, width, height, config_state); + TRACE("hwnd=%p rect=%s state=%#x\n", hwnd, wine_dbgstr_rect(&config.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.state = config_state; - } + surface->pending = config; wayland_win_data_release(data); } @@ -137,6 +148,74 @@ 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) +{ + struct wayland_surface_config config = {0}; + HWND hwnd = private; + struct wayland_win_data *data, *owner_data; + struct wayland_surface *surface, *owner; + + SetRect(&config.rect, x, y, x + width, y + height); + + TRACE("hwnd=%p rect=%s\n", hwnd, wine_dbgstr_rect(&config.rect)); + + if (!(data = wayland_win_data_get(hwnd))) return; + + if ((surface = data->wayland_surface) && wayland_surface_is_popup(surface)) + { + if (!(owner_data = wayland_win_data_get(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 + { + RECT owner_rect = map_rect_to_surface(owner, owner->window.rect); + OffsetRect(&config.rect, owner_rect.left, owner_rect.top); + } + if (owner_data) wayland_win_data_release(owner_data); + + surface->pending = config; + } + + 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) @@ -160,16 +239,10 @@ void wp_fractional_scale_handle_scale(void* user_data, /* reattach client surfaces as their rects have changed */ update_client_surfaces(hwnd); - /* 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); + update_window_state(hwnd); } static const struct wp_fractional_scale_v1_listener wp_fractional_scale_listener = @@ -320,6 +393,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 * @@ -368,46 +463,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"); } /********************************************************************** @@ -449,25 +564,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)); @@ -649,6 +764,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) { @@ -752,42 +872,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) return; - if (!(owner_data = wayland_win_data_get(surface->owner_hwnd))) return; - - if ((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_win_data_release(owner_data); -} - /********************************************************************** * wayland_surface_reconfigure * @@ -806,13 +890,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 1aae646d2a9..b36cb8d2eb8 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 @@ -281,17 +281,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; }; }; @@ -327,6 +328,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, @@ -349,6 +352,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 71bc443593e..dee9980054a 100644 --- a/dlls/winewayland.drv/window.c +++ b/dlls/winewayland.drv/window.c @@ -187,7 +187,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 */ @@ -211,6 +211,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. */ @@ -222,13 +224,11 @@ 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; } - wayland_win_data_get_config(data, &surface->window); - /* Size/position changes affect the effective pointer constraint, so update * it as needed. */ if (data->hwnd == NtUserGetForegroundWindow()) reapply_cursor_clipping(); @@ -238,8 +238,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; @@ -250,7 +251,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. */ @@ -282,32 +287,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); } @@ -447,6 +426,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(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; @@ -489,13 +470,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"); -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/11248
From: Rémi Bernon <rbernon@codeweavers.com> --- dlls/winewayland.drv/wayland_surface.c | 30 +++++++++--------- dlls/winewayland.drv/waylanddrv.h | 30 ++++++++++-------- dlls/winewayland.drv/window.c | 44 +++++++++++++------------- 3 files changed, 54 insertions(+), 50 deletions(-) diff --git a/dlls/winewayland.drv/wayland_surface.c b/dlls/winewayland.drv/wayland_surface.c index a2d408d7a59..a5b51cd952e 100644 --- a/dlls/winewayland.drv/wayland_surface.c +++ b/dlls/winewayland.drv/wayland_surface.c @@ -95,7 +95,7 @@ static void xdg_toplevel_handle_configure(void *private, int32_t width, int32_t height, struct wl_array *states) { - struct wayland_surface_config config = {0}; + struct surface_config config = {0}; struct wayland_surface *surface; HWND hwnd = private; uint32_t *state; @@ -108,19 +108,19 @@ static void xdg_toplevel_handle_configure(void *private, switch(*state) { case XDG_TOPLEVEL_STATE_MAXIMIZED: - config.state |= WAYLAND_SURFACE_CONFIG_STATE_MAXIMIZED; + config.state |= SURFACE_STATE_MAXIMIZED; break; case XDG_TOPLEVEL_STATE_RESIZING: - config.state |= WAYLAND_SURFACE_CONFIG_STATE_RESIZING; + config.state |= SURFACE_STATE_RESIZING; break; case XDG_TOPLEVEL_STATE_TILED_LEFT: case XDG_TOPLEVEL_STATE_TILED_RIGHT: case XDG_TOPLEVEL_STATE_TILED_TOP: case XDG_TOPLEVEL_STATE_TILED_BOTTOM: - config.state |= WAYLAND_SURFACE_CONFIG_STATE_TILED; + config.state |= SURFACE_STATE_TILED; break; case XDG_TOPLEVEL_STATE_FULLSCREEN: - config.state |= WAYLAND_SURFACE_CONFIG_STATE_FULLSCREEN; + config.state |= SURFACE_STATE_FULLSCREEN; break; default: break; @@ -151,7 +151,7 @@ static const struct xdg_toplevel_listener xdg_toplevel_listener = 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) { - struct wayland_surface_config config = {0}; + struct surface_config config = {0}; HWND hwnd = private; struct wayland_win_data *data, *owner_data; struct wayland_surface *surface, *owner; @@ -477,7 +477,7 @@ void wayland_surface_make_popup(struct wayland_surface *surface, if (surface->xdg_surface && surface->xdg_popup && surface->owner_hwnd == owner->hwnd) { - struct wayland_surface_config *config = surface->processing.serial ? &surface->processing : &surface->current; + struct 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)); @@ -674,7 +674,7 @@ 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 *surface, struct wayland_surface_config *conf) +BOOL wayland_surface_config_is_compatible(struct wayland_surface *surface, struct surface_config *conf) { RECT rect = map_rect_to_surface(surface, surface->window.rect); @@ -683,16 +683,16 @@ BOOL wayland_surface_config_is_compatible(struct wayland_surface *surface, struc * surface reconfiguration to provide the smaller size, so we are always * compatible with a fullscreen state. * NOTE: Fullscreen combined with maximized is the same as fullscreen. */ - if (conf->state & WAYLAND_SURFACE_CONFIG_STATE_FULLSCREEN) + if (conf->state & SURFACE_STATE_FULLSCREEN) return TRUE; /* We require the same state. */ - if ((surface->window.state ^ conf->state) & WAYLAND_SURFACE_CONFIG_STATE_MAXIMIZED) return FALSE; + if ((surface->window.state ^ conf->state) & SURFACE_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) && is_rect_smaller(rect, conf->rect)) return FALSE; + if ((conf->state & SURFACE_STATE_MAXIMIZED) && is_rect_smaller(rect, conf->rect)) return FALSE; return TRUE; } @@ -732,8 +732,8 @@ static void wayland_surface_reconfigure_geometry(struct wayland_surface *surface /* 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 | - WAYLAND_SURFACE_CONFIG_STATE_FULLSCREEN)) && + if ((surface->current.state & (SURFACE_STATE_MAXIMIZED | + SURFACE_STATE_FULLSCREEN)) && is_rect_bigger(rect, surface->current.rect)) { wayland_surface_get_rect_in_monitor(surface, &rect); @@ -742,8 +742,8 @@ static void wayland_surface_reconfigure_geometry(struct wayland_surface *surface /* 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) && - !(surface->current.state & WAYLAND_SURFACE_CONFIG_STATE_FULLSCREEN) && + if ((surface->current.state & SURFACE_STATE_MAXIMIZED) && + !(surface->current.state & SURFACE_STATE_FULLSCREEN) && is_rect_smaller(rect, surface->current.rect)) { SetRect(&rect, 0, 0, surface->current.rect.right - surface->current.rect.left, diff --git a/dlls/winewayland.drv/waylanddrv.h b/dlls/winewayland.drv/waylanddrv.h index b36cb8d2eb8..196e5314666 100644 --- a/dlls/winewayland.drv/waylanddrv.h +++ b/dlls/winewayland.drv/waylanddrv.h @@ -72,12 +72,12 @@ enum wayland_window_message WM_WAYLAND_SET_FOREGROUND, }; -enum wayland_surface_config_state +enum surface_state { - WAYLAND_SURFACE_CONFIG_STATE_MAXIMIZED = (1 << 0), - WAYLAND_SURFACE_CONFIG_STATE_RESIZING = (1 << 1), - WAYLAND_SURFACE_CONFIG_STATE_TILED = (1 << 2), - WAYLAND_SURFACE_CONFIG_STATE_FULLSCREEN = (1 << 3) + SURFACE_STATE_MAXIMIZED = (1 << 0), + SURFACE_STATE_RESIZING = (1 << 1), + SURFACE_STATE_TILED = (1 << 2), + SURFACE_STATE_FULLSCREEN = (1 << 3) }; enum wayland_surface_role @@ -224,19 +224,19 @@ struct wayland_output struct wayland_output_state current; }; -struct wayland_surface_config +struct surface_config { - RECT rect; - enum wayland_surface_config_state state; - uint32_t serial; - BOOL processed; + RECT rect; /* rect of the compositor surface (in surface coordinates) */ + enum surface_state state; /* state of the compositor surface */ + uint32_t serial; /* serial of the corresponding xdg_surface_configure event */ + BOOL processed; /* config has been fully applied to the surface win32 window */ }; struct wayland_window_config { RECT rect; RECT client_rect; - enum wayland_surface_config_state state; + enum surface_state state; /* The scale (i.e., normalized dpi) the window is rendering at. */ double scale; BOOL visible; @@ -298,7 +298,11 @@ struct wayland_surface }; struct wp_alpha_modifier_surface_v1 *wp_alpha_modifier_surface_v1; - struct wayland_surface_config pending, requested, processing, current; + struct surface_config pending; /* incomplete surface config being received from the compositor */ + struct surface_config requested; /* latest complete surface config received from the compositor */ + struct surface_config processing; /* surface config being applied to the surface win32 window */ + struct surface_config current; /* latest config that has been applied to the surface win32 window */ + BOOL resizing; struct wayland_window_config window; int content_width, content_height; @@ -335,7 +339,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 *surface, struct wayland_surface_config *conf); +BOOL wayland_surface_config_is_compatible(struct wayland_surface *surface, struct 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 dee9980054a..ca9e0ca5c36 100644 --- a/dlls/winewayland.drv/window.c +++ b/dlls/winewayland.drv/window.c @@ -135,7 +135,7 @@ void wayland_win_data_release(struct wayland_win_data *data) static void wayland_win_data_get_config(struct wayland_win_data *data, struct wayland_window_config *conf) { - enum wayland_surface_config_state window_state = 0; + enum surface_state window_state = 0; DWORD style; conf->rect = data->rects.window; @@ -150,13 +150,13 @@ static void wayland_win_data_get_config(struct wayland_win_data *data, if (data->is_fullscreen) { if ((style & WS_MAXIMIZE) && (style & WS_CAPTION) == WS_CAPTION) - window_state |= WAYLAND_SURFACE_CONFIG_STATE_MAXIMIZED; + window_state |= SURFACE_STATE_MAXIMIZED; else if (!(style & WS_MINIMIZE)) - window_state |= WAYLAND_SURFACE_CONFIG_STATE_FULLSCREEN; + window_state |= SURFACE_STATE_FULLSCREEN; } else if (style & WS_MAXIMIZE) { - window_state |= WAYLAND_SURFACE_CONFIG_STATE_MAXIMIZED; + window_state |= SURFACE_STATE_MAXIMIZED; } conf->resizeable = data->resizeable; @@ -259,26 +259,26 @@ static void wayland_win_data_update_wayland_state(struct wayland_win_data *data) { /* First do all state unsettings, before setting new state. Some * Wayland compositors misbehave if the order is reversed. */ - if (!(surface->window.state & WAYLAND_SURFACE_CONFIG_STATE_MAXIMIZED) && - (surface->current.state & WAYLAND_SURFACE_CONFIG_STATE_MAXIMIZED) && + if (!(surface->window.state & SURFACE_STATE_MAXIMIZED) && + (surface->current.state & SURFACE_STATE_MAXIMIZED) && !surface->window.minimized) { xdg_toplevel_unset_maximized(surface->xdg_toplevel); } - if (!(surface->window.state & WAYLAND_SURFACE_CONFIG_STATE_FULLSCREEN) && - (surface->current.state & WAYLAND_SURFACE_CONFIG_STATE_FULLSCREEN) && + if (!(surface->window.state & SURFACE_STATE_FULLSCREEN) && + (surface->current.state & SURFACE_STATE_FULLSCREEN) && !surface->window.minimized) { xdg_toplevel_unset_fullscreen(surface->xdg_toplevel); } - if ((surface->window.state & WAYLAND_SURFACE_CONFIG_STATE_MAXIMIZED) && - !(surface->current.state & WAYLAND_SURFACE_CONFIG_STATE_MAXIMIZED)) + if ((surface->window.state & SURFACE_STATE_MAXIMIZED) && + !(surface->current.state & SURFACE_STATE_MAXIMIZED)) { xdg_toplevel_set_maximized(surface->xdg_toplevel); } - if ((surface->window.state & WAYLAND_SURFACE_CONFIG_STATE_FULLSCREEN) && - !(surface->current.state & WAYLAND_SURFACE_CONFIG_STATE_FULLSCREEN)) + if ((surface->window.state & SURFACE_STATE_FULLSCREEN) && + !(surface->current.state & SURFACE_STATE_FULLSCREEN)) { xdg_toplevel_set_fullscreen(surface->xdg_toplevel, NULL); } @@ -487,13 +487,13 @@ static void wayland_configure_window(HWND hwnd) * size adherence, in order to avoid spurious resizes. */ if (!state) flags |= SWP_NOSIZE; - if ((state & WAYLAND_SURFACE_CONFIG_STATE_RESIZING) && !surface->resizing) + if ((state & SURFACE_STATE_RESIZING) && !surface->resizing) { surface->resizing = TRUE; needs_enter_size_move = TRUE; } - if (!(state & WAYLAND_SURFACE_CONFIG_STATE_RESIZING) && surface->resizing) + if (!(state & SURFACE_STATE_RESIZING) && surface->resizing) { surface->resizing = FALSE; needs_exit_size_move = TRUE; @@ -501,8 +501,8 @@ static void wayland_configure_window(HWND hwnd) /* Transitions between normal/max/fullscreen may entail a frame change. */ if ((state ^ surface->current.state) & - (WAYLAND_SURFACE_CONFIG_STATE_MAXIMIZED | - WAYLAND_SURFACE_CONFIG_STATE_FULLSCREEN)) + (SURFACE_STATE_MAXIMIZED | + SURFACE_STATE_FULLSCREEN)) { flags |= SWP_FRAMECHANGED; } @@ -511,7 +511,7 @@ static void wayland_configure_window(HWND hwnd) * 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) && + if ((surface->window.state & SURFACE_STATE_FULLSCREEN) && wayland_surface_config_is_compatible(surface, &surface->processing)) { flags |= SWP_NOSIZE; @@ -547,17 +547,17 @@ static void wayland_configure_window(HWND hwnd) if (needs_exit_size_move) send_message(hwnd, WM_EXITSIZEMOVE, 0, 0); style = NtUserGetWindowLongW(hwnd, GWL_STYLE); - if (!(state & WAYLAND_SURFACE_CONFIG_STATE_MAXIMIZED) != !(style & WS_MAXIMIZE) - && !(state & WAYLAND_SURFACE_CONFIG_STATE_FULLSCREEN)) + if (!(state & SURFACE_STATE_MAXIMIZED) != !(style & WS_MAXIMIZE) + && !(state & SURFACE_STATE_FULLSCREEN)) NtUserSetWindowLong(hwnd, GWL_STYLE, style ^ WS_MAXIMIZE, FALSE); /* The Wayland maximized and fullscreen states are very strict about * surface size, so don't let the application override it. The tiled state * is not as strict, but it indicates a strong size preference, so try to * respect it. */ - if (state & (WAYLAND_SURFACE_CONFIG_STATE_MAXIMIZED | - WAYLAND_SURFACE_CONFIG_STATE_FULLSCREEN | - WAYLAND_SURFACE_CONFIG_STATE_TILED)) + if (state & (SURFACE_STATE_MAXIMIZED | + SURFACE_STATE_FULLSCREEN | + SURFACE_STATE_TILED)) { flags |= SWP_NOSENDCHANGING; } -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/11248
On Mon Jun 29 18:25:28 2026 +0000, Etaash Mathamsetty wrote:
Since we are no longer updating the position I don't think we need to scale the config rects beforehand anymore. Those can just stay the way they were before Hmm yeah, you're probably right, I've done that.
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/11248#note_144678
Rebased and updated to keep the surface config rects in surface coordinates and keep the surface scaling logic as it is. Also added a change to document and reduce the wayland_surface_config structs verbosity. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/11248#note_144679
We need to move `map_rect_to_surface(surface, rect)` here since a change in scale means the popup needs to move. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/11248#note_144757
this needs to be moved before ``` if (!(xdg_positioner = xdg_positioner_create(map_rect_to_surface(surface, rect)))) goto err; ``` to avoid a reposition -- https://gitlab.winehq.org/wine/wine/-/merge_requests/11248#note_144759
For some reason this isnt triggering a pWindowPosChanged callback to the driver, probably due to the combination of SWP_NOSIZE, SWP_NOMOVE? -- https://gitlab.winehq.org/wine/wine/-/merge_requests/11248#note_144761
On Fri Jul 3 04:54:46 2026 +0000, Etaash Mathamsetty wrote:
For some reason this isnt triggering a pWindowPosChanged callback to the driver, probably due to the combination of SWP_NOSIZE, SWP_NOMOVE? nvm this is caused by another thing I already caught in one of the above review comments
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/11248#note_144762
In order for a popup to move we need to ack the xdg_popup::configure event associated with the movement of a popup. For example, let's say a popup moves from a screen of scaling 150% to 125% then the popup will be stuck at a stale x,y position since the app may not be constantly refreshing the contents of the popup. Therefore, we must ack the config (even with no new contents) to enable the popup to move. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/11248#note_144763
On Fri Jul 3 12:23:52 2026 +0000, Etaash Mathamsetty wrote:
nvm this is caused by another thing I already caught in one of the above review comments I actually wonder if we want to do that now, since we don't provide win32u with the actual popup positions either. I suspect we will now end up requesting the win32 position again, even if the compositor has moved the popup elsewhere.
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/11248#note_144799
On Fri Jul 3 04:14:49 2026 +0000, Etaash Mathamsetty wrote:
We need to move `map_rect_to_surface(surface, rect)` here since a change in scale means the popup needs to move. The more I trip on these issues the more I fail to see how the surface scaling changes can even work correctly in the first place. There's no scale event reception ack, and it indeed doesn't seem to come with a configure event either.
How can the compositor tell whether we request a surface config with the latest surface scale? We might very well be requesting a new surface config with the last scale we are aware of, but a scale change might actually be pending in the event queue and we may have missed it. The compositor will receive a new surface size or position and can only assume we used the latest scale it sent, and it might end up reconfiguring the surface with the wrong size? Later, upon receiving a surface config event, for instance after we requested a window resize or position change, we cannot really know the scale that was used to request its dimension, and our conversion from surface coordinates might be plainly wrong. Of course these are all very tight corner cases, and unlikely to happen as scale only rarely changes, but it seems to me that it changes at the very least once, after surface creation, unless the default screen scale is used. This might happen close to initial window configuration and might be intertwined with other configure events? It also changes, and sometimes several times in a row when moving window from one monitor to another when different scale is used, so reacting to these changes is going to be very racy. The successive changes are probably caused by monitor selection when surface scale changes, as changing the scale may end up with the surface back on its original monitor if no hysteresis is used. Overall it simply doesn't fit Windows model either. We can have per-monitor scaling, but per-window scaling doesn't exist there. I understand that Wayland per-surface scaling is actually most of the time just a per-monitor scaling, but it doesn't appear like this and it doesn't map well. Also, it seems to me that we simply don't implement it the right way: for instance setting a monitor scale to 200%, everything on that monitor gets scaled x2... except for Wine windows which simply stay exactly the same. Sure, they look crisp and increasing Wine DPI can make them look bigger, but it's different from what every other window does. They get scaled, crisp or blurry, and scaling matters more than blurriness in that specific use case. For these reasons I would be extremely happy if we could get right of it, eventually if we can it should be plugged into per-monitor DPI, but if we can't then so be it, blame Wayland. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/11248#note_144806
On Fri Jul 3 12:23:52 2026 +0000, Rémi Bernon wrote:
I actually wonder if we want to do that now, since we don't provide win32u with the actual popup positions either. I suspect we will now end up requesting the win32 position again, even if the compositor has moved the popup elsewhere. The win32u position remains the same but the popup needs to be placed at a different x,y position (see map_rect_to_surface) so we do still need to trigger the windowposchanged and place the popup in the new position
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/11248#note_144819
How can the compositor tell whether we request a surface config with the latest surface scale? We might very well be requesting a new surface config with the last scale we are aware of, but a scale change might actually be pending in the event queue and we may have missed it. The compositor will receive a new surface size or position and can only assume we used the latest scale it sent, and it might end up reconfiguring the surface with the wrong size?
We control the window geometry width/height, the compositor cannot do much about that beyond giving a suggestion. Let's say we assume that the 1.0 scale is the scale we use during the construction of a popup, and then after it's constructed, the compositor sends that the scale is now 1.25. Only the basis of the surface coordinates has now changed, so all we need to do is resize/reposition the toplevel/popup using the same existing win32u window rect (its basis is the same) and we are following what the compositor expects of us. Since the win32u coordinates have remained the same, and the surface scale has increased, the popup moves closer towards the top left corner of the window and its size reduces.
Later, upon receiving a surface config event, for instance after we requested a window resize or position change, we cannot really know the scale that was used to request its dimension, and our conversion from surface coordinates might be plainly wrong.
For position change, we can know the scale indirectly. Since the win32u position directly into controls the xdg_popup offset if the scale changes, then the rect in the current/processing config will be different. (However, trying to adjust the win32u rect from the surface config is insanely hard because we do not know the scale used to create the surface config rects) For window resize, if the compositor is resizing our window through, let's say, a user initiated resize, then we do run into issues since the scale could have changed between the configure event and when we try to convert the processing rect back to win32u coordinates, it will be converted wrong. Therefore, we need a better system, and I will implement that in another MR.
Also, it seems to me that we simply don't implement it the right way: for instance setting a monitor scale to 200%, everything on that monitor gets scaled x2... except for Wine windows which simply stay exactly the same. Sure, they look crisp and increasing Wine DPI can make them look bigger, but it's different from what every other window does. They get scaled, crisp or blurry, and scaling matters more than blurriness in that specific use case.
It is different from what other windows do, but this is exactly what happens on XWayland already.
For these reasons I would be extremely happy if we could get right of it, eventually if we can it should be plugged into per-monitor DPI, but if we can't then so be it, blame Wayland.
`wl_output::scale` only supports an integer scale... which is extremely annoying. I guess we could technically calculate the scaling by looking at the logical width of the xdg output and the width from wl_output. Then use wl_surface::enter/leave to update the win32u position. However, I think we can (and should) leverage both methods. From the fractional scaling protocol:
Notification of a new preferred scale for this surface that the compositor suggests that the client should use.
The compositor is only suggesting that we should try and use this scale, so we can ignore scale events without an associated configure event and avoid a ton of race conditions while still being spec compliant. (Or whenever else we can't handle them) Then we can store the scale within each surface config so we know which scale was used for each config. I'll spend some time this weekend to make this -- https://gitlab.winehq.org/wine/wine/-/merge_requests/11248#note_144830
It is different from what other windows do, but this is exactly what happens on XWayland already.
Well I don't know that we want to replicate XWayland, it seems that in this case it's also doing it wrong. If the user wants / needs to have one of their screen upscaled, it's likely to be for readability reasons. In that case, we need to scale our windows the same way every other application does. That means the window will possibly look blurry, unless we can plug it into the per-monitor DPI, but that's still better than not scaling windows at all. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/11248#note_144898
On Mon Jul 6 06:56:01 2026 +0000, Rémi Bernon wrote:
It is different from what other windows do, but this is exactly what happens on XWayland already. Well I don't know that we want to replicate XWayland, it seems that in this case it's also doing it wrong. If the user wants / needs to have one of their screen upscaled, it's likely to be for readability reasons. In that case, we need to scale our windows the same way every other application does. That means the window will possibly look blurry, unless we can plug it into the per-monitor DPI, but that's still better than not scaling windows at all. That is true, I'll work on getting the output scaling reported to wine. It will likely still use the fractional scaling protocol to get the scale value for the surface though. Hopefully I can get rid of it with that work, but it's significantly more complex than using the fractional scale protocol
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/11248#note_144899
On Mon Jul 6 08:31:23 2026 +0000, Etaash Mathamsetty wrote:
That is true, I'll work on getting the output scaling reported to wine. It might still use the fractional scaling protocol to get the scale value for the surface though. Hopefully I can get rid of it with that work, but it's significantly more complex than using the fractional scale protocol Fwiw I think we might be able to get per-monitor fractional DPI on the wl_output side, from the current_mode width/height vs logical_w/logical_h ratio. Note that in any case it won't work well on its own, because 1) we don't have very good per-monitor DPI support in Wine in general yet, but that's something to be improved on the Wine builtin modules level, and 2) we don't sync window positions in winewayland, so the window might appear on one monitor but its Win32 position might tell otherwise, that would need to be sorted out as well.
In general I feel that this surface scaling was just premature, and often getting in the way. We can either get rid of it for now, or simply ignore it as much as possible, like for instance the interactions when scaling changes and popup needs to be repositioned, because I don't think this will be solved well without the above fixed first. We should be able to say that changing the Wayland monitor scale is not well supported while a Wine application is running and it needs to be restarted to take effect. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/11248#note_144901
participants (4)
-
Etaash Mathamsetty -
Etaash Mathamsetty (@etaash.mathamsetty) -
Rémi Bernon -
Rémi Bernon (@rbernon)