Add: LeftWM workspace widget

- Show tags in workspace "0"
- Callback to switch tag on click
This commit is contained in:
Candifloss 2025-11-24 19:08:44 +05:30
parent c284df88a1
commit b679952b88
3 changed files with 83 additions and 22 deletions

View File

@ -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<Tag>,
}
#[derive(Deserialize)]
struct LeftState {
window_title: String,
workspaces: Vec<Workspace>,
}
// 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::<LeftState>(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<SharedString> = 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();
});
}

View File

@ -2,6 +2,7 @@ export component SquareIconWidget {
// Basic parameters shared by all square icon widgets
in property <int> bar_height;
in property <string> icon_text;
in property <int> icon_font_size;
in property <string> 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;
}

View File

@ -12,6 +12,9 @@ export component TopBar inherits Window {
// Right side
in property<string> current_window_title;
in property<int> tag_icon_font_size: bar_height;
in property <[string]> tag_icons;
callback go_to_tag(int);
// Time widget
in-out property <string> 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 {