srdusr
aboutsummaryrefslogtreecommitdiffstats
path: root/crates/wayland
diff options
context:
space:
mode:
authorsrdusr <[email protected]>2025-02-04 23:21:00 +0200
committersrdusr <[email protected]>2025-02-04 23:21:00 +0200
commit31942ff440c6039b943b4975714dca25372f80c5 (patch)
tree420fa5be87eb03fd3b898f3c86402380be3514cf /crates/wayland
parentdae1617a8b7ee7c40beb452046c5ec84e69931ee (diff)
downloadsrdwm-31942ff440c6039b943b4975714dca25372f80c5.tar.gz
srdwm-31942ff440c6039b943b4975714dca25372f80c5.zip
Fix CSD windows rendering with a wallpaper-visible gap at their corner
srdwm never read xdg_surface.set_window_geometry anywhere. A CSD client (GTK4/Firefox) declares its real visible content as a sub-rect inset within a larger buffer that also reserves an invisible shadow margin - that margin stays reserved in the buffer even once the tiled-state hint tells the client to stop drawing the shadow itself. Every render path was positioning content at the client's raw buffer origin instead of subtracting that declared offset, leaving the margin's width/height as a gap with wallpaper visible through it at the window's top-left corner. Confirmed by pixel-diffing the gap against the real wallpaper at that exact screen position: an exact match, ruling out "just the client's own dark theme." Fixed in three places that have to move together: udev/render.rs and winit/render.rs's content-positioning code, and sync_geometry's space.map_element call. That last one matters as much as the other two - it's what smithay's Space (and therefore click hit-testing) reads, so a render-only fix would have traded a visible gap for an invisible, same-size hit-test offset in the other direction. Also applied to the new capture-workspace off-screen render for the same reason. This likely also explains real dropdown/context-menu misplacement (and the clicks landing on the wrong spot) in CSD apps: popup positioning anchors against the same window position this fix corrects.
Diffstat (limited to 'crates/wayland')
-rw-r--r--crates/wayland/src/state/geometry.rs19
-rw-r--r--crates/wayland/src/udev/capture.rs7
-rw-r--r--crates/wayland/src/udev/render.rs25
-rw-r--r--crates/wayland/src/winit/render.rs10
4 files changed, 57 insertions, 4 deletions
diff --git a/crates/wayland/src/state/geometry.rs b/crates/wayland/src/state/geometry.rs
index a0d10fa..21cac8d 100644
--- a/crates/wayland/src/state/geometry.rs
+++ b/crates/wayland/src/state/geometry.rs
@@ -55,7 +55,24 @@ impl CompState {
let size_changed = self.last_synced_size.insert(id, size) != Some(size);
let mut moved = false;
if let Some(w) = self.id_to_window.get(&id) {
- self.space.map_element(w.clone(), (geom.x, geom.y + band), false);
+ // `w.geometry().loc` is the client's own `xdg_surface::
+ // set_window_geometry` offset - a CSD client (GTK4/Firefox
+ // concretely) declares its real visible content as a sub-rect
+ // inset within a larger buffer that also reserves an invisible
+ // shadow margin, even once the tiled-state hint below has told
+ // it to skip drawing that shadow. `render_udev_frame`/
+ // `winit/render.rs` both subtract this same offset from where
+ // they draw the window's content, specifically so the client's
+ // visible content lands at `geom.x, geom.y` instead of a
+ // shadow-margin's width/height short of it - `space` has to
+ // agree with that adjustment, not just rendering, or every
+ // click computed via `win_relative = pos - space_loc` would
+ // land `content_offset` short of whatever the user actually
+ // clicked on: rendering moves the content, hit-testing keeps
+ // routing against where the client's raw, unshifted buffer
+ // origin used to be.
+ let content_offset = w.geometry().loc;
+ self.space.map_element(w.clone(), (geom.x - content_offset.x, geom.y + band - content_offset.y), false);
moved = true;
if let Some(top) = w.toplevel() {
// xdg-shell position is a purely compositor-side concept --
diff --git a/crates/wayland/src/udev/capture.rs b/crates/wayland/src/udev/capture.rs
index 0f87d27..bcf2aa4 100644
--- a/crates/wayland/src/udev/capture.rs
+++ b/crates/wayland/src/udev/capture.rs
@@ -62,7 +62,12 @@ impl CompState {
let Some(w) = self.id_to_window.get(&id) else { continue };
let Some(surface) = crate::input::dwindow_wl_surface(w) else { continue };
let Some(geom) = self.wm.borrow().window(id).map(|w| w.geometry) else { continue };
- let loc = (geom.x - origin.0, geom.y - origin.1);
+ // Same `set_window_geometry` offset every other render path
+ // subtracts (see `udev/render.rs`'s matching fix) - without
+ // it, a CSD window's invisible shadow margin would show up as
+ // a gap in the capture too.
+ let content_offset = w.geometry().loc;
+ let loc = (geom.x - origin.0 - content_offset.x, geom.y - origin.1 - content_offset.y);
elements.extend(render_elements_from_surface_tree(&mut udev.renderer, &surface, loc, 1.0, 1.0, Kind::Unspecified));
}
diff --git a/crates/wayland/src/udev/render.rs b/crates/wayland/src/udev/render.rs
index 9642603..6c9c7d2 100644
--- a/crates/wayland/src/udev/render.rs
+++ b/crates/wayland/src/udev/render.rs
@@ -329,7 +329,30 @@ impl CompState {
if let Some(dwindow) = self.id_to_window.get(&id) {
if let Some(surface) = crate::elements::window_wl_surface(dwindow) {
let band = if w.decorated { srdwm_core::TITLEBAR_HEIGHT as i32 } else { 0 };
- let pos = (geom.x - origin.x, geom.y + band - origin.y);
+ // `dwindow.geometry().loc` is the client's own
+ // `xdg_surface.set_window_geometry` offset --
+ // GTK4 CSD clients (Firefox concretely) declare
+ // their real visible content as a sub-rect
+ // inset within a larger buffer that also holds
+ // an invisible shadow-reservation margin, even
+ // once the tiled-state hint (`sync_geometry`,
+ // `xdg_toplevel::State::Tiled*`) has told them
+ // to skip drawing that shadow - the margin
+ // itself, not just its decoration, stays
+ // reserved in the buffer. Never subtracting
+ // this meant every such client's buffer origin
+ // (0,0) - the *outer* edge of that invisible
+ // margin - landed exactly at `geom.x,geom.y`,
+ // leaving the margin's width/height of genuine
+ // gap (wallpaper visible through it) between
+ // the border this compositor draws and where
+ // the client's actual visible content begins.
+ // Reported live as "an extra layer or border
+ // over each window" - confirmed by diffing a
+ // corner crop against the real wallpaper at
+ // that exact screen position, pixel for pixel.
+ let content_offset = dwindow.geometry().loc;
+ let pos = (geom.x - origin.x - content_offset.x, geom.y + band - origin.y - content_offset.y);
let mut rounded_elem = None;
if rounded_corners_enabled {
let epoch = self.content_epoch.get(&id).copied().unwrap_or(0);
diff --git a/crates/wayland/src/winit/render.rs b/crates/wayland/src/winit/render.rs
index 735ff34..ec4fd69 100644
--- a/crates/wayland/src/winit/render.rs
+++ b/crates/wayland/src/winit/render.rs
@@ -247,7 +247,15 @@ impl WaylandPlatform {
if let Some(dwindow) = self.state.id_to_window.get(&id) {
if let Some(surface) = crate::elements::window_wl_surface(dwindow) {
let band = if w.decorated { srdwm_core::TITLEBAR_HEIGHT as i32 } else { 0 };
- let pos = (geom.x, geom.y + band);
+ // See the matching fix in `udev/render.rs`'s render loop
+ // for the full explanation: a CSD client's own
+ // `set_window_geometry` offset (its declared visible
+ // content within a larger buffer that also reserves an
+ // invisible shadow margin) was never subtracted, so
+ // that margin's worth of gap showed through at this
+ // window's top-left corner.
+ let content_offset = dwindow.geometry().loc;
+ let pos = (geom.x - content_offset.x, geom.y + band - content_offset.y);
let rounded = rounded_corners_enabled.then_some(self.state.rounded_corners_program.as_ref()).flatten().and_then(|program| {
let corners = if w.decorated { crate::rounded_corners::RoundedCorners::BOTTOM_ONLY } else { crate::rounded_corners::RoundedCorners::ALL };
crate::rounded_corners::rounded_content_element(renderer, program, &surface, pos, w.opacity, w.corner_radius as f32, corners)