srdusr
aboutsummaryrefslogtreecommitdiffstats
path: root/crates/core
diff options
context:
space:
mode:
authorsrdusr <[email protected]>2024-06-22 22:34:00 +0200
committersrdusr <[email protected]>2024-06-22 22:34:00 +0200
commit0818b56dd9c70b1097c5ef24be0f62baaf9ec999 (patch)
tree4b0a739b9aaaa428e9d66eff9ae332c2f1f2d51b /crates/core
parentab0916391532031376817ed4ab6ab234428559a2 (diff)
downloadsrdwm-0818b56dd9c70b1097c5ef24be0f62baaf9ec999.tar.gz
srdwm-0818b56dd9c70b1097c5ef24be0f62baaf9ec999.zip
Bypass smithay's Space rendering for window/layer content: real per-window opacity
The user asked for per-window opacity (MISSING.md's `windowrule = opacity` gap) and pushed back on treating smithay's convenience wrappers as a hard ceiling: "don't rely on smithay, it won't have everything we need." Looked again at why opacity was ruled out earlier - `render_output`/ `space_render_elements` take one `alpha` for the whole frame's `self.space` content, no per-element control - and found a path that doesn't need nesting smithay's internal `SpaceRenderElements` type (the approach that hit an unresolvable generic-bounds wall investigating fullscreen-hiding earlier): call `render_elements_from_surface_tree` directly, once per window and once per layer-shell surface, each with its own alpha, wrapping the result in the *existing* `OverlayElement::Surface` variant. That primitive was already proven safe in this codebase (cursor.rs's client-image path, this file's own popup rendering) - reusing it here for a window's main content is the same call, not a new one. Both `udev.rs` and `winit.rs`'s render loops now build window content and layer-shell surfaces themselves (`elements.rs`: `surface_content_elements`, `output_layer_elements`, `window_wl_surface` for the Wayland/XWayland split), in the correct front-to-back order, then call `OutputDamageTracker::render_output` directly instead of the `space::render_output`/`space_render_elements` convenience wrappers. Content itself needs no occlusion clipping against `occluders` (unlike border/ titlebar bitmaps) - pushed in the same front-to-back order as everything else, ordinary painter's-algorithm draw order already occludes it correctly, the same property it had via `self.space`'s own order before. `self.space` stays mapped and `resync_stacking_order`-maintained exactly as before; only the render step stopped reading from it. A comment in winit.rs's render loop warned that a near-identical earlier attempt was reverted for a real ordering bug (whichever window was created first always painted in front, regardless of focus). That bug's actual root cause, identified and fixed since, was `Space::map_element` silently re-stacking on every geometry sync, independent of which render path was used - see `resync_stacking_order`. This rewrite never reads `Space`'s internal order for rendering at all (`ids` comes from `WindowManager.order` directly, the same source `hit_test` already trusts), so that specific bug class can't recur here regardless of whether `resync_stacking_order` ever drifts again. Bonus from the same infrastructure: the bar/dock now genuinely don't render at all (not just get covered) for a fullscreen window - `output_layer_elements` skips `Layer::Top`/`Overlay` entirely when any visible window is fullscreen, the hardening this work backed away from earlier for being too risky to build via the nested-SpaceRenderElements approach. `capture_offscreen` (winit.rs's screencopy path) picked up opacity-aware content and layer-shell inclusion too, though not full parity with the on-screen loop (still no border/shadow strips there - a pre-existing, separately-flagged gap). Opacity itself: `Window.opacity` (core), `WindowRuleActions.opacity` / `srd.rule(..., { opacity = 0.9 })`, `srd.window.set_opacity()`. Caught live, before commit: opacity was wired into `add_window`'s own rule match but not `reapply_rules_if_pending` - the *only* path a class-based rule actually takes effect through for a native Wayland client, since `add_window`'s own attempt always runs against a still-empty `app_id` (see the regression test next to the existing one covering the identical historical bug for `decorated`). Found by setting an isolated `SRDWM_CONFIG_PATH` test config with `srd.rule({ class = "Alacritty" }, { opacity = 0.4 })` against a nested instance and pixel-sampling a real screenshot: predicted blend (242,230,53) at 0.4 over (10,10,15) is (103,98,30); measured (105,100,33). Verified live in a nested session, screenshotting the *host* compositor (shows the real on-screen render, unlike grim against the nested socket, which - separately discovered this work - routes through `capture_offscreen`): stacking order correct with two overlapping windows (topmost fully occludes the one behind it, the exact scenario the reverted attempt got wrong), opacity blend matches prediction. Also confirmed, by testing the previous commit against the same scene, that upside-down content on this backend is a pre-existing bug unrelated to this change -- noted, not fixed here. cargo build --workspace (all 9 crates), cargo clippy --workspace (0 new warnings), cargo test --workspace (197 tests, 0 failed, includes 2 new regression tests).
Diffstat (limited to 'crates/core')
-rw-r--r--crates/core/src/manager.rs31
-rw-r--r--crates/core/src/rules.rs2
-rw-r--r--crates/core/src/window.rs8
3 files changed, 41 insertions, 0 deletions
diff --git a/crates/core/src/manager.rs b/crates/core/src/manager.rs
index 2c92ac1..d45eaf6 100644
--- a/crates/core/src/manager.rs
+++ b/crates/core/src/manager.rs
@@ -266,6 +266,9 @@ impl WindowManager {
if let Some(pinned) = a.pinned {
window.always_on_top = pinned;
}
+ if let Some(opacity) = a.opacity {
+ window.opacity = opacity.clamp(0.0, 1.0);
+ }
}
if let Some(monitor) = self.primary_monitor() {
@@ -338,6 +341,9 @@ impl WindowManager {
if let Some(pinned) = actions.pinned {
window.always_on_top = pinned;
}
+ if let Some(opacity) = actions.opacity {
+ window.opacity = opacity.clamp(0.0, 1.0);
+ }
if let Some(geometry) = actions.geometry {
window.geometry = geometry;
}
@@ -1807,6 +1813,31 @@ mod tests {
}
#[test]
+ fn opacity_rule_applies_on_the_deferred_retry_same_as_other_actions() {
+ // Regression test: `opacity` was added to `add_window`'s own rule
+ // application but missed here, in the deferred retry
+ // `reapply_rules_if_pending` - confirmed live: a rule like
+ // `srd.rule({ class = "Alacritty" }, { opacity = 0.4 })` never took
+ // effect for any real native Wayland client, since (per the test
+ // above) that's the *only* path a class-based rule actually
+ // matches through for one of those - `add_window`'s own match
+ // attempt always fails first, against an as-yet-empty `app_id`.
+ let mut wm = wm_with_monitor();
+ wm.add_rule(WindowRule {
+ matcher: crate::rules::WindowMatch { class: Some("alacritty".into()), ..Default::default() },
+ actions: crate::rules::WindowRuleActions { opacity: Some(0.4), ..Default::default() },
+ });
+ let id = wm.alloc_window_id();
+ wm.add_window(Window::new(id, ""));
+ assert_eq!(wm.window(id).unwrap().opacity, 1.0, "no app_id yet, so no match - must not have applied early");
+
+ let w = wm.window_mut(id).unwrap();
+ w.app_id = "Alacritty".into();
+ wm.reapply_rules_if_pending(id);
+ assert_eq!(wm.window(id).unwrap().opacity, 0.4, "app_id now known - the rule must apply on retry");
+ }
+
+ #[test]
fn fullscreen_from_maximized_still_restores_the_pre_maximize_size() {
// Both share `restore_geometry`; entering fullscreen from a
// maximised window must not overwrite it with the monitor rect, or
diff --git a/crates/core/src/rules.rs b/crates/core/src/rules.rs
index 148b627..e0e87cb 100644
--- a/crates/core/src/rules.rs
+++ b/crates/core/src/rules.rs
@@ -88,6 +88,8 @@ pub struct WindowRuleActions {
pub border_width: Option<u32>,
/// Always-on-top (Hyprland's `pin`).
pub pinned: Option<bool>,
+ /// Content opacity, `0.0`..=`1.0` (Hyprland's `windowrule = opacity`).
+ pub opacity: Option<f32>,
}
#[derive(Debug, Clone, Default)]
diff --git a/crates/core/src/window.rs b/crates/core/src/window.rs
index e30f501..774b0e2 100644
--- a/crates/core/src/window.rs
+++ b/crates/core/src/window.rs
@@ -90,6 +90,13 @@ pub struct Window {
pub always_on_top: bool,
pub border_color: (u8, u8, u8),
pub border_width: u32,
+ /// This window's own content opacity, `0.0`..=`1.0`. Only the content
+ /// (the client's own surface tree) is affected - srdwm's own
+ /// decoration (titlebar/border/shadow) always renders fully opaque
+ /// regardless, the same way a native macOS/Windows translucent-window
+ /// effect still keeps its frame legible. Set via `srd.window.
+ /// set_opacity()` or a rule's `opacity` action.
+ pub opacity: f32,
pub workspace: usize,
pub monitor: u32,
/// Whether `WindowManager`'s class/title-matched rules have already
@@ -135,6 +142,7 @@ impl Window {
always_on_top: false,
border_color: (136, 192, 208), // Nord accent, matches legacy theme default
border_width: 2,
+ opacity: 1.0,
workspace: 0,
monitor: 0,
rules_applied: false,