Add: Volume widget

- Use `amixer` command
- Comment out bg color lines to use default vals
This commit is contained in:
Candifloss 2025-11-23 00:45:00 +05:30
parent 3dca171063
commit fa206d7f42
4 changed files with 127 additions and 8 deletions

View File

@ -12,7 +12,6 @@ dirs = "6.0.0"
i-slint-backend-winit = { version = "1.14.1", features = ["x11"] }
serde = { version = "1.0.228", features = ["derive"] }
slint = { version = "1.14.1", features = ["backend-winit"] }
tokio = { version = "1.48.0", features = ["macros", "rt-multi-thread", "time"] }
toml = "0.9.8"
[build-dependencies]

View File

@ -2,6 +2,7 @@ mod batterywidget;
mod common;
mod datewidget;
mod timewidget;
mod volumewidget;
use crate::TopBar;
@ -9,6 +10,7 @@ pub fn install_callbacks(ui: &TopBar) {
timewidget::install(ui);
datewidget::install(ui);
batterywidget::install(ui);
volumewidget::install(ui);
// In the future:
// networkwidget::install(ui);

View File

@ -0,0 +1,97 @@
use super::common::run_cmd;
use crate::TopBar;
use slint::{ComponentHandle, SharedString, Timer, TimerMode};
use std::process::Command;
use std::time::Duration;
// Run `amixer get Master` and return stdout as String
fn read_amixer() -> String {
String::from_utf8(
Command::new("amixer")
.args(["get", "Master"])
.output()
.unwrap()
.stdout,
)
.unwrap_or_default()
}
// Parse volume percentage (0100)
fn parse_volume(amixer_out: &str) -> u8 {
// Looks for "[42%]" pattern
for token in amixer_out.split_whitespace() {
if let Some(stripped) = token.strip_prefix('[').and_then(|t| t.strip_suffix("%]"))
&& let Ok(val) = stripped.parse::<u8>()
{
return val;
}
}
0
}
// Detect mute: `[off]` means muted
fn is_muted(amixer_out: &str) -> bool {
amixer_out.contains("[off]")
}
// Select simple volume icon from level
fn select_icon(vol: u8, muted: bool) -> SharedString {
let icon = if muted {
"" // mute
} else if vol == 0 {
"" // empty
} else if vol <= 30 {
"" // low
} else if vol <= 70 {
"" // medium
} else {
"" // high
};
SharedString::from(icon)
}
// Build tooltip text
fn make_tooltip(vol: u8, muted: bool) -> SharedString {
if muted {
SharedString::from(format!("Volume: {vol}%"))
} else {
SharedString::from("Volume: {vol}%")
}
}
// Timer loop: update volume every second
fn start_volume_updater(ui: &TopBar) {
let weak = ui.as_weak();
let timer = Box::leak(Box::new(Timer::default()));
timer.start(TimerMode::Repeated, Duration::from_secs(1), move || {
if let Some(ui) = weak.upgrade() {
let out = read_amixer();
//println!("{out}");
let vol = parse_volume(&out);
let muted = is_muted(&out);
// Compute values
let icon = select_icon(vol, muted);
let tooltip = make_tooltip(vol, muted);
// Push to UI
ui.set_volume_icon(icon);
ui.set_volume_tooltip(tooltip);
}
});
}
// Public entry: connect callback + start updater
pub fn install(ui: &TopBar) {
let weak = ui.as_weak();
ui.on_show_volume(move || {
if weak.upgrade().is_some() {
run_cmd("xclock"); // placeholder
}
});
start_volume_updater(ui);
}

View File

@ -21,13 +21,22 @@ export component TopBar inherits Window {
// Battery widget
in property <string> battery_tooltip;
in property <string> battery_icon;
in property <color> battery_icon_color: #ffffff;
in property <color> battery_bg_normal: #8e9162;
in property <color> battery_bg_hover: #6d8a4d;
in property <color> battery_bg_clicked: #4a5f70;
in property <color> battery_icon_color;
/*in property <color> battery_bg_normal; // Enable later. Use defaults now.
in property <color> battery_bg_hover;
in property <color> battery_bg_clicked;*/
callback show_battery();
// Volume widget
in property <string> volume_tooltip;
in property <string> volume_icon;
/*in property <color> volume_icon_color;
in property <color> volume_bg_normal;
in property <color> volume_bg_hover;
in property <color> volume_bg_clicked;*/
callback show_volume();
title: "chocobar";
width: bar_width *1px;
height: bar_height *1px;
@ -72,15 +81,27 @@ export component TopBar inherits Window {
HorizontalLayout {
alignment: end; // Right-align
// SquareIconWidget - Volume
SquareIconWidget {
bar_height: root.bar_height;
icon_text: root.volume_icon;
tooltip_text: root.volume_tooltip;
/*icon_color: root.volume_icon_color;
bg_normal: root.volume_bg_normal;
bg_hover: root.volume_bg_hover;
bg_clicked: root.volume_bg_clicked;*/
square_btn_callback => root.show_volume();
}
// SquareIconWidget - Battery
SquareIconWidget {
bar_height: root.bar_height;
icon_text: root.battery_icon;
tooltip_text: root.battery_tooltip;
icon_color: root.battery_icon_color;
bg_normal: root.battery_bg_normal;
/*bg_normal: root.battery_bg_normal;
bg_hover: root.battery_bg_hover;
bg_clicked: root.battery_bg_clicked;
bg_clicked: root.battery_bg_clicked;*/
square_btn_callback => root.show_battery();
}