git.lucas.co / cce-status-interface
status bar
git clone https://git.lucas.co/cce-status-interface.git

commit6aa372f083a864aab2fe327043fedc79cf1b15f6
parentb237c4c50f
authorLucas Galante <[email protected]>
date2026-08-18 14:52
refactor: canonical pointers for light_source_position and module sides

light_source_position gets one shared reader in config.rs at its real
location (window_manager { light_source_position }), replacing two
divergent copies — main.rs only degree-converted integers, modules.rs
only floats; now any value above 2pi is taken as legacy degrees. The
per-module side lookup reads /layout/status_bar/<name> (the key the
compositor persists super+drag snaps into) before falling back to the
fuzzy search, and get_module_side now uses the cached merged config
instead of re-parsing the shared file on its own.

Co-Authored-By: Claude Fable 5 <[email protected]>

 src/config.rs  | 37 +++++++++++++++++++++++++++++++++++++
 src/main.rs    | 45 +++++++++++++++++++++++----------------------
 src/modules.rs | 13 +------------
 3 files changed, 61 insertions(+), 34 deletions(-)

diff --git a/src/config.rs b/src/config.rs
index 203d28c..7f36302 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -217,6 +217,26 @@ pub(crate) fn parse_font_for_alias(content: &str, alias: &str) -> Option<String>
     None
 }
 
+/// The DE-wide light angle, canonical at `window_manager { light_source_position }`.
+/// Radians normally; a value above 2π is taken as legacy degrees (e.g. `135`)
+/// and converted. (This unifies the two previous readers, one of which only
+/// degree-converted integer values.)
+pub(crate) fn light_source_position_from(val: &serde_json::Value) -> f32 {
+    let raw = pointer_or_fuzzy(val, "/window_manager/light_source_position", "light_source_position")
+        .and_then(|v| v.as_f64())
+        .map(|f| f as f32)
+        .unwrap_or(2.356_194_5); // 135°, the compositor default
+    if raw > 2.0 * std::f32::consts::PI {
+        raw.to_radians()
+    } else {
+        raw
+    }
+}
+
+pub(crate) fn read_light_source_position_from_config() -> f32 {
+    light_source_position_from(&get_cached_config())
+}
+
 pub(crate) fn read_status_background_blur_from_config() -> f32 {
     cfg_f32("/style/status/background_blur", "status_background_blur").unwrap_or(0.0)
 }
@@ -421,6 +441,23 @@ style {
         );
     }
 
+    // --- light_source_position ---
+
+    #[test]
+    fn light_source_position_canonical_and_units() {
+        // Canonical location, radians as-is.
+        let val = parse_kdl("window_manager {\n    light_source_position (f64)2.5\n}");
+        assert!((light_source_position_from(&val) - 2.5).abs() < 1e-6);
+
+        // A value above 2π is legacy degrees.
+        let val = parse_kdl("window_manager {\n    light_source_position (f64)135.0\n}");
+        assert!((light_source_position_from(&val) - 135.0f32.to_radians()).abs() < 1e-6);
+
+        // Absent: the compositor's 135° default.
+        let val = serde_json::json!({});
+        assert!((light_source_position_from(&val) - 2.356_194_5).abs() < 1e-6);
+    }
+
     // --- color space (spec: text = raw sRGB, quads = linearized) ---
 
     #[test]
diff --git a/src/main.rs b/src/main.rs
index 0db839a..2ab14d1 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -861,35 +861,21 @@ impl StatusApp {
 
 
 fn get_module_side(name: &str) -> Side {
-    let content = std::fs::read_to_string(cce_ui::config::get_config_path()).unwrap_or_default();
-    let val = cce_ui::config::parse_kdl_to_json(&content);
-    module_side_from_json(&val, name)
+    module_side_from_json(&get_cached_config(), name)
 }
 
 fn module_side_from_json(val: &serde_json::Value, name: &str) -> Side {
     if name == "light_source" {
-        let mut light_pos = 2.356194490192345_f32; // Default 135 deg in rad
-        if let Some(wm_obj) = json_find_key(val, "window_manager") {
-            if let Some(pos_val) = json_find_key(&wm_obj, "light_source_position") {
-                if let Some(f) = pos_val.as_f64() {
-                    light_pos = f as f32;
-                } else if let Some(i) = pos_val.as_i64() {
-                    let deg = i as f32;
-                    if deg > 2.0 * std::f32::consts::PI {
-                        light_pos = deg.to_radians();
-                    } else {
-                        light_pos = deg;
-                    }
-                }
-            }
-        }
-        
+        // The light module ignores any status_bar side entry: it sits on
+        // whichever side the configured light angle points at.
+        let light_pos = crate::config::light_source_position_from(val);
+
         let two_pi = 2.0 * std::f32::consts::PI;
         let mut angle = light_pos % two_pi;
         if angle < 0.0 {
             angle += two_pi;
         }
-        
+
         let pi = std::f32::consts::PI;
         // Side mapping: Left side is roughly [5pi/8, 11pi/8)
         if angle >= 5.0 * pi / 8.0 && angle < 11.0 * pi / 8.0 {
@@ -899,7 +885,10 @@ fn module_side_from_json(val: &serde_json::Value, name: &str) -> Side {
         }
     }
 
-    if let Some(side_val) = json_find_key(val, name) {
+    // Canonical: `layout { status_bar <name>="top-left" }` — the same key the
+    // compositor persists a super+drag snap into.
+    let pointer = format!("/layout/status_bar/{}", name);
+    if let Some(side_val) = pointer_or_fuzzy(val, &pointer, name) {
         if let Some(side_str) = side_val.as_str() {
             match side_str.to_lowercase().as_str() {
                 "left" | "top-left" | "bottom-left" | "top-center" | "bottom-center" => return Side::Left,
@@ -1901,6 +1890,17 @@ mod tests {
         }
     }
 
+    #[test]
+    fn module_side_canonical_location_wins() {
+        // `layout { status_bar clock="left" }` beats a stray same-named key
+        // the fuzzy fallback would otherwise find.
+        let val = serde_json::json!({
+            "layout": {"status_bar": {"clock": "left"}},
+            "stray": {"clock": "right"}
+        });
+        assert_eq!(module_side_from_json(&val, "clock"), Side::Left);
+    }
+
     #[test]
     fn module_side_defaults() {
         let val = serde_json::json!({});
@@ -1927,8 +1927,9 @@ mod tests {
             Side::Left
         );
         assert_eq!(module_side_from_json(&mk(serde_json::json!(0.0)), "light_source"), Side::Right);
-        // Integers > 2π are degrees, otherwise radians.
+        // Values > 2π are degrees (int or float), otherwise radians.
         assert_eq!(module_side_from_json(&mk(serde_json::json!(180)), "light_source"), Side::Left);
+        assert_eq!(module_side_from_json(&mk(serde_json::json!(135.0)), "light_source"), Side::Left);
         assert_eq!(module_side_from_json(&mk(serde_json::json!(3)), "light_source"), Side::Left);
         assert_eq!(module_side_from_json(&mk(serde_json::json!(0)), "light_source"), Side::Right);
     }
diff --git a/src/modules.rs b/src/modules.rs
index 156569c..04a1280 100644
--- a/src/modules.rs
+++ b/src/modules.rs
@@ -729,18 +729,7 @@ impl StatusModule for TrayModule {
 pub struct LightSourceModule;
 
 pub(crate) fn get_light_source_pos_from_config() -> f32 {
-    let val = crate::get_cached_config();
-    if let Some(pos_val) = crate::json_find_key(&val, "light_source_position") {
-        if let Some(f) = pos_val.as_f64() {
-            let val = f as f32;
-            if val > 2.0 * std::f32::consts::PI {
-                return val.to_radians();
-            } else {
-                return val;
-            }
-        }
-    }
-    2.35619
+    crate::config::read_light_source_position_from_config()
 }
 
 impl StatusModule for LightSourceModule {