diff options
| -rw-r--r-- | crates/core/src/theme.rs | 23 | ||||
| -rw-r--r-- | crates/srdwm/src/main.rs | 1 | ||||
| -rw-r--r-- | crates/wayland/src/protocols/xdg_decoration.rs | 5 | ||||
| -rw-r--r-- | docs/DEFAULTS.md | 74 | ||||
| -rw-r--r-- | docs/TODO.md | 58 |
5 files changed, 161 insertions, 0 deletions
diff --git a/crates/core/src/theme.rs b/crates/core/src/theme.rs index 026688a..649ec64 100644 --- a/crates/core/src/theme.rs +++ b/crates/core/src/theme.rs @@ -175,6 +175,28 @@ pub struct ThemeConfig { /// Asked for as titlebars able to use "decorations/buttons of the /// program/dynamic". pub dynamic_buttons: bool, + /// Enforce server-side decoration on every client that negotiates it, + /// instead of honouring what the client asked for. + /// + /// `xdg-decoration` explicitly allows this: "The compositor can decide + /// not to use the client's mode and enforce a different mode instead", + /// and "the specified mode must be obeyed by the client". So a toolkit + /// that asks for client-side - winit does, measured - can be given + /// srdwm's titlebar anyway, which is what makes every negotiating + /// window look the same. + /// + /// Off by default, because it cannot help the case it looks like it + /// should. A client that never creates a decoration object at all is + /// outside the protocol: "if compositor and client do not negotiate + /// the use of a server-side decoration ... clients continue to + /// self-decorate as they see fit". GTK is exactly that client - it + /// creates no decoration object, measured directly - so forcing this + /// on cannot move a single GTK button, while it *can* stack srdwm's + /// titlebar on top of a client that draws its own regardless (the + /// Firefox case this project already hit once). Turn it on to make + /// negotiating toolkits uniform; use `rules.lua`'s `decorated = false` + /// for the ones that draw their own anyway. + pub force_server_side: bool, } impl Default for ThemeConfig { @@ -194,6 +216,7 @@ impl Default for ThemeConfig { button_glyph_always: false, traffic_light_buttons: true, dynamic_buttons: true, + force_server_side: false, } } } diff --git a/crates/srdwm/src/main.rs b/crates/srdwm/src/main.rs index 6c2f04b..9772e65 100644 --- a/crates/srdwm/src/main.rs +++ b/crates/srdwm/src/main.rs @@ -321,6 +321,7 @@ fn apply_general_settings(engine: &Engine, wm: &Rc<RefCell<WindowManager>>) { // "dynamic" (default: only the buttons the window can actually use) or // "fixed" (always the full set) - see `ThemeConfig::dynamic_buttons`. theme.dynamic_buttons = engine.get_string("theme.decorations.title_bar.button_mode", "dynamic") != "fixed"; + theme.force_server_side = engine.get_bool("theme.decorations.force_server_side", false); let border_width = engine.get_f64("theme.decorations.border.width", 2.0).max(0.0) as u32; theme.default_border_width = border_width; // 12, not the original 6: matches real macOS's own ~0.36 radius-to- diff --git a/crates/wayland/src/protocols/xdg_decoration.rs b/crates/wayland/src/protocols/xdg_decoration.rs index 25973c0..c3c7f0b 100644 --- a/crates/wayland/src/protocols/xdg_decoration.rs +++ b/crates/wayland/src/protocols/xdg_decoration.rs @@ -37,7 +37,12 @@ impl XdgDecorationHandler for CompState { /// the way for exactly those clients, while everything that accepts /// (or has no preference and gets offered) server-side still gets our /// titlebar as before. + /// `theme.decorations.force_server_side` overrides the client's request + /// - see `ThemeConfig::force_server_side` for why that is allowed and + /// why it is off by default. fn request_mode(&mut self, toplevel: ToplevelSurface, mode: DecorationMode) { + let forced = self.wm.borrow().theme.force_server_side; + let mode = if forced { DecorationMode::ServerSide } else { mode }; toplevel.with_pending_state(|state| { state.decoration_mode = Some(mode); }); diff --git a/docs/DEFAULTS.md b/docs/DEFAULTS.md index daa65d8..fd10861 100644 --- a/docs/DEFAULTS.md +++ b/docs/DEFAULTS.md @@ -971,3 +971,77 @@ is not intercepted until the next startup. Combos are reported in canonical order (`Ctrl+Mod4+l`), which is what the compositor matches a real keypress against - not necessarily how the combo was written in the config. + +## Making client-side decorated apps match srdwm's titlebar + +A window's chrome is drawn by one of two parties, and which one is decided +by the `xdg-decoration` protocol. Three behaviours were measured directly +against a nested srdwm, not assumed: + +| toolkit | creates a decoration object | asks for | +|---|---|---| +| Qt / KDE (Dolphin) | yes | server-side | +| winit (Alacritty) | yes | **client-side** | +| GTK 3 / GTK 4 (Nemo) | **no, never** | - | + +That table is the whole problem. The protocol says "the compositor can +decide not to use the client's mode and enforce a different mode instead", +and "the specified mode must be obeyed by the client" - so the first two +rows are srdwm's to control. But it also says "if compositor and client do +not negotiate the use of a server-side decoration ... clients continue to +self-decorate as they see fit", and GTK never negotiates at all. No +compositor setting can reach a GTK window's buttons, because GTK is not +having the conversation. + +### For toolkits that negotiate: force server-side + +```lua +srd.set("theme.decorations.force_server_side", true) +``` + +Off by default. It makes every negotiating client take srdwm's titlebar, +including one that asked for client-side. Verified: with it off, Alacritty +draws its own content straight to the window's top edge; with it on, the +same window gets srdwm's titlebar band. + +The reason it is not the default is that it cannot help the case it looks +like it should, and can hurt: a client that draws its own chrome regardless +of what it negotiated (Firefox, historically) ends up with srdwm's titlebar +stacked on top of its own. Use `rules.lua`'s `decorated = false` for those. + +### For GTK: set the desktop's button layout + +GTK reads its button layout from the desktop, not from the compositor. GTK 4 +on Wayland reads it from `xdg-desktop-portal` +(`org.freedesktop.portal.Settings`, key `org.gnome.desktop.wm.preferences` +`button-layout`); GTK 3 reads the `gtk-decoration-layout` GtkSettings +property. Both resolve to the same string on a normal setup. + +```sh +gsettings set org.gnome.desktop.wm.preferences button-layout ':minimize,maximize,close' +``` + +The colon separates the left corner from the right, so a leading colon puts +every button on the right. Match it to srdwm's own `button_side`: + +| srdwm `button_side` | button-layout | +|---|---| +| `"right"` (default) | `:minimize,maximize,close` | +| `"left"` | `close,minimize,maximize:` | + +This is exactly how KDE does it - `kde-gtk-config` exists to write GTK's +setting to match KWin's own decoration. The value persists in dconf, so it +survives a reboot without any startup script. + +Verified with a screenshot A/B on one Nemo window, changing only this +setting: buttons moved from x=25/49/73 (left) to x=817/841/865 (right), +landing in the same place and the same order as srdwm's own titlebar buttons +directly above them. + +### What still cannot match + +The button *style*. srdwm's `button_style` draws srdwm's own titlebar; a +GTK app draws its buttons with its GTK theme. Position and order can be made +identical, as above; whether they are flat glyphs or coloured dots is the +GTK theme's decision. On WhiteSur-Dark they are macOS-style dots by design. +Changing that means changing the GTK theme. diff --git a/docs/TODO.md b/docs/TODO.md index d6e0cad..971d0cd 100644 --- a/docs/TODO.md +++ b/docs/TODO.md @@ -1,5 +1,63 @@ # TODO / planned features - master checklist +## Deep dive: can every window use the same decorations, client- or server-side (2026-08-28) + +Asked after being told "srdwm can only control its own titlebar" - correctly +pushed back on, because that answer was too quick. It is half wrong, and the +half that is right is right for a different reason than given. + +**Measured first, against a nested srdwm, one client at a time:** + + Qt/KDE (Dolphin) creates a decoration object, asks for server-side + winit (Alacritty) creates a decoration object, asks for CLIENT-side + GTK3/4 (Nemo) never creates a decoration object at all + +**The spec, read rather than remembered** (`xdg-decoration-unstable-v1`): +"The compositor can decide not to use the client's mode and enforce a +different mode instead", and "the specified mode must be obeyed by the +client". So rows one and two are entirely srdwm's to decide - it had simply +been choosing to defer. The same spec closes the door on row three: "if +compositor and client do not negotiate the use of a server-side decoration +... clients continue to self-decorate as they see fit". GTK is not having +the conversation, so no compositor setting can reach its buttons. + +**What the other desktops actually do.** KWin defaults to server-side on +Wayland and offers a per-window rule ("No titlebar and frame", Force/No) to +push CSD clients back to server-side - the same override this protocol text +allows. For GTK it does not use the protocol at all: `kde-gtk-config` exists +purely to write GTK's own setting so GTK draws its buttons where KWin would +have. GNOME goes the other way: GTK reads the layout from the desktop, and +on Wayland GTK4 takes it from `xdg-desktop-portal`'s +`org.freedesktop.portal.Settings`, key `org.gnome.desktop.wm.preferences` +`button-layout`. + +**Both halves built and verified.** + +1. `theme.decorations.force_server_side` (default off). With it off, + Alacritty draws its own content straight to the window's top edge; with + it on, the same window gets srdwm's titlebar - sampled at three rows, + `(0,0,0)` content versus `(46,52,64)` titlebar. Off by default because it + cannot move a GTK button and *can* stack srdwm's titlebar on a client + that draws its own regardless, which is the Firefox case this project + already hit. + +2. The GTK half needs no srdwm code, only the right desktop setting. The + portal on this machine was serving `close,minimize,maximize:` - buttons + on the left - while srdwm's own `button_side` was `right`. That single + contradiction is the whole "some windows are still using traffic lights + on the wrong side" report. Setting + `org.gnome.desktop.wm.preferences button-layout` to + `:minimize,maximize,close` moved Nemo's own buttons from x=25/49/73 to + x=817/841/865 - the same position and order as srdwm's own buttons + directly above them, screenshotted before and after with nothing else + changed. + +**What genuinely cannot match:** button *style*. Position and order can be +made identical; whether the buttons are flat glyphs or coloured dots is the +GTK theme's decision, and on WhiteSur-Dark they are macOS dots by design. + +275 core tests, 525 total, clippy clean. + ## Five reports after the second restart: a fix that landed on the wrong branch, and a config that fought itself (2026-08-28) **The maximize border was still there because the fix landed on the wrong |