status bar
git clone https://git.lucas.co/cce-status-interface.git
Refactor ScrollingList to List and fix window decoration blur and styling
src/main.rs | 376 +++++++++++++++++++++++++++++++++++++++++++++------------
src/modules.rs | 78 ++++++------
2 files changed, 334 insertions(+), 120 deletions(-)
diff --git a/src/main.rs b/src/main.rs
index a8db286..f794b38 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -313,11 +313,14 @@ struct StatusApp {
right_modules: Vec<Box<dyn StatusModule>>,
sender: calloop::channel::Sender<CustomEvent>,
last_config_modified: Option<std::time::SystemTime>,
+ selected_module_name: Option<String>,
+ selected_module_side: Option<Side>,
}
impl StatusApp {
#[allow(unused_assignments)]
fn rebuild_layout(&mut self) {
+ log::info!("[cce-status] rebuild_layout module={:?} size={}x{}", self.selected_module_name, self.width, self.height);
let font_family = read_status_font_from_config();
let font_size = read_status_font_size_from_config();
let show_separators = false;
@@ -340,6 +343,7 @@ impl StatusApp {
self.text_items.clear();
self.input_regions.clear();
self.module_bounds.clear();
+ self.tray_item_bounds.clear();
let left_modules = std::mem::take(&mut self.left_modules);
let right_modules = std::mem::take(&mut self.right_modules);
@@ -348,12 +352,19 @@ impl StatusApp {
let status_box_radius = read_status_box_corner_radius_from_config();
self.status_bar.set_rect(0.0, 0.0, sw_logical, bar_h);
- self.status_bar.set_bg_color(self.current_bg_color);
+ if self.selected_module_name.is_some() {
+ self.status_bar.set_bg_color([0.0, 0.0, 0.0, 0.0]);
+ } else {
+ self.status_bar.set_bg_color(self.current_bg_color);
+ }
self.viewport_bounds.clear();
self.layout_bounds = None;
- let mut left_x = 12.0;
+ let is_single = self.selected_module_name.is_some();
+ let margin_padding = if is_single { 6.0 } else { 12.0 };
+
+ let mut left_x = margin_padding;
let mut is_first_left = true;
for module in &left_modules {
let w = module.width(
@@ -391,7 +402,7 @@ impl StatusApp {
h: bar_h,
radius: status_box_radius,
color,
- corners: (false, false, true, true),
+ corners: if self.selected_module_name.is_some() { (true, true, true, true) } else { (false, false, true, true) },
});
}
}
@@ -436,8 +447,6 @@ impl StatusApp {
let mut right_x = sw_logical - 12.0;
let mut is_first_right = true;
- self.tray_item_bounds.clear();
-
for module in right_modules.iter().rev() {
let w = module.width(
&self.stats,
@@ -483,7 +492,7 @@ impl StatusApp {
h: bar_h,
radius: status_box_radius,
color,
- corners: (false, false, true, true),
+ corners: if self.selected_module_name.is_some() { (true, true, true, true) } else { (false, false, true, true) },
});
}
}
@@ -608,6 +617,14 @@ impl StatusApp {
}
}
+ if self.selected_module_name.is_some() {
+ let old_w = self.width;
+ self.width = (left_x + margin_padding).round() as u32;
+ eprintln!("[module-{}] rebuild_layout: width calculated as {} (was {})", self.selected_module_name.as_deref().unwrap_or("none"), self.width, old_w);
+ self.input_regions.clear();
+ self.input_regions.push((0, 0, self.width as i32, bar_h.round() as i32));
+ }
+
self.left_modules = left_modules;
self.right_modules = right_modules;
self.needs_rebuild = false;
@@ -682,12 +699,12 @@ impl StatusApp {
fn trigger_switcher(&mut self, is_switcher_mode: bool) {
let switcher_source = "window".to_string();
- // 1. Check if any clear-cloud instance is already running
+ // 1. Check if any cce-cloud instance is already running
let mut running_cloud_pid = None;
if let Some(pid) = self.active_cloud_pid {
if std::path::Path::new(&format!("/proc/{}", pid)).exists() {
if let Ok(comm) = std::fs::read_to_string(format!("/proc/{}/comm", pid)) {
- if comm.trim() == "clear-cloud" {
+ if comm.trim() == "cce-cloud" {
running_cloud_pid = Some(pid);
}
}
@@ -697,13 +714,13 @@ impl StatusApp {
if let Some(pid) = running_cloud_pid {
if is_switcher_mode && self.active_cloud_source.as_ref() == Some(&switcher_source) {
if let Some(ref writer) = self.active_switcher_stdin {
- eprintln!("[switcher] Already open, sending cycle command to clear-cloud stdin");
+ eprintln!("[switcher] Already open, sending cycle command to cce-cloud stdin");
let _ = writer.0.send("__cce_switcher_next__\n".to_string());
return;
}
} else {
// Kill it to switch focus
- eprintln!("[switcher] Killing existing clear-cloud PID {}", pid);
+ eprintln!("[switcher] Killing existing cce-cloud PID {}", pid);
let _ = std::process::Command::new("kill").arg(pid.to_string()).status();
self.active_cloud_pid = None;
@@ -831,7 +848,7 @@ impl StatusApp {
}
}
- let mut child = match std::process::Command::new(get_clear_cloud_cmd())
+ let mut child = match std::process::Command::new(get_cce_cloud_cmd())
.args(&cmd_args)
.stdin(std::process::Stdio::piped())
.stdout(std::process::Stdio::piped())
@@ -840,7 +857,7 @@ impl StatusApp {
{
Ok(c) => c,
Err(e) => {
- eprintln!("[switcher] Failed to spawn clear-cloud: {:?}", e);
+ eprintln!("[switcher] Failed to spawn cce-cloud: {:?}", e);
let _ = thread_sender.send(CustomEvent::CloudClosed { pid: 0, source: switcher_source_clone });
return;
}
@@ -940,25 +957,96 @@ fn get_active_viewport_from_camera(viewport_json: &str) -> u32 {
1
}
+fn get_module_side(name: &str) -> Side {
+ match name {
+ "viewport" | "window" => Side::Left,
+ _ => Side::Right,
+ }
+}
+
+fn parse_selected_module_from_args() -> Option<(String, Side)> {
+ let args: Vec<String> = std::env::args().collect();
+ for i in 0..args.len() {
+ if args[i] == "--module" && i + 1 < args.len() {
+ let name = args[i + 1].clone();
+ let side = get_module_side(&name);
+ return Some((name, side));
+ }
+ }
+ None
+}
+
impl cce_ui::engine::Application for StatusApp {
type Message = CustomEvent;
fn new(_qh: &wayland_client::QueueHandle<cce_ui::engine::EngineState<Self>>, sender: calloop::channel::Sender<Self::Message>) -> Self {
- let sender_viewport = sender.clone();
- let sender_layout = sender.clone();
- let sender_title = sender.clone();
- let sender_modifiers = sender.clone();
- let sender_stats = sender.clone();
- let sender_tray = sender.clone();
- let sender_switcher = sender.clone();
-
- tokio::spawn(spawn_status_listener("viewport", sender_viewport));
- tokio::spawn(spawn_status_listener("layout", sender_layout));
- tokio::spawn(spawn_status_listener("title", sender_title));
- tokio::spawn(spawn_status_listener("modifiers", sender_modifiers));
- tokio::spawn(spawn_system_stats(sender_stats));
- tokio::spawn(spawn_status_tray(sender_tray));
- tokio::spawn(spawn_switcher_listener(sender_switcher));
+ let selected_module = parse_selected_module_from_args();
+
+ let mut left_modules: Vec<Box<dyn StatusModule>> = Vec::new();
+ let mut right_modules: Vec<Box<dyn StatusModule>> = Vec::new();
+
+ let mut has_viewport = false;
+ let mut has_window = false;
+
+ if let Some((ref name, _)) = selected_module {
+ let module: Box<dyn StatusModule> = match name.as_str() {
+ "viewport" => {
+ has_viewport = true;
+ Box::new(ViewportModule)
+ }
+ "window" => {
+ has_window = true;
+ Box::new(WindowModule)
+ }
+ "tray" => Box::new(TrayModule),
+ "cpu" => Box::new(CpuModule),
+ "memory" => Box::new(MemoryModule),
+ "brightness" => Box::new(BrightnessModule),
+ "volume" => Box::new(VolumeModule),
+ "battery" => Box::new(BatteryModule),
+ "clock" => Box::new(ClockModule),
+ _ => panic!("Unknown module: {}", name),
+ };
+ left_modules.push(module);
+ } else {
+ left_modules.push(Box::new(ViewportModule));
+ left_modules.push(Box::new(WindowModule));
+ right_modules.push(Box::new(TrayModule));
+ right_modules.push(Box::new(CpuModule));
+ right_modules.push(Box::new(MemoryModule));
+ right_modules.push(Box::new(BrightnessModule));
+ right_modules.push(Box::new(VolumeModule));
+ right_modules.push(Box::new(BatteryModule));
+ right_modules.push(Box::new(ClockModule));
+ has_viewport = true;
+ has_window = true;
+ }
+
+ if has_viewport {
+ tokio::spawn(spawn_status_listener("viewport", sender.clone()));
+ tokio::spawn(spawn_status_listener("layout", sender.clone()));
+ }
+ if has_window {
+ tokio::spawn(spawn_status_listener("title", sender.clone()));
+ }
+ if selected_module.is_none() {
+ tokio::spawn(spawn_status_listener("modifiers", sender.clone()));
+ }
+ let is_primary_for_switcher = selected_module.as_ref().map_or(true, |(name, _)| name == "window");
+ if is_primary_for_switcher {
+ tokio::spawn(spawn_switcher_listener(sender.clone()));
+ }
+
+ let has_tray = selected_module.as_ref().map_or(true, |(name, _)| name == "tray");
+ if has_tray {
+ tokio::spawn(spawn_status_tray(sender.clone()));
+ }
+ let has_stats = selected_module.as_ref().map_or(true, |(name, _)| {
+ name == "cpu" || name == "memory" || name == "brightness" || name == "volume" || name == "battery" || name == "clock"
+ });
+ if has_stats {
+ tokio::spawn(spawn_system_stats(sender.clone()));
+ }
let font_system = FontSystem::new();
@@ -966,7 +1054,7 @@ impl cce_ui::engine::Application for StatusApp {
viewport: String::new(),
layout: String::new(),
title: String::new(),
- stats: None,
+ stats: if has_stats { Some(get_initial_stats()) } else { None },
tray_items: HashMap::new(),
cursor_pos: (0.0, 0.0),
hovered_tray_item: None,
@@ -985,7 +1073,7 @@ impl cce_ui::engine::Application for StatusApp {
separators: Vec::new(),
text_items: Vec::new(),
scale_factor: 1.0,
- width: 1920,
+ width: if selected_module.is_some() { 120 } else { 1920 },
height: read_status_height_from_config() as u32,
needs_rebuild: true,
current_bg_color: color::STATUS_BG,
@@ -993,21 +1081,12 @@ impl cce_ui::engine::Application for StatusApp {
super_pressed: false,
dragged_module: None,
module_bounds: Vec::new(),
- left_modules: vec![
- Box::new(ViewportModule),
- Box::new(WindowModule),
- ],
- right_modules: vec![
- Box::new(TrayModule),
- Box::new(CpuModule),
- Box::new(MemoryModule),
- Box::new(BrightnessModule),
- Box::new(VolumeModule),
- Box::new(BatteryModule),
- Box::new(ClockModule),
- ],
+ left_modules,
+ right_modules,
sender,
last_config_modified: std::fs::metadata("/home/lsgalante/.config/cce/config.kdl").ok().and_then(|m| m.modified().ok()),
+ selected_module_name: selected_module.as_ref().map(|(n, _)| n.clone()),
+ selected_module_side: selected_module.as_ref().map(|(_, s)| s.clone()),
};
app.rebuild_layout();
@@ -1015,16 +1094,29 @@ impl cce_ui::engine::Application for StatusApp {
}
fn settings(&self) -> cce_ui::engine::WindowSettings {
+ let app_id = if let (Some(ref name), Some(ref side)) = (&self.selected_module_name, &self.selected_module_side) {
+ format!("cce-status-{:?}-{}", side, name).to_lowercase()
+ } else {
+ "cce-status".to_string()
+ };
cce_ui::engine::WindowSettings {
title: "Status Interface".to_string(),
- app_id: "cce-status".to_string(),
- width: 1920,
- height: read_status_height_from_config() as u32,
+ app_id,
+ width: self.width,
+ height: self.height,
fullscreen: false,
min_size: None,
}
}
+ fn desired_size(&self) -> Option<(u32, u32)> {
+ if self.selected_module_name.is_some() {
+ Some((self.width, self.height))
+ } else {
+ None
+ }
+ }
+
fn update(&mut self, msg: Self::Message, needs_rebuild: &mut bool, _exit: &mut bool) {
match msg {
CustomEvent::ViewportUpdated(t) => {
@@ -1048,6 +1140,7 @@ impl cce_ui::engine::Application for StatusApp {
}
}
CustomEvent::SystemStatsUpdated(s) => {
+ eprintln!("[module-{}] stats updated, current width={}", self.selected_module_name.as_deref().unwrap_or("none"), self.width);
self.stats = Some(s);
}
CustomEvent::TrayUpdated(item) => {
@@ -1120,8 +1213,8 @@ impl cce_ui::engine::Application for StatusApp {
cce_ui::scale::set_scale_factor(scale as f32);
self.rebuild_layout();
}
- // let (sb_x, sb_y, sb_w, sb_h) = self.status_bar.rect();
- // quads.push((sb_x, sb_y, sb_w, sb_h, self.status_bar.color()));
+ let (sb_x, sb_y, sb_w, sb_h) = self.status_bar.rect();
+ quads.push((sb_x, sb_y, sb_w, sb_h, self.status_bar.color()));
for r in &self.rects {
quads.push((r.x, r.y, r.w, r.h, r.color));
}
@@ -1255,12 +1348,12 @@ impl cce_ui::engine::Application for StatusApp {
let id = bound.id.clone();
let tray_source = format!("tray:{}", id);
- // Check if any clear-cloud instance is already running
+ // Check if any cce-cloud instance is already running
let mut running_cloud_pid = None;
if let Some(pid) = self.active_cloud_pid {
if std::path::Path::new(&format!("/proc/{}", pid)).exists() {
if let Ok(comm) = std::fs::read_to_string(format!("/proc/{}/comm", pid)) {
- if comm.trim() == "clear-cloud" {
+ if comm.trim() == "cce-cloud" {
running_cloud_pid = Some(pid);
}
}
@@ -1270,7 +1363,7 @@ impl cce_ui::engine::Application for StatusApp {
if let Some(pid) = running_cloud_pid {
// There is an active dialog open.
// Kill it regardless of which one it is.
- eprintln!("[tray-click] clear-cloud (PID {}) is running, killing it", pid);
+ eprintln!("[tray-click] cce-cloud (PID {}) is running, killing it", pid);
let _ = std::process::Command::new("kill").arg(pid.to_string()).status();
self.active_cloud_pid = None;
@@ -1340,8 +1433,8 @@ impl cce_ui::engine::Application for StatusApp {
if should_show_menu {
if let Some(menu_p) = menu_path {
menu_shown = true;
- if let Err(e) = show_clear_cloud_menu(&conn, destination, menu_p.as_str(), x_pos, y_pos, true, thread_sender.clone(), tray_source_clone.clone()).await {
- eprintln!("[tray-click] show_clear_cloud_menu failed: {:?}", e);
+ if let Err(e) = show_cce_cloud_menu(&conn, destination, menu_p.as_str(), x_pos, y_pos, true, thread_sender.clone(), tray_source_clone.clone()).await {
+ eprintln!("[tray-click] show_cce_cloud_menu failed: {:?}", e);
}
}
} else if btn_code == 272 {
@@ -1349,8 +1442,8 @@ impl cce_ui::engine::Application for StatusApp {
eprintln!("[tray-click] Activate failed: {:?}", e);
if let Some(menu_p) = menu_path {
menu_shown = true;
- if let Err(e) = show_clear_cloud_menu(&conn, destination, menu_p.as_str(), x_pos, y_pos, true, thread_sender.clone(), tray_source_clone.clone()).await {
- eprintln!("[tray-click] Fallback show_clear_cloud_menu failed: {:?}", e);
+ if let Err(e) = show_cce_cloud_menu(&conn, destination, menu_p.as_str(), x_pos, y_pos, true, thread_sender.clone(), tray_source_clone.clone()).await {
+ eprintln!("[tray-click] Fallback show_cce_cloud_menu failed: {:?}", e);
}
}
}
@@ -1392,12 +1485,12 @@ impl cce_ui::engine::Application for StatusApp {
eprintln!("[module-right-click] Right-clicked module: {}", mb.name);
let context_source = format!("context_menu:{}", mb.name);
- // Check if any clear-cloud instance is already running
+ // Check if any cce-cloud instance is already running
let mut running_cloud_pid = None;
if let Some(pid) = self.active_cloud_pid {
if std::path::Path::new(&format!("/proc/{}", pid)).exists() {
if let Ok(comm) = std::fs::read_to_string(format!("/proc/{}/comm", pid)) {
- if comm.trim() == "clear-cloud" {
+ if comm.trim() == "cce-cloud" {
running_cloud_pid = Some(pid);
}
}
@@ -1405,7 +1498,7 @@ impl cce_ui::engine::Application for StatusApp {
}
if let Some(pid) = running_cloud_pid {
- eprintln!("[module-right-click] clear-cloud (PID {}) is running, killing it", pid);
+ eprintln!("[module-right-click] cce-cloud (PID {}) is running, killing it", pid);
let _ = std::process::Command::new("kill").arg(pid.to_string()).status();
self.active_cloud_pid = None;
@@ -1438,7 +1531,7 @@ impl cce_ui::engine::Application for StatusApp {
]
}).to_string();
- if let Ok(mut child) = std::process::Command::new(get_clear_cloud_cmd())
+ if let Ok(mut child) = std::process::Command::new(get_cce_cloud_cmd())
.args([
"--json",
"-x",
@@ -1453,7 +1546,7 @@ impl cce_ui::engine::Application for StatusApp {
{
let pid = child.id();
self.active_cloud_pid = Some(pid);
- eprintln!("[module-right-click] Spawned clear-cloud with PID {}", pid);
+ eprintln!("[module-right-click] Spawned cce-cloud with PID {}", pid);
let thread_sender = self.sender.clone();
let context_source_clone = context_source.clone();
@@ -1465,7 +1558,7 @@ impl cce_ui::engine::Application for StatusApp {
if let Ok(output) = child.wait_with_output() {
let err_str = String::from_utf8_lossy(&output.stderr);
if !err_str.is_empty() {
- eprintln!("[clear-cloud context stderr] {}", err_str);
+ eprintln!("[cce-cloud context stderr] {}", err_str);
}
}
let _ = thread_sender.send(CustomEvent::CloudClosed { pid, source: context_source_clone });
@@ -1492,12 +1585,12 @@ impl cce_ui::engine::Application for StatusApp {
eprintln!("[layout-click] Layout mode clicked!");
let layout_source = "layout".to_string();
- // Check if any clear-cloud instance is already running
+ // Check if any cce-cloud instance is already running
let mut running_cloud_pid = None;
if let Some(pid) = self.active_cloud_pid {
if std::path::Path::new(&format!("/proc/{}", pid)).exists() {
if let Ok(comm) = std::fs::read_to_string(format!("/proc/{}/comm", pid)) {
- if comm.trim() == "clear-cloud" {
+ if comm.trim() == "cce-cloud" {
running_cloud_pid = Some(pid);
}
}
@@ -1507,7 +1600,7 @@ impl cce_ui::engine::Application for StatusApp {
if let Some(pid) = running_cloud_pid {
// There is an active dialog open.
// Kill it regardless of which one it is.
- eprintln!("[layout-click] clear-cloud (PID {}) is running, killing it", pid);
+ eprintln!("[layout-click] cce-cloud (PID {}) is running, killing it", pid);
let _ = std::process::Command::new("kill").arg(pid.to_string()).status();
self.active_cloud_pid = None;
@@ -1549,7 +1642,7 @@ impl cce_ui::engine::Application for StatusApp {
]
}).to_string();
- if let Ok(mut child) = std::process::Command::new(get_clear_cloud_cmd())
+ if let Ok(mut child) = std::process::Command::new(get_cce_cloud_cmd())
.args([
"--json",
"-x",
@@ -1564,7 +1657,7 @@ impl cce_ui::engine::Application for StatusApp {
{
let pid = child.id();
self.active_cloud_pid = Some(pid);
- eprintln!("[layout-click] Spawned clear-cloud with PID {}", pid);
+ eprintln!("[layout-click] Spawned cce-cloud with PID {}", pid);
let active_viewport = get_active_viewport_from_camera(&self.viewport);
let thread_sender = self.sender.clone();
@@ -1577,7 +1670,7 @@ impl cce_ui::engine::Application for StatusApp {
if let Ok(output) = child.wait_with_output() {
let err_str = String::from_utf8_lossy(&output.stderr);
if !err_str.is_empty() {
- eprintln!("[clear-cloud stderr] {}", err_str);
+ eprintln!("[cce-cloud stderr] {}", err_str);
}
if output.status.success() {
let out_str = String::from_utf8_lossy(&output.stdout);
@@ -1766,6 +1859,31 @@ async fn read_volume() -> Option<(String, bool)> {
}
}
+fn get_initial_stats() -> SystemStats {
+ let clock = chrono::Local::now().format("%A, %B %d, %Y %I:%M %p").to_string();
+ let memory = read_memory_usage().unwrap_or_else(|| "Mem N/A".to_string());
+
+ let (battery_str, battery_capacity, battery_charging) = if let Some((s, cap, chg)) = read_battery_details() {
+ (s, cap, chg)
+ } else {
+ ("".to_string(), 0, false)
+ };
+ let (volume, volume_muted) = pollster::block_on(read_volume()).unwrap_or_else(|| ("".to_string(), false));
+ let brightness = read_brightness().unwrap_or_default();
+
+ SystemStats {
+ clock,
+ memory,
+ cpu: "Cpu 0.0%".to_string(),
+ battery: battery_str,
+ battery_capacity,
+ battery_charging,
+ volume,
+ volume_muted,
+ brightness,
+ }
+}
+
async fn spawn_system_stats(sender: calloop::channel::Sender<CustomEvent>) {
eprintln!("[spawn_system_stats] Starting system stats loop!");
let mut last_cpu = read_cpu_ticks().unwrap_or((0, 0));
@@ -1976,14 +2094,14 @@ fn parse_menu_item(
})
}
-fn get_clear_cloud_cmd() -> String {
+fn get_cce_cloud_cmd() -> String {
if let Ok(home) = std::env::var("HOME") {
- let path = format!("{}/.local/bin/clear-cloud", home);
+ let path = format!("{}/.local/bin/cce-cloud", home);
if std::path::Path::new(&path).exists() {
return path;
}
}
- "clear-cloud".to_string()
+ "cce-cloud".to_string()
}
fn get_currently_focused_window() -> Option<String> {
@@ -2028,7 +2146,7 @@ fn get_currently_focused_window() -> Option<String> {
None
}
-async fn show_clear_cloud_menu(
+async fn show_cce_cloud_menu(
conn: &zbus::Connection,
destination: &str,
menu_path: &str,
@@ -2224,7 +2342,7 @@ async fn show_clear_cloud_menu(
cmd_args.push("--align-right".to_string());
}
- let mut child = std::process::Command::new(get_clear_cloud_cmd())
+ let mut child = std::process::Command::new(get_cce_cloud_cmd())
.args(&cmd_args)
.stdin(std::process::Stdio::piped())
.stdout(std::process::Stdio::piped())
@@ -2731,6 +2849,7 @@ fn main() {
.init();
let args: Vec<String> = std::env::args().collect();
+ eprintln!("cce-status-interface started with args = {:?}", args);
if args.len() > 1 && args[1] == "--trigger-switcher" {
let rt = tokio::runtime::Runtime::new().expect("tokio runtime");
rt.block_on(async {
@@ -2744,6 +2863,106 @@ fn main() {
return;
}
+ let mut has_module = false;
+ let mut monolithic = false;
+ for i in 0..args.len() {
+ if args[i] == "--module" {
+ has_module = true;
+ }
+ if args[i] == "--monolithic" {
+ monolithic = true;
+ }
+ }
+
+ if !has_module && !monolithic {
+ log::info!("Starting cce-status-interface launcher daemon...");
+ let rt = tokio::runtime::Runtime::new().expect("tokio runtime");
+ rt.block_on(async {
+ use tokio::signal::unix::{signal, SignalKind};
+ let mut sigint = signal(SignalKind::interrupt()).expect("SIGINT");
+ let mut sigterm = signal(SignalKind::terminate()).expect("SIGTERM");
+
+ let modules = vec![
+ "viewport", "window", "tray", "cpu", "memory", "brightness",
+ "volume", "battery", "clock"
+ ];
+ let current_exe = std::env::current_exe().unwrap_or_else(|_| std::path::PathBuf::from("/home/lsgalante/.local/bin/cce-status-interface"));
+
+ let mut active_children: std::collections::HashMap<String, std::process::Child> = std::collections::HashMap::new();
+
+ for module in &modules {
+ let child = std::process::Command::new(¤t_exe)
+ .arg("--module")
+ .arg(module)
+ .spawn();
+ match child {
+ Ok(c) => {
+ log::info!("Spawned module process for: {}", module);
+ active_children.insert(module.to_string(), c);
+ }
+ Err(e) => {
+ log::error!("Failed to spawn module process for {}: {:?}", module, e);
+ }
+ }
+ }
+
+ loop {
+ tokio::select! {
+ _ = sigint.recv() => {
+ log::info!("Received SIGINT, shutting down...");
+ break;
+ }
+ _ = sigterm.recv() => {
+ log::info!("Received SIGTERM, shutting down...");
+ break;
+ }
+ _ = tokio::time::sleep(std::time::Duration::from_millis(500)) => {
+ for module in &modules {
+ let mut restart = false;
+ if let Some(child) = active_children.get_mut(*module) {
+ match child.try_wait() {
+ Ok(Some(status)) => {
+ log::warn!("Module process '{}' exited with status: {:?}. Restarting...", module, status);
+ restart = true;
+ }
+ Ok(None) => {}
+ Err(e) => {
+ log::error!("Error checking status for module '{}': {:?}. Restarting...", module, e);
+ restart = true;
+ }
+ }
+ } else {
+ restart = true;
+ }
+
+ if restart {
+ let child = std::process::Command::new(¤t_exe)
+ .arg("--module")
+ .arg(module)
+ .spawn();
+ match child {
+ Ok(c) => {
+ log::info!("Restarted module process for: {}", module);
+ active_children.insert(module.to_string(), c);
+ }
+ Err(e) => {
+ log::error!("Failed to restart module process for {}: {:?}", module, e);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ for (module, mut child) in active_children {
+ log::info!("Killing module process: {}", module);
+ let _ = child.kill();
+ }
+ });
+ return;
+ }
+
let rt = tokio::runtime::Runtime::new().expect("tokio runtime");
let _guard = rt.enter();
@@ -2888,19 +3107,20 @@ fn parse_font_for_alias(content: &str, alias: &str) -> Option<String> {
fn read_bg_color_from_config() -> Option<[f32; 4]> {
let content = std::fs::read_to_string("/home/lsgalante/.config/cce/config.kdl").ok()?;
- parse_color_from_key(&content, "desktop_background_color")
+ parse_color_from_key(&content, "background_color")
.or_else(|| parse_color_from_key(&content, "low_color"))
- .or_else(|| parse_color_from_key(&content, "background_color"))
+ .or_else(|| parse_color_from_key(&content, "desktop_gap_color"))
}
fn parse_color_from_key(content: &str, key: &str) -> Option<[f32; 4]> {
let val = parse_json(content);
if let Some(s) = json_find_key(&val, key).and_then(|v| v.as_str()) {
- if let Some(rgb) = parse_hex(s) {
- let r = (rgb[0] as f32 / 255.0).powf(2.2);
- let g = (rgb[1] as f32 / 255.0).powf(2.2);
- let b = (rgb[2] as f32 / 255.0).powf(2.2);
- return Some([r, g, b, 1.0]);
+ if let Some(rgba) = parse_hex_rgba(s) {
+ let r = (rgba[0] as f32 / 255.0).powf(2.2);
+ let g = (rgba[1] as f32 / 255.0).powf(2.2);
+ let b = (rgba[2] as f32 / 255.0).powf(2.2);
+ let a = rgba[3] as f32 / 255.0;
+ return Some([r, g, b, a]);
}
}
None
diff --git a/src/modules.rs b/src/modules.rs
index 36e495c..129d3b9 100644
--- a/src/modules.rs
+++ b/src/modules.rs
@@ -273,12 +273,13 @@ impl StatusModule for ClockModule {
_tray_items: &HashMap<String, TrayItem>,
padding: f32,
) -> f32 {
- if let Some(ref s) = stats {
- let label = Label::new_with_family(font_system, &s.clock, font_size, [0.0, 0.0, 0.0, 1.0], font_family);
- label.w + 2.0 * padding
+ let text = if let Some(ref s) = stats {
+ &s.clock
} else {
- 0.0
- }
+ "Monday, January 01, 2000 00:00 AM"
+ };
+ let label = Label::new_with_family(font_system, text, font_size, [0.0, 0.0, 0.0, 1.0], font_family);
+ label.w + 2.0 * padding
}
fn render(
@@ -331,16 +332,13 @@ impl StatusModule for BatteryModule {
_tray_items: &HashMap<String, TrayItem>,
padding: f32,
) -> f32 {
- if let Some(ref s) = stats {
- if !s.battery.is_empty() {
- let label = Label::new_with_family(font_system, &s.battery, font_size, [0.0, 0.0, 0.0, 1.0], font_family);
- label.w + 2.0 * padding
- } else {
- 0.0
- }
+ let text = if let Some(ref s) = stats {
+ if !s.battery.is_empty() { &s.battery } else { "Bat 100%" }
} else {
- 0.0
- }
+ "Bat 100%"
+ };
+ let label = Label::new_with_family(font_system, text, font_size, [0.0, 0.0, 0.0, 1.0], font_family);
+ label.w + 2.0 * padding
}
fn render(
@@ -400,16 +398,13 @@ impl StatusModule for VolumeModule {
_tray_items: &HashMap<String, TrayItem>,
padding: f32,
) -> f32 {
- if let Some(ref s) = stats {
- if !s.volume.is_empty() {
- let label = Label::new_with_family(font_system, &s.volume, font_size, [0.0, 0.0, 0.0, 1.0], font_family);
- label.w + 2.0 * padding
- } else {
- 0.0
- }
+ let text = if let Some(ref s) = stats {
+ if !s.volume.is_empty() { &s.volume } else { "Vol 100%" }
} else {
- 0.0
- }
+ "Vol 100%"
+ };
+ let label = Label::new_with_family(font_system, text, font_size, [0.0, 0.0, 0.0, 1.0], font_family);
+ label.w + 2.0 * padding
}
fn render(
@@ -482,16 +477,13 @@ impl StatusModule for BrightnessModule {
_tray_items: &HashMap<String, TrayItem>,
padding: f32,
) -> f32 {
- if let Some(ref s) = stats {
- if !s.brightness.is_empty() {
- let label = Label::new_with_family(font_system, &s.brightness, font_size, [0.0, 0.0, 0.0, 1.0], font_family);
- label.w + 2.0 * padding
- } else {
- 0.0
- }
+ let text = if let Some(ref s) = stats {
+ if !s.brightness.is_empty() { &s.brightness } else { "Bri 100%" }
} else {
- 0.0
- }
+ "Bri 100%"
+ };
+ let label = Label::new_with_family(font_system, text, font_size, [0.0, 0.0, 0.0, 1.0], font_family);
+ label.w + 2.0 * padding
}
fn render(
@@ -546,12 +538,13 @@ impl StatusModule for MemoryModule {
_tray_items: &HashMap<String, TrayItem>,
padding: f32,
) -> f32 {
- if let Some(ref s) = stats {
- let label = Label::new_with_family(font_system, &s.memory, font_size, [0.0, 0.0, 0.0, 1.0], font_family);
- label.w + 2.0 * padding
+ let text = if let Some(ref s) = stats {
+ if !s.memory.is_empty() { &s.memory } else { "Mem 0.0/0.0G" }
} else {
- 0.0
- }
+ "Mem 0.0/0.0G"
+ };
+ let label = Label::new_with_family(font_system, text, font_size, [0.0, 0.0, 0.0, 1.0], font_family);
+ label.w + 2.0 * padding
}
fn render(
@@ -604,12 +597,13 @@ impl StatusModule for CpuModule {
_tray_items: &HashMap<String, TrayItem>,
padding: f32,
) -> f32 {
- if let Some(ref s) = stats {
- let label = Label::new_with_family(font_system, &s.cpu, font_size, [0.0, 0.0, 0.0, 1.0], font_family);
- label.w + 2.0 * padding
+ let text = if let Some(ref s) = stats {
+ if !s.cpu.is_empty() { &s.cpu } else { "Cpu 0.0%" }
} else {
- 0.0
- }
+ "Cpu 0.0%"
+ };
+ let label = Label::new_with_family(font_system, text, font_size, [0.0, 0.0, 0.0, 1.0], font_family);
+ label.w + 2.0 * padding
}
fn render(