Add BatteryWidget

- Working battery icon
- Read from `sys` files
- Calculate values in Rust and pass it to Slint
This commit is contained in:
Candifloss 2025-11-20 22:44:56 +05:30
parent 48a116c547
commit b0eaa42063
4 changed files with 162 additions and 1 deletions

View File

@ -0,0 +1,107 @@
use super::common::run_cmd;
use crate::TopBar;
use slint::{ComponentHandle, SharedString, Timer, TimerMode};
use std::{fs, time::Duration};
// Read and trim a file
fn read_sys(path: &str) -> String {
fs::read_to_string(path)
.unwrap_or_default()
.trim()
.to_string()
}
// Convert battery state strings into normalized tokens
fn normalize_status(s: &str) -> String {
s.to_lowercase()
.chars()
.filter(char::is_ascii_alphabetic)
.collect()
}
// Map numeric capacity → level tags
fn capacity_level(cap: u8) -> String {
match cap {
0..=10 => "critical".into(),
11..=25 => "low".into(),
26..=75 => "normal".into(),
76..=95 => "high".into(),
_ => "full".into(),
}
}
// Choose icon from status + level
fn select_icon(status: &str, level: &str) -> SharedString {
let icon = match status {
"charging" => match level {
"critical" => "󰢜",
"low" => "󰂇",
"normal" => "󰢝",
"high" => "󰂋",
"full" => "󰂄",
_ => "󰂑",
},
"discharging" => match level {
"critical" => "󰂎",
"low" => "󰁻",
"normal" => "󰁿",
"high" => "󰂂",
"full" => "󰁹",
_ => "󰂑",
},
"notcharging" => "󰂃",
"full" => "󰂄",
_ => "󰂑", // unknown fallback
};
SharedString::from(icon)
}
// Main battery updater logic
fn start_battery_updater(ui: &TopBar) {
let weak = ui.as_weak();
// Timer fires every second (battery can change quickly)
let timer = Box::leak(Box::new(Timer::default()));
timer.start(TimerMode::Repeated, Duration::from_secs(1), move || {
if let Some(ui) = weak.upgrade() {
// Read sysfs values
let cap_str = read_sys("/sys/class/power_supply/BAT0/capacity");
let status_str = read_sys("/sys/class/power_supply/BAT0/status");
let level_str = read_sys("/sys/class/power_supply/BAT0/capacity_level");
// Normalize values
let status = normalize_status(&status_str);
let cap: u8 = cap_str.parse().unwrap_or(0);
let level = normalize_status(&level_str); // low, normal, full, etc.
let level_calc = capacity_level(cap); // fallback level
// Prefer kernel level if valid; otherwise use cap-based level
let final_level = if level.is_empty() { level_calc } else { level };
// Select icon
let icon = select_icon(&status, &final_level);
// Update Slint properties
ui.set_battery_status(status.into());
ui.set_battery_capacity(cap.into());
ui.set_battery_capacity_level(final_level.into());
ui.set_battery_icon(icon);
}
});
}
// Public entry: connect click callback + start updater
pub fn install(ui: &TopBar) {
let weak = ui.as_weak();
// Click → open battery monitor
ui.on_show_battery(move || {
if weak.upgrade().is_some() {
run_cmd("xclock"); // Fix later
}
});
start_battery_updater(ui);
}

View File

@ -1,3 +1,4 @@
mod batterywidget;
mod common;
mod datewidget;
mod timewidget;
@ -7,8 +8,8 @@ use crate::TopBar;
pub fn install_callbacks(ui: &TopBar) {
timewidget::install(ui);
datewidget::install(ui);
batterywidget::install(ui);
// In the future:
// networkwidget::install(ui);
// batterywidget::install(ui);
}

35
ui/battery-widget.slint Normal file
View File

@ -0,0 +1,35 @@
export component BatteryWidget {
in property <int> bar_height;
in property <string> battery_status; // charging / discharging / notcharging / unknown
in property <int> battery_capacity; // numeric percentage
in property <string> battery_capacity_level; // critical / low / normal / high / full
in property <string> battery_icon;
callback show_battery();
height: bar_height * 1px;
width: bar_height * 1px;
Rectangle {
background: touch_area.pressed ? #4a5f70
: touch_area.has-hover ? #6d8a4d
: #8e9162;
border-radius: 3px;
HorizontalLayout {
padding-left: 3px;
padding-right: 3px;
Text {
text: battery_icon;
color: white;
vertical-alignment: center;
horizontal-alignment: center;
}
}
touch_area := TouchArea {
clicked => { show_battery(); }
}
}
}

View File

@ -1,5 +1,6 @@
import { TimeWidget } from "time-widget.slint";
import { DateWidget } from "date-widget.slint";
import { BatteryWidget } from "battery-widget.slint";
export component TopBar inherits Window {
@ -17,6 +18,14 @@ export component TopBar inherits Window {
in-out property <string> date_text;
callback show_calendar();
// Battery widget
in property <string> battery_status;
in property <int> battery_capacity;
in property <string> battery_capacity_level;
in property <string> battery_icon;
callback show_battery();
title: "chocobar";
width: bar_width *1px;
height: bar_height *1px;
@ -61,6 +70,15 @@ export component TopBar inherits Window {
HorizontalLayout {
alignment: end; // Right-align
BatteryWidget {
bar_height: root.bar_height;
battery_status: root.battery_status;
battery_capacity: root.battery_capacity;
battery_capacity_level: root.battery_capacity_level;
battery_icon: root.battery_icon;
show_battery => root.show_battery();
}
DateWidget {
// Get from root
date_text: root.date_text;