itop/src/app/mod.rs
electria b20158820b
feat: process list
this is unnacceptably unperformant;
I think I've reached the point at which sysinfo doesn't do what I want,
which is keep track of a table of processes efficiently enough.
2026-08-18 14:11:01 -07:00

205 lines
6.3 KiB
Rust

use std::time::Duration;
use iced::{
Color, Error, Event, Length, Renderer, Subscription, Task, Theme, application, event,
keyboard::{self, Key},
theme, widget,
};
use crate::{
app::{graph::Graph, utils::maybe_widget},
sysinfo_snapshot::{ProcessSnapshot, SysinfoSnapshot},
utils::Toggle,
};
mod graph;
mod info_stream;
mod utils;
type Element<'a> = iced::Element<'a, Message, Theme, Renderer>;
enum Message {
ToggleMain,
ToggleCpu,
ToggleMemory,
ToggleProcesses,
Info(SysinfoSnapshot),
}
#[derive(Default)]
struct State {
uptime: Duration,
is_main_section_shown: bool,
total_cpu_graph: Graph,
is_cpu_section_shown: bool,
cpu_graphs: Vec<Graph>,
is_memory_section_shown: bool,
memory_graph: Graph,
swap_graph: Graph,
is_processes_section_shown: bool,
processes: Vec<ProcessSnapshot>,
}
impl State {
fn new() -> (Self, Task<Message>) {
let mut state = Self {
is_main_section_shown: true,
..Default::default()
};
state.total_cpu_graph.show_borders = false;
(state, info_stream::create())
}
fn update(&mut self, message: Message) -> Task<Message> {
match message {
Message::ToggleMain => {
self.is_main_section_shown.toggle();
self.check_borders();
}
Message::ToggleCpu => {
self.is_cpu_section_shown.toggle();
self.check_borders();
}
Message::ToggleMemory => {
self.is_memory_section_shown.toggle();
self.check_borders();
}
Message::ToggleProcesses => {
self.is_processes_section_shown.toggle();
self.check_borders();
}
Message::Info(snapshot) => {
self.uptime = snapshot.uptime();
self.total_cpu_graph.update(snapshot.cpu_total);
self.memory_graph
.update(snapshot.memory as f32 / snapshot.memory_total as f32 * 100.);
self.swap_graph
.update(snapshot.swap as f32 / snapshot.swap_total as f32 * 100.);
if self.cpu_graphs.len() != snapshot.cpus.len() {
self.cpu_graphs = snapshot
.cpus
.into_iter()
.map(|value| Graph::new(value))
.collect();
} else {
self.cpu_graphs
.iter_mut()
.zip(snapshot.cpus.into_iter())
.for_each(|(graph, value)| graph.update(value));
}
self.processes = snapshot.processes;
self.processes
.sort_by(|a, b| b.cpu_usage.total_cmp(&a.cpu_usage));
}
}
Task::none()
}
fn view(&self) -> Element<'_> {
widget::column([
widget::row([
maybe_widget(self.is_main_section_shown, || {
widget::canvas(&self.total_cpu_graph)
.width(Length::Fill)
.height(Length::Fill)
.into()
}),
maybe_widget(self.is_cpu_section_shown, || {
widget::grid(
self.cpu_graphs
.iter()
.map(|graph| widget::Canvas::new(graph).into()),
)
.columns(2)
.height(Length::Fill)
.into()
}),
])
.into(),
widget::row([
maybe_widget(self.is_memory_section_shown, || {
widget::row([
widget::canvas(&self.memory_graph)
.width(Length::Fill)
.height(Length::Fill)
.into(),
widget::canvas(&self.swap_graph)
.width(Length::Fill)
.height(Length::Fill)
.into(),
])
.into()
}),
maybe_widget(self.is_processes_section_shown, || {
widget::scrollable(
widget::grid(self.processes.iter().flat_map(|process| {
[
widget::text(&process.name).into(),
widget::text(process.cpu_usage).into(),
widget::text(format!("{:?}", process.cpu_time())).into(),
widget::text(process.pid).into(),
]
}))
.height(Length::Shrink)
.columns(4),
)
.width(Length::Fill)
.height(Length::Fill)
.into()
}),
])
.into(),
])
.into()
}
fn subscription(&self) -> Subscription<Message> {
event::listen().filter_map(|event| match event {
Event::Keyboard(keyboard::Event::KeyPressed { modified_key, .. }) => {
match modified_key.as_ref() {
Key::Character("M" | "1") => Some(Message::ToggleMain),
Key::Character("c" | "2") => Some(Message::ToggleCpu),
Key::Character("m" | "3") => Some(Message::ToggleMemory),
Key::Character("p" | "4") => Some(Message::ToggleProcesses),
_ => None,
}
}
_ => None,
})
}
fn title(&self) -> String {
format!("itop {:?}", self.uptime)
}
fn check_borders(&mut self) {
self.total_cpu_graph.show_borders =
self.is_cpu_section_shown || self.is_memory_section_shown;
}
}
pub fn run() -> Result<(), Error> {
application(State::new, State::update, State::view)
.subscription(State::subscription)
.title(State::title)
.theme(theme::Theme::custom(
"high-contrast-dark",
theme::Palette {
background: Color::BLACK,
text: Color::WHITE,
..theme::Palette::DARK
},
))
.run()
}