[PATCH 0/1] MR10259: winewayland.drv: Add support for server-side decorations
Some compositors support server-side decorated windows. Adding support for SSD will make some windows style(for example: regedit, winecfg. etc) better match the system. There was already a MR https://gitlab.winehq.org/wine/wine/-/merge_requests/8004 previously, but it had not been addressed for a long time. I have re-implemented the functionality here, and part of the processing code references the previous handling logic(in wayland_configure_window function). -- https://gitlab.winehq.org/wine/wine/-/merge_requests/10259
From: chenzhengyong <chenzhengyong@uniontech.com> Some compositors support server-side decorated windows. Adding support for SSD will make some window style better match the system. Signed-off-by: chenzhengyong <chenzhengyong@uniontech.com> --- dlls/winewayland.drv/Makefile.in | 1 + dlls/winewayland.drv/wayland.c | 5 + dlls/winewayland.drv/wayland_surface.c | 57 ++++++- dlls/winewayland.drv/waylanddrv.h | 7 +- dlls/winewayland.drv/waylanddrv_main.c | 1 + dlls/winewayland.drv/window.c | 97 ++++++++++- .../xdg-decoration-unstable-v1.xml | 160 ++++++++++++++++++ 7 files changed, 323 insertions(+), 5 deletions(-) create mode 100644 dlls/winewayland.drv/xdg-decoration-unstable-v1.xml diff --git a/dlls/winewayland.drv/Makefile.in b/dlls/winewayland.drv/Makefile.in index 1f1b2ccf8af..da238941d25 100644 --- a/dlls/winewayland.drv/Makefile.in +++ b/dlls/winewayland.drv/Makefile.in @@ -27,6 +27,7 @@ SOURCES = \ window.c \ window_surface.c \ wlr-data-control-unstable-v1.xml \ + xdg-decoration-unstable-v1.xml \ xdg-output-unstable-v1.xml \ xdg-shell.xml \ xdg-toplevel-icon-v1.xml diff --git a/dlls/winewayland.drv/wayland.c b/dlls/winewayland.drv/wayland.c index b164210adf0..9e14d49115f 100644 --- a/dlls/winewayland.drv/wayland.c +++ b/dlls/winewayland.drv/wayland.c @@ -200,6 +200,11 @@ static void registry_handle_global(void *data, struct wl_registry *registry, wl_registry_bind(registry, id, &wp_cursor_shape_manager_v1_interface, version < 2 ? version : 2); } + else if (strcmp(interface, "zxdg_decoration_manager_v1") == 0) + { + process_wayland.zxdg_decoration_manager_v1 = + wl_registry_bind(registry, id, &zxdg_decoration_manager_v1_interface, 1); + } } static void registry_handle_global_remove(void *data, struct wl_registry *registry, diff --git a/dlls/winewayland.drv/wayland_surface.c b/dlls/winewayland.drv/wayland_surface.c index 2f8275ddb52..f1aeb5d93a2 100644 --- a/dlls/winewayland.drv/wayland_surface.c +++ b/dlls/winewayland.drv/wayland_surface.c @@ -138,6 +138,30 @@ static const struct xdg_toplevel_listener xdg_toplevel_listener = xdg_toplevel_handle_close }; +static void xdg_decoration_handle_configure(void *private, + struct zxdg_toplevel_decoration_v1 *zxdg_toplevel_decoration_v1, + uint32_t mode) +{ + struct wayland_surface *surface; + HWND hwnd = private; + struct wayland_win_data *data; + + TRACE("hwnd=%p mode=%u\n", hwnd, mode); + if (!(data = wayland_win_data_get(hwnd))) return; + + if ((surface = data->wayland_surface) && wayland_surface_is_toplevel(surface)) + { + surface->decoration_mode = mode; + } + + wayland_win_data_release(data); +} + +static const struct zxdg_toplevel_decoration_v1_listener zxdg_decoration_listener = +{ + xdg_decoration_handle_configure +}; + /********************************************************************** * wayland_surface_create * @@ -246,7 +270,7 @@ void wayland_surface_destroy(struct wayland_surface *surface) * * Gives the toplevel role to a plain wayland surface. */ -void wayland_surface_make_toplevel(struct wayland_surface *surface) +void wayland_surface_make_toplevel(struct wayland_surface *surface, BOOL decorated) { WCHAR text[1024]; @@ -267,6 +291,31 @@ void wayland_surface_make_toplevel(struct wayland_surface *surface) if (!surface->xdg_toplevel) goto err; xdg_toplevel_add_listener(surface->xdg_toplevel, &xdg_toplevel_listener, surface->hwnd); + if (process_wayland.zxdg_decoration_manager_v1) + { + surface->zxdg_toplevel_decoration = + zxdg_decoration_manager_v1_get_toplevel_decoration(process_wayland.zxdg_decoration_manager_v1, + surface->xdg_toplevel); + if (surface->zxdg_toplevel_decoration) { + if (decorated) + { + zxdg_toplevel_decoration_v1_set_mode(surface->zxdg_toplevel_decoration, + ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE); + surface->decoration_mode = ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE; + } + else + { + zxdg_toplevel_decoration_v1_set_mode(surface->zxdg_toplevel_decoration, + ZXDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE); + surface->decoration_mode = ZXDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE; + + } + zxdg_toplevel_decoration_v1_add_listener(surface->zxdg_toplevel_decoration, + &zxdg_decoration_listener, + surface->hwnd); + } + } + if (process_name) xdg_toplevel_set_app_id(surface->xdg_toplevel, process_name); @@ -353,6 +402,12 @@ void wayland_surface_clear_role(struct wayland_surface *surface) surface->xdg_toplevel_icon = NULL; } + if (surface->zxdg_toplevel_decoration) + { + zxdg_toplevel_decoration_v1_destroy(surface->zxdg_toplevel_decoration); + surface->zxdg_toplevel_decoration = NULL; + } + if (surface->xdg_toplevel) { xdg_toplevel_destroy(surface->xdg_toplevel); diff --git a/dlls/winewayland.drv/waylanddrv.h b/dlls/winewayland.drv/waylanddrv.h index ad41a1b474e..92ed9940be8 100644 --- a/dlls/winewayland.drv/waylanddrv.h +++ b/dlls/winewayland.drv/waylanddrv.h @@ -38,6 +38,7 @@ #include "xdg-shell-client-protocol.h" #include "wlr-data-control-unstable-v1-client-protocol.h" #include "xdg-toplevel-icon-v1-client-protocol.h" +#include "xdg-decoration-unstable-v1-client-protocol.h" #include "windef.h" #include "winbase.h" @@ -178,6 +179,7 @@ struct wayland struct wl_data_device_manager *wl_data_device_manager; struct xdg_toplevel_icon_manager_v1 *xdg_toplevel_icon_manager_v1; struct wp_cursor_shape_manager_v1 *wp_cursor_shape_manager_v1; + struct zxdg_decoration_manager_v1 *zxdg_decoration_manager_v1; struct wayland_seat seat; struct wayland_keyboard keyboard; struct wayland_pointer pointer; @@ -283,6 +285,8 @@ struct wayland_surface HWND toplevel_hwnd; }; }; + struct zxdg_toplevel_decoration_v1 *zxdg_toplevel_decoration; + enum zxdg_toplevel_decoration_v1_mode decoration_mode; struct wayland_surface_config pending, requested, processing, current; BOOL resizing; @@ -311,7 +315,7 @@ void wayland_output_use_xdg_extension(struct wayland_output *output); struct wayland_surface *wayland_surface_create(HWND hwnd); void wayland_surface_destroy(struct wayland_surface *surface); -void wayland_surface_make_toplevel(struct wayland_surface *surface); +void wayland_surface_make_toplevel(struct wayland_surface *surface, BOOL decorated); void wayland_surface_make_subsurface(struct wayland_surface *surface, struct wayland_surface *parent); void wayland_surface_clear_role(struct wayland_surface *surface); @@ -458,5 +462,6 @@ BOOL WAYLAND_WindowPosChanging(HWND hwnd, UINT swp_flags, BOOL shaped, const str BOOL WAYLAND_CreateWindowSurface(HWND hwnd, BOOL layered, const RECT *surface_rect, struct window_surface **surface); UINT WAYLAND_VulkanInit(UINT version, void *vulkan_handle, const struct vulkan_driver_funcs **driver_funcs); UINT WAYLAND_OpenGLInit(UINT version, const struct opengl_funcs *opengl_funcs, const struct opengl_driver_funcs **driver_funcs); +BOOL WAYLAND_GetWindowStyleMasks( HWND hwnd, UINT style, UINT ex_style, UINT *style_mask, UINT *ex_style_mask ); #endif /* __WINE_WAYLANDDRV_H */ diff --git a/dlls/winewayland.drv/waylanddrv_main.c b/dlls/winewayland.drv/waylanddrv_main.c index cdb5dd8a956..e7b52e5b2f0 100644 --- a/dlls/winewayland.drv/waylanddrv_main.c +++ b/dlls/winewayland.drv/waylanddrv_main.c @@ -56,6 +56,7 @@ static const struct user_driver_funcs waylanddrv_funcs = .pCreateWindowSurface = WAYLAND_CreateWindowSurface, .pVulkanInit = WAYLAND_VulkanInit, .pOpenGLInit = WAYLAND_OpenGLInit, + .pGetWindowStyleMasks = WAYLAND_GetWindowStyleMasks, }; static void wayland_init_process_name(void) diff --git a/dlls/winewayland.drv/window.c b/dlls/winewayland.drv/window.c index 07e0858fb39..fb352ff019a 100644 --- a/dlls/winewayland.drv/window.c +++ b/dlls/winewayland.drv/window.c @@ -148,6 +148,48 @@ void wayland_win_data_release(struct wayland_win_data *data) pthread_mutex_unlock(&win_data_mutex); } +/*********************************************************************** + * get_mwm_decoration_for_style + */ +static BOOL get_mwm_decoration_for_style( DWORD style, DWORD ex_style ) +{ + if (ex_style & WS_EX_TOOLWINDOW) return FALSE; + if (ex_style & WS_EX_LAYERED) return FALSE; + + if ((style & WS_CAPTION) == WS_CAPTION) + { + return TRUE; + } + return FALSE; +} + +/*********************************************************************** + * get_mwm_decoration + * get window should have decoration + */ +static BOOL get_mwm_decoration( struct wayland_win_data *data ) +{ + DWORD style, ex_style; + + if (data->hwnd == NtUserGetDesktopWindow()) + { + /* force some styles for the desktop to get the correct decorations */ + style = WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX; + ex_style = WS_EX_APPWINDOW; + } + else + { + style = NtUserGetWindowLongW( data->hwnd, GWL_STYLE ); + ex_style = NtUserGetWindowLongW( data->hwnd, GWL_EXSTYLE ); + } + + if (EqualRect( &data->rects.window, &data->rects.visible )) + { + return FALSE; + } + return get_mwm_decoration_for_style( style, ex_style ); +} + static void wayland_win_data_get_config(struct wayland_win_data *data, struct wayland_window_config *conf) { @@ -232,7 +274,7 @@ static BOOL wayland_win_data_create_wayland_surface(struct wayland_win_data *dat wayland_surface_clear_role(surface); break; case WAYLAND_SURFACE_ROLE_TOPLEVEL: - wayland_surface_make_toplevel(surface); + wayland_surface_make_toplevel(surface, get_mwm_decoration(data)); break; case WAYLAND_SURFACE_ROLE_SUBSURFACE: wayland_surface_make_subsurface(surface, toplevel_surface); @@ -491,7 +533,7 @@ static void wayland_configure_window(HWND hwnd) INT window_surf_width, window_surf_height; UINT flags = 0; uint32_t state; - DWORD style; + DWORD style, flip_style = 0; BOOL needs_enter_size_move = FALSE; BOOL needs_exit_size_move = FALSE; struct wayland_win_data *data; @@ -588,7 +630,20 @@ static void wayland_configure_window(HWND hwnd) style = NtUserGetWindowLongW(hwnd, GWL_STYLE); if (!(state & WAYLAND_SURFACE_CONFIG_STATE_MAXIMIZED) != !(style & WS_MAXIMIZE)) - NtUserSetWindowLong(hwnd, GWL_STYLE, style ^ WS_MAXIMIZE, FALSE); + flip_style |= WS_MAXIMIZE; + + if (surface->decoration_mode) { + if (surface->decoration_mode == ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE) + flip_style |= style & WS_CAPTION; + else + flip_style |= ~style & WS_CAPTION; + } + + if (flip_style & WS_CAPTION) + flags |= SWP_FRAMECHANGED; + + if (flip_style) + NtUserSetWindowLong(hwnd, GWL_STYLE, style ^ flip_style, FALSE); /* The Wayland maximized and fullscreen states are very strict about * surface size, so don't let the application override it. The tiled state @@ -874,3 +929,39 @@ void ensure_window_surface_contents(HWND hwnd) wayland_win_data_release(data); } + +BOOL WAYLAND_GetWindowStyleMasks( HWND hwnd, UINT style, UINT ex_style, UINT *style_mask, UINT *ex_style_mask ) +{ + BOOL decor = get_mwm_decoration_for_style( style, ex_style ); + struct wayland_win_data *data; + struct wayland_surface *wayland_surface; + + if ((data = wayland_win_data_get( hwnd ))) + { + if (!data->managed) + { + decor = FALSE; + } + /* compositor don't support xdg-decoration-unstable-v1 */ + else if (!process_wayland.zxdg_decoration_manager_v1) + { + decor = FALSE; + } + else if ((wayland_surface = data->wayland_surface)) + { + /* compositor support xdg-decoration-unstable-v1 but client-side decoration */ + if (wayland_surface->decoration_mode == ZXDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE) + decor = FALSE; + } + wayland_win_data_release( data ); + } + + *style_mask = *ex_style_mask = 0; + if (decor) + { + *style_mask |= WS_CAPTION | WS_DLGFRAME | WS_THICKFRAME; + *ex_style_mask |= WS_EX_DLGMODALFRAME; + } + + return TRUE; +} diff --git a/dlls/winewayland.drv/xdg-decoration-unstable-v1.xml b/dlls/winewayland.drv/xdg-decoration-unstable-v1.xml new file mode 100644 index 00000000000..82ca247c745 --- /dev/null +++ b/dlls/winewayland.drv/xdg-decoration-unstable-v1.xml @@ -0,0 +1,160 @@ +<?xml version="1.0" encoding="UTF-8"?> +<protocol name="xdg_decoration_unstable_v1"> + <copyright> + Copyright © 2018 Simon Ser + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice (including the next + paragraph) shall be included in all copies or substantial portions of the + Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. + </copyright> + + <interface name="zxdg_decoration_manager_v1" version="1"> + <description summary="window decoration manager"> + This interface allows a compositor to announce support for server-side + decorations. + + A window decoration is a set of window controls as deemed appropriate by + the party managing them, such as user interface components used to move, + resize and change a window's state. + + A client can use this protocol to request being decorated by a supporting + compositor. + + If compositor and client do not negotiate the use of a server-side + decoration using this protocol, clients continue to self-decorate as they + see fit. + + Warning! The protocol described in this file is experimental and + backward incompatible changes may be made. Backward compatible changes + may be added together with the corresponding interface version bump. + Backward incompatible changes are done by bumping the version number in + the protocol and interface names and resetting the interface version. + Once the protocol is to be declared stable, the 'z' prefix and the + version number in the protocol and interface names are removed and the + interface version number is reset. + </description> + + <request name="destroy" type="destructor"> + <description summary="destroy the decoration manager object"> + Destroy the decoration manager. This doesn't destroy objects created + with the manager. + </description> + </request> + + <request name="get_toplevel_decoration"> + <description summary="create a new toplevel decoration object"> + Create a new decoration object associated with the given toplevel. + + Creating an xdg_toplevel_decoration from an xdg_toplevel which has a + buffer attached or committed is a client error, and any attempts by a + client to attach or manipulate a buffer prior to the first + xdg_toplevel_decoration.configure event must also be treated as + errors. + </description> + <arg name="id" type="new_id" interface="zxdg_toplevel_decoration_v1"/> + <arg name="toplevel" type="object" interface="xdg_toplevel"/> + </request> + </interface> + + <interface name="zxdg_toplevel_decoration_v1" version="1"> + <description summary="decoration object for a toplevel surface"> + The decoration object allows the compositor to toggle server-side window + decorations for a toplevel surface. The client can request to switch to + another mode. + + The xdg_toplevel_decoration object must be destroyed before its + xdg_toplevel. + </description> + + <enum name="error"> + <entry name="unconfigured_buffer" value="0" + summary="xdg_toplevel has a buffer attached before configure"/> + <entry name="already_constructed" value="1" + summary="xdg_toplevel already has a decoration object"/> + <entry name="orphaned" value="2" + summary="xdg_toplevel destroyed before the decoration object"/> + <entry name="invalid_mode" value="3" summary="invalid mode"/> + </enum> + + <request name="destroy" type="destructor"> + <description summary="destroy the decoration object"> + Switch back to a mode without any server-side decorations at the next + commit. + </description> + </request> + + <enum name="mode"> + <description summary="window decoration modes"> + These values describe window decoration modes. + </description> + <entry name="client_side" value="1" + summary="no server-side window decoration"/> + <entry name="server_side" value="2" + summary="server-side window decoration"/> + </enum> + + <request name="set_mode"> + <description summary="set the decoration mode"> + Set the toplevel surface decoration mode. This informs the compositor + that the client prefers the provided decoration mode. + + After requesting a decoration mode, the compositor will respond by + emitting an xdg_surface.configure event. The client should then update + its content, drawing it without decorations if the received mode is + server-side decorations. The client must also acknowledge the configure + when committing the new content (see xdg_surface.ack_configure). + + The compositor can decide not to use the client's mode and enforce a + different mode instead. + + Clients whose decoration mode depend on the xdg_toplevel state may send + a set_mode request in response to an xdg_surface.configure event and wait + for the next xdg_surface.configure event to prevent unwanted state. + Such clients are responsible for preventing configure loops and must + make sure not to send multiple successive set_mode requests with the + same decoration mode. + + If an invalid mode is supplied by the client, the invalid_mode protocol + error is raised by the compositor. + </description> + <arg name="mode" type="uint" enum="mode" summary="the decoration mode"/> + </request> + + <request name="unset_mode"> + <description summary="unset the decoration mode"> + Unset the toplevel surface decoration mode. This informs the compositor + that the client doesn't prefer a particular decoration mode. + + This request has the same semantics as set_mode. + </description> + </request> + + <event name="configure"> + <description summary="notify a decoration mode change"> + The configure event configures the effective decoration mode. The + configured state should not be applied immediately. Clients must send an + ack_configure in response to this event. See xdg_surface.configure and + xdg_surface.ack_configure for details. + + A configure event can be sent at any time. The specified mode must be + obeyed by the client. + </description> + <arg name="mode" type="uint" enum="mode" summary="the decoration mode"/> + </event> + </interface> +</protocol> -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10259
does window maximization work with these patches? At on my local version it didn't until I fixed an issue with the window size -- https://gitlab.winehq.org/wine/wine/-/merge_requests/10259#note_131495
Etaash Mathamsetty (@etaash.mathamsetty) commented about dlls/winewayland.drv/wayland_surface.c:
if (!surface->xdg_toplevel) goto err; xdg_toplevel_add_listener(surface->xdg_toplevel, &xdg_toplevel_listener, surface->hwnd);
+ if (process_wayland.zxdg_decoration_manager_v1) + { + surface->zxdg_toplevel_decoration = + zxdg_decoration_manager_v1_get_toplevel_decoration(process_wayland.zxdg_decoration_manager_v1, + surface->xdg_toplevel); + if (surface->zxdg_toplevel_decoration) { + if (decorated)
It would be better to avoid creating a toplevel decoration entirely if we want no server side decorations as the set_mode is simply a hint -- https://gitlab.winehq.org/wine/wine/-/merge_requests/10259#note_131496
On my local version it causes context menus to get cut off on kwin {width=321 height=389} I assume a similar issue would exist in yours since it seems to be the compositor doing this to draw its decorations -- https://gitlab.winehq.org/wine/wine/-/merge_requests/10259#note_131497
I have tested it on KDE Plasma 6.6 (with the KWin compositor) and deepin V25 (with the Treeland compositor), and I did not encounter this issue. Could it be that your KWin compositor is not up to date? -- https://gitlab.winehq.org/wine/wine/-/merge_requests/10259#note_131511
On Sat Mar 7 03:05:16 2026 +0000, Etaash Mathamsetty wrote:
does window maximization work with these patches? At on my local version it didn't until I fixed an issue with the window size Yes, there is an issue when restoring from maximized. I have another patch to handle maximize and minimize, but Wayland does not currently support the minimized state, so this commit has not been submitted yet. I'm figuring out how to port over the logic for restoring from the maximized state.
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/10259#note_131512
On Fri Mar 6 21:43:42 2026 +0000, Etaash Mathamsetty wrote:
It would be better to avoid creating a toplevel decoration entirely if we want no server side decorations as the set_mode is simply a hint You're right, I'll fix it.
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/10259#note_131513
participants (3)
-
chenzhengyong -
Etaash Mathamsetty (@etaash.mathamsetty) -
zhengyong chen (@chenzhengyong)