srdusr
aboutsummaryrefslogtreecommitdiffstats
path: root/crates
diff options
context:
space:
mode:
authorsrdusr <[email protected]>2025-03-03 01:47:00 +0200
committersrdusr <[email protected]>2025-03-03 01:47:00 +0200
commitf83082a3b9b75e6f1bc4187850bea1b7408cffca (patch)
tree4135aaa04f4aeea60a597062883d0bcf9c7f2867 /crates
parentd303c276e289c9c7146cf74ccad2743246922248 (diff)
downloadsrdwm-f83082a3b9b75e6f1bc4187850bea1b7408cffca.tar.gz
srdwm-f83082a3b9b75e6f1bc4187850bea1b7408cffca.zip
Fix VT-switch DPMS blank-screen and CSD corner-crop staircase bugs
VT-switch resume: reasserting the CRTC's mode-setting state was never enough on its own - display power (DPMS) is separate KMS state, and nothing here ever touched it after a real switch-away-and-back. Page flips kept succeeding with zero errors logged for the rest of a real 30+ minute session while the panel itself simply stayed dark, which is what actually explains a user report of the screen and keyboard input never recovering after one VT switch. Sets DPMS-on unconditionally on every resume now, the same property zwlr_output_power_v1 already writes for an explicit client request. Corner rendering: both backends' side-strip crop (the fix that keeps a window's flat left/right border strips from poking a solid-coloured square through the top/bottom strip's own rounded curve) only ever activated for `w.decorated` windows. An undecorated/CSD window's crop depends on its *content* actually getting masked to match - the winit backend never checked that at all, so every CSD window with a nonzero border_width got the uncropped, "staircase" artifact unconditionally, confirmed live via a highlighted border colour and raw pixel sampling. Ported the udev backend's own `border_curve_is_safe` check (decorated OR content-will-be-masked) into winit, using the cheap "does this surface have subsurface children" test both content- masking code paths already gate success on, rather than duplicating either one's real (comparatively expensive) rendering work just to probe it. Full workspace build + clippy -D warnings + test suite (378 tests) green.
Diffstat (limited to 'crates')
-rw-r--r--crates/wayland/src/udev/session.rs33
-rw-r--r--crates/wayland/src/winit/render.rs49
2 files changed, 75 insertions, 7 deletions
diff --git a/crates/wayland/src/udev/session.rs b/crates/wayland/src/udev/session.rs
index e39c578..a6b7349 100644
--- a/crates/wayland/src/udev/session.rs
+++ b/crates/wayland/src/udev/session.rs
@@ -102,6 +102,39 @@ pub(crate) fn register_session_notifier(handle: &LoopHandle<'static, CompState>,
if let Err(e) = card.set_crtc(head.crtc, Some(fb), (0, 0), &[head.connector], Some(head.mode)) {
log::warn!("udev: failed to reassert crtc on resume: {e}");
}
+ // A real VT-switch-back can leave the panel itself
+ // powered down (DPMS off/standby) even once the CRTC
+ // above is genuinely re-driving it - mode-setting
+ // and display power are two separate pieces of KMS
+ // state, and only the former was ever touched here.
+ // Confirmed live: page flips kept succeeding with no
+ // error logged anywhere (this backend's own
+ // `copy_and_flip` never saw a failure) for the rest
+ // of a 30+ minute session after a real switch-away-
+ // and-back, and neither the screen nor keyboard
+ // input ever recovered on their own - every frame
+ // was genuinely being composited and flipped to a
+ // CRTC the panel simply wasn't lit to show. Setting
+ // "DPMS" straight to on here, unconditionally, is
+ // the same property `set_output_power` already
+ // writes for `zwlr_output_power_v1` - this is just
+ // that same write happening automatically on every
+ // resume instead of only when a client explicitly
+ // asks, since nothing else in this codebase ever
+ // calls it after a VT switch. A no-op on hardware
+ // that never needed it (the property is simply
+ // already `on`, or genuinely absent on some virtual/
+ // headless outputs - `find` below just yields
+ // nothing and this silently continues to the next
+ // head either way).
+ const DRM_MODE_DPMS_ON: u64 = 0;
+ if let Ok(props) = card.get_properties(head.connector) {
+ if let Some(dpms_prop) = props.as_props_and_values().0.iter().copied().find(|&h| card.get_property(h).is_ok_and(|info| info.name().to_str() == Ok("DPMS"))) {
+ if let Err(e) = card.set_property(head.connector, dpms_prop, DRM_MODE_DPMS_ON) {
+ log::warn!("udev: failed to reassert DPMS-on for output {} on resume: {e}", head.output.name());
+ }
+ }
+ }
// Force a full repaint: contents are undefined after
// the VT switch (another VT's session may have
// scanned out something else entirely in between).
diff --git a/crates/wayland/src/winit/render.rs b/crates/wayland/src/winit/render.rs
index c245aa2..f4cb7af 100644
--- a/crates/wayland/src/winit/render.rs
+++ b/crates/wayland/src/winit/render.rs
@@ -201,6 +201,37 @@ impl WaylandPlatform {
// really committed - see `effective_frame`'s own doc comment
// and the matching comment in `udev/render.rs`'s render loop.
let frame = self.state.effective_frame(id, geom);
+ // Whether this window's *content* will actually get its
+ // corners rounded this frame - an undecorated (CSD) window's
+ // border has no titlebar band to safely absorb its own top/
+ // bottom strip's curve-only "extra" rows into (see `decoration::
+ // border_top_visible_rows`'s doc comment), so cropping them --
+ // and the flat side strips below, which would otherwise poke a
+ // solid-coloured straight line through that curve's own
+ // transparent cutout - is only safe when the *content* is
+ // also going to be cut to match. This used to check `w.decorated`
+ // alone, which is wrong for exactly the CSD case those crops
+ // exist for in the first place: an undecorated window's border
+ // never got cropped at all, regardless of whether its content
+ // masking would actually succeed, guaranteeing the square-
+ // meets-curve artifact this doc comment is describing on every
+ // single CSD window with a nonzero `border_width` - reported
+ // live (screenshot + raw pixel sampling) as a hard right-angle
+ // "staircase" where a smooth curve should be, on both a CSD
+ // window (Firefox, content masking legitimately in play) and,
+ // via the matching bug in `udev/render.rs`, windows this same
+ // reasoning also affects there. `rounded_content_element`'s own
+ // (and `rounded_corners_pixman::masked_content_buffer`'s, on
+ // the udev backend) only real failure mode is a surface with
+ // subsurface children - checking that directly here is the
+ // same cheap, single-condition test both of those already
+ // gate on internally, without needing to actually run either
+ // one's real (comparatively expensive) rendering work twice
+ // just to ask "would this have worked".
+ let content_will_be_masked = rounded_corners_enabled
+ && self.wm.borrow().resizing_window() != Some(id)
+ && self.state.id_to_window.get(&id).and_then(crate::elements::window_wl_surface).is_some_and(|surface| smithay::wayland::compositor::get_children(&surface).is_empty());
+ let border_curve_is_safe = w.decorated || content_will_be_masked;
if let Some(deco) = self.state.decorations.get(&id) {
// Fragment-clipped, same as udev/render.rs's matching titlebar
// push - see that comment for why all-or-nothing (skip
@@ -252,7 +283,7 @@ impl WaylandPlatform {
// titlebar band to safely absorb this buffer's own
// corner-curve-only extra rows, so they're cropped
// away instead of landing on real content.
- let (row0, rows, shift) = decoration::border_top_visible_rows(w.decorated, w.border_width, w.corner_radius);
+ let (row0, rows, shift) = decoration::border_top_visible_rows(border_curve_is_safe, w.border_width, w.corner_radius);
let pos = (strips[0].x as f64, (strips[0].y + shift as i32) as f64);
let src = Some(Rectangle::new(Point::from((0.0, row0 as f64)), Size::from((strips[0].width as f64, rows as f64))));
match MemoryRenderBufferRenderElement::from_buffer(renderer, pos, buffer, None, src, None, Kind::Unspecified) {
@@ -268,7 +299,7 @@ impl WaylandPlatform {
if let Some(buffer) = self.state.border_bottom_decorations.get(&id) {
// See `decoration::border_bottom_visible_rows`'s
// own doc comment.
- let (row0, rows, shift) = decoration::border_bottom_visible_rows(w.decorated, w.border_width, w.corner_radius);
+ let (row0, rows, shift) = decoration::border_bottom_visible_rows(border_curve_is_safe, w.border_width, w.corner_radius);
let pos = (strips[1].x as f64, (strips[1].y - shift as i32) as f64);
let src = Some(Rectangle::new(Point::from((0.0, row0 as f64)), Size::from((strips[1].width as f64, rows as f64))));
match MemoryRenderBufferRenderElement::from_buffer(renderer, pos, buffer, None, src, None, Kind::Unspecified) {
@@ -286,11 +317,15 @@ impl WaylandPlatform {
// solid fill bled through the curve's own transparent
// cutout as a straight vertical line poking out of an
// otherwise correctly-rounded corner - reported live,
- // confirmed via raw pixel sampling on the udev backend;
- // this backend shares the identical strip geometry and was
- // never actually confirmed clean, just never specifically
- // screenshotted the same way.
- let extra = if w.decorated { w.border_width.max(w.corner_radius).saturating_sub(w.border_width) } else { 0 };
+ // confirmed via raw pixel sampling: a hard right-angle
+ // "staircase" instead of a curve, on an undecorated (CSD)
+ // window specifically, since `border_curve_is_safe` (not
+ // `w.decorated` alone - see this window's own doc comment
+ // above) is what actually decides whether this crop is
+ // safe to apply, and the CSD case is exactly the one
+ // `w.decorated` alone always answered "no" to regardless
+ // of whether content masking would really succeed.
+ let extra = if border_curve_is_safe { w.border_width.max(w.corner_radius).saturating_sub(w.border_width) } else { 0 };
let mut side_strips = [strips[2], strips[3]];
for s in &mut side_strips {
s.y += extra as i32;