diff --git a/src/widgets/leftwmdata.rs b/src/widgets/leftwmdata.rs index 36fce64..8c948e3 100644 --- a/src/widgets/leftwmdata.rs +++ b/src/widgets/leftwmdata.rs @@ -1,17 +1,30 @@ use crate::TopBar; -use slint::{ComponentHandle, Timer, TimerMode, SharedString}; +use serde::Deserialize; +use slint::{ComponentHandle, SharedString, Timer, TimerMode, VecModel}; use std::process::Command; use std::time::Duration; -use serde::Deserialize; -// Minimal JSON structure +// Minimal JSON structures extracted from leftwm-state -q +#[derive(Deserialize)] +struct Tag { + index: usize, + mine: bool, + busy: bool, +} + +#[derive(Deserialize)] +struct Workspace { + tags: Vec, +} + #[derive(Deserialize)] struct LeftState { window_title: String, + workspaces: Vec, } -// Run leftwm-state and return JSON output (or empty string) -fn get_leftwm_state() -> String { +// Helpers +fn run_leftwm_state() -> String { Command::new("leftwm-state") .arg("-q") .output() @@ -19,31 +32,64 @@ fn get_leftwm_state() -> String { .unwrap_or_default() } -// Parse only the window title -fn parse_window_title(json: &str) -> String { - serde_json::from_str::(json) - .map(|v| v.window_title) - .unwrap_or_default() +// Pick icon based on tag state +fn icon_for_tag(tag: &Tag) -> &'static str { + if tag.mine { + "" + } else if tag.busy { + "" + } else { + "" + } } -// Main updater loop +// Main polling loop fn start_leftwm_updater(ui: &TopBar) { let weak = ui.as_weak(); let timer = Box::leak(Box::new(Timer::default())); - - // Poll every ~300 ms (fast enough for responsiveness) timer.start(TimerMode::Repeated, Duration::from_millis(300), move || { - if let Some(ui) = weak.upgrade() { - let state = get_leftwm_state(); - let title = parse_window_title(&state); - // Send to UI - ui.set_current_window_title(SharedString::from(title)); - } + let Some(ui) = weak.upgrade() else { return }; + + // Run leftwm-state -q once + let json = run_leftwm_state(); + + // Parse JSON + let parsed: LeftState = match serde_json::from_str(&json) { + Ok(v) => v, + Err(_) => return, + }; + + // Update window title + ui.set_current_window_title(SharedString::from(parsed.window_title)); + + // Extract current workspace (always index 0) + let Some(ws) = parsed.workspaces.get(0) else { return }; + + // Build vector of icons + let icons: Vec = ws + .tags + .iter() + .map(|t| SharedString::from(icon_for_tag(t))) + .collect(); + + // Push array to Slint as VecModel + let model = std::rc::Rc::new(VecModel::from(icons)); + ui.set_tag_icons(model.into()); }); } -// Public entry +// Public entry point pub fn install(ui: &TopBar) { + // Install the polling loop start_leftwm_updater(ui); + + // Tag switching callback + ui.on_go_to_tag(|i| { + // Command: `leftwm-command SendWorkspaceToTag workspace tag_number` + // Assuming workspace 0 (first monitor) + let _ = Command::new("leftwm-command") + .arg(format!("SendWorkspaceToTag 0 {i}")) + .spawn(); + }); } diff --git a/ui/square-icon-widget.slint b/ui/square-icon-widget.slint index b247e79..20d4f7b 100644 --- a/ui/square-icon-widget.slint +++ b/ui/square-icon-widget.slint @@ -2,6 +2,7 @@ export component SquareIconWidget { // Basic parameters shared by all square icon widgets in property bar_height; in property icon_text; + in property icon_font_size; in property tooltip_text; // Colors @@ -34,6 +35,7 @@ export component SquareIconWidget { Text { text: icon_text; // The icon color: icon_color; + font-size: icon_font_size *1px; vertical-alignment: center; horizontal-alignment: center; } diff --git a/ui/topbar.slint b/ui/topbar.slint index ae9db93..98c1255 100644 --- a/ui/topbar.slint +++ b/ui/topbar.slint @@ -12,6 +12,9 @@ export component TopBar inherits Window { // Right side in property current_window_title; + in property tag_icon_font_size: bar_height; + in property <[string]> tag_icons; + callback go_to_tag(int); // Time widget in-out property time_text; @@ -114,7 +117,7 @@ export component TopBar inherits Window { // Left side Rectangle { width: parent.width/3; - //background: #46adbb79; + background: #46adbb79; HorizontalLayout { alignment: start; // Left-align padding-left: 4px; @@ -133,11 +136,21 @@ export component TopBar inherits Window { // Middle Rectangle { background: #5d8b3c79; + width: parent.width/3; + HorizontalLayout { + alignment: center; // Right-align + for ico[i] in root.tag_icons: SquareIconWidget { + icon_text: ico; + bar_height: root.bar_height; + icon_font_size: root.tag_icon_font_size; + square_btn_callback => root.go_to_tag(i); + } + } } // Right side Rectangle { - //background: #c2779a7a; + background: #c2779a7a; width: parent.width/3; HorizontalLayout {