git.lucas.co / cce-compositor
Wayland compositor (wlroots)
git clone https://git.lucas.co/cce-compositor.git

commitd77a94bec29c979259ef2f1019bf7a3f0fb9879a
parentf8b9796d60
authorLucas Galante <[email protected]>
date2026-08-06 10:17
feat: drive the traveling light_source segment (day fraction + minute tick)

Pass the local day fraction (libc localtime_r) into ArrangeParams and
re-arrange once a minute (sun_timer) so the segment's perimeter
position follows the clock (~a few px per tick).

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

 src/server/window_manager.rs | 42 ++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 42 insertions(+)

diff --git a/src/server/window_manager.rs b/src/server/window_manager.rs
index f34868c..bc477ea 100644
--- a/src/server/window_manager.rs
+++ b/src/server/window_manager.rs
@@ -89,6 +89,9 @@ pub struct WindowManager {
     pub gesture_binds: Vec<crate::config::GestureBind>,
     pub ipc_rx: Option<std::sync::mpsc::Receiver<crate::ipc_server::IpcRequest>>,
     pub ipc_timer: *mut ffi::wl_event_source,
+    /// Minute tick for the traveling light_source segment: re-arranges so
+    /// its perimeter position follows the time of day.
+    pub sun_timer: *mut ffi::wl_event_source,
     /// Window-stream subscribers (cce-remote's live view); frames are
     /// produced by `handle_stream_timer` when a subscribed window is dirty.
     pub stream_hub: Option<crate::stream_server::StreamHub>,
@@ -243,6 +246,7 @@ impl WindowManager {
         self.gesture_binds = Vec::new();
         self.ipc_rx = None;
         self.ipc_timer = std::ptr::null_mut();
+        self.sun_timer = std::ptr::null_mut();
         self.stream_hub = None;
         self.stream_timer = std::ptr::null_mut();
         self.startup = Vec::new();
@@ -274,6 +278,14 @@ impl WindowManager {
         }
         ffi::wl_event_source_timer_update(self.ipc_timer, 10);
 
+        self.sun_timer = ffi::wl_event_loop_add_timer(event_loop, Some(handle_sun_timer), self as *mut WindowManager as *mut _);
+        if self.sun_timer.is_null() {
+            ffi::wl_event_source_remove(self.timeout);
+            ffi::wl_event_source_remove(self.ipc_timer);
+            return Err("Failed to create sun timer event source");
+        }
+        ffi::wl_event_source_timer_update(self.sun_timer, 60_000);
+
         self.clean_exit_timer = ffi::wl_event_loop_add_timer(event_loop, Some(handle_clean_exit_timeout), self as *mut WindowManager as *mut _);
         if self.clean_exit_timer.is_null() {
             ffi::wl_event_source_remove(self.timeout);
@@ -893,6 +905,10 @@ impl WindowManager {
             ffi::wl_event_source_remove(self.clean_exit_timer);
             self.clean_exit_timer = std::ptr::null_mut();
         }
+        if !self.sun_timer.is_null() {
+            ffi::wl_event_source_remove(self.sun_timer);
+            self.sun_timer = std::ptr::null_mut();
+        }
         if !self.animation_timer.is_null() {
             ffi::wl_event_source_remove(self.animation_timer);
             self.animation_timer = std::ptr::null_mut();
@@ -1808,6 +1824,7 @@ impl WindowManager {
             status_hide_mode: self.status_hide_mode,
             hide_mode_preview: self.layout.status_module_hide_mode_preview as i32,
             status_module_spacing: self.layout.status_module_spacing as i32,
+            day_fraction: Some(local_day_fraction()),
             status_blur: self.layout.status_background_blur > 0.001,
             window_blur: self.layout.window_blur,
             opacity_enabled: self.layout.window_opacity,
@@ -3706,6 +3723,31 @@ impl WindowManager {
     }
 }
 
+/// Local time as a fraction of the day (0 = midnight, 0.5 = noon) — the
+/// input driving the light_source segment's perimeter position.
+pub fn local_day_fraction() -> f64 {
+    unsafe {
+        let now = libc::time(std::ptr::null_mut());
+        let mut tm: libc::tm = std::mem::zeroed();
+        if libc::localtime_r(&now, &mut tm).is_null() {
+            return 0.5;
+        }
+        (tm.tm_hour as f64 * 3600.0 + tm.tm_min as f64 * 60.0 + tm.tm_sec as f64) / 86400.0
+    }
+}
+
+/// Minute tick: the light_source segment's perimeter position depends on the
+/// time of day, so a periodic re-arrange keeps it drifting (~a few px/min).
+unsafe extern "C" fn handle_sun_timer(data: *mut std::ffi::c_void) -> std::os::raw::c_int {
+    let wm = data as *mut WindowManager;
+    if wm.is_null() {
+        return 0;
+    }
+    (*wm).dirty_windowing();
+    ffi::wl_event_source_timer_update((*wm).sun_timer, 60_000);
+    0
+}
+
 unsafe extern "C" fn handle_ipc_timer(data: *mut std::ffi::c_void) -> std::os::raw::c_int {
     let wm = data as *mut WindowManager;
     if wm.is_null() {