diff options
| author | Adam Malczewski <[email protected]> | 2026-06-15 18:43:20 +0900 |
|---|---|---|
| committer | Adam Malczewski <[email protected]> | 2026-06-15 18:43:20 +0900 |
| commit | aeeda3135ddb4f1b633ccf2e46c4832b35888c7b (patch) | |
| tree | 5509779ca8647e1b7962aade741625b3ff128b17 | |
| parent | e73dfd27ba95d42b80c90d622250ad1f3be9bf33 (diff) | |
| download | unbox-aeeda3135ddb4f1b633ccf2e46c4832b35888c7b.tar.gz unbox-aeeda3135ddb4f1b633ccf2e46c4832b35888c7b.zip | |
ext-xdg-shell(rml-compositing): Toplevel::set_size + server-side decorations
- Toplevel::set_size(w,h): expose wlr_xdg_toplevel_set_size (async configure;
0 = client picks). Lets the window field size a client to its tile.
- Advertise xdg-decoration and force SERVER_SIDE when the RML field draws chrome
(create(prefer_server_side_decorations), wired from host-bin's
--rml-compositing) so clients drop their own CSD titlebars; classic path keeps
CSD. The mode is applied only once the xdg surface is initialized (on the
surface commit / request_mode), avoiding a wlr_xdg_surface_schedule_configure
assertion when the client creates the decoration before its initial commit.
| -rw-r--r-- | packages/ext-xdg-shell/include/unbox/ext-xdg-shell/ext_xdg_shell.hpp | 25 | ||||
| -rw-r--r-- | packages/ext-xdg-shell/src/extension.cpp | 99 |
2 files changed, 117 insertions, 7 deletions
diff --git a/packages/ext-xdg-shell/include/unbox/ext-xdg-shell/ext_xdg_shell.hpp b/packages/ext-xdg-shell/include/unbox/ext-xdg-shell/ext_xdg_shell.hpp index 6063525..f8a9708 100644 --- a/packages/ext-xdg-shell/include/unbox/ext-xdg-shell/ext_xdg_shell.hpp +++ b/packages/ext-xdg-shell/include/unbox/ext-xdg-shell/ext_xdg_shell.hpp @@ -60,6 +60,20 @@ public: // its own unmap/destroy then fires normally. virtual void close() = 0; + // Ask the client to render this window at width×height (an xdg_toplevel + // configure carrying the size). It is a REQUEST: the configure is async and + // the client picks the next buffer size — it may honour, clamp, or ignore it + // (per its min/max hints), and the new buffer arrives on a later commit, not + // synchronously. Passing 0 for a dimension cedes that axis to the client + // ("you choose"); negatives are clamped to 0. No-op if the toplevel is no + // longer mapped. Used by the window field to make a client paint AT its tile + // size (so the live texture fills the tile 1:1) instead of being scaled into + // it. This is a SIZE request only — it does NOT move the window or change the + // on-screen rectangle (RCSS/the wm still owns placement). Calling it + // repeatedly with the SAME size is cheap but still re-sends a configure, so + // callers should send only on an actual size change. + virtual void set_size(int width, int height) = 0; + // ---- Minimize mechanism (slice 10 / stage dock) ---------------------- // // Neutral compositor-side mechanism the stage dock drives to minimize a @@ -160,6 +174,15 @@ protected: // Construct the extension; install() it into the Server (ownership transfer). // Construction is side-effect free — all wiring happens in activate(). The // manifest is { id: "xdg-shell", tier: core, depends_on: {} }. -[[nodiscard]] auto create() -> std::unique_ptr<unbox::kernel::Extension>; +// +// prefer_server_side_decorations: when true, the extension advertises the +// xdg-decoration manager and forces SERVER_SIDE mode, so clients DROP their own +// titlebars (CSD) — used when something else draws the chrome (the RML window +// field). When false (the classic wlr_scene path, no server chrome), clients +// keep client-side decorations. Note: some toolkits (notably GTK/libadwaita) +// ignore the server's SSD preference and always draw CSD — that is a client +// limitation, not addressable here. +[[nodiscard]] auto create(bool prefer_server_side_decorations = false) + -> std::unique_ptr<unbox::kernel::Extension>; } // namespace unbox::ext_xdg_shell diff --git a/packages/ext-xdg-shell/src/extension.cpp b/packages/ext-xdg-shell/src/extension.cpp index 7d0f5a2..0332bc9 100644 --- a/packages/ext-xdg-shell/src/extension.cpp +++ b/packages/ext-xdg-shell/src/extension.cpp @@ -7,6 +7,7 @@ #include <unbox/kernel/listener.hpp> #include <unbox/kernel/wlr.hpp> +#include <algorithm> #include <cstdint> #include <memory> #include <stdexcept> @@ -68,6 +69,16 @@ struct ToplevelEntry final : Toplevel { wlr_xdg_toplevel_send_close(xdg_toplevel); } } + void set_size(int width, int height) override { + // Async size request (the same wlr call begin_resize uses for grabs and + // handle_new_toplevel uses for the initial 0×0). Clamp negatives to 0 + // ("client picks"); the client replies on a later commit. No-op when + // detached (a destroyed underlying toplevel). + if (xdg_toplevel != nullptr) { + wlr_xdg_toplevel_set_size(xdg_toplevel, static_cast<std::uint32_t>(std::max(0, width)), + static_cast<std::uint32_t>(std::max(0, height))); + } + } // ---- minimize mechanism (slice 10 / b1) ---- [[nodiscard]] auto geometry() const -> wlr_box override { @@ -114,10 +125,21 @@ struct PopupEntry { Listener destroy; }; +// One xdg-toplevel-decoration object (the client opted into decoration +// negotiation). We re-assert our preferred mode whenever the client (re)requests +// one, and drop the entry on destroy. Listeners live as long as the entry. +struct DecorationEntry { + wlr_xdg_toplevel_decoration_v1* deco = nullptr; + Listener commit; // toplevel surface commit: apply the mode once initialized + Listener request_mode; // client asked for a mode: re-assert ours + Listener destroy; +}; + class XdgShellExtension final : public kernel::Extension, public ActivationProbe { public: - XdgShellExtension() - : manifest_{"xdg-shell", kernel::Tier::core, {}} {} + explicit XdgShellExtension(bool prefer_server_side_decorations) + : prefer_ssd_(prefer_server_side_decorations), + manifest_{"xdg-shell", kernel::Tier::core, {}} {} [[nodiscard]] auto manifest() const -> const kernel::Manifest& override { return manifest_; } @@ -144,6 +166,20 @@ public: handle_new_popup(static_cast<wlr_xdg_popup*>(data)); }); + // xdg-decoration: advertise the manager so clients negotiate who draws + // the titlebar. When the RML window field draws the chrome + // (prefer_ssd_), we force SERVER_SIDE so clients drop their own CSD; + // otherwise (classic wlr_scene path, no server chrome) we leave them + // CLIENT_SIDE. Owned by the display; dies with it. + decoration_mgr_ = wlr_xdg_decoration_manager_v1_create(host.display()); + if (decoration_mgr_ != nullptr) { + new_decoration_.connect( + decoration_mgr_->events.new_toplevel_decoration, [this](void* data) { + handle_new_decoration( + static_cast<wlr_xdg_toplevel_decoration_v1*>(data)); + }); + } + // Pointer routing is ENTIRELY ours: the kernel moves the cursor and // emits, but forwards NOTHING to clients (host.hpp catalogue). We // hit-test the scene and call wlr_seat_pointer_notify_enter/motion/ @@ -326,6 +362,50 @@ private: }); } + // The preferred decoration mode for THIS session (RML field draws chrome => + // server-side; classic path => client-side). + [[nodiscard]] auto preferred_decoration_mode() const + -> wlr_xdg_toplevel_decoration_v1_mode { + return prefer_ssd_ ? WLR_XDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE + : WLR_XDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE; + } + + // Apply our preferred mode, but ONLY once the xdg_surface is initialized: + // set_mode schedules a configure, and wlr_xdg_surface_schedule_configure + // asserts `initialized` (the client creates the decoration object + requests + // a mode BEFORE its initial commit, so a naive set_mode there aborts). Guarded + // + idempotent via scheduled_mode, so the repeated commit/request_mode calls + // schedule at most one configure. + void apply_decoration_mode(DecorationEntry* entry) { + wlr_xdg_toplevel_decoration_v1* deco = entry->deco; + const auto mode = preferred_decoration_mode(); + if (deco->toplevel->base->initialized && deco->scheduled_mode != mode) { + wlr_xdg_toplevel_decoration_v1_set_mode(deco, mode); + } + } + + void handle_new_decoration(wlr_xdg_toplevel_decoration_v1* deco) { + auto owned = std::make_unique<DecorationEntry>(); + DecorationEntry* entry = owned.get(); + entry->deco = deco; + decorations_.emplace(deco, std::move(owned)); + + // Apply on the toplevel's surface commits (initialized is true by the + // commit event — same point the initial 0x0 configure is sent), and again + // whenever the client requests a mode (some toolkits ask for CSD; we + // re-assert SSD so the field's chrome is the only decoration). The + // immediate call covers a decoration created on an ALREADY-initialized + // surface; all routes are guarded by apply_decoration_mode. + entry->commit.connect(deco->toplevel->base->surface->events.commit, + [this, entry](void*) { apply_decoration_mode(entry); }); + entry->request_mode.connect(deco->events.request_mode, + [this, entry](void*) { apply_decoration_mode(entry); }); + entry->destroy.connect(deco->events.destroy, [this, entry](void*) { + decorations_.erase(entry->deco); // last action: drops `entry` + its Listeners + }); + apply_decoration_mode(entry); + } + // ---- scene hit-test (mirrors the former kernel toplevel_at) ---- auto toplevel_at(double lx, double ly, wlr_surface** surface, double* sx, double* sy) -> ToplevelEntry* { @@ -634,9 +714,11 @@ private: double origin_y = 0.0; }; + bool prefer_ssd_ = false; // force server-side decorations (RML field chrome) kernel::Manifest manifest_; Host* host_ = nullptr; wlr_xdg_shell* xdg_shell_ = nullptr; + wlr_xdg_decoration_manager_v1* decoration_mgr_ = nullptr; bool activated_ = false; // Exported hooks (adopt()ed; stable members — never moved). @@ -645,9 +727,11 @@ private: kernel::Event<const ToplevelEvent&> on_focused_; ServiceImpl service_{}; - // Window/popup ownership. + // Window/popup/decoration ownership. std::unordered_map<wlr_xdg_toplevel*, std::unique_ptr<ToplevelEntry>> toplevels_; std::unordered_map<wlr_xdg_popup*, std::unique_ptr<PopupEntry>> popups_; + std::unordered_map<wlr_xdg_toplevel_decoration_v1*, std::unique_ptr<DecorationEntry>> + decorations_; // Grab state (one at a time). The pure machine owns the move/resize/none // mode + button-down tracking; these hold the geometry for the active grab. @@ -668,6 +752,7 @@ private: // Raw xdg-shell signal listeners (RAII). Listener new_toplevel_; Listener new_popup_; + Listener new_decoration_; // Kernel-catalogue subscriptions (RAII; dropped on destruction). Subscription sub_motion_; @@ -687,12 +772,14 @@ void ToplevelEntry::focus() { ext->focus_toplevel(this); } } // namespace -auto create() -> std::unique_ptr<kernel::Extension> { - return std::make_unique<XdgShellExtension>(); +auto create(bool prefer_server_side_decorations) -> std::unique_ptr<kernel::Extension> { + return std::make_unique<XdgShellExtension>(prefer_server_side_decorations); } auto make_extension_with_probe() -> ExtensionWithProbe { - auto ext = std::make_unique<XdgShellExtension>(); + // Tests do not exercise decoration mode; default to client-side (the classic + // posture), matching a non-RML session. + auto ext = std::make_unique<XdgShellExtension>(false); ActivationProbe* probe = ext.get(); return {std::move(ext), probe}; } |
