From c17d7dd05a33bf29ec3d85b9db5222bee47398f1 Mon Sep 17 00:00:00 2001 From: Ilia Bozhinov Date: Fri, 22 May 2026 21:33:13 +0200 Subject: [PATCH] xdg-shell: allow forcing client/server side decorations for particular surfaces --- src/api/wayfire/plugin.hpp | 2 +- .../wayfire/unstable/xdg-toplevel-base.hpp | 13 ++ src/view/xdg-shell/xdg-toplevel-view.cpp | 122 +++++++++++++++++- 3 files changed, 132 insertions(+), 5 deletions(-) diff --git a/src/api/wayfire/plugin.hpp b/src/api/wayfire/plugin.hpp index fd834af89..ab116f05d 100644 --- a/src/api/wayfire/plugin.hpp +++ b/src/api/wayfire/plugin.hpp @@ -107,7 +107,7 @@ using wayfire_plugin_load_func = wf::plugin_interface_t * (*)(); /** * The version is defined as macro as well, to allow conditional compilation. */ -#define WAYFIRE_API_ABI_VERSION_MACRO 2026'05'07 +#define WAYFIRE_API_ABI_VERSION_MACRO 2026'05'22 /** * The version of Wayfire's API/ABI diff --git a/src/api/wayfire/unstable/xdg-toplevel-base.hpp b/src/api/wayfire/unstable/xdg-toplevel-base.hpp index 78cdd6228..f72cb39f1 100644 --- a/src/api/wayfire/unstable/xdg-toplevel-base.hpp +++ b/src/api/wayfire/unstable/xdg-toplevel-base.hpp @@ -6,6 +6,19 @@ namespace wf { +enum class xdg_toplevel_decoration_mode_t +{ + CLIENT_SIDE, + SERVER_SIDE, +}; + +/** + * Request that an xdg-toplevel surface renegotiate its decoration mode. + * + * Returns false if the surface is not an xdg-toplevel surface. + */ +bool renegotiate_xdg_decoration(wlr_surface *surface, xdg_toplevel_decoration_mode_t mode); + /** * A base class for xdg_toplevel-based views which implements the view_interface_t (but not toplevel_view_t, * see @xdg_toplevel_view_t for the full implementation). diff --git a/src/view/xdg-shell/xdg-toplevel-view.cpp b/src/view/xdg-shell/xdg-toplevel-view.cpp index c042ac7fd..bbd4acda9 100644 --- a/src/view/xdg-shell/xdg-toplevel-view.cpp +++ b/src/view/xdg-shell/xdg-toplevel-view.cpp @@ -13,6 +13,8 @@ #include "wayfire/seat.hpp" #include "wayfire/util.hpp" #include "wayfire/view.hpp" +#include +#include #include #include #include @@ -366,6 +368,74 @@ void wf::xdg_toplevel_view_t::set_decoration_mode(bool use_csd) } /* decorations impl */ +struct wf_xdg_decoration_t; + +namespace +{ +using decoration_mode_t = wlr_xdg_toplevel_decoration_v1_mode; + +static decoration_mode_t to_wlr_mode(wf::xdg_toplevel_decoration_mode_t mode) +{ + switch (mode) + { + case wf::xdg_toplevel_decoration_mode_t::CLIENT_SIDE: + return WLR_XDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE; + + case wf::xdg_toplevel_decoration_mode_t::SERVER_SIDE: + return WLR_XDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE; + } + + return WLR_XDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE; +} + +struct xdg_decoration_surface_state_t +{ + wlr_addon addon; + std::optional forced_mode; + wf_xdg_decoration_t *decor = nullptr; + + xdg_decoration_surface_state_t(wlr_surface *surface) + { + wlr_addon_init(&addon, &surface->addons, nullptr, &addon_impl); + } + + ~xdg_decoration_surface_state_t() + { + wlr_addon_finish(&addon); + } + + static xdg_decoration_surface_state_t *get(wlr_surface *surface) + { + auto addon = wlr_addon_find(&surface->addons, nullptr, &addon_impl); + return addon ? wl_container_of(addon, (xdg_decoration_surface_state_t*)nullptr, addon) : nullptr; + } + + static xdg_decoration_surface_state_t *get_or_create(wlr_surface *surface) + { + auto state = get(surface); + if (state) + { + return state; + } + + return new xdg_decoration_surface_state_t(surface); + } + + static void destroy_addon(wlr_addon *addon) + { + auto state = wl_container_of(addon, (xdg_decoration_surface_state_t*)nullptr, addon); + delete state; + } + + static const wlr_addon_interface addon_impl; +}; + +const wlr_addon_interface xdg_decoration_surface_state_t::addon_impl = { + .name = "wayfire-xdg-decoration-state", + .destroy = xdg_decoration_surface_state_t::destroy_addon, +}; +} + struct wf_server_decoration_t { wlr_server_decoration *decor; @@ -409,8 +479,28 @@ struct wf_xdg_decoration_t wf::option_wrapper_t deco_mode{"core/preferred_decoration_mode"}; wf::option_wrapper_t force_preferred{"workarounds/force_preferred_decoration_mode"}; + xdg_decoration_surface_state_t *surface_state() + { + return xdg_decoration_surface_state_t::get_or_create(decor->toplevel->base->surface); + } + + void apply_mode(decoration_mode_t mode) + { + if (decor->toplevel->base->initialized) + { + wlr_xdg_toplevel_decoration_v1_set_mode(decor, mode); + } + } + std::function mode_request = [&] (void*) { + auto state = surface_state(); + if (state->forced_mode) + { + apply_mode(*state->forced_mode); + return; + } + wlr_xdg_toplevel_decoration_v1_mode default_mode = WLR_XDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE; if ((std::string)deco_mode == "server") @@ -424,10 +514,7 @@ struct wf_xdg_decoration_t mode = default_mode; } - if (decor->toplevel->base->initialized) - { - wlr_xdg_toplevel_decoration_v1_set_mode(decor, mode); - } + apply_mode(mode); }; std::function commit = [&] (void*) @@ -453,10 +540,13 @@ struct wf_xdg_decoration_t wf_xdg_decoration_t(wlr_xdg_toplevel_decoration_v1 *_decor) : decor(_decor) { + surface_state()->decor = this; + on_mode_request.set_callback(mode_request); on_commit.set_callback(commit); on_destroy.set_callback([&] (void*) { + surface_state()->decor = nullptr; uses_csd.erase(decor->toplevel->base->surface); delete this; }); @@ -467,6 +557,30 @@ struct wf_xdg_decoration_t } }; +bool wf::renegotiate_xdg_decoration(wlr_surface *surface, xdg_toplevel_decoration_mode_t mode) +{ + if (!surface) + { + return false; + } + + auto xdg_surface = wlr_xdg_surface_try_from_wlr_surface(surface); + if (!xdg_surface || (xdg_surface->role != WLR_XDG_SURFACE_ROLE_TOPLEVEL) || !xdg_surface->toplevel) + { + return false; + } + + auto state = xdg_decoration_surface_state_t::get_or_create(surface); + state->forced_mode = to_wlr_mode(mode); + + if (state->decor) + { + state->decor->apply_mode(*state->forced_mode); + } + + return true; +} + static wf::wl_listener_wrapper on_org_kde_decoration_created; static void init_legacy_decoration() {