2 * Wayland window handling
4 * Copyright 2020 Alexandros Frantzis for Collabora Ltd
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2.1 of the License, or (at your option) any later version.
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this library; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
30 #include "waylanddrv.h"
32 #include "wine/debug.h"
34 WINE_DEFAULT_DEBUG_CHANNEL(waylanddrv
);
36 /* private window data */
37 struct wayland_win_data
39 struct rb_entry entry
;
40 /* hwnd that this private data belongs to */
42 /* wayland surface (if any) for this window */
43 struct wayland_surface
*wayland_surface
;
44 /* wine window_surface backing this window */
45 struct window_surface
*window_surface
;
46 /* USER window rectangle relative to win32 parent window client area */
48 /* USER client rectangle relative to win32 parent window client area */
52 static int wayland_win_data_cmp_rb(const void *key
,
53 const struct rb_entry
*entry
)
55 HWND key_hwnd
= (HWND
)key
; /* cast to work around const */
56 const struct wayland_win_data
*entry_win_data
=
57 RB_ENTRY_VALUE(entry
, const struct wayland_win_data
, entry
);
59 if (key_hwnd
< entry_win_data
->hwnd
) return -1;
60 if (key_hwnd
> entry_win_data
->hwnd
) return 1;
64 static pthread_mutex_t win_data_mutex
= PTHREAD_MUTEX_INITIALIZER
;
65 static struct rb_tree win_data_rb
= { wayland_win_data_cmp_rb
};
67 /***********************************************************************
68 * wayland_win_data_create
70 * Create a data window structure for an existing window.
72 static struct wayland_win_data
*wayland_win_data_create(HWND hwnd
,
73 const RECT
*window_rect
,
74 const RECT
*client_rect
)
76 struct wayland_win_data
*data
;
77 struct rb_entry
*rb_entry
;
80 /* Don't create win data for desktop or HWND_MESSAGE windows. */
81 if (!(parent
= NtUserGetAncestor(hwnd
, GA_PARENT
))) return NULL
;
82 if (parent
!= NtUserGetDesktopWindow() && !NtUserGetAncestor(parent
, GA_PARENT
))
85 if (!(data
= calloc(1, sizeof(*data
)))) return NULL
;
88 data
->window_rect
= *window_rect
;
89 data
->client_rect
= *client_rect
;
91 pthread_mutex_lock(&win_data_mutex
);
93 /* Check that another thread hasn't already created the wayland_win_data. */
94 if ((rb_entry
= rb_get(&win_data_rb
, hwnd
)))
97 return RB_ENTRY_VALUE(rb_entry
, struct wayland_win_data
, entry
);
100 rb_put(&win_data_rb
, hwnd
, &data
->entry
);
102 TRACE("hwnd=%p\n", data
->hwnd
);
107 /***********************************************************************
108 * wayland_win_data_destroy
110 static void wayland_win_data_destroy(struct wayland_win_data
*data
)
112 TRACE("hwnd=%p\n", data
->hwnd
);
114 rb_remove(&win_data_rb
, &data
->entry
);
116 pthread_mutex_unlock(&win_data_mutex
);
118 if (data
->window_surface
)
120 wayland_window_surface_update_wayland_surface(data
->window_surface
, NULL
);
121 window_surface_release(data
->window_surface
);
123 if (data
->wayland_surface
) wayland_surface_destroy(data
->wayland_surface
);
127 /***********************************************************************
128 * wayland_win_data_get
130 * Lock and return the data structure associated with a window.
132 static struct wayland_win_data
*wayland_win_data_get(HWND hwnd
)
134 struct rb_entry
*rb_entry
;
136 pthread_mutex_lock(&win_data_mutex
);
138 if ((rb_entry
= rb_get(&win_data_rb
, hwnd
)))
139 return RB_ENTRY_VALUE(rb_entry
, struct wayland_win_data
, entry
);
141 pthread_mutex_unlock(&win_data_mutex
);
146 /***********************************************************************
147 * wayland_win_data_release
149 * Release the data returned by wayland_win_data_get.
151 static void wayland_win_data_release(struct wayland_win_data
*data
)
154 pthread_mutex_unlock(&win_data_mutex
);
157 static void wayland_win_data_get_config(struct wayland_win_data
*data
,
158 struct wayland_window_config
*conf
)
160 enum wayland_surface_config_state window_state
= 0;
163 conf
->rect
= data
->window_rect
;
164 conf
->client_rect
= data
->client_rect
;
165 style
= NtUserGetWindowLongW(data
->hwnd
, GWL_STYLE
);
167 TRACE("window=%s style=%#lx\n", wine_dbgstr_rect(&conf
->rect
), (long)style
);
169 /* The fullscreen state is implied by the window position and style. */
170 if (NtUserIsWindowRectFullScreen(&conf
->rect
))
172 if ((style
& WS_MAXIMIZE
) && (style
& WS_CAPTION
) == WS_CAPTION
)
173 window_state
|= WAYLAND_SURFACE_CONFIG_STATE_MAXIMIZED
;
174 else if (!(style
& WS_MINIMIZE
))
175 window_state
|= WAYLAND_SURFACE_CONFIG_STATE_FULLSCREEN
;
177 else if (style
& WS_MAXIMIZE
)
179 window_state
|= WAYLAND_SURFACE_CONFIG_STATE_MAXIMIZED
;
182 conf
->state
= window_state
;
183 conf
->scale
= NtUserGetDpiForWindow(data
->hwnd
) / 96.0;
186 static void wayland_win_data_update_wayland_surface(struct wayland_win_data
*data
)
188 struct wayland_surface
*surface
= data
->wayland_surface
;
189 HWND parent
= NtUserGetAncestor(data
->hwnd
, GA_PARENT
);
190 BOOL visible
, xdg_visible
;
192 TRACE("hwnd=%p\n", data
->hwnd
);
194 /* We don't want wayland surfaces for child windows. */
195 if (parent
!= NtUserGetDesktopWindow() && parent
!= 0)
197 if (data
->window_surface
)
198 wayland_window_surface_update_wayland_surface(data
->window_surface
, NULL
);
199 if (surface
) wayland_surface_destroy(surface
);
204 /* Otherwise ensure that we have a wayland surface. */
205 if (!surface
&& !(surface
= wayland_surface_create(data
->hwnd
))) return;
207 visible
= (NtUserGetWindowLongW(data
->hwnd
, GWL_STYLE
) & WS_VISIBLE
) == WS_VISIBLE
;
208 xdg_visible
= surface
->xdg_toplevel
!= NULL
;
210 pthread_mutex_lock(&surface
->mutex
);
212 if (visible
!= xdg_visible
)
214 /* If we have a pre-existing surface ensure it has no role. */
215 if (data
->wayland_surface
) wayland_surface_clear_role(surface
);
216 /* If the window is a visible toplevel make it a wayland
217 * xdg_toplevel. Otherwise keep it role-less to avoid polluting the
218 * compositor with empty xdg_toplevels. */
219 if (visible
) wayland_surface_make_toplevel(surface
);
222 wayland_win_data_get_config(data
, &surface
->window
);
224 pthread_mutex_unlock(&surface
->mutex
);
226 if (data
->window_surface
)
227 wayland_window_surface_update_wayland_surface(data
->window_surface
, surface
);
230 TRACE("hwnd=%p surface=%p=>%p\n", data
->hwnd
, data
->wayland_surface
, surface
);
231 data
->wayland_surface
= surface
;
234 static void wayland_win_data_update_wayland_state(struct wayland_win_data
*data
)
236 struct wayland_surface
*surface
= data
->wayland_surface
;
237 BOOL processing_config
;
239 pthread_mutex_lock(&surface
->mutex
);
241 if (!surface
->xdg_toplevel
) goto out
;
243 processing_config
= surface
->processing
.serial
&&
244 !surface
->processing
.processed
;
246 TRACE("hwnd=%p window_state=%#x %s->state=%#x\n",
247 data
->hwnd
, surface
->window
.state
,
248 processing_config
? "processing" : "current",
249 processing_config
? surface
->processing
.state
: surface
->current
.state
);
251 /* If we are not processing a compositor requested config, use the
252 * window state to determine and update the Wayland state. */
253 if (!processing_config
)
255 /* First do all state unsettings, before setting new state. Some
256 * Wayland compositors misbehave if the order is reversed. */
257 if (!(surface
->window
.state
& WAYLAND_SURFACE_CONFIG_STATE_MAXIMIZED
) &&
258 (surface
->current
.state
& WAYLAND_SURFACE_CONFIG_STATE_MAXIMIZED
))
260 xdg_toplevel_unset_maximized(surface
->xdg_toplevel
);
262 if (!(surface
->window
.state
& WAYLAND_SURFACE_CONFIG_STATE_FULLSCREEN
) &&
263 (surface
->current
.state
& WAYLAND_SURFACE_CONFIG_STATE_FULLSCREEN
))
265 xdg_toplevel_unset_fullscreen(surface
->xdg_toplevel
);
268 if ((surface
->window
.state
& WAYLAND_SURFACE_CONFIG_STATE_MAXIMIZED
) &&
269 !(surface
->current
.state
& WAYLAND_SURFACE_CONFIG_STATE_MAXIMIZED
))
271 xdg_toplevel_set_maximized(surface
->xdg_toplevel
);
273 if ((surface
->window
.state
& WAYLAND_SURFACE_CONFIG_STATE_FULLSCREEN
) &&
274 !(surface
->current
.state
& WAYLAND_SURFACE_CONFIG_STATE_FULLSCREEN
))
276 xdg_toplevel_set_fullscreen(surface
->xdg_toplevel
, NULL
);
281 surface
->processing
.processed
= TRUE
;
285 pthread_mutex_unlock(&surface
->mutex
);
286 wl_display_flush(process_wayland
.wl_display
);
289 /***********************************************************************
290 * WAYLAND_DestroyWindow
292 void WAYLAND_DestroyWindow(HWND hwnd
)
294 struct wayland_win_data
*data
;
298 if (!(data
= wayland_win_data_get(hwnd
))) return;
299 wayland_win_data_destroy(data
);
302 /***********************************************************************
303 * WAYLAND_WindowPosChanging
305 BOOL
WAYLAND_WindowPosChanging(HWND hwnd
, HWND insert_after
, UINT swp_flags
,
306 const RECT
*window_rect
, const RECT
*client_rect
,
307 RECT
*visible_rect
, struct window_surface
**surface
)
309 struct wayland_win_data
*data
= wayland_win_data_get(hwnd
);
314 TRACE("hwnd %p window %s client %s visible %s after %p flags %08x\n",
315 hwnd
, wine_dbgstr_rect(window_rect
), wine_dbgstr_rect(client_rect
),
316 wine_dbgstr_rect(visible_rect
), insert_after
, swp_flags
);
318 if (!data
&& !(data
= wayland_win_data_create(hwnd
, window_rect
, client_rect
)))
321 /* Release the dummy surface wine provides for toplevels. */
322 if (*surface
) window_surface_release(*surface
);
325 parent
= NtUserGetAncestor(hwnd
, GA_PARENT
);
326 visible
= ((NtUserGetWindowLongW(hwnd
, GWL_STYLE
) & WS_VISIBLE
) ||
327 (swp_flags
& SWP_SHOWWINDOW
)) &&
328 !(swp_flags
& SWP_HIDEWINDOW
);
330 /* Check if we don't want a dedicated window surface. */
331 if ((parent
&& parent
!= NtUserGetDesktopWindow()) || !visible
) goto done
;
333 surface_rect
= *window_rect
;
334 OffsetRect(&surface_rect
, -surface_rect
.left
, -surface_rect
.top
);
336 /* Check if we can reuse our current window surface. */
337 if (data
->window_surface
&&
338 EqualRect(&data
->window_surface
->rect
, &surface_rect
))
340 window_surface_add_ref(data
->window_surface
);
341 *surface
= data
->window_surface
;
342 TRACE("reusing surface %p\n", *surface
);
346 *surface
= wayland_window_surface_create(data
->hwnd
, &surface_rect
);
349 wayland_win_data_release(data
);
353 /***********************************************************************
354 * WAYLAND_WindowPosChanged
356 void WAYLAND_WindowPosChanged(HWND hwnd
, HWND insert_after
, UINT swp_flags
,
357 const RECT
*window_rect
, const RECT
*client_rect
,
358 const RECT
*visible_rect
, const RECT
*valid_rects
,
359 struct window_surface
*surface
)
361 struct wayland_win_data
*data
= wayland_win_data_get(hwnd
);
363 TRACE("hwnd %p window %s client %s visible %s after %p flags %08x\n",
364 hwnd
, wine_dbgstr_rect(window_rect
), wine_dbgstr_rect(client_rect
),
365 wine_dbgstr_rect(visible_rect
), insert_after
, swp_flags
);
369 data
->window_rect
= *window_rect
;
370 data
->client_rect
= *client_rect
;
372 if (surface
) window_surface_add_ref(surface
);
373 if (data
->window_surface
) window_surface_release(data
->window_surface
);
374 data
->window_surface
= surface
;
376 wayland_win_data_update_wayland_surface(data
);
377 if (data
->wayland_surface
) wayland_win_data_update_wayland_state(data
);
379 wayland_win_data_release(data
);
382 static void wayland_resize_desktop(void)
384 RECT virtual_rect
= NtUserGetVirtualScreenRect();
385 NtUserSetWindowPos(NtUserGetDesktopWindow(), 0,
386 virtual_rect
.left
, virtual_rect
.top
,
387 virtual_rect
.right
- virtual_rect
.left
,
388 virtual_rect
.bottom
- virtual_rect
.top
,
389 SWP_NOZORDER
| SWP_NOACTIVATE
| SWP_DEFERERASE
);
392 static void wayland_configure_window(HWND hwnd
)
394 struct wayland_surface
*surface
;
395 INT width
, height
, window_width
, window_height
;
396 INT window_surf_width
, window_surf_height
;
400 BOOL needs_enter_size_move
= FALSE
;
401 BOOL needs_exit_size_move
= FALSE
;
403 if (!(surface
= wayland_surface_lock_hwnd(hwnd
))) return;
405 if (!surface
->xdg_toplevel
)
407 TRACE("missing xdg_toplevel, returning\n");
408 pthread_mutex_unlock(&surface
->mutex
);
412 if (!surface
->requested
.serial
)
414 TRACE("requested configure event already handled, returning\n");
415 pthread_mutex_unlock(&surface
->mutex
);
419 surface
->processing
= surface
->requested
;
420 memset(&surface
->requested
, 0, sizeof(surface
->requested
));
422 state
= surface
->processing
.state
;
423 /* Ignore size hints if we don't have a state that requires strict
424 * size adherence, in order to avoid spurious resizes. */
427 width
= surface
->processing
.width
;
428 height
= surface
->processing
.height
;
435 if ((state
& WAYLAND_SURFACE_CONFIG_STATE_RESIZING
) && !surface
->resizing
)
437 surface
->resizing
= TRUE
;
438 needs_enter_size_move
= TRUE
;
441 if (!(state
& WAYLAND_SURFACE_CONFIG_STATE_RESIZING
) && surface
->resizing
)
443 surface
->resizing
= FALSE
;
444 needs_exit_size_move
= TRUE
;
447 /* Transitions between normal/max/fullscreen may entail a frame change. */
448 if ((state
^ surface
->current
.state
) &
449 (WAYLAND_SURFACE_CONFIG_STATE_MAXIMIZED
|
450 WAYLAND_SURFACE_CONFIG_STATE_FULLSCREEN
))
452 flags
|= SWP_FRAMECHANGED
;
455 wayland_surface_coords_from_window(surface
,
456 surface
->window
.rect
.right
-
457 surface
->window
.rect
.left
,
458 surface
->window
.rect
.bottom
-
459 surface
->window
.rect
.top
,
460 &window_surf_width
, &window_surf_height
);
462 /* If the window is already fullscreen and its size is compatible with what
463 * the compositor is requesting, don't force a resize, since some applications
464 * are very insistent on a particular fullscreen size (which may not match
465 * the monitor size). */
466 if ((surface
->window
.state
& WAYLAND_SURFACE_CONFIG_STATE_FULLSCREEN
) &&
467 wayland_surface_config_is_compatible(&surface
->processing
,
468 window_surf_width
, window_surf_height
,
469 surface
->window
.state
))
474 wayland_surface_coords_to_window(surface
, width
, height
,
475 &window_width
, &window_height
);
477 pthread_mutex_unlock(&surface
->mutex
);
479 TRACE("processing=%dx%d,%#x\n", width
, height
, state
);
481 if (needs_enter_size_move
) send_message(hwnd
, WM_ENTERSIZEMOVE
, 0, 0);
482 if (needs_exit_size_move
) send_message(hwnd
, WM_EXITSIZEMOVE
, 0, 0);
484 flags
|= SWP_NOACTIVATE
| SWP_NOZORDER
| SWP_NOOWNERZORDER
| SWP_NOMOVE
;
485 if (window_width
== 0 || window_height
== 0) flags
|= SWP_NOSIZE
;
487 style
= NtUserGetWindowLongW(hwnd
, GWL_STYLE
);
488 if (!(state
& WAYLAND_SURFACE_CONFIG_STATE_MAXIMIZED
) != !(style
& WS_MAXIMIZE
))
489 NtUserSetWindowLong(hwnd
, GWL_STYLE
, style
^ WS_MAXIMIZE
, FALSE
);
491 /* The Wayland maximized and fullscreen states are very strict about
492 * surface size, so don't let the application override it. The tiled state
493 * is not as strict, but it indicates a strong size preference, so try to
495 if (state
& (WAYLAND_SURFACE_CONFIG_STATE_MAXIMIZED
|
496 WAYLAND_SURFACE_CONFIG_STATE_FULLSCREEN
|
497 WAYLAND_SURFACE_CONFIG_STATE_TILED
))
499 flags
|= SWP_NOSENDCHANGING
;
502 NtUserSetWindowPos(hwnd
, 0, 0, 0, window_width
, window_height
, flags
);
505 /**********************************************************************
506 * WAYLAND_WindowMessage
508 LRESULT
WAYLAND_WindowMessage(HWND hwnd
, UINT msg
, WPARAM wp
, LPARAM lp
)
512 case WM_WAYLAND_INIT_DISPLAY_DEVICES
:
513 wayland_init_display_devices(TRUE
);
514 wayland_resize_desktop();
516 case WM_WAYLAND_CONFIGURE
:
517 wayland_configure_window(hwnd
);
520 FIXME("got window msg %x hwnd %p wp %lx lp %lx\n", msg
, hwnd
, (long)wp
, lp
);
525 /**********************************************************************
526 * WAYLAND_DesktopWindowProc
528 LRESULT
WAYLAND_DesktopWindowProc(HWND hwnd
, UINT msg
, WPARAM wp
, LPARAM lp
)
532 case WM_DISPLAYCHANGE
:
533 wayland_resize_desktop();
537 return NtUserMessageCall(hwnd
, msg
, wp
, lp
, 0, NtUserDefWindowProc
, FALSE
);
540 static enum xdg_toplevel_resize_edge
hittest_to_resize_edge(WPARAM hittest
)
544 case WMSZ_LEFT
: return XDG_TOPLEVEL_RESIZE_EDGE_LEFT
;
545 case WMSZ_RIGHT
: return XDG_TOPLEVEL_RESIZE_EDGE_RIGHT
;
546 case WMSZ_TOP
: return XDG_TOPLEVEL_RESIZE_EDGE_TOP
;
547 case WMSZ_TOPLEFT
: return XDG_TOPLEVEL_RESIZE_EDGE_TOP_LEFT
;
548 case WMSZ_TOPRIGHT
: return XDG_TOPLEVEL_RESIZE_EDGE_TOP_RIGHT
;
549 case WMSZ_BOTTOM
: return XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM
;
550 case WMSZ_BOTTOMLEFT
: return XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM_LEFT
;
551 case WMSZ_BOTTOMRIGHT
: return XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM_RIGHT
;
552 default: return XDG_TOPLEVEL_RESIZE_EDGE_NONE
;
556 /***********************************************************************
559 LRESULT
WAYLAND_SysCommand(HWND hwnd
, WPARAM wparam
, LPARAM lparam
)
562 WPARAM command
= wparam
& 0xfff0;
563 uint32_t button_serial
;
564 struct wl_seat
*wl_seat
;
565 struct wayland_surface
*surface
;
567 TRACE("cmd=%lx hwnd=%p, %lx, %lx\n",
568 (long)command
, hwnd
, (long)wparam
, lparam
);
570 pthread_mutex_lock(&process_wayland
.pointer
.mutex
);
571 if (process_wayland
.pointer
.focused_hwnd
== hwnd
)
572 button_serial
= process_wayland
.pointer
.button_serial
;
575 pthread_mutex_unlock(&process_wayland
.pointer
.mutex
);
577 if (command
== SC_MOVE
|| command
== SC_SIZE
)
579 if ((surface
= wayland_surface_lock_hwnd(hwnd
)))
581 pthread_mutex_lock(&process_wayland
.seat
.mutex
);
582 wl_seat
= process_wayland
.seat
.wl_seat
;
583 if (wl_seat
&& surface
->xdg_toplevel
&& button_serial
)
585 if (command
== SC_MOVE
)
587 xdg_toplevel_move(surface
->xdg_toplevel
, wl_seat
, button_serial
);
589 else if (command
== SC_SIZE
)
591 xdg_toplevel_resize(surface
->xdg_toplevel
, wl_seat
, button_serial
,
592 hittest_to_resize_edge(wparam
& 0x0f));
595 pthread_mutex_unlock(&process_wayland
.seat
.mutex
);
596 pthread_mutex_unlock(&surface
->mutex
);
601 wl_display_flush(process_wayland
.wl_display
);
605 /**********************************************************************
606 * wayland_window_flush
608 * Flush the window_surface associated with a HWND.
610 void wayland_window_flush(HWND hwnd
)
612 struct wayland_win_data
*data
= wayland_win_data_get(hwnd
);
616 if (data
->window_surface
)
617 data
->window_surface
->funcs
->flush(data
->window_surface
);
619 wayland_win_data_release(data
);
622 /**********************************************************************
623 * wayland_surface_lock_hwnd
625 struct wayland_surface
*wayland_surface_lock_hwnd(HWND hwnd
)
627 struct wayland_win_data
*data
= wayland_win_data_get(hwnd
);
628 struct wayland_surface
*surface
;
630 if (!data
) return NULL
;
632 if ((surface
= data
->wayland_surface
)) pthread_mutex_lock(&surface
->mutex
);
634 wayland_win_data_release(data
);