From: Etaash Mathamsetty <etaash.mathamsetty@gmail.com> --- dlls/winewayland.drv/wayland.c | 8 +- dlls/winewayland.drv/wayland_surface.c | 261 ++++++++++++++++--------- dlls/winewayland.drv/waylanddrv.h | 14 +- dlls/winewayland.drv/window.c | 53 ++--- 4 files changed, 199 insertions(+), 137 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 015777ee9ea..e094642c878 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); @@ -87,6 +99,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 +126,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 = rect; surface->pending.state = config_state; } @@ -137,6 +150,64 @@ 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; + struct wayland_surface *surface; + 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 = rect; + surface->pending.state = 0; + } + + 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) @@ -162,16 +233,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 = @@ -322,6 +387,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 * @@ -370,46 +457,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; + struct xdg_positioner *xdg_positioner; + RECT rect = surface->window.rect; - wayland_surface_clear_role(surface); - surface->role = WAYLAND_SURFACE_ROLE_SUBSURFACE; + assert(!surface->role || surface->role == WAYLAND_SURFACE_ROLE_POPUP); - TRACE("surface=%p owner=%p\n", surface, owner); + OffsetRect(&rect, -owner->window.rect.left, -owner->window.rect.top); + rect = map_rect_to_surface(surface, rect); - 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; + if (surface->current.rect.left == rect.left && surface->current.rect.top == rect.top) return; + + TRACE("surface=%p owner=%p rect=%s, repositioning\n", surface, owner, wine_dbgstr_rect(&rect)); + + if (!(xdg_positioner = xdg_positioner_create(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); + + if (!(xdg_positioner = xdg_positioner_create(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"); } /********************************************************************** @@ -451,25 +558,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)); @@ -646,8 +753,12 @@ static void wayland_surface_reconfigure_geometry(struct wayland_surface *surface if (!IsRectEmpty(&rect)) { 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) { @@ -751,39 +862,6 @@ static BOOL wayland_surface_reconfigure_xdg(struct wayland_surface *surface, REC 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 * @@ -793,10 +871,12 @@ 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); + struct wayland_win_data *owner_data; + struct wayland_surface *owner; + 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(&window->rect), window->state, wine_dbgstr_rect(&surface->processing.rect), surface->processing.state, wine_dbgstr_rect(&surface->current.rect), surface->current.state); @@ -804,14 +884,15 @@ BOOL wayland_surface_reconfigure(struct wayland_surface *surface) { case WAYLAND_SURFACE_ROLE_NONE: break; + case WAYLAND_SURFACE_ROLE_POPUP: + if (!(owner_data = wayland_win_data_get_nolock(surface->owner_hwnd))) return FALSE; + if ((owner = owner_data->wayland_surface)) OffsetRect(&rect, -owner->window.rect.left, -owner->window.rect.top); + /* fallthrough */ case WAYLAND_SURFACE_ROLE_TOPLEVEL: + rect = map_rect_to_surface(surface, rect); if (!surface->xdg_surface) break; /* surface role has been cleared */ if (!wayland_surface_reconfigure_xdg(surface, rect)) 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, rect.right - rect.left, rect.bottom - rect.top); diff --git a/dlls/winewayland.drv/waylanddrv.h b/dlls/winewayland.drv/waylanddrv.h index e0df8e56bb5..fe8494fa03b 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, @@ -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 afef743755b..d3a722390ea 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; @@ -512,13 +494,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