From 3c47912ae0a2b81d06e4e9101b5b4e2f1e2997d9 Mon Sep 17 00:00:00 2001 From: srdusr <99972264+srdusr@users.noreply.github.com> Date: Mon, 27 Jul 2026 01:33:00 +0200 Subject: Reach GTK4's window buttons too, and speak the decoration protocol GTK knows Two findings from reading what other projects do, and then measuring this machine rather than trusting the reading. GTK has never implemented xdg-decoration, so a compositor that advertises only that protocol is invisible to every GTK client on the decoration question. What GTK does implement is KDE's older org_kde_kwin_server_decoration - confirmed by reading libgtk-4's own symbol strings, where the manager, the mode enum and the default-mode handler are all present, and absent from libgtk-3. That is the channel a KDE session uses. srdwm now advertises it alongside xdg-decoration, with both answering from the same policy (theme.default_decorated, theme.force_server_side) so a client is told the same thing whichever it asks through. Measured what that actually buys, with WAYLAND_DEBUG on a real GTK4 client: it binds the manager and receives default_mode(2) = Server, and then never creates a decoration object for its window. So it changes nothing for a GTK application's own header bar, and it is kept because it is the correct thing to advertise and because clients that do honour it - Qt and KDE's own -- now get server-side decoration from srdwm instead of nothing. The second finding is the one that fixes what was reported. GTK3 and GTK4 put their window buttons under different CSS selectors, and the generated stylesheet named only GTK3's. A diagnostic rule proved it both ways: a flat colour reached Nemo (GTK3) through `headerbar button.titlebutton` and gnome-calculator (GTK4) through `windowcontrols button`, and neither selector reached the other toolkit. Every style is now written for both, so a GTK4 application is styled rather than silently skipped. `headerbar` itself works in both, so the titlebar block needed no split. Verified on screen: gnome-calculator, a GTK4/libadwaita application, now draws its header bar in srdwm's own titlebar colour with srdwm's text colour, where before it kept its theme's. Also worth writing down, because it bounds what any of this can achieve: an application's header bar is its own widget. No protocol removes it. GTK_CSD=0 does not, the KDE protocol does not, and neither does forcing server-side decoration - that only adds a second titlebar above the first. What a compositor can do is make the two look like one, which is what this does. --- crates/wayland/src/protocols/kde_decoration.rs | 74 ++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 crates/wayland/src/protocols/kde_decoration.rs (limited to 'crates/wayland/src/protocols') diff --git a/crates/wayland/src/protocols/kde_decoration.rs b/crates/wayland/src/protocols/kde_decoration.rs new file mode 100644 index 0000000..2448a39 --- /dev/null +++ b/crates/wayland/src/protocols/kde_decoration.rs @@ -0,0 +1,74 @@ +//! `org_kde_kwin_server_decoration`: KDE's older decoration protocol, and +//! the only decoration protocol GTK actually speaks. +//! +//! GTK has never implemented `xdg-decoration`. It does implement this one: +//! `org_kde_kwin_server_decoration_manager` is present in libgtk-4 on this +//! machine (confirmed by reading the library's own symbol strings), and it +//! is how a KDE session gets GTK applications to stop drawing their own +//! frame. A compositor that advertises only `xdg-decoration` is invisible +//! to those clients, which is why "set the decoration once and every +//! application follows" stopped at srdwm's own titlebars. +//! +//! Both protocols are advertised, and both answer with the same policy +//! (`theme.default_decorated`, `theme.force_server_side`), so a client is +//! told the same thing whichever one it asks through. +//! +//! The protocol's own warning applies here: a client may ignore the mode +//! the compositor suggests and ask for its own. That is honoured the same +//! way `xdg_decoration.rs` honours it, and for the same reason - a client +//! that draws its own titlebar regardless (Firefox with its system-titlebar +//! setting off) would otherwise get srdwm's row on top of its own. + +use smithay::reexports::wayland_protocols_misc::server_decoration::server::org_kde_kwin_server_decoration::{ + Mode, OrgKdeKwinServerDecoration, +}; +use smithay::reexports::wayland_server::protocol::wl_surface::WlSurface; +use smithay::reexports::wayland_server::WEnum; +use smithay::wayland::shell::kde::decoration::{KdeDecorationHandler, KdeDecorationState}; + +use crate::state::CompState; + +impl KdeDecorationHandler for CompState { + fn kde_decoration_state(&self) -> &KdeDecorationState { + &self.kde_decoration_state + } + + /// Tells a client, the moment it asks, which mode this compositor + /// wants - the same answer `XdgDecorationHandler::new_decoration` + /// gives through the other protocol. + /// + /// The manager's own default mode (set once, at startup) is what a + /// client sees before it creates a decoration object at all; this is + /// what it sees afterward, and it has to agree, or a client that reads + /// both ends up with two different answers. + fn new_decoration(&mut self, surface: &WlSurface, decoration: &OrgKdeKwinServerDecoration) { + let server = self.wm.borrow().theme.default_decorated; + decoration.mode(if server { Mode::Server } else { Mode::Client }); + self.set_decorated_from_mode(surface, server); + } + + /// Honours what the client asked for, unless `force_server_side` says + /// otherwise - identical policy to the xdg-decoration path, and the + /// mode is echoed back either way because the protocol requires the + /// compositor to confirm what it decided. + fn request_mode(&mut self, surface: &WlSurface, decoration: &OrgKdeKwinServerDecoration, mode: WEnum) { + let WEnum::Value(requested) = mode else { return }; + let forced = self.wm.borrow().theme.force_server_side; + // `Mode::None` means no decoration at all, which for srdwm's + // purposes is a client saying it wants nothing drawn around it -- + // treated as client-side, the same as `Mode::Client`, rather than + // as a request for a titlebar. + let server = forced || requested == Mode::Server; + let granted = if server { Mode::Server } else { requested }; + decoration.mode(granted); + self.set_decorated_from_mode(surface, server); + } + + /// The client is going away, or has dropped its decoration object. + /// Nothing to undo: `remove_window` already clears everything keyed on + /// this surface, and a surface with no decoration object keeps + /// whatever mode it last negotiated, exactly as before. + fn release(&mut self, _decoration: &OrgKdeKwinServerDecoration, _surface: &WlSurface) {} +} + +smithay::delegate_kde_decoration!(CompState); -- cgit v1.2.3