winewayland.drv: Handle application-initiated fullscreen state.
[wine.git] / dlls / winewayland.drv / window.c
blob2854279bd90c5762413437d7fdd691cbe2755e6e
1 /*
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
21 #if 0
22 #pragma makedep unix
23 #endif
25 #include "config.h"
27 #include <assert.h>
28 #include <stdlib.h>
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 */
41 HWND hwnd;
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 */
47 RECT window_rect;
50 static int wayland_win_data_cmp_rb(const void *key,
51 const struct rb_entry *entry)
53 HWND key_hwnd = (HWND)key; /* cast to work around const */
54 const struct wayland_win_data *entry_win_data =
55 RB_ENTRY_VALUE(entry, const struct wayland_win_data, entry);
57 if (key_hwnd < entry_win_data->hwnd) return -1;
58 if (key_hwnd > entry_win_data->hwnd) return 1;
59 return 0;
62 static pthread_mutex_t win_data_mutex = PTHREAD_MUTEX_INITIALIZER;
63 static struct rb_tree win_data_rb = { wayland_win_data_cmp_rb };
65 /***********************************************************************
66 * wayland_win_data_create
68 * Create a data window structure for an existing window.
70 static struct wayland_win_data *wayland_win_data_create(HWND hwnd,
71 const RECT *window_rect)
73 struct wayland_win_data *data;
74 struct rb_entry *rb_entry;
75 HWND parent;
77 /* Don't create win data for desktop or HWND_MESSAGE windows. */
78 if (!(parent = NtUserGetAncestor(hwnd, GA_PARENT))) return NULL;
79 if (parent != NtUserGetDesktopWindow() && !NtUserGetAncestor(parent, GA_PARENT))
80 return NULL;
82 if (!(data = calloc(1, sizeof(*data)))) return NULL;
84 data->hwnd = hwnd;
85 data->window_rect = *window_rect;
87 pthread_mutex_lock(&win_data_mutex);
89 /* Check that another thread hasn't already created the wayland_win_data. */
90 if ((rb_entry = rb_get(&win_data_rb, hwnd)))
92 free(data);
93 return RB_ENTRY_VALUE(rb_entry, struct wayland_win_data, entry);
96 rb_put(&win_data_rb, hwnd, &data->entry);
98 TRACE("hwnd=%p\n", data->hwnd);
100 return data;
103 /***********************************************************************
104 * wayland_win_data_destroy
106 static void wayland_win_data_destroy(struct wayland_win_data *data)
108 TRACE("hwnd=%p\n", data->hwnd);
110 rb_remove(&win_data_rb, &data->entry);
112 pthread_mutex_unlock(&win_data_mutex);
114 if (data->window_surface)
116 wayland_window_surface_update_wayland_surface(data->window_surface, NULL);
117 window_surface_release(data->window_surface);
119 if (data->wayland_surface) wayland_surface_destroy(data->wayland_surface);
120 free(data);
123 /***********************************************************************
124 * wayland_win_data_get
126 * Lock and return the data structure associated with a window.
128 static struct wayland_win_data *wayland_win_data_get(HWND hwnd)
130 struct rb_entry *rb_entry;
132 pthread_mutex_lock(&win_data_mutex);
134 if ((rb_entry = rb_get(&win_data_rb, hwnd)))
135 return RB_ENTRY_VALUE(rb_entry, struct wayland_win_data, entry);
137 pthread_mutex_unlock(&win_data_mutex);
139 return NULL;
142 /***********************************************************************
143 * wayland_win_data_release
145 * Release the data returned by wayland_win_data_get.
147 static void wayland_win_data_release(struct wayland_win_data *data)
149 assert(data);
150 pthread_mutex_unlock(&win_data_mutex);
153 static void wayland_win_data_get_config(struct wayland_win_data *data,
154 struct wayland_window_config *conf)
156 enum wayland_surface_config_state window_state = 0;
157 DWORD style;
159 conf->rect = data->window_rect;
160 style = NtUserGetWindowLongW(data->hwnd, GWL_STYLE);
162 TRACE("window=%s style=%#lx\n", wine_dbgstr_rect(&conf->rect), (long)style);
164 /* The fullscreen state is implied by the window position and style. */
165 if (NtUserIsWindowRectFullScreen(&conf->rect))
167 if ((style & WS_MAXIMIZE) && (style & WS_CAPTION) == WS_CAPTION)
168 window_state |= WAYLAND_SURFACE_CONFIG_STATE_MAXIMIZED;
169 else if (!(style & WS_MINIMIZE))
170 window_state |= WAYLAND_SURFACE_CONFIG_STATE_FULLSCREEN;
172 else if (style & WS_MAXIMIZE)
174 window_state |= WAYLAND_SURFACE_CONFIG_STATE_MAXIMIZED;
177 conf->state = window_state;
180 static void wayland_win_data_update_wayland_surface(struct wayland_win_data *data)
182 struct wayland_surface *surface = data->wayland_surface;
183 HWND parent = NtUserGetAncestor(data->hwnd, GA_PARENT);
184 BOOL visible, xdg_visible;
186 TRACE("hwnd=%p\n", data->hwnd);
188 /* We don't want wayland surfaces for child windows. */
189 if (parent != NtUserGetDesktopWindow() && parent != 0)
191 if (data->window_surface)
192 wayland_window_surface_update_wayland_surface(data->window_surface, NULL);
193 if (surface) wayland_surface_destroy(surface);
194 surface = NULL;
195 goto out;
198 /* Otherwise ensure that we have a wayland surface. */
199 if (!surface && !(surface = wayland_surface_create(data->hwnd))) return;
201 visible = (NtUserGetWindowLongW(data->hwnd, GWL_STYLE) & WS_VISIBLE) == WS_VISIBLE;
202 xdg_visible = surface->xdg_toplevel != NULL;
204 pthread_mutex_lock(&surface->mutex);
206 if (visible != xdg_visible)
208 /* If we have a pre-existing surface ensure it has no role. */
209 if (data->wayland_surface) wayland_surface_clear_role(surface);
210 /* If the window is a visible toplevel make it a wayland
211 * xdg_toplevel. Otherwise keep it role-less to avoid polluting the
212 * compositor with empty xdg_toplevels. */
213 if (visible) wayland_surface_make_toplevel(surface);
216 wayland_win_data_get_config(data, &surface->window);
218 pthread_mutex_unlock(&surface->mutex);
220 if (data->window_surface)
221 wayland_window_surface_update_wayland_surface(data->window_surface, surface);
223 out:
224 TRACE("hwnd=%p surface=%p=>%p\n", data->hwnd, data->wayland_surface, surface);
225 data->wayland_surface = surface;
228 static void wayland_win_data_update_wayland_state(struct wayland_win_data *data)
230 struct wayland_surface *surface = data->wayland_surface;
231 BOOL processing_config;
233 pthread_mutex_lock(&surface->mutex);
235 if (!surface->xdg_toplevel) goto out;
237 processing_config = surface->processing.serial &&
238 !surface->processing.processed;
240 TRACE("hwnd=%p window_state=%#x %s->state=%#x\n",
241 data->hwnd, surface->window.state,
242 processing_config ? "processing" : "current",
243 processing_config ? surface->processing.state : surface->current.state);
245 /* If we are not processing a compositor requested config, use the
246 * window state to determine and update the Wayland state. */
247 if (!processing_config)
249 /* First do all state unsettings, before setting new state. Some
250 * Wayland compositors misbehave if the order is reversed. */
251 if (!(surface->window.state & WAYLAND_SURFACE_CONFIG_STATE_MAXIMIZED) &&
252 (surface->current.state & WAYLAND_SURFACE_CONFIG_STATE_MAXIMIZED))
254 xdg_toplevel_unset_maximized(surface->xdg_toplevel);
256 if (!(surface->window.state & WAYLAND_SURFACE_CONFIG_STATE_FULLSCREEN) &&
257 (surface->current.state & WAYLAND_SURFACE_CONFIG_STATE_FULLSCREEN))
259 xdg_toplevel_unset_fullscreen(surface->xdg_toplevel);
262 if ((surface->window.state & WAYLAND_SURFACE_CONFIG_STATE_MAXIMIZED) &&
263 !(surface->current.state & WAYLAND_SURFACE_CONFIG_STATE_MAXIMIZED))
265 xdg_toplevel_set_maximized(surface->xdg_toplevel);
267 if ((surface->window.state & WAYLAND_SURFACE_CONFIG_STATE_FULLSCREEN) &&
268 !(surface->current.state & WAYLAND_SURFACE_CONFIG_STATE_FULLSCREEN))
270 xdg_toplevel_set_fullscreen(surface->xdg_toplevel, NULL);
273 else
275 surface->processing.processed = TRUE;
278 out:
279 pthread_mutex_unlock(&surface->mutex);
280 wl_display_flush(process_wayland.wl_display);
283 /***********************************************************************
284 * WAYLAND_DestroyWindow
286 void WAYLAND_DestroyWindow(HWND hwnd)
288 struct wayland_win_data *data;
290 TRACE("%p\n", hwnd);
292 if (!(data = wayland_win_data_get(hwnd))) return;
293 wayland_win_data_destroy(data);
296 /***********************************************************************
297 * WAYLAND_WindowPosChanging
299 BOOL WAYLAND_WindowPosChanging(HWND hwnd, HWND insert_after, UINT swp_flags,
300 const RECT *window_rect, const RECT *client_rect,
301 RECT *visible_rect, struct window_surface **surface)
303 struct wayland_win_data *data = wayland_win_data_get(hwnd);
304 HWND parent;
305 BOOL visible;
306 RECT surface_rect;
308 TRACE("hwnd %p window %s client %s visible %s after %p flags %08x\n",
309 hwnd, wine_dbgstr_rect(window_rect), wine_dbgstr_rect(client_rect),
310 wine_dbgstr_rect(visible_rect), insert_after, swp_flags);
312 if (!data && !(data = wayland_win_data_create(hwnd, window_rect))) return TRUE;
314 /* Release the dummy surface wine provides for toplevels. */
315 if (*surface) window_surface_release(*surface);
316 *surface = NULL;
318 parent = NtUserGetAncestor(hwnd, GA_PARENT);
319 visible = ((NtUserGetWindowLongW(hwnd, GWL_STYLE) & WS_VISIBLE) ||
320 (swp_flags & SWP_SHOWWINDOW)) &&
321 !(swp_flags & SWP_HIDEWINDOW);
323 /* Check if we don't want a dedicated window surface. */
324 if ((parent && parent != NtUserGetDesktopWindow()) || !visible) goto done;
326 surface_rect = *window_rect;
327 OffsetRect(&surface_rect, -surface_rect.left, -surface_rect.top);
329 /* Check if we can reuse our current window surface. */
330 if (data->window_surface &&
331 EqualRect(&data->window_surface->rect, &surface_rect))
333 window_surface_add_ref(data->window_surface);
334 *surface = data->window_surface;
335 TRACE("reusing surface %p\n", *surface);
336 goto done;
339 *surface = wayland_window_surface_create(data->hwnd, &surface_rect);
341 done:
342 wayland_win_data_release(data);
343 return TRUE;
346 /***********************************************************************
347 * WAYLAND_WindowPosChanged
349 void WAYLAND_WindowPosChanged(HWND hwnd, HWND insert_after, UINT swp_flags,
350 const RECT *window_rect, const RECT *client_rect,
351 const RECT *visible_rect, const RECT *valid_rects,
352 struct window_surface *surface)
354 struct wayland_win_data *data = wayland_win_data_get(hwnd);
356 TRACE("hwnd %p window %s client %s visible %s after %p flags %08x\n",
357 hwnd, wine_dbgstr_rect(window_rect), wine_dbgstr_rect(client_rect),
358 wine_dbgstr_rect(visible_rect), insert_after, swp_flags);
360 if (!data) return;
362 data->window_rect = *window_rect;
364 if (surface) window_surface_add_ref(surface);
365 if (data->window_surface) window_surface_release(data->window_surface);
366 data->window_surface = surface;
368 wayland_win_data_update_wayland_surface(data);
369 if (data->wayland_surface) wayland_win_data_update_wayland_state(data);
371 wayland_win_data_release(data);
374 static void wayland_resize_desktop(void)
376 RECT virtual_rect = NtUserGetVirtualScreenRect();
377 NtUserSetWindowPos(NtUserGetDesktopWindow(), 0,
378 virtual_rect.left, virtual_rect.top,
379 virtual_rect.right - virtual_rect.left,
380 virtual_rect.bottom - virtual_rect.top,
381 SWP_NOZORDER | SWP_NOACTIVATE | SWP_DEFERERASE);
384 static void wayland_configure_window(HWND hwnd)
386 struct wayland_surface *surface;
387 INT width, height;
388 UINT flags = 0;
389 uint32_t state;
390 DWORD style;
391 BOOL needs_enter_size_move = FALSE;
392 BOOL needs_exit_size_move = FALSE;
394 if (!(surface = wayland_surface_lock_hwnd(hwnd))) return;
396 if (!surface->xdg_toplevel)
398 TRACE("missing xdg_toplevel, returning\n");
399 pthread_mutex_unlock(&surface->mutex);
400 return;
403 if (!surface->requested.serial)
405 TRACE("requested configure event already handled, returning\n");
406 pthread_mutex_unlock(&surface->mutex);
407 return;
410 surface->processing = surface->requested;
411 memset(&surface->requested, 0, sizeof(surface->requested));
413 width = surface->processing.width;
414 height = surface->processing.height;
415 state = surface->processing.state;
417 if ((state & WAYLAND_SURFACE_CONFIG_STATE_RESIZING) && !surface->resizing)
419 surface->resizing = TRUE;
420 needs_enter_size_move = TRUE;
423 if (!(state & WAYLAND_SURFACE_CONFIG_STATE_RESIZING) && surface->resizing)
425 surface->resizing = FALSE;
426 needs_exit_size_move = TRUE;
429 /* Transitions between normal/max/fullscreen may entail a frame change. */
430 if ((state ^ surface->current.state) &
431 (WAYLAND_SURFACE_CONFIG_STATE_MAXIMIZED |
432 WAYLAND_SURFACE_CONFIG_STATE_FULLSCREEN))
434 flags |= SWP_FRAMECHANGED;
437 pthread_mutex_unlock(&surface->mutex);
439 TRACE("processing=%dx%d,%#x\n", width, height, state);
441 if (needs_enter_size_move) send_message(hwnd, WM_ENTERSIZEMOVE, 0, 0);
442 if (needs_exit_size_move) send_message(hwnd, WM_EXITSIZEMOVE, 0, 0);
444 flags |= SWP_NOACTIVATE | SWP_NOZORDER | SWP_NOOWNERZORDER | SWP_NOMOVE;
445 if (width == 0 || height == 0) flags |= SWP_NOSIZE;
447 style = NtUserGetWindowLongW(hwnd, GWL_STYLE);
448 if (!(state & WAYLAND_SURFACE_CONFIG_STATE_MAXIMIZED) != !(style & WS_MAXIMIZE))
449 NtUserSetWindowLong(hwnd, GWL_STYLE, style ^ WS_MAXIMIZE, FALSE);
451 /* The Wayland maximized and fullscreen states are very strict about
452 * surface size, so don't let the application override it. The tiled state
453 * is not as strict, but it indicates a strong size preference, so try to
454 * respect it. */
455 if (state & (WAYLAND_SURFACE_CONFIG_STATE_MAXIMIZED |
456 WAYLAND_SURFACE_CONFIG_STATE_FULLSCREEN |
457 WAYLAND_SURFACE_CONFIG_STATE_TILED))
459 flags |= SWP_NOSENDCHANGING;
462 NtUserSetWindowPos(hwnd, 0, 0, 0, width, height, flags);
465 /**********************************************************************
466 * WAYLAND_WindowMessage
468 LRESULT WAYLAND_WindowMessage(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp)
470 switch (msg)
472 case WM_WAYLAND_INIT_DISPLAY_DEVICES:
473 wayland_init_display_devices(TRUE);
474 wayland_resize_desktop();
475 return 0;
476 case WM_WAYLAND_CONFIGURE:
477 wayland_configure_window(hwnd);
478 return 0;
479 default:
480 FIXME("got window msg %x hwnd %p wp %lx lp %lx\n", msg, hwnd, (long)wp, lp);
481 return 0;
485 /**********************************************************************
486 * WAYLAND_DesktopWindowProc
488 LRESULT WAYLAND_DesktopWindowProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp)
490 switch (msg)
492 case WM_DISPLAYCHANGE:
493 wayland_resize_desktop();
494 break;
497 return NtUserMessageCall(hwnd, msg, wp, lp, 0, NtUserDefWindowProc, FALSE);
500 static enum xdg_toplevel_resize_edge hittest_to_resize_edge(WPARAM hittest)
502 switch (hittest)
504 case WMSZ_LEFT: return XDG_TOPLEVEL_RESIZE_EDGE_LEFT;
505 case WMSZ_RIGHT: return XDG_TOPLEVEL_RESIZE_EDGE_RIGHT;
506 case WMSZ_TOP: return XDG_TOPLEVEL_RESIZE_EDGE_TOP;
507 case WMSZ_TOPLEFT: return XDG_TOPLEVEL_RESIZE_EDGE_TOP_LEFT;
508 case WMSZ_TOPRIGHT: return XDG_TOPLEVEL_RESIZE_EDGE_TOP_RIGHT;
509 case WMSZ_BOTTOM: return XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM;
510 case WMSZ_BOTTOMLEFT: return XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM_LEFT;
511 case WMSZ_BOTTOMRIGHT: return XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM_RIGHT;
512 default: return XDG_TOPLEVEL_RESIZE_EDGE_NONE;
516 /***********************************************************************
517 * WAYLAND_SysCommand
519 LRESULT WAYLAND_SysCommand(HWND hwnd, WPARAM wparam, LPARAM lparam)
521 LRESULT ret = -1;
522 WPARAM command = wparam & 0xfff0;
523 uint32_t button_serial;
524 struct wl_seat *wl_seat;
525 struct wayland_surface *surface;
527 TRACE("cmd=%lx hwnd=%p, %lx, %lx\n",
528 (long)command, hwnd, (long)wparam, lparam);
530 pthread_mutex_lock(&process_wayland.pointer.mutex);
531 if (process_wayland.pointer.focused_hwnd == hwnd)
532 button_serial = process_wayland.pointer.button_serial;
533 else
534 button_serial = 0;
535 pthread_mutex_unlock(&process_wayland.pointer.mutex);
537 if (command == SC_MOVE || command == SC_SIZE)
539 if ((surface = wayland_surface_lock_hwnd(hwnd)))
541 pthread_mutex_lock(&process_wayland.seat.mutex);
542 wl_seat = process_wayland.seat.wl_seat;
543 if (wl_seat && surface->xdg_toplevel && button_serial)
545 if (command == SC_MOVE)
547 xdg_toplevel_move(surface->xdg_toplevel, wl_seat, button_serial);
549 else if (command == SC_SIZE)
551 xdg_toplevel_resize(surface->xdg_toplevel, wl_seat, button_serial,
552 hittest_to_resize_edge(wparam & 0x0f));
555 pthread_mutex_unlock(&process_wayland.seat.mutex);
556 pthread_mutex_unlock(&surface->mutex);
557 ret = 0;
561 wl_display_flush(process_wayland.wl_display);
562 return ret;
565 /**********************************************************************
566 * wayland_window_flush
568 * Flush the window_surface associated with a HWND.
570 void wayland_window_flush(HWND hwnd)
572 struct wayland_win_data *data = wayland_win_data_get(hwnd);
574 if (!data) return;
576 if (data->window_surface)
577 data->window_surface->funcs->flush(data->window_surface);
579 wayland_win_data_release(data);
582 /**********************************************************************
583 * wayland_surface_lock_hwnd
585 struct wayland_surface *wayland_surface_lock_hwnd(HWND hwnd)
587 struct wayland_win_data *data = wayland_win_data_get(hwnd);
588 struct wayland_surface *surface;
590 if (!data) return NULL;
592 if ((surface = data->wayland_surface)) pthread_mutex_lock(&surface->mutex);
594 wayland_win_data_release(data);
596 return surface;