wine/dlls/winewayland.drv/window.c
Alexandros Frantzis fc8570a1a6 winewayland.drv: Do not commit buffers to unconfigured surfaces.
The xdg-shell protocol disallows buffer commits to a wl_surface with an
xdg_surface based role before we ack the first configure event. Failing
to adhere to this requirement will get our client disconnected by the
compositor with a protocol error.
2023-06-29 13:57:04 +02:00

341 lines
11 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;
};
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)
{
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;
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_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;
if (visible != xdg_visible)
{
pthread_mutex_lock(&surface->mutex);
/* 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);
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;
}
/***********************************************************************
* 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))) 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;
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);
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);
}
/**********************************************************************
* 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;
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);
}
/**********************************************************************
* 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);
}