lots of basic widgets from text

This commit is contained in:
Chris Cochrun 2025-05-12 20:32:19 -05:00
parent 146148a7cd
commit ae1c1b82af

View file

@ -1,14 +1,17 @@
use std::cmp; use std::cmp;
use std::fs::read_to_string;
use chrono::prelude::*; use chrono::prelude::*;
use hyprland::data::Workspaces; use hyprland::data::{Client, Workspace, Workspaces};
use hyprland::event_listener; use hyprland::event_listener::{self, AsyncEventListener};
use hyprland::shared::{HyprData, WorkspaceType}; use hyprland::shared::{HyprData, HyprDataActiveOptional, HyprDataVec};
use iced::font::Weight; use iced::font::Weight;
use iced::widget::{container, horizontal_space, row, text, Text}; use iced::futures::SinkExt;
use iced::stream;
use iced::widget::{container, horizontal_space, row, text};
use iced::{ use iced::{
time, Background, Border, Color, Element, Event, Font, Length, Shadow, Subscription, Task, time, Background, Border, Color, Element, Event, Font, Length, Shadow, Subscription, Task,
Theme, Vector, Vector,
}; };
use iced_layershell::build_pattern::{application, MainSettings}; use iced_layershell::build_pattern::{application, MainSettings};
@ -16,6 +19,7 @@ use iced_layershell::reexport::{Anchor, KeyboardInteractivity};
use iced_layershell::settings::LayerShellSettings; use iced_layershell::settings::LayerShellSettings;
use iced_layershell::to_layer_message; use iced_layershell::to_layer_message;
use iced_runtime::futures::event; use iced_runtime::futures::event;
use miette::{IntoDiagnostic, Result};
use sysinfo::{Disks, System}; use sysinfo::{Disks, System};
use tracing::level_filters::LevelFilter; use tracing::level_filters::LevelFilter;
use tracing::{debug, error}; use tracing::{debug, error};
@ -57,9 +61,6 @@ fn main() -> iced_layershell::Result {
application(Panel::namespace, Panel::update, Panel::view) application(Panel::namespace, Panel::update, Panel::view)
.subscription(Panel::subscription) .subscription(Panel::subscription)
.subscription(|_| {
time::every(time::Duration::from_millis(1000)).map(|_| Message::Time(Local::now()))
})
.settings(settings) .settings(settings)
.style(Panel::style) .style(Panel::style)
.run_with(Panel::new) .run_with(Panel::new)
@ -70,12 +71,13 @@ struct Panel {
time: String, time: String,
cpu: String, cpu: String,
memory: String, memory: String,
battery: String, battery: Option<Battery>,
disk: String, disk: String,
system: System, system: System,
disks: Disks, disks: Disks,
workspaces: Workspaces, workspaces: Vec<Workspace>,
active_workspace: i32, active_workspace: i32,
active_window: String,
apps: Vec<String>, apps: Vec<String>,
} }
@ -85,7 +87,9 @@ enum Message {
Launch(usize), Launch(usize),
Time(DateTime<Local>), Time(DateTime<Local>),
IcedEvent(Event), IcedEvent(Event),
WorkspaceChange(WorkspaceType), WorkspaceChange(Vec<Workspace>),
ActiveWorkspaceChange(event_listener::WorkspaceEventData),
ActiveWindowChange(String),
} }
impl Panel { impl Panel {
@ -94,18 +98,27 @@ impl Panel {
error!("Couldn't get hyprland info: {}", e); error!("Couldn't get hyprland info: {}", e);
e e
}); });
let active_window = Client::get_active().unwrap().unwrap().title;
let battery = match Battery::get() {
Ok(b) => Some(b),
Err(e) => {
error!("{e}");
None
}
};
( (
Self { Self {
system: System::new_all(), system: System::new_all(),
disks: Disks::new_with_refreshed_list(), disks: Disks::new_with_refreshed_list(),
workspaces: workspaces.unwrap(), workspaces: workspaces.unwrap().to_vec(),
active_workspace: 1, active_workspace: 1,
time: String::new(), time: String::new(),
cpu: String::new(), cpu: String::new(),
memory: String::new(), memory: String::new(),
battery: String::new(),
disk: String::new(), disk: String::new(),
apps: vec![String::new()], apps: vec![String::new()],
active_window,
battery,
}, },
Task::none(), Task::none(),
) )
@ -129,6 +142,13 @@ impl Panel {
convert((disk.total_space() - disk.available_space()) as f64) convert((disk.total_space() - disk.available_space()) as f64)
); );
}; };
self.battery = match Battery::get() {
Ok(b) => Some(b),
Err(e) => {
error!("{e}");
None
}
};
// let used_space = 393; // let used_space = 393;
// self.disk = format!("󰋊 {}", used_space); // self.disk = format!("󰋊 {}", used_space);
self.cpu = format!("{}%", self.system.global_cpu_usage().round()); self.cpu = format!("{}%", self.system.global_cpu_usage().round());
@ -149,26 +169,113 @@ impl Panel {
Message::IcedEvent(event) => { Message::IcedEvent(event) => {
debug!(?event) debug!(?event)
} }
Message::WorkspaceChange(workspaces) => {
// debug!(?workspaces);
self.workspaces = workspaces;
}
Message::ActiveWorkspaceChange(data) => {
// debug!(?data);
self.active_workspace = data.id;
}
Message::ActiveWindowChange(w) => {
self.active_window = w;
}
_ => unreachable!(), _ => unreachable!(),
Message::WorkspaceChange(workspace_type) => todo!(),
} }
Task::none() Task::none()
} }
fn subscription(&self) -> Subscription<Message> { fn subscription(&self) -> Subscription<Message> {
event::listen().map(Message::IcedEvent) let iced_events = event::listen().map(Message::IcedEvent);
let timed_events =
time::every(time::Duration::from_millis(1000)).map(|_| Message::Time(Local::now()));
let hyprland_events = self.hyprland_subscription();
Subscription::batch([iced_events, timed_events, hyprland_events])
} }
// fn hyprland_subscription(&self) -> Subscription<Message> { fn hyprland_subscription(&self) -> Subscription<Message> {
// let mut event_listener = event_listener::EventListener::new(); Subscription::run(|| {
// event_listener.add_workspace_changed_handler(|x| Message::WorkspaceChange(x)); stream::channel(1, |mut sender| async move {
// Subscription::run(|| event_listener.start_listener()) let mut workspaces = Workspaces::get_async()
// } .await
.map_err(|e| {
error!("Couldn't get hyprland info: {}", e);
e
})
.unwrap()
.to_vec();
workspaces.sort_by(|a, b| a.id.cmp(&b.id));
sender
.send(Message::WorkspaceChange(workspaces))
.await
.unwrap_or_else(|err| {
eprintln!("Trying to send workspaces failed with err: {err}");
});
if let Ok(window) = Client::get_active_async().await {
if let Some(w) = window {
sender
.send(Message::ActiveWindowChange(w.title.trim().to_owned()))
.await
.unwrap_or_else(|e| {
error!("Trying to send window failed with err: {e}");
});
}
}
let mut listener = AsyncEventListener::new();
let senderx = sender.clone();
listener.add_active_window_changed_handler(move |data| {
let mut sender = senderx.clone();
Box::pin(async move {
if let Some(data) = data {
sender
.send(Message::ActiveWindowChange(data.title))
.await
.unwrap();
}
})
});
let senderx = sender.clone();
listener.add_workspace_changed_handler(move |data| {
let mut sender = senderx.clone();
Box::pin(async move {
sender
.send(Message::ActiveWorkspaceChange(data))
.await
.unwrap_or_else(|e| error!("Error in sending: {e}"));
let mut workspaces = Workspaces::get_async()
.await
.map_err(|e| {
error!("Couldn't get hyprland info: {}", e);
e
})
.unwrap()
.to_vec();
workspaces.sort_by(|a, b| a.id.cmp(&b.id));
sender
.send(Message::WorkspaceChange(workspaces))
.await
.unwrap_or_else(|err| {
eprintln!("Trying to send workspaces failed with err: {err}");
});
})
});
listener
.start_listener_async()
.await
.expect("Failed to listen for hyprland events");
})
})
}
fn view(&self) -> Element<Message> { fn view(&self) -> Element<Message> {
let workspaces: Vec<Element<'_, Message>> = self let workspaces: Vec<Element<'_, Message>> = self
.workspaces .workspaces
.iter() .iter()
.filter(|w| w.name != "special:special")
.map(|w| { .map(|w| {
text!("{}", { text!("{}", {
match w.id { match w.id {
@ -195,17 +302,39 @@ impl Panel {
}) })
.collect(); .collect();
let workspaces = row(workspaces).spacing(20).padding([0, 5]); let workspaces = row(workspaces).spacing(20).padding([0, 5]);
let window = text!("{}", self.active_window).color(Color::parse("#57c7ff").unwrap());
let disk = text!("{}", self.disk).color(Color::parse("#ff9f43").unwrap()); let disk = text!("{}", self.disk).color(Color::parse("#ff9f43").unwrap());
let mem = text!("{}", self.memory).color(Color::parse("#f3f99d").unwrap()); let mem = text!("{}", self.memory).color(Color::parse("#f3f99d").unwrap());
let cpu = text!("{}", self.cpu).color(Color::parse("#57c7ff").unwrap()); let cpu = text!("{}", self.cpu).color(Color::parse("#57c7ff").unwrap());
let clock = text!("{}", self.time); let clock = text!("{}", self.time);
let clock = clock.color(Color::parse("#5af78e").unwrap()); let clock = clock.color(Color::parse("#5af78e").unwrap());
let battery_icon = if let Some(battery) = &self.battery {
match battery.status {
BatteryStatus::Charging => "󱟦",
BatteryStatus::Charged => "󱟢",
BatteryStatus::Draining => "󱟤",
}
} else {
""
};
let battery = text!(
"{} {}",
battery_icon,
if let Some(battery) = &self.battery {
battery.capacity.to_string()
} else {
"".to_string()
}
)
.color(Color::parse("#ff6ac1").unwrap());
let row = row!( let row = row!(
container(workspaces), container(row!(workspaces, window, horizontal_space()).spacing(10)),
container(horizontal_space()),
container(clock), container(clock),
container(row!(horizontal_space(), disk, cpu, mem).spacing(5)) container(row!(horizontal_space(), disk, cpu, mem, battery).spacing(5))
) )
.spacing(5) .spacing(5)
.padding([0, 20]); .padding([0, 20]);
@ -263,3 +392,37 @@ pub fn convert(num: f64) -> String {
let unit = units[exponent as usize]; let unit = units[exponent as usize];
format!("{}{} {}", negative, pretty_bytes, unit) format!("{}{} {}", negative, pretty_bytes, unit)
} }
#[derive(Debug)]
struct Battery {
capacity: u8,
status: BatteryStatus,
}
impl Battery {
fn get() -> Result<Battery> {
let capacity = read_to_string("/sys/class/power_supply/BAT1/capacity")
.into_diagnostic()?
.trim()
.parse()
.into_diagnostic()?;
let status = match read_to_string("/sys/class/power_supply/BAT1/status")
.into_diagnostic()?
.trim()
{
"Not charging" => BatteryStatus::Charged,
"Discharging" => BatteryStatus::Draining,
"Charging" => BatteryStatus::Charging,
_ => BatteryStatus::Draining,
};
Ok(Self { capacity, status })
}
}
#[derive(Debug)]
enum BatteryStatus {
Charging,
Charged,
Draining,
}