use gtk::prelude::*;
use gtk::{glib, pango, EventControllerKey, Inhibit};
use sysinfo::{self, Pid, ProcessExt};
use std::cell::{Cell, RefCell};
use std::fmt;
use std::iter;
use std::rc::Rc;
use crate::graph::GraphWidget;
use crate::notebook::NoteBook;
use crate::utils::{format_number, get_main_window, graph_label_units, RotateVec};
#[allow(dead_code)]
pub struct ProcDialog {
working_directory: gtk::Label,
memory_usage: gtk::Label,
disk_usage: gtk::Label,
cpu_usage: gtk::Label,
run_time: gtk::Label,
pub popup: gtk::Window,
pub pid: Pid,
notebook: NoteBook,
ram_usage_history: Rc<RefCell<GraphWidget>>,
cpu_usage_history: Rc<RefCell<GraphWidget>>,
disk_usage_history: Rc<RefCell<GraphWidget>>,
memory_peak: RefCell<u64>,
memory_peak_label: gtk::Label,
disk_peak: RefCell<u64>,
disk_peak_label: gtk::Label,
pub is_dead: bool,
pub to_be_removed: Rc<Cell<bool>>,
}
impl fmt::Debug for ProcDialog {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "ProcDialog {{ pid: {} }}", self.pid)
}
}
impl ProcDialog {
pub fn update(&self, process: &sysinfo::Process) {
if self.is_dead {
return;
}
self.working_directory
.set_text(&process.cwd().display().to_string());
let memory = process.memory();
let memory_s = format_number(memory);
self.memory_usage.set_text(&memory_s);
if memory > *self.memory_peak.borrow() {
*self.memory_peak.borrow_mut() = memory;
self.memory_peak_label.set_text(&memory_s);
}
let disk_usage = process.disk_usage();
let disk_usage = disk_usage.written_bytes + disk_usage.read_bytes;
let disk_usage_s = format_number(disk_usage);
self.disk_usage.set_text(&disk_usage_s);
if disk_usage > *self.disk_peak.borrow() {
*self.disk_peak.borrow_mut() = disk_usage;
self.disk_peak_label.set_text(&disk_usage_s);
}
self.cpu_usage
.set_text(&format!("{:.1}%", process.cpu_usage()));
self.run_time.set_text(&format_time(process.run_time()));
let t = self.ram_usage_history.borrow_mut();
t.data(0, |d| {
d.move_start();
*d.get_mut(0).expect("cannot get data 0") = memory as f32;
});
t.queue_draw();
let t = self.cpu_usage_history.borrow_mut();
t.data(0, |d| {
d.move_start();
*d.get_mut(0).expect("cannot get data 0") = process.cpu_usage();
});
t.queue_draw();
let t = self.disk_usage_history.borrow_mut();
t.data(0, |d| {
d.move_start();
*d.get_mut(0).expect("cannot get data 0") = disk_usage as f32;
});
t.queue_draw();
}
pub fn need_remove(&self) -> bool {
self.to_be_removed.get()
}
pub fn set_dead(&mut self) {
if self.is_dead {
return;
}
self.is_dead = true;
self.memory_usage.set_text("0");
self.disk_usage.set_text("0");
self.cpu_usage.set_text("0%");
let time = self.run_time.text();
let s = format!("Ran for {}", if time.is_empty() { "0s" } else { &time },);
self.run_time.set_text(&s);
}
}
fn format_time(t: u64) -> String {
format!(
"{}{}{}{}s",
{
let days = t / 86_400;
if days > 0 {
format!("{}d ", days)
} else {
"".to_owned()
}
},
{
let hours = t / 3_600 % 24;
if hours > 0 {
format!("{}h ", hours)
} else {
"".to_owned()
}
},
{
let minutes = t / 60 % 60;
if minutes > 0 {
format!("{}m ", minutes)
} else {
"".to_owned()
}
},
t % 60
)
}
fn create_and_add_new_label(scroll: >k::Box, title: &str, text: &str) -> gtk::Label {
let horizontal_layout = gtk::Box::new(gtk::Orientation::Horizontal, 0);
horizontal_layout.set_margin_top(5);
horizontal_layout.set_margin_bottom(5);
horizontal_layout.set_margin_end(5);
horizontal_layout.set_margin_start(5);
let label = gtk::Label::new(None);
label.set_justify(gtk::Justification::Left);
label.set_markup(&format!("<b>{}:</b> ", title));
let text = gtk::Label::new(Some(text));
text.set_selectable(true);
text.set_justify(gtk::Justification::Left);
text.set_wrap(true);
text.set_wrap_mode(pango::WrapMode::Char);
horizontal_layout.append(&label);
horizontal_layout.append(&text);
scroll.append(&horizontal_layout);
text
}
fn append_text_column(tree: >k::TreeView, pos: i32) {
let column = gtk::TreeViewColumn::new();
let cell = gtk::CellRendererText::new();
column.pack_start(&cell, true);
column.add_attribute(&cell, "text", pos);
if pos == 1 {
cell.set_wrap_width(247);
cell.set_wrap_mode(pango::WrapMode::Char);
column.set_expand(true);
}
tree.append_column(&column);
}
pub fn create_process_dialog(process: &sysinfo::Process, total_memory: u64) -> ProcDialog {
let mut notebook = NoteBook::new();
let popup = gtk::Window::new();
popup.set_title(Some(&format!("Information about {}", process.name())));
popup.set_transient_for(get_main_window().as_ref());
popup.set_destroy_with_parent(true);
let scroll = gtk::ScrolledWindow::new();
let close_button = gtk::Button::with_label("Close");
close_button.add_css_class("button-with-margin");
let vertical_layout = gtk::Box::new(gtk::Orientation::Vertical, 0);
scroll.set_policy(gtk::PolicyType::Never, gtk::PolicyType::Automatic);
let running_since = process.run_time();
let labels = gtk::Box::new(gtk::Orientation::Vertical, 0);
create_and_add_new_label(&labels, "name", process.name());
create_and_add_new_label(&labels, "pid", &process.pid().to_string());
let memory_peak = process.memory();
let memory_usage =
create_and_add_new_label(&labels, "memory usage", &format_number(memory_peak));
let memory_peak_label =
create_and_add_new_label(&labels, "memory usage peak", &format_number(memory_peak));
let disk_peak = process.disk_usage();
let disk_peak = disk_peak.written_bytes + disk_peak.read_bytes;
let s;
#[cfg(not(any(windows, target_os = "freebsd")))]
{
s = "disk I/O usage";
}
#[cfg(any(windows, target_os = "freebsd"))]
{
s = "I/O usage";
}
let disk_usage = create_and_add_new_label(&labels, s, &format_number(disk_peak));
let disk_peak_label =
create_and_add_new_label(&labels, &format!("{} peak", s), &format_number(disk_peak));
let cpu_usage = create_and_add_new_label(
&labels,
"cpu usage",
&format!("{:.1}%", process.cpu_usage()),
);
let run_time = create_and_add_new_label(&labels, "Running since", &format_time(running_since));
create_and_add_new_label(
&labels,
"command",
&format!(
"[{}]",
process
.cmd()
.iter()
.map(|x| format!("\"{}\"", x))
.collect::<Vec<_>>()
.join(", ")
),
);
create_and_add_new_label(
&labels,
"executable path",
&process.exe().display().to_string(),
);
let working_directory = create_and_add_new_label(
&labels,
"current working directory",
&process.cwd().display().to_string(),
);
create_and_add_new_label(
&labels,
"root directory",
&process.root().display().to_string(),
);
let env_tree = gtk::TreeView::new();
let list_store = gtk::ListStore::new(&[glib::Type::STRING, glib::Type::STRING]);
env_tree.set_headers_visible(false);
env_tree.set_model(Some(&list_store));
append_text_column(&env_tree, 0);
append_text_column(&env_tree, 1);
for env in process.environ() {
let mut parts = env.splitn(2, '=');
let name = match parts.next() {
Some(n) => n,
None => continue,
};
let value = parts.next().unwrap_or("");
list_store.insert_with_values(None, &[(0, &name), (1, &value)]);
}
let components = gtk::Box::new(gtk::Orientation::Vertical, 0);
components.append(&labels);
if !process.environ().is_empty() {
let label = gtk::Label::new(None);
label.set_markup("<b>Environment variables</b>");
components.append(&label);
components.append(&env_tree);
}
scroll.set_child(Some(&components));
scroll.set_hexpand(true);
scroll.set_vexpand(true);
vertical_layout.append(&scroll);
vertical_layout.append(&close_button);
notebook.create_tab("Information", &vertical_layout);
let vertical_layout = gtk::Box::new(gtk::Orientation::Vertical, 0);
vertical_layout.set_spacing(5);
vertical_layout.set_margin_top(10);
vertical_layout.set_margin_bottom(10);
vertical_layout.set_margin_start(5);
vertical_layout.set_margin_end(5);
let scroll = gtk::ScrolledWindow::new();
let cpu_usage_history = GraphWidget::new(Some(100.), false); cpu_usage_history.set_display_labels(false);
cpu_usage_history.set_minimum(Some(100.));
let ram_usage_history = GraphWidget::new(Some(total_memory as f32), false);
ram_usage_history.set_display_labels(false);
ram_usage_history.set_overhead(Some(20.));
let disk_usage_history = GraphWidget::new(Some(0f32), false);
disk_usage_history.set_display_labels(false);
disk_usage_history.set_overhead(Some(20.));
cpu_usage_history.push(
RotateVec::new(iter::repeat(0f32).take(61).collect()),
"",
None,
);
cpu_usage_history.set_labels_callback(Some(Box::new(|v| {
if v > 100. {
let nb = v.ceil() as u64;
[
nb.to_string(),
(nb / 2).to_string(),
"0".to_string(),
"%".to_string(),
]
} else {
[
"100".to_string(),
"50".to_string(),
"0".to_string(),
"%".to_string(),
]
}
})));
vertical_layout.append(>k::Label::new(Some("Process usage")));
vertical_layout.append(&cpu_usage_history);
cpu_usage_history.queue_draw();
let cpu_usage_history = Rc::new(RefCell::new(cpu_usage_history));
ram_usage_history.push(
RotateVec::new(iter::repeat(0f32).take(61).collect()),
"",
None,
);
disk_usage_history.push(
RotateVec::new(iter::repeat(0f32).take(61).collect()),
"",
None,
);
ram_usage_history.set_labels_callback(Some(Box::new(graph_label_units)));
disk_usage_history.set_labels_callback(Some(Box::new(graph_label_units)));
vertical_layout.append(>k::Label::new(Some("Memory usage")));
vertical_layout.append(&ram_usage_history);
ram_usage_history.queue_draw();
let ram_usage_history = Rc::new(RefCell::new(ram_usage_history));
#[cfg(not(windows))]
{
vertical_layout.append(>k::Label::new(Some("Disk I/O usage")));
}
#[cfg(windows)]
{
vertical_layout.append(>k::Label::new(Some("I/O usage")));
}
vertical_layout.append(&disk_usage_history);
disk_usage_history.queue_draw();
let disk_usage_history = Rc::new(RefCell::new(disk_usage_history));
scroll.set_child(Some(&vertical_layout));
scroll.connect_show(
glib::clone!(@weak ram_usage_history, @weak cpu_usage_history, @weak disk_usage_history => move |_| {
ram_usage_history.borrow().show();
cpu_usage_history.borrow().show();
disk_usage_history.borrow().show();
}),
);
notebook.create_tab("Resources usage", &scroll);
popup.set_child(Some(¬ebook.notebook));
popup.set_size_request(500, 600);
close_button.connect_clicked(glib::clone!(@weak popup => move |_| {
popup.close();
}));
let to_be_removed = Rc::new(Cell::new(false));
popup.connect_destroy(glib::clone!(@weak to_be_removed => move |_| {
to_be_removed.set(true);
}));
popup.connect_close_request(
glib::clone!(@weak to_be_removed => @default-return Inhibit(false), move |_| {
to_be_removed.set(true);
Inhibit(false)
}),
);
let event_controller = EventControllerKey::new();
event_controller.connect_key_pressed(
glib::clone!(@weak popup, @weak to_be_removed => @default-return Inhibit(false), move |_, key, _, _modifier| {
if key == gtk::gdk::Key::Escape {
popup.close();
to_be_removed.set(true);
}
Inhibit(false)
}),
);
popup.add_controller(&event_controller);
popup.set_resizable(true);
popup.show();
let adjust = scroll.vadjustment();
adjust.set_value(0.);
scroll.set_vadjustment(Some(&adjust));
ProcDialog {
working_directory,
memory_usage,
disk_usage,
cpu_usage,
run_time,
popup,
pid: process.pid(),
notebook,
ram_usage_history,
cpu_usage_history,
disk_usage_history,
memory_peak: RefCell::new(memory_peak),
memory_peak_label,
disk_peak: RefCell::new(disk_peak),
disk_peak_label,
is_dead: false,
to_be_removed,
}
}