Wayland compositor (wlroots)
git clone https://git.lucas.co/cce-compositor.git
feat(shadow): `surface { shadow tiled=false }` drops the drop shadow on tiled windows
The scenefx drop shadow was all-or-nothing: `shadow enabled=false` took it
off floating windows too. A new `tiled` key under the same node keeps the
shadow for floating windows and suppresses it for Tiled ones (Maximized is
an alias of Tiled, so it follows). Default true — nothing changes for
existing configs.
The gate lives in `Window::wants_tiled_shadow` and is folded into both
want_shadow sites, so a float/tile toggle or a `reload` restyles on the
next arrange. The bevel's focus-glint argument used to be fed the shadow
decision; it now takes the ungated decorated-window predicate so the glint
is unaffected by the switch.
Verified headless (cce-shadow, debug build): with the key off a Tiled
cce-files reports its shadow node en=0 and Floating en=1; with the key
removed Tiled goes back to en=1.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01FJYu168CifcJ25Bh6mQRPr
src/server/config.rs | 15 +++++++++++++++
src/server/window.rs | 23 +++++++++++++++++++----
2 files changed, 34 insertions(+), 4 deletions(-)
diff --git a/src/server/config.rs b/src/server/config.rs
index 0cecaae..c3ce9ea 100644
--- a/src/server/config.rs
+++ b/src/server/config.rs
@@ -90,6 +90,10 @@ pub struct Layout {
/// `light_source_position` (down-right for the default top-left light).
pub shadow_offset_x: i32,
pub shadow_offset_y: i32,
+ /// Whether tiled (and maximized — an alias of Tiled) windows cast the
+ /// shadow at all. Off leaves shadows to floating windows only, so a
+ /// tiled layout reads as a flat sheet with no smearing across cell gaps.
+ pub shadow_tiled: bool,
/// Edge bevel: a lit chamfer drawn around the INSIDE of a decorated
/// window's edge (scenefx bevel node), lit from `bevel_light_*` — the
/// same top-left source the drop shadow is offset away from.
@@ -247,6 +251,7 @@ impl Default for Layout {
shadow_color: [0.0, 0.0, 0.0, 0.55],
shadow_offset_x: 7,
shadow_offset_y: 7,
+ shadow_tiled: true,
desktop_snap: true,
overview_anim: None,
desktop_snap_threshold: 24.0,
@@ -521,6 +526,8 @@ pub struct SurfaceConfig {
pub shadow_offset_x: i64,
#[serde(default = "default_shadow_offset_y")]
pub shadow_offset_y: i64,
+ #[serde(default = "default_shadow_tiled")]
+ pub shadow_tiled: bool,
#[serde(default = "default_bevel_enabled")]
pub bevel_enabled: bool,
#[serde(default = "default_bevel_thickness")]
@@ -546,6 +553,7 @@ fn default_shadow_sigma() -> f64 { 22.0 }
fn default_shadow_color() -> String { "#0000008c".to_string() }
fn default_shadow_offset_x() -> i64 { 7 }
fn default_shadow_offset_y() -> i64 { 7 }
+fn default_shadow_tiled() -> bool { true }
fn default_bevel_enabled() -> bool { true }
fn default_bevel_thickness() -> f64 { 10.0 }
/// Compass point the light comes FROM, matching the shadow's top-left source.
@@ -612,6 +620,7 @@ impl Default for SurfaceConfig {
shadow_color: default_shadow_color(),
shadow_offset_x: default_shadow_offset_x(),
shadow_offset_y: default_shadow_offset_y(),
+ shadow_tiled: default_shadow_tiled(),
bevel_enabled: default_bevel_enabled(),
bevel_thickness: default_bevel_thickness(),
bevel_light: default_bevel_light(),
@@ -2172,6 +2181,11 @@ fn parse_kdl_config(content: &str) -> Result<Config, String> {
surface.shadow_offset_y = val;
}
}
+ "tiled" => {
+ if let Some(val) = entry.value().as_bool() {
+ surface.shadow_tiled = val;
+ }
+ }
_ => {}
}
}
@@ -2438,6 +2452,7 @@ pub fn parse_config(path: &str, state: &mut crate::window_manager::WindowManager
state.layout.shadow_color = parse_hex_color_rgba(&config.surface.shadow_color);
state.layout.shadow_offset_x = config.surface.shadow_offset_x as i32;
state.layout.shadow_offset_y = config.surface.shadow_offset_y as i32;
+ state.layout.shadow_tiled = config.surface.shadow_tiled;
state.layout.bevel_enabled = config.surface.bevel_enabled;
state.layout.bevel_thickness = config.surface.bevel_thickness.max(0.0) as f32;
let (bevel_lx, bevel_ly) = parse_light_direction(&config.surface.bevel_light);
diff --git a/src/server/window.rs b/src/server/window.rs
index 05ff877..30d6303 100644
--- a/src/server/window.rs
+++ b/src/server/window.rs
@@ -496,6 +496,15 @@ impl Window {
false
}
+ /// The `surface { shadow tiled=false }` switch: a Tiled window (which is
+ /// also what Maximized resolves to) casts no drop shadow when it is off.
+ /// Floating, popup and every other mode are unaffected. Evaluated on both
+ /// render paths, so a float/tile toggle restyles on the next arrange.
+ pub unsafe fn wants_tiled_shadow(&self) -> bool {
+ (*self.server).wm.layout.shadow_tiled
+ || self.tiling_mode != crate::tiling::TilingMode::Tiled
+ }
+
pub unsafe fn is_fullscreen(&self) -> bool {
self.tiling_mode == crate::tiling::TilingMode::Fullscreen
|| !self.wm_requested.fullscreen.is_null()
@@ -2660,7 +2669,10 @@ impl Window {
// (cf. the window_background rect, which scales it the same way).
(radius as f64 * self.scale) as i32,
);
- let want_shadow = !is_status && (self.wm_requested.ssd || is_decorated) && !self.is_fullscreen();
+ // The decorated-window predicate feeds two things: the shadow, and
+ // the bevel's focus glint. Only the shadow honours the tiled switch.
+ let want_decor = !is_status && (self.wm_requested.ssd || is_decorated) && !self.is_fullscreen();
+ let want_shadow = want_decor && self.wants_tiled_shadow();
// The bevel keys on its OWN app list, not on is_decorated:
// every cce-ui app draws its own bevel, so a compositor one
// would sit on top of it.
@@ -2668,7 +2680,7 @@ impl Window {
&& !self.is_fullscreen()
&& (*self.server).wm.is_beveled_app(&app_id);
self.update_shadow(width, height, radius, want_shadow);
- self.update_bevel(width, height, radius, want_bevel, want_shadow);
+ self.update_bevel(width, height, radius, want_bevel, want_decor);
self.update_droplet(width, height);
ffi::river_scene_node_set_opacity(self.tree as *mut ffi::wlr_scene_node, requested.opacity);
@@ -3131,7 +3143,10 @@ impl Window {
// the motion path touches them. Left stale they keep the scale
// from before the gesture, so the punch-out overruns the shrunken
// window and swallows the shadow whole.
- let want_shadow = !is_status && (self.wm_requested.ssd || is_decorated) && !self.is_fullscreen();
+ // The decorated-window predicate feeds two things: the shadow, and
+ // the bevel's focus glint. Only the shadow honours the tiled switch.
+ let want_decor = !is_status && (self.wm_requested.ssd || is_decorated) && !self.is_fullscreen();
+ let want_shadow = want_decor && self.wants_tiled_shadow();
// The bevel keys on its OWN app list, not on is_decorated:
// every cce-ui app draws its own bevel, so a compositor one
// would sit on top of it.
@@ -3139,7 +3154,7 @@ impl Window {
&& !self.is_fullscreen()
&& (*self.server).wm.is_beveled_app(&app_id);
self.update_shadow(width, height, radius, want_shadow);
- self.update_bevel(width, height, radius, want_bevel, want_shadow);
+ self.update_bevel(width, height, radius, want_bevel, want_decor);
self.update_droplet(width, height);
}