Compare commits
No commits in common. "aaa21aff212c1e597e124c639a7cb3917d5e46f0" and "40e63f04fd251167b12f870703b90ebb25abd799" have entirely different histories.
aaa21aff21
...
40e63f04fd
5 changed files with 3 additions and 6202 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -1,3 +0,0 @@
|
||||||
target/
|
|
||||||
.direnv/
|
|
||||||
.aider*
|
|
5618
Cargo.lock
generated
5618
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
@ -8,8 +8,7 @@ description = "A shell for a computadora"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
clap = { version = "4.5.20", features = ["debug", "derive"] }
|
clap = { version = "4.5.20", features = ["debug", "derive"] }
|
||||||
iced = { version = "0.13.1", features = ["tokio", "advanced", "svg", "image", "wgpu"]}
|
libcosmic = { git = "https://github.com/pop-os/libcosmic", features = ["debug", "winit", "tokio", "xdg-portal", "dbus-config", "a11y", "wayland", "wgpu", "multi-window", "single-instance"] }
|
||||||
iced_runtime = { version = "0.13", features = [ "multi-window" ] }
|
|
||||||
miette = { version = "7.2.0", features = ["fancy"] }
|
miette = { version = "7.2.0", features = ["fancy"] }
|
||||||
pretty_assertions = "1.4.1"
|
pretty_assertions = "1.4.1"
|
||||||
serde = { version = "1.0.213", features = ["derive"] }
|
serde = { version = "1.0.213", features = ["derive"] }
|
||||||
|
@ -19,10 +18,4 @@ ron = "0.8.1"
|
||||||
dirs = "5.0.1"
|
dirs = "5.0.1"
|
||||||
tokio = "1.41.1"
|
tokio = "1.41.1"
|
||||||
crisp = { git = "https://git.tfcconnection.org/chris/crisp", version = "0.1.3" }
|
crisp = { git = "https://git.tfcconnection.org/chris/crisp", version = "0.1.3" }
|
||||||
iced_layershell = "0.13.0"
|
|
||||||
chrono = "0.4.39"
|
|
||||||
sysinfo = "0.35.0"
|
|
||||||
hyprland = { git = "https://github.com/hyprland-community/hyprland-rs", branch = "master" }
|
|
||||||
system-tray = "0.7.0"
|
|
||||||
freedesktop-icons = "0.4.0"
|
|
||||||
|
|
||||||
|
|
16
justfile
16
justfile
|
@ -1,16 +0,0 @@
|
||||||
default:
|
|
||||||
list
|
|
||||||
|
|
||||||
run:
|
|
||||||
RUST_LOG=debug cargo run
|
|
||||||
|
|
||||||
build:
|
|
||||||
cargo build
|
|
||||||
|
|
||||||
watch:
|
|
||||||
RUST_LOG=debug cargo watch -- cargo run
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
alias r := run
|
|
||||||
alias b := build
|
|
559
src/main.rs
559
src/main.rs
|
@ -1,558 +1,3 @@
|
||||||
use std::cmp;
|
fn main() -> cosmic::iced::Result {
|
||||||
use std::collections::HashMap;
|
println!("hello")
|
||||||
use std::fmt::Display;
|
|
||||||
use std::fs::read_to_string;
|
|
||||||
use std::path::PathBuf;
|
|
||||||
|
|
||||||
use chrono::prelude::*;
|
|
||||||
use freedesktop_icons::lookup;
|
|
||||||
use hyprland::data::{Client, Workspace, Workspaces};
|
|
||||||
use hyprland::event_listener::{self, AsyncEventListener};
|
|
||||||
use hyprland::shared::{HyprData, HyprDataActiveOptional, HyprDataVec};
|
|
||||||
use iced::font::Weight;
|
|
||||||
use iced::futures::SinkExt;
|
|
||||||
use iced::stream;
|
|
||||||
use iced::widget::image::Handle;
|
|
||||||
use iced::widget::{button, container, horizontal_space, image, row, svg, text};
|
|
||||||
use iced::{
|
|
||||||
time, Background, Border, Color, Element, Event, Font, Length, Shadow, Subscription, Task,
|
|
||||||
Vector,
|
|
||||||
};
|
|
||||||
|
|
||||||
use iced_layershell::build_pattern::{application, MainSettings};
|
|
||||||
use iced_layershell::reexport::{Anchor, KeyboardInteractivity};
|
|
||||||
use iced_layershell::settings::LayerShellSettings;
|
|
||||||
use iced_layershell::to_layer_message;
|
|
||||||
use iced_runtime::futures::event;
|
|
||||||
use miette::{IntoDiagnostic, Result};
|
|
||||||
use sysinfo::{Disks, System};
|
|
||||||
use system_tray::item::StatusNotifierItem;
|
|
||||||
use system_tray::menu::TrayMenu;
|
|
||||||
use tracing::level_filters::LevelFilter;
|
|
||||||
use tracing::{debug, error};
|
|
||||||
use tracing_subscriber::EnvFilter;
|
|
||||||
|
|
||||||
fn main() -> iced_layershell::Result {
|
|
||||||
let timer =
|
|
||||||
tracing_subscriber::fmt::time::ChronoLocal::new("%Y-%m-%d_%I:%M:%S%.6f %P".to_owned());
|
|
||||||
let filter = EnvFilter::builder()
|
|
||||||
.with_default_directive(LevelFilter::WARN.into())
|
|
||||||
.parse_lossy("shelly=debug");
|
|
||||||
tracing_subscriber::FmtSubscriber::builder()
|
|
||||||
.pretty()
|
|
||||||
.with_line_number(true)
|
|
||||||
.with_level(true)
|
|
||||||
.with_target(true)
|
|
||||||
.with_env_filter(filter)
|
|
||||||
.with_target(true)
|
|
||||||
.with_timer(timer)
|
|
||||||
.init();
|
|
||||||
|
|
||||||
debug!("Starting shelly");
|
|
||||||
|
|
||||||
let mut font = Font::with_name("VictorMono Nerd Font");
|
|
||||||
font.weight = Weight::Bold;
|
|
||||||
let settings = MainSettings {
|
|
||||||
layer_settings: LayerShellSettings {
|
|
||||||
size: Some((1400, 60)),
|
|
||||||
anchor: Anchor::Bottom | Anchor::Left | Anchor::Right,
|
|
||||||
margin: (0, 0, 0, 0),
|
|
||||||
exclusive_zone: 60,
|
|
||||||
keyboard_interactivity: KeyboardInteractivity::None,
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
default_font: font,
|
|
||||||
// antialiasing: true,
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
|
|
||||||
application(Panel::namespace, Panel::update, Panel::view)
|
|
||||||
.subscription(Panel::subscription)
|
|
||||||
.settings(settings)
|
|
||||||
.style(Panel::style)
|
|
||||||
.run_with(Panel::new)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
struct Panel {
|
|
||||||
time: String,
|
|
||||||
cpu: String,
|
|
||||||
memory: String,
|
|
||||||
battery: Option<Battery>,
|
|
||||||
disk: String,
|
|
||||||
system: System,
|
|
||||||
disks: Disks,
|
|
||||||
workspaces: Vec<Workspace>,
|
|
||||||
active_workspace: i32,
|
|
||||||
active_window: String,
|
|
||||||
active_window_icon_svg: Option<svg::Handle>,
|
|
||||||
active_window_icon_raster: Option<Handle>,
|
|
||||||
system_tray: Option<SystemTray>,
|
|
||||||
apps: Vec<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[to_layer_message]
|
|
||||||
#[derive(Debug)]
|
|
||||||
enum Message {
|
|
||||||
Launch(usize),
|
|
||||||
Time(DateTime<Local>),
|
|
||||||
IcedEvent(Event),
|
|
||||||
WorkspaceChange(Vec<Workspace>),
|
|
||||||
ActiveWorkspaceChange(event_listener::WorkspaceEventData),
|
|
||||||
ActiveWindowChange((String, String)),
|
|
||||||
AddSystemTray(SystemTray),
|
|
||||||
None,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Panel {
|
|
||||||
fn new() -> (Self, Task<Message>) {
|
|
||||||
let workspaces = Workspaces::get().map_err(|e| {
|
|
||||||
error!("Couldn't get hyprland info: {}", e);
|
|
||||||
e
|
|
||||||
});
|
|
||||||
let client = Client::get_active().unwrap().unwrap();
|
|
||||||
let icon = lookup(&client.class)
|
|
||||||
.with_theme("Papirus Dark")
|
|
||||||
.with_cache()
|
|
||||||
.find();
|
|
||||||
let active_window_icon_svg = icon
|
|
||||||
.clone()
|
|
||||||
.filter(|icon| icon.ends_with("svg"))
|
|
||||||
.map(|icon| svg::Handle::from_path(icon));
|
|
||||||
let active_window_icon_raster = icon.map(|icon| image::Handle::from_path(icon));
|
|
||||||
let active_window = client.title;
|
|
||||||
let battery = match Battery::get() {
|
|
||||||
Ok(b) => Some(b),
|
|
||||||
Err(e) => {
|
|
||||||
error!("{e}");
|
|
||||||
None
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let mut app = Self {
|
|
||||||
system: System::new_all(),
|
|
||||||
disks: Disks::new_with_refreshed_list(),
|
|
||||||
workspaces: workspaces.unwrap().to_vec(),
|
|
||||||
active_workspace: 1,
|
|
||||||
time: String::new(),
|
|
||||||
cpu: String::new(),
|
|
||||||
memory: String::new(),
|
|
||||||
disk: String::new(),
|
|
||||||
apps: vec![String::new()],
|
|
||||||
active_window,
|
|
||||||
active_window_icon_svg,
|
|
||||||
active_window_icon_raster,
|
|
||||||
battery,
|
|
||||||
system_tray: None,
|
|
||||||
};
|
|
||||||
|
|
||||||
let sys_task = Task::perform(add_sys_tray(), |m| m);
|
|
||||||
|
|
||||||
(app, sys_task)
|
|
||||||
}
|
|
||||||
fn namespace(&self) -> String {
|
|
||||||
String::from("panel")
|
|
||||||
}
|
|
||||||
|
|
||||||
fn update(&mut self, message: Message) -> Task<Message> {
|
|
||||||
match message {
|
|
||||||
Message::Launch(index) => {}
|
|
||||||
Message::Time(instant) => {
|
|
||||||
self.system.refresh_memory();
|
|
||||||
self.system.refresh_cpu_usage();
|
|
||||||
// disk.refresh();
|
|
||||||
// let used_space = disk.available_space() - disk.total_space();
|
|
||||||
let disk = self.disks.first_mut();
|
|
||||||
if let Some(disk) = disk {
|
|
||||||
disk.refresh();
|
|
||||||
self.disk = format!(
|
|
||||||
" {}",
|
|
||||||
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;
|
|
||||||
// self.disk = format!(" {}", used_space);
|
|
||||||
self.cpu = format!(" {}%", self.system.global_cpu_usage().round());
|
|
||||||
let memory = ((self.system.used_memory() as f64
|
|
||||||
/ self.system.total_memory() as f64)
|
|
||||||
.fract()
|
|
||||||
* 100.0)
|
|
||||||
.trunc();
|
|
||||||
self.memory = format!(" {}%", memory);
|
|
||||||
self.time = instant.format("%a %b %d, %I:%M %p").to_string();
|
|
||||||
}
|
|
||||||
Message::AnchorChange(anchor) => todo!(),
|
|
||||||
Message::AnchorSizeChange(anchor, _) => todo!(),
|
|
||||||
Message::LayerChange(layer) => todo!(),
|
|
||||||
Message::MarginChange(_) => todo!(),
|
|
||||||
Message::SizeChange(_) => todo!(),
|
|
||||||
Message::VirtualKeyboardPressed { time, key } => todo!(),
|
|
||||||
Message::IcedEvent(event) => {
|
|
||||||
debug!(?event)
|
|
||||||
}
|
|
||||||
Message::WorkspaceChange(workspaces) => {
|
|
||||||
// debug!(?workspaces);
|
|
||||||
self.workspaces = workspaces;
|
|
||||||
}
|
|
||||||
Message::ActiveWorkspaceChange(data) => {
|
|
||||||
// debug!(?data);
|
|
||||||
self.active_workspace = data.id;
|
|
||||||
}
|
|
||||||
Message::ActiveWindowChange(w) => {
|
|
||||||
debug!(?w);
|
|
||||||
self.active_window =
|
|
||||||
w.0.trim_start_matches("\u{180e}\u{200b}\u{200c}")
|
|
||||||
.to_string();
|
|
||||||
let class;
|
|
||||||
if &w.1 == "lw" {
|
|
||||||
class = "LibreWolf";
|
|
||||||
} else {
|
|
||||||
class = &w.1;
|
|
||||||
}
|
|
||||||
let icon = lookup(class)
|
|
||||||
.with_theme("Papirus-Dark")
|
|
||||||
.force_svg()
|
|
||||||
.with_size(16)
|
|
||||||
.find();
|
|
||||||
debug!(?icon);
|
|
||||||
self.active_window_icon_raster = icon
|
|
||||||
.clone()
|
|
||||||
.filter(|icon| !icon.ends_with("svg"))
|
|
||||||
.map(|icon| image::Handle::from_path(icon));
|
|
||||||
let svg = icon.map(|icon| svg::Handle::from_path(icon));
|
|
||||||
debug!(?svg);
|
|
||||||
self.active_window_icon_svg = svg;
|
|
||||||
}
|
|
||||||
Message::AddSystemTray(system_tray) => {
|
|
||||||
debug!(?system_tray);
|
|
||||||
self.system_tray = Some(system_tray);
|
|
||||||
}
|
|
||||||
Message::None => {}
|
|
||||||
_ => unreachable!(),
|
|
||||||
}
|
|
||||||
Task::none()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn subscription(&self) -> Subscription<Message> {
|
|
||||||
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> {
|
|
||||||
Subscription::run(|| {
|
|
||||||
stream::channel(1, |mut sender| async move {
|
|
||||||
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(),
|
|
||||||
w.initial_class.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 {
|
|
||||||
let client = Client::get_active().unwrap().unwrap();
|
|
||||||
sender
|
|
||||||
.send(Message::ActiveWindowChange((
|
|
||||||
client.title,
|
|
||||||
client.initial_class,
|
|
||||||
)))
|
|
||||||
.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> {
|
|
||||||
let workspaces: Vec<Element<'_, Message>> = self
|
|
||||||
.workspaces
|
|
||||||
.iter()
|
|
||||||
.filter(|w| w.name != "special:special")
|
|
||||||
.map(|w| {
|
|
||||||
text!("{}", {
|
|
||||||
match w.id {
|
|
||||||
1 => "".to_owned(),
|
|
||||||
2 => "".to_owned(),
|
|
||||||
3 => "".to_owned(),
|
|
||||||
4 => "".to_owned(),
|
|
||||||
5 => "".to_owned(),
|
|
||||||
6 => "".to_owned(),
|
|
||||||
7 => "".to_owned(),
|
|
||||||
8 => "".to_owned(),
|
|
||||||
9 => "".to_owned(),
|
|
||||||
_ => w.name.clone(),
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.color({
|
|
||||||
if w.id == self.active_workspace {
|
|
||||||
Color::parse("#ff6ac1").unwrap()
|
|
||||||
} else {
|
|
||||||
Color::parse("#57c7ff").unwrap()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.into()
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
let workspaces = row(workspaces).spacing(20).padding([0, 5]);
|
|
||||||
|
|
||||||
let icon: Element<Message>;
|
|
||||||
// if let Some(handle) = &self.active_window_icon_raster {
|
|
||||||
// icon = image(handle).into();
|
|
||||||
// } else {
|
|
||||||
// if let Some(handle) = &self.active_window_icon_svg {
|
|
||||||
// icon = svg(handle.clone()).width(24).height(24).into();
|
|
||||||
// } else {
|
|
||||||
// icon = image("/home/chris/pics/wojaks/reddit_the_xenomorph_s bigass wojak folder/Chads/ChristianChad.png").into();
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
if let Some(handle) = &self.active_window_icon_svg {
|
|
||||||
icon = svg(handle.clone()).width(16).height(16).into();
|
|
||||||
} else {
|
|
||||||
if let Some(handle) = &self.active_window_icon_raster {
|
|
||||||
icon = image(handle.clone()).width(16).height(16).into();
|
|
||||||
} else {
|
|
||||||
icon = image("/home/chris/pics/wojaks/reddit_the_xenomorph_s bigass wojak folder/Chads/ChristianChad.png").width(16).height(16).into();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let icon = container(icon).center(Length::Shrink).padding(4);
|
|
||||||
|
|
||||||
let window = text!("{}", self.active_window).color(Color::parse("#57c7ff").unwrap());
|
|
||||||
let disk = text!("{}", self.disk).color(Color::parse("#ff9f43").unwrap());
|
|
||||||
let mem = text!("{}", self.memory).color(Color::parse("#f3f99d").unwrap());
|
|
||||||
let cpu = text!("{}", self.cpu).color(Color::parse("#57c7ff").unwrap());
|
|
||||||
let clock = text!("{}", self.time);
|
|
||||||
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 spacer = text!(" | ");
|
|
||||||
|
|
||||||
// let sys_tray = vec![button()]
|
|
||||||
|
|
||||||
let row = row!(
|
|
||||||
container(row!(workspaces, icon, window, horizontal_space()).spacing(10)),
|
|
||||||
container(clock),
|
|
||||||
container(row!(horizontal_space(), disk, cpu, mem, battery).spacing(5)),
|
|
||||||
spacer,
|
|
||||||
)
|
|
||||||
.spacing(5)
|
|
||||||
.padding([0, 20]);
|
|
||||||
let mut shadow = Shadow::default();
|
|
||||||
shadow.color = Color::BLACK;
|
|
||||||
shadow.offset = Vector::new(4.5, 4.5);
|
|
||||||
shadow.blur_radius = 18.5;
|
|
||||||
container(
|
|
||||||
container(row)
|
|
||||||
.style(move |t: &iced::Theme| {
|
|
||||||
container::rounded_box(t)
|
|
||||||
.border(Border::default().rounded(20))
|
|
||||||
.background(
|
|
||||||
Background::Color(Color::parse("#282a36").unwrap()).scale_alpha(0.95),
|
|
||||||
)
|
|
||||||
.shadow(shadow)
|
|
||||||
})
|
|
||||||
.center(Length::Fill),
|
|
||||||
)
|
|
||||||
.padding([15, 20])
|
|
||||||
.center(Length::Fill)
|
|
||||||
.style(move |t: &iced::Theme| {
|
|
||||||
container::rounded_box(t)
|
|
||||||
.border(Border::default().rounded(20))
|
|
||||||
.background(Background::Color(Color::TRANSPARENT))
|
|
||||||
})
|
|
||||||
.into()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn style(&self, theme: &iced::Theme) -> iced_layershell::Appearance {
|
|
||||||
use iced_layershell::Appearance;
|
|
||||||
Appearance {
|
|
||||||
background_color: Color::TRANSPARENT,
|
|
||||||
text_color: theme.palette().text,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn add_sys_tray() -> Message {
|
|
||||||
let sys_tray = SystemTray::new().await;
|
|
||||||
match sys_tray {
|
|
||||||
Ok(s) => Message::AddSystemTray(s),
|
|
||||||
Err(e) => {
|
|
||||||
error!(?e, "Sys tray couldn't be initialized");
|
|
||||||
Message::None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn convert(num: f64) -> String {
|
|
||||||
let negative = if num.is_sign_positive() { "" } else { "-" };
|
|
||||||
let num = num.abs();
|
|
||||||
let units = ["B", "kB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"];
|
|
||||||
if num < 1_f64 {
|
|
||||||
return format!("{}{} {}", negative, num, "B");
|
|
||||||
}
|
|
||||||
let delimiter = 1000_f64;
|
|
||||||
let exponent = cmp::min(
|
|
||||||
(num.ln() / delimiter.ln()).floor() as i32,
|
|
||||||
(units.len() - 1) as i32,
|
|
||||||
);
|
|
||||||
let pretty_bytes = format!("{:.2}", num / delimiter.powi(exponent))
|
|
||||||
.parse::<f64>()
|
|
||||||
.unwrap()
|
|
||||||
* 1_f64;
|
|
||||||
let unit = units[exponent as usize];
|
|
||||||
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,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
struct SystemTray {
|
|
||||||
client: system_tray::client::Client,
|
|
||||||
items: HashMap<String, (StatusNotifierItem, Option<TrayMenu>)>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SystemTray {
|
|
||||||
async fn new() -> Result<Self> {
|
|
||||||
let client = system_tray::client::Client::new().await.into_diagnostic()?;
|
|
||||||
let items = client.items();
|
|
||||||
let items = items
|
|
||||||
.lock()
|
|
||||||
.map_err(|_| SystemTrayError::SomeError)
|
|
||||||
.into_diagnostic()?
|
|
||||||
.clone();
|
|
||||||
Ok(Self { client, items })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
enum SystemTrayError {
|
|
||||||
SomeError,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::error::Error for SystemTrayError {}
|
|
||||||
|
|
||||||
impl Display for SystemTrayError {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
write!(f, "SystemTrayError::SomeError")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue