wine/dlls/winewayland.drv/window.c
Alexandros Frantzis dc35c8a7b9 winewayland.drv: Ensure Vulkan parent surface is mapped with proper size.
The client area subsurface (the target of Vulkan rendering) is not going
to be presented properly (or at all) by the compositor if its parent
surface is not mapped or doesn't have the right size, even though the
window may be visible from Wine's perspective. To avoid this issue,
ensure that the parent surface has up-to-date contents in terms of size,
in case these haven't been provided yet, or will never be provided
(e.g., when the Vulkan rendering is fullscreen).
2023-11-29 20:44:56 +01:00

639 lines
21 KiB
C

/*
* Wayland window handling
*
* Copyright 2020 Alexandros Frantzis for Collabora Ltd
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
*/
#if 0
#pragma makedep unix
#endif
#include "config.h"
#include <assert.h>
#include <stdlib.h>
#include "waylanddrv.h"
#include "wine/debug.h"
WINE_DEFAULT_DEBUG_CHANNEL(waylanddrv);
/* private window data */
struct wayland_win_data
{
struct rb_entry entry;
/* hwnd that this private data belongs to */
HWND hwnd;
/* wayland surface (if any) for this window */
struct wayland_surface *wayland_surface;
/* wine window_surface backing this window */
struct window_surface *window_surface;
/* USER window rectangle relative to win32 parent window client area */
RECT window_rect;
/* USER client rectangle relative to win32 parent window client area */
RECT client_rect;
};
static int wayland_win_data_cmp_rb(const void *key,
const struct rb_entry *entry)
{
HWND key_hwnd = (HWND)key; /* cast to work around const */
const struct wayland_win_data *entry_win_data =
RB_ENTRY_VALUE(entry, const struct wayland_win_data, entry);
if (key_hwnd < entry_win_data->hwnd) return -1;
if (key_hwnd > entry_win_data->hwnd) return 1;
return 0;
}
static pthread_mutex_t win_data_mutex = PTHREAD_MUTEX_INITIALIZER;
static struct rb_tree win_data_rb = { wayland_win_data_cmp_rb };
/***********************************************************************
* wayland_win_data_create
*
* Create a data window structure for an existing window.
*/
static struct wayland_win_data *wayland_win_data_create(HWND hwnd,
const RECT *window_rect,
const RECT *client_rect)
{
struct wayland_win_data *data;
struct rb_entry *rb_entry;
HWND parent;
/* Don't create win data for desktop or HWND_MESSAGE windows. */
if (!(parent = NtUserGetAncestor(hwnd, GA_PARENT))) return NULL;
if (parent != NtUserGetDesktopWindow() && !NtUserGetAncestor(parent, GA_PARENT))
return NULL;
if (!(data = calloc(1, sizeof(*data)))) return NULL;
data->hwnd = hwnd;
data->window_rect = *window_rect;
data->client_rect = *client_rect;
pthread_mutex_lock(&win_data_mutex);
/* Check that another thread hasn't already created the wayland_win_data. */
if ((rb_entry = rb_get(&win_data_rb, hwnd)))
{
free(data);
return RB_ENTRY_VALUE(rb_entry, struct wayland_win_data, entry);
}
rb_put(&win_data_rb, hwnd, &data->entry);
TRACE("hwnd=%p\n", data->hwnd);
return data;
}
/***********************************************************************
* wayland_win_data_destroy
*/
static void wayland_win_data_destroy(struct wayland_win_data *data)
{
TRACE("hwnd=%p\n", data->hwnd);
rb_remove(&win_data_rb, &data->entry);
pthread_mutex_unlock(&win_data_mutex);
if (data->window_surface)
{
wayland_window_surface_update_wayland_surface(data->window_surface, NULL);
window_surface_release(data->window_surface);
}
if (data->wayland_surface) wayland_surface_destroy(data->wayland_surface);
free(data);
}
/***********************************************************************
* wayland_win_data_get
*
* Lock and return the data structure associated with a window.
*/
static struct wayland_win_data *wayland_win_data_get(HWND hwnd)
{
struct rb_entry *rb_entry;
pthread_mutex_lock(&win_data_mutex);
if ((rb_entry = rb_get(&win_data_rb, hwnd)))
return RB_ENTRY_VALUE(rb_entry, struct wayland_win_data, entry);
pthread_mutex_unlock(&win_data_mutex);
return NULL;
}
/***********************************************************************
* wayland_win_data_release
*
* Release the data returned by wayland_win_data_get.
*/
static void wayland_win_data_release(struct wayland_win_data *data)
{
assert(data);
pthread_mutex_unlock(&win_data_mutex);
}
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;
DWORD style;
conf->rect = data->window_rect;
conf->client_rect = data->client_rect;
style = NtUserGetWindowLongW(data->hwnd, GWL_STYLE);
TRACE("window=%s style=%#lx\n", wine_dbgstr_rect(&conf->rect), (long)style);
/* The fullscreen state is implied by the window position and style. */
if (NtUserIsWindowRectFullScreen(&conf->rect))
{
if ((style & WS_MAXIMIZE) && (style & WS_CAPTION) == WS_CAPTION)
window_state |= WAYLAND_SURFACE_CONFIG_STATE_MAXIMIZED;
else if (!(style & WS_MINIMIZE))
window_state |= WAYLAND_SURFACE_CONFIG_STATE_FULLSCREEN;
}
else if (style & WS_MAXIMIZE)
{
window_state |= WAYLAND_SURFACE_CONFIG_STATE_MAXIMIZED;
}
conf->state = window_state;
conf->scale = NtUserGetDpiForWindow(data->hwnd) / 96.0;
conf->visible = (style & WS_VISIBLE) == WS_VISIBLE;
}
static void wayland_win_data_update_wayland_surface(struct wayland_win_data *data)
{
struct wayland_surface *surface = data->wayland_surface;
HWND parent = NtUserGetAncestor(data->hwnd, GA_PARENT);
BOOL visible, xdg_visible;
TRACE("hwnd=%p\n", data->hwnd);
/* We don't want wayland surfaces for child windows. */
if (parent != NtUserGetDesktopWindow() && parent != 0)
{
if (data->window_surface)
wayland_window_surface_update_wayland_surface(data->window_surface, NULL);
if (surface) wayland_surface_destroy(surface);
surface = NULL;
goto out;
}
/* Otherwise ensure that we have a wayland surface. */
if (!surface && !(surface = wayland_surface_create(data->hwnd))) return;
visible = (NtUserGetWindowLongW(data->hwnd, GWL_STYLE) & WS_VISIBLE) == WS_VISIBLE;
xdg_visible = surface->xdg_toplevel != NULL;
pthread_mutex_lock(&surface->mutex);
if (visible != xdg_visible)
{
/* If we have a pre-existing surface ensure it has no role. */
if (data->wayland_surface) wayland_surface_clear_role(surface);
/* 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. */
if (visible) wayland_surface_make_toplevel(surface);
}
wayland_win_data_get_config(data, &surface->window);
pthread_mutex_unlock(&surface->mutex);
if (data->window_surface)
wayland_window_surface_update_wayland_surface(data->window_surface, surface);
out:
TRACE("hwnd=%p surface=%p=>%p\n", data->hwnd, data->wayland_surface, surface);
data->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;
pthread_mutex_lock(&surface->mutex);
if (!surface->xdg_toplevel) goto out;
processing_config = surface->processing.serial &&
!surface->processing.processed;
TRACE("hwnd=%p window_state=%#x %s->state=%#x\n",
data->hwnd, surface->window.state,
processing_config ? "processing" : "current",
processing_config ? surface->processing.state : surface->current.state);
/* If we are not processing a compositor requested config, use the
* window state to determine and update the Wayland state. */
if (!processing_config)
{
/* 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))
{
xdg_toplevel_unset_maximized(surface->xdg_toplevel);
}
if (!(surface->window.state & WAYLAND_SURFACE_CONFIG_STATE_FULLSCREEN) &&
(surface->current.state & WAYLAND_SURFACE_CONFIG_STATE_FULLSCREEN))
{
xdg_toplevel_unset_fullscreen(surface->xdg_toplevel);
}
if ((surface->window.state & WAYLAND_SURFACE_CONFIG_STATE_MAXIMIZED) &&
!(surface->current.state & WAYLAND_SURFACE_CONFIG_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))
{
xdg_toplevel_set_fullscreen(surface->xdg_toplevel, NULL);
}
}
else
{
surface->processing.processed = TRUE;
}
out:
pthread_mutex_unlock(&surface->mutex);
wl_display_flush(process_wayland.wl_display);
}
/***********************************************************************
* WAYLAND_DestroyWindow
*/
void WAYLAND_DestroyWindow(HWND hwnd)
{
struct wayland_win_data *data;
TRACE("%p\n", hwnd);
if (!(data = wayland_win_data_get(hwnd))) return;
wayland_win_data_destroy(data);
}
/***********************************************************************
* WAYLAND_WindowPosChanging
*/
BOOL WAYLAND_WindowPosChanging(HWND hwnd, HWND insert_after, UINT swp_flags,
const RECT *window_rect, const RECT *client_rect,
RECT *visible_rect, struct window_surface **surface)
{
struct wayland_win_data *data = wayland_win_data_get(hwnd);
HWND parent;
BOOL visible;
RECT surface_rect;
TRACE("hwnd %p window %s client %s visible %s after %p flags %08x\n",
hwnd, wine_dbgstr_rect(window_rect), wine_dbgstr_rect(client_rect),
wine_dbgstr_rect(visible_rect), insert_after, swp_flags);
if (!data && !(data = wayland_win_data_create(hwnd, window_rect, client_rect)))
return TRUE;
/* Release the dummy surface wine provides for toplevels. */
if (*surface) window_surface_release(*surface);
*surface = NULL;
parent = NtUserGetAncestor(hwnd, GA_PARENT);
visible = ((NtUserGetWindowLongW(hwnd, GWL_STYLE) & WS_VISIBLE) ||
(swp_flags & SWP_SHOWWINDOW)) &&
!(swp_flags & SWP_HIDEWINDOW);
/* Check if we don't want a dedicated window surface. */
if ((parent && parent != NtUserGetDesktopWindow()) || !visible) goto done;
surface_rect = *window_rect;
OffsetRect(&surface_rect, -surface_rect.left, -surface_rect.top);
/* Check if we can reuse our current window surface. */
if (data->window_surface &&
EqualRect(&data->window_surface->rect, &surface_rect))
{
window_surface_add_ref(data->window_surface);
*surface = data->window_surface;
TRACE("reusing surface %p\n", *surface);
goto done;
}
*surface = wayland_window_surface_create(data->hwnd, &surface_rect);
done:
wayland_win_data_release(data);
return TRUE;
}
/***********************************************************************
* WAYLAND_WindowPosChanged
*/
void WAYLAND_WindowPosChanged(HWND hwnd, HWND insert_after, UINT swp_flags,
const RECT *window_rect, const RECT *client_rect,
const RECT *visible_rect, const RECT *valid_rects,
struct window_surface *surface)
{
struct wayland_win_data *data = wayland_win_data_get(hwnd);
TRACE("hwnd %p window %s client %s visible %s after %p flags %08x\n",
hwnd, wine_dbgstr_rect(window_rect), wine_dbgstr_rect(client_rect),
wine_dbgstr_rect(visible_rect), insert_after, swp_flags);
if (!data) return;
data->window_rect = *window_rect;
data->client_rect = *client_rect;
if (surface) window_surface_add_ref(surface);
if (data->window_surface) window_surface_release(data->window_surface);
data->window_surface = surface;
wayland_win_data_update_wayland_surface(data);
if (data->wayland_surface) wayland_win_data_update_wayland_state(data);
wayland_win_data_release(data);
}
static void wayland_resize_desktop(void)
{
RECT virtual_rect = NtUserGetVirtualScreenRect();
NtUserSetWindowPos(NtUserGetDesktopWindow(), 0,
virtual_rect.left, virtual_rect.top,
virtual_rect.right - virtual_rect.left,
virtual_rect.bottom - virtual_rect.top,
SWP_NOZORDER | SWP_NOACTIVATE | SWP_DEFERERASE);
}
static void wayland_configure_window(HWND hwnd)
{
struct wayland_surface *surface;
INT width, height, window_width, window_height;
INT window_surf_width, window_surf_height;
UINT flags = 0;
uint32_t state;
DWORD style;
BOOL needs_enter_size_move = FALSE;
BOOL needs_exit_size_move = FALSE;
if (!(surface = wayland_surface_lock_hwnd(hwnd))) return;
if (!surface->xdg_toplevel)
{
TRACE("missing xdg_toplevel, returning\n");
pthread_mutex_unlock(&surface->mutex);
return;
}
if (!surface->requested.serial)
{
TRACE("requested configure event already handled, returning\n");
pthread_mutex_unlock(&surface->mutex);
return;
}
surface->processing = surface->requested;
memset(&surface->requested, 0, sizeof(surface->requested));
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.width;
height = surface->processing.height;
}
else
{
width = height = 0;
}
if ((state & WAYLAND_SURFACE_CONFIG_STATE_RESIZING) && !surface->resizing)
{
surface->resizing = TRUE;
needs_enter_size_move = TRUE;
}
if (!(state & WAYLAND_SURFACE_CONFIG_STATE_RESIZING) && surface->resizing)
{
surface->resizing = FALSE;
needs_exit_size_move = TRUE;
}
/* 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))
{
flags |= SWP_FRAMECHANGED;
}
wayland_surface_coords_from_window(surface,
surface->window.rect.right -
surface->window.rect.left,
surface->window.rect.bottom -
surface->window.rect.top,
&window_surf_width, &window_surf_height);
/* 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,
window_surf_width, window_surf_height,
surface->window.state))
{
flags |= SWP_NOSIZE;
}
wayland_surface_coords_to_window(surface, width, height,
&window_width, &window_height);
pthread_mutex_unlock(&surface->mutex);
TRACE("processing=%dx%d,%#x\n", width, height, 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);
flags |= SWP_NOACTIVATE | SWP_NOZORDER | SWP_NOOWNERZORDER | SWP_NOMOVE;
if (window_width == 0 || window_height == 0) flags |= SWP_NOSIZE;
style = NtUserGetWindowLongW(hwnd, GWL_STYLE);
if (!(state & WAYLAND_SURFACE_CONFIG_STATE_MAXIMIZED) != !(style & WS_MAXIMIZE))
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))
{
flags |= SWP_NOSENDCHANGING;
}
NtUserSetWindowPos(hwnd, 0, 0, 0, window_width, window_height, flags);
}
/**********************************************************************
* WAYLAND_WindowMessage
*/
LRESULT WAYLAND_WindowMessage(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp)
{
switch (msg)
{
case WM_WAYLAND_INIT_DISPLAY_DEVICES:
wayland_init_display_devices(TRUE);
wayland_resize_desktop();
return 0;
case WM_WAYLAND_CONFIGURE:
wayland_configure_window(hwnd);
return 0;
default:
FIXME("got window msg %x hwnd %p wp %lx lp %lx\n", msg, hwnd, (long)wp, lp);
return 0;
}
}
/**********************************************************************
* WAYLAND_DesktopWindowProc
*/
LRESULT WAYLAND_DesktopWindowProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp)
{
switch (msg)
{
case WM_DISPLAYCHANGE:
wayland_resize_desktop();
break;
}
return NtUserMessageCall(hwnd, msg, wp, lp, 0, NtUserDefWindowProc, FALSE);
}
static enum xdg_toplevel_resize_edge hittest_to_resize_edge(WPARAM hittest)
{
switch (hittest)
{
case WMSZ_LEFT: return XDG_TOPLEVEL_RESIZE_EDGE_LEFT;
case WMSZ_RIGHT: return XDG_TOPLEVEL_RESIZE_EDGE_RIGHT;
case WMSZ_TOP: return XDG_TOPLEVEL_RESIZE_EDGE_TOP;
case WMSZ_TOPLEFT: return XDG_TOPLEVEL_RESIZE_EDGE_TOP_LEFT;
case WMSZ_TOPRIGHT: return XDG_TOPLEVEL_RESIZE_EDGE_TOP_RIGHT;
case WMSZ_BOTTOM: return XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM;
case WMSZ_BOTTOMLEFT: return XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM_LEFT;
case WMSZ_BOTTOMRIGHT: return XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM_RIGHT;
default: return XDG_TOPLEVEL_RESIZE_EDGE_NONE;
}
}
/***********************************************************************
* WAYLAND_SysCommand
*/
LRESULT WAYLAND_SysCommand(HWND hwnd, WPARAM wparam, LPARAM lparam)
{
LRESULT ret = -1;
WPARAM command = wparam & 0xfff0;
uint32_t button_serial;
struct wl_seat *wl_seat;
struct wayland_surface *surface;
TRACE("cmd=%lx hwnd=%p, %lx, %lx\n",
(long)command, hwnd, (long)wparam, lparam);
pthread_mutex_lock(&process_wayland.pointer.mutex);
if (process_wayland.pointer.focused_hwnd == hwnd)
button_serial = process_wayland.pointer.button_serial;
else
button_serial = 0;
pthread_mutex_unlock(&process_wayland.pointer.mutex);
if (command == SC_MOVE || command == SC_SIZE)
{
if ((surface = wayland_surface_lock_hwnd(hwnd)))
{
pthread_mutex_lock(&process_wayland.seat.mutex);
wl_seat = process_wayland.seat.wl_seat;
if (wl_seat && surface->xdg_toplevel && button_serial)
{
if (command == SC_MOVE)
{
xdg_toplevel_move(surface->xdg_toplevel, wl_seat, button_serial);
}
else if (command == SC_SIZE)
{
xdg_toplevel_resize(surface->xdg_toplevel, wl_seat, button_serial,
hittest_to_resize_edge(wparam & 0x0f));
}
}
pthread_mutex_unlock(&process_wayland.seat.mutex);
pthread_mutex_unlock(&surface->mutex);
ret = 0;
}
}
wl_display_flush(process_wayland.wl_display);
return ret;
}
/**********************************************************************
* wayland_window_flush
*
* Flush the window_surface associated with a HWND.
*/
void wayland_window_flush(HWND hwnd)
{
struct wayland_win_data *data = wayland_win_data_get(hwnd);
if (!data) return;
if (data->window_surface)
data->window_surface->funcs->flush(data->window_surface);
wayland_win_data_release(data);
}
/**********************************************************************
* wayland_surface_lock_hwnd
*/
struct wayland_surface *wayland_surface_lock_hwnd(HWND hwnd)
{
struct wayland_win_data *data = wayland_win_data_get(hwnd);
struct wayland_surface *surface;
if (!data) return NULL;
if ((surface = data->wayland_surface)) pthread_mutex_lock(&surface->mutex);
wayland_win_data_release(data);
return surface;
}