From 9ad8b7ec9f257455624f3ccbbfca77ef657dce41 Mon Sep 17 00:00:00 2001 From: nokyan Date: Tue, 30 Jul 2024 23:22:12 +0200 Subject: [PATCH 1/2] Protect against NaN and infinities better and in more places --- src/ui/pages/cpu.rs | 7 +++-- src/ui/pages/gpu.rs | 4 +-- src/ui/pages/memory.rs | 4 +-- src/utils/app.rs | 8 ++--- src/utils/mod.rs | 68 +++++++++++++++++++++++++++++++++++------- src/utils/process.rs | 11 +++---- 6 files changed, 75 insertions(+), 27 deletions(-) diff --git a/src/ui/pages/cpu.rs b/src/ui/pages/cpu.rs index 62c81d8b..6f9d19e9 100644 --- a/src/ui/pages/cpu.rs +++ b/src/ui/pages/cpu.rs @@ -8,7 +8,7 @@ use crate::ui::widgets::graph_box::ResGraphBox; use crate::utils::cpu::{CpuData, CpuInfo}; use crate::utils::settings::SETTINGS; use crate::utils::units::{convert_frequency, convert_temperature}; -use crate::utils::{cpu, NaNDefault, NUM_CPUS}; +use crate::utils::{cpu, FiniteOr, NUM_CPUS}; mod imp { use std::cell::{Cell, RefCell}; @@ -347,7 +347,8 @@ impl ResCPU { .saturating_sub(imp.old_total_usage.get().1); let work_total_time = sum_total_delta.saturating_sub(idle_total_delta); - let total_fraction = ((work_total_time as f64) / (sum_total_delta as f64)).nan_default(0.0); + let total_fraction = + ((work_total_time as f64) / (sum_total_delta as f64)).finite_or_default(); imp.total_cpu.graph().push_data_point(total_fraction); @@ -375,7 +376,7 @@ impl ResCPU { let work_thread_time = sum_thread_delta.saturating_sub(idle_thread_delta); let curr_threadbox = &imp.thread_graphs.borrow()[i]; let thread_fraction = - ((work_thread_time as f64) / (sum_thread_delta as f64)).nan_default(0.0); + ((work_thread_time as f64) / (sum_thread_delta as f64)).finite_or_default(); curr_threadbox.graph().push_data_point(thread_fraction); curr_threadbox.set_subtitle(&format!("{} %", (thread_fraction * 100.0).round())); diff --git a/src/ui/pages/gpu.rs b/src/ui/pages/gpu.rs index 85621ecb..afb4e595 100644 --- a/src/ui/pages/gpu.rs +++ b/src/ui/pages/gpu.rs @@ -5,7 +5,7 @@ use crate::config::PROFILE; use crate::i18n::{i18n, i18n_f}; use crate::utils::gpu::{Gpu, GpuData}; use crate::utils::units::{convert_frequency, convert_power, convert_storage, convert_temperature}; -use crate::utils::{pci, NaNDefault}; +use crate::utils::{pci, FiniteOr}; mod imp { use std::cell::{Cell, RefCell}; @@ -361,7 +361,7 @@ impl ResGPU { let used_vram_fraction = if let (Some(total_vram), Some(used_vram)) = (total_vram, used_vram) { - Some((*used_vram as f64 / *total_vram as f64).nan_default(0.0)) + Some((*used_vram as f64 / *total_vram as f64).finite_or_default()) } else { None }; diff --git a/src/ui/pages/memory.rs b/src/ui/pages/memory.rs index 13c1b5d2..0749a186 100644 --- a/src/ui/pages/memory.rs +++ b/src/ui/pages/memory.rs @@ -5,7 +5,7 @@ use crate::config::PROFILE; use crate::i18n::{i18n, i18n_f}; use crate::utils::memory::{self, MemoryData, MemoryDevice}; use crate::utils::units::convert_storage; -use crate::utils::NaNDefault; +use crate::utils::FiniteOr; mod imp { use std::cell::{Cell, RefCell}; @@ -331,7 +331,7 @@ impl ResMemory { let used_swap = total_swap.saturating_sub(free_swap); let memory_fraction = used_mem as f64 / total_mem as f64; - let swap_fraction = (used_swap as f64 / total_swap as f64).nan_default(0.0); + let swap_fraction = (used_swap as f64 / total_swap as f64).finite_or_default(); let formatted_used_mem = convert_storage(used_mem as f64, false); let formatted_total_mem = convert_storage(total_mem as f64, false); diff --git a/src/utils/app.rs b/src/utils/app.rs index 337a8d14..65d1d62e 100644 --- a/src/utils/app.rs +++ b/src/utils/app.rs @@ -19,7 +19,7 @@ use crate::i18n::i18n; use super::{ boot_time, process::{Process, ProcessAction, ProcessItem}, - NaNDefault, TICK_RATE, + FiniteOr, TICK_RATE, }; // This contains executable names that are blacklisted from being recognized as applications @@ -479,7 +479,7 @@ impl AppsContext { } else { ((new.gfx.saturating_sub(old.gfx) as f32) / (timestamp.saturating_sub(timestamp_last) as f32)) - .nan_default(0.0) + .finite_or_default() / 1_000_000.0 } }) @@ -517,7 +517,7 @@ impl AppsContext { } else { ((new.enc.saturating_sub(old.enc) as f32) / (timestamp.saturating_sub(timestamp_last) as f32)) - .nan_default(0.0) + .finite_or_default() / 1_000_000.0 } }) @@ -555,7 +555,7 @@ impl AppsContext { } else { ((new.dec.saturating_sub(old.dec) as f32) / (timestamp.saturating_sub(timestamp_last) as f32)) - .nan_default(0.0) + .finite_or_default() / 1_000_000.0 } }) diff --git a/src/utils/mod.rs b/src/utils/mod.rs index 924b1079..204da785 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -83,27 +83,73 @@ pub fn boot_time() -> Result { }) } -pub trait NaNDefault { - /// Returns the given `default` value if the variable is NaN, +pub trait FiniteOr { + /// Returns the given `x` value if the variable is NaN or infinite, /// and returns itself otherwise. #[must_use] - fn nan_default(&self, default: Self) -> Self; + fn finite_or(&self, x: Self) -> Self; + + /// Returns itself is the variable is finite (i.e. neither NaN nor infinite), otherwise returns its default + fn finite_or_default(&self) -> Self; + + /// Returns itself is the variable is finite (i.e. neither NaN nor infinite), otherwise runs `f` + fn finite_or_else Self>(&self, f: F) -> Self + where + Self: Sized; } -impl NaNDefault for f64 { - fn nan_default(&self, default: Self) -> Self { - if self.is_nan() { - default +impl FiniteOr for f64 { + fn finite_or(&self, x: Self) -> Self { + if !self.is_finite() { + x + } else { + *self + } + } + + fn finite_or_default(&self) -> Self { + if !self.is_finite() { + Self::default() + } else { + *self + } + } + + fn finite_or_else Self>(&self, f: F) -> Self + where + Self: Sized, + { + if !self.is_finite() { + f(*self) } else { *self } } } -impl NaNDefault for f32 { - fn nan_default(&self, default: Self) -> Self { - if self.is_nan() { - default +impl FiniteOr for f32 { + fn finite_or(&self, x: Self) -> Self { + if !self.is_finite() { + x + } else { + *self + } + } + + fn finite_or_default(&self) -> Self { + if !self.is_finite() { + Self::default() + } else { + *self + } + } + + fn finite_or_else Self>(&self, f: F) -> Self + where + Self: Sized, + { + if !self.is_finite() { + f(*self) } else { *self } diff --git a/src/utils/process.rs b/src/utils/process.rs index 58fe18fc..619f3406 100644 --- a/src/utils/process.rs +++ b/src/utils/process.rs @@ -14,7 +14,7 @@ use gtk::gio::{Icon, ThemedIcon}; use crate::config; -use super::{NaNDefault, FLATPAK_APP_PATH, FLATPAK_SPAWN, IS_FLATPAK, NUM_CPUS, TICK_RATE}; +use super::{FiniteOr, FLATPAK_APP_PATH, FLATPAK_SPAWN, IS_FLATPAK, NUM_CPUS, TICK_RATE}; static OTHER_PROCESS: LazyLock> = LazyLock::new(|| { let proxy_path = if *IS_FLATPAK { @@ -289,7 +289,8 @@ impl Process { * 1000.0; let delta_time = self.data.timestamp.saturating_sub(self.timestamp_last); - delta_cpu_time / (delta_time * *TICK_RATE as u64 * *NUM_CPUS as u64) as f32 + (delta_cpu_time / (delta_time * *TICK_RATE as u64 * *NUM_CPUS as u64) as f32) + .finite_or_default() } } @@ -339,7 +340,7 @@ impl Process { } else { ((usage.gfx.saturating_sub(old_usage.gfx) as f32) / (self.data.timestamp.saturating_sub(self.timestamp_last) as f32) - .nan_default(0.0)) + .finite_or_default()) / 1_000_000.0 }; @@ -364,7 +365,7 @@ impl Process { } else { ((usage.enc.saturating_sub(old_usage.enc) as f32) / (self.data.timestamp.saturating_sub(self.timestamp_last) as f32) - .nan_default(0.0)) + .finite_or_default()) / 1_000_000.0 }; @@ -389,7 +390,7 @@ impl Process { } else { ((usage.dec.saturating_sub(old_usage.dec) as f32) / (self.data.timestamp.saturating_sub(self.timestamp_last) as f32) - .nan_default(0.0)) + .finite_or_default()) / 1_000_000.0 }; From 9abf183d55fe09f23f91ee3d0565b6d74975126f Mon Sep 17 00:00:00 2001 From: nokyan Date: Wed, 31 Jul 2024 19:48:49 +0200 Subject: [PATCH 2/2] Merge main into 'nan-infinite-protection' --- Cargo.lock | 49 -- Cargo.toml | 1 - build-aux/net.nokyan.Resources.Devel.json | 2 +- lib/process_data/Cargo.lock | 175 ++++--- lib/process_data/src/lib.rs | 21 +- src/ui/dialogs/app_dialog.rs | 60 ++- src/ui/dialogs/process_dialog.rs | 99 ++-- src/ui/mod.rs | 40 ++ .../pages/applications/application_entry.rs | 110 ++--- src/ui/pages/applications/mod.rs | 151 ++++-- src/ui/pages/processes/mod.rs | 189 +++++--- src/ui/pages/processes/process_entry.rs | 129 +++-- src/ui/window.rs | 17 +- src/utils/app.rs | 452 ++++++------------ src/utils/gpu/amd.rs | 3 +- src/utils/mod.rs | 6 +- src/utils/pci.rs | 10 +- src/utils/process.rs | 45 +- 18 files changed, 759 insertions(+), 800 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 581d5ae6..6d3f4687 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,18 +17,6 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" -[[package]] -name = "ahash" -version = "0.8.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" -dependencies = [ - "cfg-if", - "once_cell", - "version_check", - "zerocopy", -] - [[package]] name = "aho-corasick" version = "1.1.3" @@ -38,12 +26,6 @@ dependencies = [ "memchr", ] -[[package]] -name = "allocator-api2" -version = "0.2.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" - [[package]] name = "anstream" version = "0.6.15" @@ -795,10 +777,6 @@ name = "hashbrown" version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" -dependencies = [ - "ahash", - "allocator-api2", -] [[package]] name = "heck" @@ -1259,7 +1237,6 @@ dependencies = [ "glob", "gtk-macros", "gtk4", - "hashbrown", "libadwaita", "log", "nix", @@ -1618,12 +1595,6 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "852e951cb7832cb45cb1169900d19760cfa39b82bc0ea9c0e5a14ae88411c98b" -[[package]] -name = "version_check" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" - [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" @@ -1839,23 +1810,3 @@ dependencies = [ "quote", "syn", ] - -[[package]] -name = "zerocopy" -version = "0.7.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae87e3fcd617500e5d106f0380cf7b77f3c6092aae37191433159dda23cfb087" -dependencies = [ - "zerocopy-derive", -] - -[[package]] -name = "zerocopy-derive" -version = "0.7.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15e934569e47891f7d9411f1a451d947a60e000ab3bd24fbb970f000387d1b3b" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] diff --git a/Cargo.toml b/Cargo.toml index 3059958b..14877e0d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,7 +21,6 @@ gettext-rs = { version = "0.7", features = ["gettext-system"] } glob = "0.3.1" gtk = { version = "0.9.0", features = ["v4_10"], package = "gtk4" } gtk-macros = "0.3.0" -hashbrown = "0.14.5" log = "0.4.22" nix = { version = "0.29.0", default-features = false, features = ["signal"] } num_cpus = "1.16.0" diff --git a/build-aux/net.nokyan.Resources.Devel.json b/build-aux/net.nokyan.Resources.Devel.json index 6901e8f8..5c772e85 100644 --- a/build-aux/net.nokyan.Resources.Devel.json +++ b/build-aux/net.nokyan.Resources.Devel.json @@ -13,7 +13,7 @@ "--env=G_MESSAGES_DEBUG=none", "--env=RUST_BACKTRACE=full", "--env=RUST_LOG=resources=debug", - "--env=XDG_DATA_DIRS=/app/share:/usr/share:/usr/share/runtime/share:/run/host/user-share:/run/host/usr/share:/run/host/share:/var/lib/flatpak/exports/share:~/.local/share/flatpak/exports/share:/var/lib/snapd/desktop", + "--env=XDG_DATA_DIRS=/app/share:/usr/share:/usr/share/runtime/share:/run/host/user-share:/run/host/usr/share:/run/host/share:/app/local/share:/usr/local/share:/usr/local/share/runtime/share:/run/host/usr/local/share:/run/host/local/share:/var/lib/flatpak/exports/share:~/.local/share/flatpak/exports/share:/var/lib/snapd/desktop", "--filesystem=/var/lib/snapd:ro", "--filesystem=/var/lib/flatpak/app:ro", "--filesystem=/var/lib/flatpak/exports/share:ro", diff --git a/lib/process_data/Cargo.lock b/lib/process_data/Cargo.lock index 26ea4cc4..4f44dbc1 100644 --- a/lib/process_data/Cargo.lock +++ b/lib/process_data/Cargo.lock @@ -13,15 +13,15 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.75" +version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" +checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" [[package]] name = "bitflags" -version = "1.3.2" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" [[package]] name = "cc" @@ -40,9 +40,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "darling" -version = "0.10.2" +version = "0.20.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d706e75d87e35569db781a9b5e2416cff1236a47ed380831f959382ccd5f858" +checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989" dependencies = [ "darling_core", "darling_macro", @@ -50,27 +50,27 @@ dependencies = [ [[package]] name = "darling_core" -version = "0.10.2" +version = "0.20.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0c960ae2da4de88a91b2d920c2a7233b400bc33cb28453a2987822d8392519b" +checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5" dependencies = [ "fnv", "ident_case", "proc-macro2", "quote", "strsim", - "syn 1.0.109", + "syn", ] [[package]] name = "darling_macro" -version = "0.10.2" +version = "0.20.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9b5a2f4ac4969822c62224815d069952656cadc7084fdca9751e6d959189b72" +checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" dependencies = [ "darling_core", "quote", - "syn 1.0.109", + "syn", ] [[package]] @@ -124,18 +124,18 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.150" +version = "0.2.155" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c" +checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" [[package]] name = "libloading" -version = "0.7.4" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" +checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4" dependencies = [ "cfg-if", - "winapi 0.3.9", + "windows-targets", ] [[package]] @@ -152,9 +152,9 @@ checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" [[package]] name = "nvml-wrapper" -version = "0.9.0" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cd21b9f5a1cce3c3515c9ffa85f5c7443e07162dae0ccf4339bb7ca38ad3454" +checksum = "0c9bff0aa1d48904a1385ea2a8b97576fbdcbc9a3cfccd0d31fe978e1c4038c5" dependencies = [ "bitflags", "libloading", @@ -166,31 +166,31 @@ dependencies = [ [[package]] name = "nvml-wrapper-sys" -version = "0.7.0" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c961a2ea9e91c59a69b78e69090f6f5b867bb46c0c56de9482da232437c4987e" +checksum = "698d45156f28781a4e79652b6ebe2eaa0589057d588d3aec1333f6466f13fcb5" dependencies = [ "libloading", ] [[package]] name = "once_cell" -version = "1.18.0" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] name = "proc-macro2" -version = "1.0.69" +version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da" +checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" dependencies = [ "unicode-ident", ] [[package]] name = "process-data" -version = "1.3.0" +version = "1.5.1" dependencies = [ "anyhow", "glob", @@ -207,18 +207,18 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.33" +version = "1.0.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" dependencies = [ "proc-macro2", ] [[package]] name = "regex" -version = "1.10.1" +version = "1.10.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aaac441002f822bc9705a681810a4dd2963094b9ca0ddc41cb963a4c189189ea" +checksum = "b91213439dad192326a0d7c6ee3955910425f441d7038e0d6933b0aec5c4517f" dependencies = [ "aho-corasick", "memchr", @@ -228,9 +228,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.2" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5011c7e263a695dc8ca064cddb722af1be54e517a280b12a5356f98366899e5d" +checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" dependencies = [ "aho-corasick", "memchr", @@ -245,22 +245,22 @@ checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" [[package]] name = "serde" -version = "1.0.189" +version = "1.0.204" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e422a44e74ad4001bdc8eede9a4570ab52f71190e9c076d14369f38b9200537" +checksum = "bc76f558e0cbb2a839d37354c575f1dc3fdc6546b5be373ba43d95f231bf7c12" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.189" +version = "1.0.204" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e48d1f918009ce3145511378cf68d613e3b3d9137d67272562080d68a2b32d5" +checksum = "e0cd7e117be63d3c3678776753929474f3b04a43a080c744d6b0ae2a8c28e222" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn", ] [[package]] @@ -271,7 +271,7 @@ checksum = "3081f5ffbb02284dda55132aa26daecedd7372a42417bbbab6f14ab7d6bb9145" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn", ] [[package]] @@ -282,26 +282,15 @@ checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" [[package]] name = "strsim" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6446ced80d6c486436db5c078dde11a9f73d42b57fb273121e160b84f63d894c" - -[[package]] -name = "syn" -version = "1.0.109" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "syn" -version = "2.0.38" +version = "2.0.72" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e96b79aaa137db8f61e26363a0c9b47d8b4ec75da28b7d1d614c2303e232408b" +checksum = "dc4b9b9bf2add8093d3f2c0204471e951b2285580335de42f9d2534f3ae7a8af" dependencies = [ "proc-macro2", "quote", @@ -310,9 +299,9 @@ dependencies = [ [[package]] name = "syscalls" -version = "0.6.15" +version = "0.6.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56b389b38331a454883a34fd19f25cbd1510b3510ff7aa28cb8d6de85d888439" +checksum = "43d0e35dc7d73976a53c7e6d7d177ef804a0c0ee774ec77bcc520c2216fd7cbe" dependencies = [ "serde", "serde_repr", @@ -347,7 +336,7 @@ checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn", ] [[package]] @@ -364,9 +353,9 @@ checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" [[package]] name = "uzers" -version = "0.11.3" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76d283dc7e8c901e79e32d077866eaf599156cbf427fffa8289aecc52c5c3f63" +checksum = "7d85875e16d59b3b1549efce83ff8251a64923b03bef94add0a1862847448de4" dependencies = [ "libc", "log", @@ -406,14 +395,78 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + [[package]] name = "wrapcenum-derive" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6bcc065c85ad2c3bd12aa4118bf164835712e25080c392557801a13292c60aec" +checksum = "a76ff259533532054cfbaefb115c613203c73707017459206380f03b3b3f266e" dependencies = [ "darling", "proc-macro2", "quote", - "syn 1.0.109", + "syn", ] diff --git a/lib/process_data/src/lib.rs b/lib/process_data/src/lib.rs index 1fb587af..076ac8ce 100644 --- a/lib/process_data/src/lib.rs +++ b/lib/process_data/src/lib.rs @@ -215,7 +215,7 @@ impl ProcessData { let data = ProcessData::try_from_path(entry); if let Ok(data) = data { - process_data.push(data) + process_data.push(data); } } @@ -268,10 +268,13 @@ impl ProcessData { let containerization = match &proc_path.join("root").join(".flatpak-info").exists() { true => Containerization::Flatpak, - false => match commandline.starts_with("/snap/") { - true => Containerization::Snap, - false => Containerization::None, - }, + false => { + if commandline.starts_with("/snap/") { + Containerization::Snap + } else { + Containerization::None + } + } }; let read_bytes = io.as_ref().and_then(|io| { @@ -312,8 +315,8 @@ impl ProcessData { }) } - fn gpu_usage_stats(proc_path: &PathBuf, pid: i32) -> BTreeMap { - let nvidia_stats = Self::nvidia_gpu_stats_all(pid).unwrap_or_default(); + fn gpu_usage_stats(proc_path: &Path, pid: i32) -> BTreeMap { + let nvidia_stats = Self::nvidia_gpu_stats_all(pid); let mut other_stats = Self::other_gpu_usage_stats(proc_path, pid).unwrap_or_default(); other_stats.extend(nvidia_stats); other_stats @@ -494,7 +497,7 @@ impl ProcessData { bail!("unable to find gpu information in this fdinfo"); } - fn nvidia_gpu_stats_all(pid: i32) -> Result> { + fn nvidia_gpu_stats_all(pid: i32) -> BTreeMap { let mut return_map = BTreeMap::new(); for (pci_slot, _) in NVML_DEVICES.iter() { @@ -503,7 +506,7 @@ impl ProcessData { } } - Ok(return_map) + return_map } fn nvidia_gpu_stats(pid: i32, pci_slot: PciSlot) -> Result { diff --git a/src/ui/dialogs/app_dialog.rs b/src/ui/dialogs/app_dialog.rs index 65a68518..56f1cd5c 100644 --- a/src/ui/dialogs/app_dialog.rs +++ b/src/ui/dialogs/app_dialog.rs @@ -1,12 +1,10 @@ -use adw::{prelude::*, subclass::prelude::*}; -use gtk::gio::ThemedIcon; -use gtk::glib; -use process_data::Containerization; - use crate::config::PROFILE; use crate::i18n::i18n; -use crate::utils::app::AppItem; +use crate::ui::pages::applications::application_entry::ApplicationEntry; use crate::utils::units::{convert_speed, convert_storage}; +use adw::{prelude::*, subclass::prelude::*}; +use gtk::gio::ThemedIcon; +use gtk::glib; mod imp { @@ -102,16 +100,16 @@ impl ResAppDialog { glib::Object::new::() } - pub fn init(&self, app: &AppItem) { + pub fn init(&self, app: &ApplicationEntry) { self.setup_widgets(app); } - pub fn setup_widgets(&self, app: &AppItem) { + pub fn setup_widgets(&self, app: &ApplicationEntry) { let imp = self.imp(); - if app.id.is_none() // this will be the case for System Processes + if app.id().is_none() // this will be the case for System Processes || app - .icon + .icon() .downcast_ref::() .is_some_and(|themed_icon| { themed_icon @@ -128,68 +126,64 @@ impl ResAppDialog { imp.icon.set_css_classes(&["big-bubble"]); } - imp.icon.set_from_gicon(&app.icon); + imp.icon.set_from_gicon(&app.icon()); - imp.name.set_label(&app.display_name); + imp.name.set_label(&app.name()); - if let Some(description) = &app.description { + if let Some(description) = &app.description() { imp.description.set_label(description); } else { imp.description.set_visible(false); } - if let Some(id) = &app.id { + if let Some(id) = &app.id() { imp.id.set_subtitle(id); } else { imp.id.set_visible(false); } - imp.running_since.set_subtitle(&app.running_since); + imp.running_since + .set_subtitle(&app.running_since().unwrap_or_else(|| i18n("N/A").into())); - let containerized = match app.containerization { - Containerization::None => i18n("No"), - Containerization::Flatpak => i18n("Yes (Flatpak)"), - Containerization::Snap => i18n("Yes (Snap)"), - }; - imp.containerized.set_subtitle(&containerized); + imp.containerized.set_subtitle(&app.containerization()); self.update(app); } - pub fn update(&self, app: &AppItem) { + pub fn update(&self, app: &ApplicationEntry) { let imp = self.imp(); imp.cpu_usage - .set_subtitle(&format!("{:.1} %", app.cpu_time_ratio * 100.0)); + .set_subtitle(&format!("{:.1} %", app.cpu_usage() * 100.0)); imp.memory_usage - .set_subtitle(&convert_storage(app.memory_usage as f64, false)); + .set_subtitle(&convert_storage(app.memory_usage() as f64, false)); imp.drive_read_speed - .set_subtitle(&convert_speed(app.read_speed, false)); + .set_subtitle(&convert_speed(app.read_speed(), false)); imp.drive_read_total - .set_subtitle(&convert_storage(app.read_total as f64, false)); + .set_subtitle(&convert_storage(app.read_total() as f64, false)); imp.drive_write_speed - .set_subtitle(&convert_speed(app.write_speed, false)); + .set_subtitle(&convert_speed(app.write_speed(), false)); imp.drive_write_total - .set_subtitle(&convert_storage(app.write_total as f64, false)); + .set_subtitle(&convert_storage(app.write_total() as f64, false)); imp.gpu_usage - .set_subtitle(&format!("{:.1} %", app.gpu_usage * 100.0)); + .set_subtitle(&format!("{:.1} %", app.gpu_usage() * 100.0)); imp.vram_usage - .set_subtitle(&convert_storage(app.gpu_mem_usage as f64, false)); + .set_subtitle(&convert_storage(app.gpu_mem_usage() as f64, false)); imp.encoder_usage - .set_subtitle(&format!("{:.1} %", app.enc_usage * 100.0)); + .set_subtitle(&format!("{:.1} %", app.enc_usage() * 100.0)); imp.decoder_usage - .set_subtitle(&format!("{:.1} %", app.dec_usage * 100.0)); + .set_subtitle(&format!("{:.1} %", app.dec_usage() * 100.0)); imp.processes_amount - .set_subtitle(&app.processes_amount.to_string()); + .set_subtitle(&app.running_processes().to_string()); } } diff --git a/src/ui/dialogs/process_dialog.rs b/src/ui/dialogs/process_dialog.rs index ea0bfe22..10546505 100644 --- a/src/ui/dialogs/process_dialog.rs +++ b/src/ui/dialogs/process_dialog.rs @@ -1,12 +1,9 @@ use adw::{prelude::*, subclass::prelude::*}; -use anyhow::Context; use gtk::glib; -use process_data::Containerization; use crate::config::PROFILE; use crate::i18n::i18n; -use crate::utils::boot_time; -use crate::utils::process::ProcessItem; +use crate::ui::pages::processes::process_entry::ProcessEntry; use crate::utils::units::{convert_speed, convert_storage, format_time}; mod imp { @@ -109,106 +106,96 @@ impl ResProcessDialog { glib::Object::new::() } - pub fn init>(&self, process: &ProcessItem, user: S) { + pub fn init>(&self, process: &ProcessEntry, user: S) { self.setup_widgets(process, user.as_ref()); } - pub fn setup_widgets(&self, process: &ProcessItem, user: &str) { + pub fn setup_widgets(&self, process: &ProcessEntry, user: &str) { let imp = self.imp(); - imp.name.set_label(&process.display_name); + imp.name.set_label(&process.name()); imp.user.set_subtitle(user); - imp.pid.set_subtitle(&process.pid.to_string()); + imp.pid.set_subtitle(&process.pid().to_string()); imp.running_since.set_subtitle( - &boot_time() - .and_then(|boot_time| { - boot_time - .add_seconds(process.starttime) - .context("unable to add seconds to boot time") - }) - .and_then(|time| time.format("%c").context("unable to format running_since")) - .map(|gstr| gstr.to_string()) - .unwrap_or(i18n("N/A")), + &process + .running_since() + .unwrap_or_else(|| i18n("N/A").into()), ); - imp.commandline.set_subtitle(&process.commandline); - imp.commandline.set_tooltip_text(Some(&process.commandline)); + imp.commandline.set_subtitle(&process.commandline()); + imp.commandline + .set_tooltip_text(Some(&process.commandline())); imp.cgroup - .set_subtitle(&process.cgroup.clone().unwrap_or_else(|| i18n("N/A"))); - imp.cgroup - .set_tooltip_text(Some(&process.cgroup.clone().unwrap_or_else(|| i18n("N/A")))); + .set_subtitle(&process.cgroup().unwrap_or_else(|| i18n("N/A").into())); + imp.cgroup.set_tooltip_text(Some( + &process.cgroup().unwrap_or_else(|| i18n("N/A").into()), + )); - let containerized = match process.containerization { - Containerization::None => i18n("No"), - Containerization::Flatpak => i18n("Yes (Flatpak)"), - Containerization::Snap => i18n("Yes (Snap)"), - }; - imp.containerized.set_subtitle(&containerized); + imp.containerized.set_subtitle(&process.containerization()); self.update(process); } - pub fn update(&self, process: &ProcessItem) { + pub fn update(&self, process: &ProcessEntry) { let imp = self.imp(); imp.cpu_usage - .set_subtitle(&format!("{:.1} %", process.cpu_time_ratio * 100.0)); + .set_subtitle(&format!("{:.1} %", process.cpu_usage() * 100.0)); imp.memory_usage - .set_subtitle(&convert_storage(process.memory_usage as f64, false)); + .set_subtitle(&convert_storage(process.memory_usage() as f64, false)); - if let Some(read_speed) = process.read_speed { - imp.drive_read_speed - .set_subtitle(&convert_speed(read_speed, false)); - } else { + if process.read_speed() == -1.0 { imp.drive_read_speed.set_subtitle(&i18n("N/A")); + } else { + imp.drive_read_speed + .set_subtitle(&convert_speed(process.read_speed(), false)); } - if let Some(read_total) = process.read_total { - imp.drive_read_total - .set_subtitle(&convert_storage(read_total as f64, false)); - } else { + if process.read_total() == -1 { imp.drive_read_total.set_subtitle(&i18n("N/A")); + } else { + imp.drive_read_total + .set_subtitle(&convert_storage(process.read_total() as f64, false)); } - if let Some(write_speed) = process.write_speed { - imp.drive_write_speed - .set_subtitle(&convert_speed(write_speed, false)); - } else { + if process.write_speed() == -1.0 { imp.drive_write_speed.set_subtitle(&i18n("N/A")); + } else { + imp.drive_write_speed + .set_subtitle(&convert_speed(process.write_speed(), false)); } - if let Some(write_total) = process.write_total { - imp.drive_write_total - .set_subtitle(&convert_storage(write_total as f64, false)); - } else { + if process.write_total() == -1 { imp.drive_write_total.set_subtitle(&i18n("N/A")); + } else { + imp.drive_write_total + .set_subtitle(&convert_storage(process.write_total() as f64, false)); } imp.gpu_usage - .set_subtitle(&format!("{:.1} %", process.gpu_usage * 100.0)); + .set_subtitle(&format!("{:.1} %", process.gpu_usage() * 100.0)); imp.vram_usage - .set_subtitle(&convert_storage(process.gpu_mem_usage as f64, false)); + .set_subtitle(&convert_storage(process.gpu_mem_usage() as f64, false)); imp.encoder_usage - .set_subtitle(&format!("{:.1} %", process.enc_usage * 100.0)); + .set_subtitle(&format!("{:.1} %", process.enc_usage() * 100.0)); imp.decoder_usage - .set_subtitle(&format!("{:.1} %", process.dec_usage * 100.0)); + .set_subtitle(&format!("{:.1} %", process.dec_usage() * 100.0)); - imp.total_cpu_time.set_subtitle(&format_time( - process.user_cpu_time + process.system_cpu_time, - )); + imp.total_cpu_time + .set_subtitle(&format_time(process.total_cpu_time())); imp.user_cpu_time - .set_subtitle(&format_time(process.user_cpu_time)); + .set_subtitle(&format_time(process.user_cpu_time())); imp.system_cpu_time - .set_subtitle(&format_time(process.system_cpu_time)); + .set_subtitle(&format_time(process.system_cpu_time())); } } diff --git a/src/ui/mod.rs b/src/ui/mod.rs index b6403841..e2c1293c 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -1,3 +1,43 @@ +extern crate paste; + +#[macro_export] +macro_rules! gstring_getter_setter { + ($($gstring_name:ident),*) => { + $( + pub fn $gstring_name(&self) -> glib::GString { + let $gstring_name = self.$gstring_name.take(); + self.$gstring_name.set($gstring_name.clone()); + $gstring_name + } + + paste::paste! { + pub fn [](&self, $gstring_name: &str) { + self.$gstring_name.set(glib::GString::from($gstring_name)); + } + } + )* + }; +} + +#[macro_export] +macro_rules! gstring_option_getter_setter { + ($($gstring_name:ident),*) => { + $( + pub fn $gstring_name(&self) -> Option { + let $gstring_name = self.$gstring_name.take(); + self.$gstring_name.set($gstring_name.clone()); + $gstring_name + } + + paste::paste! { + pub fn [](&self, $gstring_name: Option<&str>) { + self.$gstring_name.set($gstring_name.map(glib::GString::from)); + } + } + )* + }; +} + pub mod dialogs; pub mod pages; pub mod widgets; diff --git a/src/ui/pages/applications/application_entry.rs b/src/ui/pages/applications/application_entry.rs index 03fb6e77..ba00a9ea 100644 --- a/src/ui/pages/applications/application_entry.rs +++ b/src/ui/pages/applications/application_entry.rs @@ -1,12 +1,13 @@ -use gtk::{ - glib::{self}, - subclass::prelude::ObjectSubclassIsExt, -}; +use gtk::glib::{self}; +use process_data::Containerization; -use crate::utils::app::AppItem; +use crate::{ + i18n::i18n, + utils::app::{App, AppsContext}, +}; mod imp { - use std::cell::{Cell, RefCell}; + use std::cell::Cell; use glib::object::Cast; use gtk::{ @@ -16,6 +17,8 @@ mod imp { subclass::prelude::{DerivedObjectProperties, ObjectImpl, ObjectImplExt, ObjectSubclass}, }; + use crate::gstring_getter_setter; + use super::*; #[derive(Properties)] @@ -63,12 +66,19 @@ mod imp { #[property(get, set)] gpu_mem_usage: Cell, + #[property(get = Self::running_since, set = Self::set_running_since)] + running_since: Cell>, + + #[property(get = Self::containerization, set = Self::set_containerization)] + containerization: Cell, + + #[property(get, set)] + running_processes: Cell, + // TODO: Make this properly dynamic, don't use a variable that's never read #[property(get = Self::symbolic)] #[allow(dead_code)] symbolic: Cell, - - pub app_item: RefCell>, } impl Default for ApplicationEntry { @@ -84,46 +94,22 @@ mod imp { read_total: Cell::new(0), write_speed: Cell::new(0.0), write_total: Cell::new(0), - app_item: RefCell::new(None), gpu_usage: Cell::new(0.0), enc_usage: Cell::new(0.0), dec_usage: Cell::new(0.0), gpu_mem_usage: Cell::new(0), symbolic: Cell::new(false), + running_since: Cell::new(None), + containerization: Cell::new(glib::GString::default()), + running_processes: Cell::new(0), } } } impl ApplicationEntry { - pub fn name(&self) -> glib::GString { - let name = self.name.take(); - self.name.set(name.clone()); - name - } + gstring_getter_setter!(name, containerization); - pub fn set_name(&self, name: &str) { - self.name.set(glib::GString::from(name)); - } - - pub fn description(&self) -> Option { - let description = self.description.take(); - self.description.set(description.clone()); - description - } - - pub fn set_description(&self, description: Option<&str>) { - self.description.set(description.map(glib::GString::from)); - } - - pub fn id(&self) -> Option { - let id = self.id.take(); - self.id.set(id.clone()); - id - } - - pub fn set_id(&self, id: Option<&str>) { - self.id.set(id.map(glib::GString::from)); - } + gstring_option_getter_setter!(description, id, running_since); pub fn icon(&self) -> Icon { let icon = self.icon.replace(ThemedIcon::new("generic-process").into()); @@ -188,35 +174,35 @@ glib::wrapper! { } impl ApplicationEntry { - pub fn new(app_item: AppItem) -> Self { + pub fn new(app: &App, apps_context: &AppsContext) -> Self { + let containerization = match app.containerization { + Containerization::None => i18n("No"), + Containerization::Flatpak => i18n("Yes (Flatpak)"), + Containerization::Snap => i18n("Yes (Snap)"), + }; + let this: Self = glib::Object::builder() - .property("name", &app_item.display_name) - .property("icon", &app_item.icon) - .property("id", &app_item.id) + .property("name", &app.display_name) + .property("icon", &app.icon) + .property("id", &app.id) + .property("containerization", containerization) + .property("running_since", app.running_since(apps_context).ok()) .build(); - this.update(app_item); + this.update(app, apps_context); this } - pub fn update(&self, app_item: AppItem) { - self.set_cpu_usage(app_item.cpu_time_ratio); - self.set_memory_usage(app_item.memory_usage as u64); - self.set_read_speed(app_item.read_speed); - self.set_read_total(app_item.read_total); - self.set_write_speed(app_item.write_speed); - self.set_write_total(app_item.write_total); - self.set_gpu_usage(app_item.gpu_usage); - self.set_enc_usage(app_item.enc_usage); - self.set_dec_usage(app_item.dec_usage); - self.set_gpu_mem_usage(app_item.gpu_mem_usage); - - self.imp().app_item.replace(Some(app_item)); - } - - pub fn app_item(&self) -> Option { - let imp = self.imp(); - let item = imp.app_item.take(); - imp.app_item.replace(item.clone()); - item + pub fn update(&self, app: &App, apps_context: &AppsContext) { + self.set_cpu_usage(app.cpu_time_ratio(apps_context)); + self.set_memory_usage(app.memory_usage(apps_context) as u64); + self.set_read_speed(app.read_speed(apps_context)); + self.set_read_total(app.read_total(apps_context)); + self.set_write_speed(app.write_speed(apps_context)); + self.set_write_total(app.write_total(apps_context)); + self.set_gpu_usage(app.gpu_usage(apps_context)); + self.set_enc_usage(app.enc_usage(apps_context)); + self.set_dec_usage(app.dec_usage(apps_context)); + self.set_gpu_mem_usage(app.gpu_mem_usage(apps_context)); + self.set_running_processes(app.running_processes() as u32); } } diff --git a/src/ui/pages/applications/mod.rs b/src/ui/pages/applications/mod.rs index 4492421c..0c2cb0f1 100644 --- a/src/ui/pages/applications/mod.rs +++ b/src/ui/pages/applications/mod.rs @@ -1,4 +1,4 @@ -mod application_entry; +pub mod application_entry; mod application_name_cell; use std::collections::HashSet; @@ -16,7 +16,7 @@ use crate::config::PROFILE; use crate::i18n::{i18n, i18n_f}; use crate::ui::dialogs::app_dialog::ResAppDialog; use crate::ui::window::{Action, MainWindow}; -use crate::utils::app::{AppItem, AppsContext}; +use crate::utils::app::AppsContext; use crate::utils::process::ProcessAction; use crate::utils::settings::SETTINGS; use crate::utils::units::{convert_speed, convert_storage}; @@ -184,10 +184,8 @@ mod imp { if let Some(application_entry) = res_applications.imp().popped_over_app.borrow().as_ref() { - if let Some(app_item) = application_entry.app_item() { - res_applications - .execute_process_action_dialog(app_item, ProcessAction::TERM); - } + res_applications + .execute_process_action_dialog(application_entry, ProcessAction::TERM); } }, ); @@ -199,10 +197,8 @@ mod imp { if let Some(application_entry) = res_applications.imp().popped_over_app.borrow().as_ref() { - if let Some(app_item) = application_entry.app_item() { - res_applications - .execute_process_action_dialog(app_item, ProcessAction::KILL); - } + res_applications + .execute_process_action_dialog(application_entry, ProcessAction::KILL); } }, ); @@ -214,10 +210,8 @@ mod imp { if let Some(application_entry) = res_applications.imp().popped_over_app.borrow().as_ref() { - if let Some(app_item) = application_entry.app_item() { - res_applications - .execute_process_action_dialog(app_item, ProcessAction::STOP); - } + res_applications + .execute_process_action_dialog(application_entry, ProcessAction::STOP); } }, ); @@ -229,10 +223,8 @@ mod imp { if let Some(application_entry) = res_applications.imp().popped_over_app.borrow().as_ref() { - if let Some(app_item) = application_entry.app_item() { - res_applications - .execute_process_action_dialog(app_item, ProcessAction::CONT); - } + res_applications + .execute_process_action_dialog(application_entry, ProcessAction::CONT); } }, ); @@ -244,8 +236,7 @@ mod imp { if let Some(application_entry) = res_applications.imp().popped_over_app.borrow().as_ref() { - res_applications - .open_information_dialog(&application_entry.app_item().unwrap()); + res_applications.open_information_dialog(application_entry); } }, ); @@ -254,8 +245,8 @@ mod imp { "applications.kill-application", None, move |res_applications, _, _| { - if let Some(app) = res_applications.get_selected_app_item() { - res_applications.execute_process_action_dialog(app, ProcessAction::KILL); + if let Some(app) = res_applications.get_selected_app_entry() { + res_applications.execute_process_action_dialog(&app, ProcessAction::KILL); } }, ); @@ -264,8 +255,8 @@ mod imp { "applications.halt-application", None, move |res_applications, _, _| { - if let Some(app) = res_applications.get_selected_app_item() { - res_applications.execute_process_action_dialog(app, ProcessAction::STOP); + if let Some(app) = res_applications.get_selected_app_entry() { + res_applications.execute_process_action_dialog(&app, ProcessAction::STOP); } }, ); @@ -274,8 +265,8 @@ mod imp { "applications.continue-application", None, move |res_applications, _, _| { - if let Some(app) = res_applications.get_selected_app_item() { - res_applications.execute_process_action_dialog(app, ProcessAction::CONT); + if let Some(app) = res_applications.get_selected_app_entry() { + res_applications.execute_process_action_dialog(&app, ProcessAction::CONT); } }, ); @@ -481,7 +472,7 @@ impl ResApplications { .selected_item() .map(|object| object.downcast::().unwrap()); if let Some(selection) = selection_option { - this.open_information_dialog(&selection.app_item().unwrap()); + this.open_information_dialog(&selection); } } )); @@ -490,8 +481,8 @@ impl ResApplications { #[weak(rename_to = this)] self, move |_| { - if let Some(app) = this.get_selected_app_item() { - this.execute_process_action_dialog(app, ProcessAction::TERM); + if let Some(app) = this.get_selected_app_entry() { + this.execute_process_action_dialog(&app, ProcessAction::TERM); } } )); @@ -530,13 +521,13 @@ impl ResApplications { } } - pub fn open_information_dialog(&self, app_item: &AppItem) { + pub fn open_information_dialog(&self, app: &ApplicationEntry) { let imp = self.imp(); let app_dialog = ResAppDialog::new(); - app_dialog.init(app_item); + app_dialog.init(app); app_dialog.present(Some(&MainWindow::default())); *imp.open_dialog.borrow_mut() = Some(( - app_item.id.as_ref().map(std::string::ToString::to_string), + app.id().as_ref().map(std::string::ToString::to_string), app_dialog, )); } @@ -557,22 +548,22 @@ impl ResApplications { .contains(&search_string) } - pub fn get_selected_app_item(&self) -> Option { + pub fn get_selected_app_entry(&self) -> Option { self.imp() .selection_model .borrow() .selected_item() - .and_then(|object| object.downcast::().unwrap().app_item()) + .and_then(|object| object.downcast::().ok()) } - pub fn refresh_apps_list(&self, apps: &AppsContext) { + pub fn refresh_apps_list(&self, apps_context: &AppsContext) { let imp = self.imp(); let store = imp.store.borrow_mut(); let mut dialog_opt = &*imp.open_dialog.borrow_mut(); - let mut new_items = apps.app_items(); let mut ids_to_remove = HashSet::new(); + let mut already_existing_ids = HashSet::new(); // change process entries of apps that have run before store @@ -582,8 +573,8 @@ impl ResApplications { let app_id = object.id().map(|gs| gs.to_string()); // filter out apps that have run before but don't anymore if app_id.is_some() // don't try to filter out "System Processes" - && !apps - .get_app(&app_id.clone().unwrap_or_default()) + && !apps_context + .get_app(&app_id) .unwrap() .is_running() { @@ -596,13 +587,15 @@ impl ResApplications { *imp.popped_over_app.borrow_mut() = None; ids_to_remove.insert(app_id.clone()); } - if let Some((_, new_item)) = new_items.remove_entry(&app_id) { + + if let Some(app) = apps_context.get_app(&app_id) { + object.update(app, apps_context); if let Some((dialog_id, dialog)) = dialog_opt { if *dialog_id == app_id { - dialog.update(&new_item); + dialog.update(&object); } } - object.update(new_item); + already_existing_ids.insert(app_id); } }); @@ -619,9 +612,12 @@ impl ResApplications { }); // add the newly started apps to the store - let items: Vec = new_items - .drain() - .map(|(_, new_item)| ApplicationEntry::new(new_item)) + let items: Vec = apps_context + .running_apps_iter() + .filter(|app| { + !already_existing_ids.contains(&app.id) && !ids_to_remove.contains(&app.id) + }) + .map(|new_item| ApplicationEntry::new(new_item, apps_context)) .collect(); store.extend_from_slice(&items); @@ -636,13 +632,15 @@ impl ResApplications { ); } - pub fn execute_process_action_dialog(&self, app: AppItem, action: ProcessAction) { + pub fn execute_process_action_dialog(&self, app: &ApplicationEntry, action: ProcessAction) { // Nothing too bad can happen on Continue so dont show the dialog if action == ProcessAction::CONT { let main_context = MainContext::default(); main_context.spawn_local(clone!( #[weak(rename_to = this)] self, + #[weak] + app, async move { let imp = this.imp(); let _ = imp @@ -651,7 +649,7 @@ impl ResApplications { .unwrap() .send(Action::ManipulateApp( action, - app.id.unwrap(), + app.id().unwrap().to_string(), imp.toast_overlay.get(), )) .await; @@ -662,7 +660,7 @@ impl ResApplications { // Confirmation dialog & warning let dialog = adw::AlertDialog::builder() - .heading(get_action_name(action, &[&app.display_name])) + .heading(get_action_name(action, &[&app.name()])) .body(get_app_action_warning(action)) .build(); @@ -679,6 +677,8 @@ impl ResApplications { clone!( #[weak(rename_to = this)] self, + #[weak] + app, move |_, response| { if response == "yes" { let main_context = MainContext::default(); @@ -695,7 +695,7 @@ impl ResApplications { .unwrap() .send(Action::ManipulateApp( action, - app.id.clone().unwrap(), + app.id().unwrap().to_string(), imp.toast_overlay.get(), )) .await; @@ -739,6 +739,11 @@ impl ResApplications { .bind(&row, "symbolic", Widget::NONE); }); + name_col_factory.connect_teardown(move |_factory, item| { + let item = item.downcast_ref::(); + item.unwrap().set_child(None::<&ResApplicationNameCell>); + }); + let name_col_sorter = StringSorter::builder() .ignore_case(true) .expression(gtk::PropertyExpression::new( @@ -780,6 +785,11 @@ impl ResApplications { .bind(&row, "text", Widget::NONE); }); + memory_col_factory.connect_teardown(move |_factory, item| { + let item = item.downcast_ref::(); + item.unwrap().set_child(None::<>k::Inscription>); + }); + let memory_col_sorter = NumericSorter::builder() .sort_order(SortType::Ascending) .expression(gtk::PropertyExpression::new( @@ -832,6 +842,11 @@ impl ResApplications { .bind(&row, "text", Widget::NONE); }); + cpu_col_factory.connect_teardown(move |_factory, item| { + let item = item.downcast_ref::(); + item.unwrap().set_child(None::<>k::Inscription>); + }); + let cpu_col_sorter = NumericSorter::builder() .sort_order(SortType::Ascending) .expression(gtk::PropertyExpression::new( @@ -885,6 +900,11 @@ impl ResApplications { .bind(&row, "text", Widget::NONE); }); + read_speed_col_factory.connect_teardown(move |_factory, item| { + let item = item.downcast_ref::(); + item.unwrap().set_child(None::<>k::Inscription>); + }); + let read_speed_col_sorter = NumericSorter::builder() .sort_order(SortType::Ascending) .expression(gtk::PropertyExpression::new( @@ -934,6 +954,11 @@ impl ResApplications { .bind(&row, "text", Widget::NONE); }); + read_total_col_factory.connect_teardown(move |_factory, item| { + let item = item.downcast_ref::(); + item.unwrap().set_child(None::<>k::Inscription>); + }); + let read_total_col_sorter = NumericSorter::builder() .sort_order(SortType::Ascending) .expression(gtk::PropertyExpression::new( @@ -987,6 +1012,11 @@ impl ResApplications { .bind(&row, "text", Widget::NONE); }); + write_speed_col_factory.connect_teardown(move |_factory, item| { + let item = item.downcast_ref::(); + item.unwrap().set_child(None::<>k::Inscription>); + }); + let write_speed_col_sorter = NumericSorter::builder() .sort_order(SortType::Ascending) .expression(gtk::PropertyExpression::new( @@ -1038,6 +1068,11 @@ impl ResApplications { .bind(&row, "text", Widget::NONE); }); + write_total_col_factory.connect_teardown(move |_factory, item| { + let item = item.downcast_ref::(); + item.unwrap().set_child(None::<>k::Inscription>); + }); + let write_total_col_sorter = NumericSorter::builder() .sort_order(SortType::Ascending) .expression(gtk::PropertyExpression::new( @@ -1086,6 +1121,11 @@ impl ResApplications { .bind(&row, "text", Widget::NONE); }); + gpu_col_factory.connect_teardown(move |_factory, item| { + let item = item.downcast_ref::(); + item.unwrap().set_child(None::<>k::Inscription>); + }); + let gpu_col_sorter = NumericSorter::builder() .sort_order(SortType::Ascending) .expression(gtk::PropertyExpression::new( @@ -1135,6 +1175,11 @@ impl ResApplications { .bind(&row, "text", Widget::NONE); }); + encoder_col_factory.connect_teardown(move |_factory, item| { + let item = item.downcast_ref::(); + item.unwrap().set_child(None::<>k::Inscription>); + }); + let encoder_col_sorter = NumericSorter::builder() .sort_order(SortType::Ascending) .expression(gtk::PropertyExpression::new( @@ -1184,6 +1229,11 @@ impl ResApplications { .bind(&row, "text", Widget::NONE); }); + decoder_col_factory.connect_teardown(move |_factory, item| { + let item = item.downcast_ref::(); + item.unwrap().set_child(None::<>k::Inscription>); + }); + let decoder_col_sorter = NumericSorter::builder() .sort_order(SortType::Ascending) .expression(gtk::PropertyExpression::new( @@ -1230,6 +1280,11 @@ impl ResApplications { .bind(&row, "text", Widget::NONE); }); + gpu_mem_col_factory.connect_teardown(move |_factory, item| { + let item = item.downcast_ref::(); + item.unwrap().set_child(None::<>k::Inscription>); + }); + let gpu_mem_col_sorter = NumericSorter::builder() .sort_order(SortType::Ascending) .expression(gtk::PropertyExpression::new( diff --git a/src/ui/pages/processes/mod.rs b/src/ui/pages/processes/mod.rs index eddbb036..874b7068 100644 --- a/src/ui/pages/processes/mod.rs +++ b/src/ui/pages/processes/mod.rs @@ -1,4 +1,4 @@ -mod process_entry; +pub mod process_entry; mod process_name_cell; use std::collections::HashSet; @@ -17,7 +17,7 @@ use crate::i18n::{i18n, i18n_f}; use crate::ui::dialogs::process_dialog::ResProcessDialog; use crate::ui::window::{Action, MainWindow}; use crate::utils::app::AppsContext; -use crate::utils::process::{ProcessAction, ProcessItem}; +use crate::utils::process::ProcessAction; use crate::utils::settings::SETTINGS; use crate::utils::units::{convert_speed, convert_storage, format_time}; use crate::utils::NUM_CPUS; @@ -191,10 +191,8 @@ mod imp { if let Some(process_entry) = res_processes.imp().popped_over_process.borrow().as_ref() { - if let Some(process_item) = process_entry.process_item() { - res_processes - .execute_process_action_dialog(process_item, ProcessAction::TERM); - } + res_processes + .execute_process_action_dialog(process_entry, ProcessAction::TERM); } }, ); @@ -206,10 +204,8 @@ mod imp { if let Some(process_entry) = res_processes.imp().popped_over_process.borrow().as_ref() { - if let Some(process_item) = process_entry.process_item() { - res_processes - .execute_process_action_dialog(process_item, ProcessAction::KILL); - } + res_processes + .execute_process_action_dialog(process_entry, ProcessAction::KILL); } }, ); @@ -221,10 +217,8 @@ mod imp { if let Some(process_entry) = res_processes.imp().popped_over_process.borrow().as_ref() { - if let Some(process_item) = process_entry.process_item() { - res_processes - .execute_process_action_dialog(process_item, ProcessAction::STOP); - } + res_processes + .execute_process_action_dialog(process_entry, ProcessAction::STOP); } }, ); @@ -236,10 +230,8 @@ mod imp { if let Some(process_entry) = res_processes.imp().popped_over_process.borrow().as_ref() { - if let Some(process_item) = process_entry.process_item() { - res_processes - .execute_process_action_dialog(process_item, ProcessAction::CONT); - } + res_processes + .execute_process_action_dialog(process_entry, ProcessAction::CONT); } }, ); @@ -251,8 +243,7 @@ mod imp { if let Some(process_entry) = res_processes.imp().popped_over_process.borrow().as_ref() { - res_processes - .open_information_dialog(&process_entry.process_item().unwrap()); + res_processes.open_information_dialog(process_entry); } }, ); @@ -261,8 +252,8 @@ mod imp { "processes.kill-process", None, move |res_processes, _, _| { - if let Some(process) = res_processes.get_selected_process_item() { - res_processes.execute_process_action_dialog(process, ProcessAction::KILL); + if let Some(process) = res_processes.get_selected_process_entry() { + res_processes.execute_process_action_dialog(&process, ProcessAction::KILL); } }, ); @@ -271,8 +262,8 @@ mod imp { "processes.halt-process", None, move |res_processes, _, _| { - if let Some(process) = res_processes.get_selected_process_item() { - res_processes.execute_process_action_dialog(process, ProcessAction::STOP); + if let Some(process) = res_processes.get_selected_process_entry() { + res_processes.execute_process_action_dialog(&process, ProcessAction::STOP); } }, ); @@ -281,8 +272,8 @@ mod imp { "processes.continue-process", None, move |res_processes, _, _| { - if let Some(process) = res_processes.get_selected_process_item() { - res_processes.execute_process_action_dialog(process, ProcessAction::CONT); + if let Some(process) = res_processes.get_selected_process_entry() { + res_processes.execute_process_action_dialog(&process, ProcessAction::CONT); } }, ); @@ -485,7 +476,7 @@ impl ResProcesses { .selected_item() .map(|object| object.downcast::().unwrap()); if let Some(selection) = selection_option { - this.open_information_dialog(&selection.process_item().unwrap()); + this.open_information_dialog(&selection); } } )); @@ -494,8 +485,8 @@ impl ResProcesses { #[weak(rename_to = this)] self, move |_| { - if let Some(app) = this.get_selected_process_item() { - this.execute_process_action_dialog(app, ProcessAction::TERM); + if let Some(process) = this.get_selected_process_entry() { + this.execute_process_action_dialog(&process, ProcessAction::TERM); } } )); @@ -534,12 +525,12 @@ impl ResProcesses { } } - pub fn open_information_dialog(&self, process: &ProcessItem) { + pub fn open_information_dialog(&self, process: &ProcessEntry) { let imp = self.imp(); let process_dialog = ResProcessDialog::new(); - process_dialog.init(process, &process.user); + process_dialog.init(process, process.user()); process_dialog.present(Some(&MainWindow::default())); - *imp.open_dialog.borrow_mut() = Some((process.pid, process_dialog)); + *imp.open_dialog.borrow_mut() = Some((process.pid(), process_dialog)); } fn search_filter(&self, obj: &Object) -> bool { @@ -551,44 +542,44 @@ impl ResProcesses { || item.commandline().to_lowercase().contains(&search_string) } - pub fn get_selected_process_item(&self) -> Option { + pub fn get_selected_process_entry(&self) -> Option { self.imp() .selection_model .borrow() .selected_item() - .and_then(|object| object.downcast::().unwrap().process_item()) + .and_then(|object| object.downcast::().ok()) } - pub fn refresh_processes_list(&self, apps: &AppsContext) { + pub fn refresh_processes_list(&self, apps_context: &AppsContext) { let imp = self.imp(); let store = imp.store.borrow_mut(); let mut dialog_opt = &*imp.open_dialog.borrow_mut(); - let mut new_items = apps.process_items(); let mut pids_to_remove = HashSet::new(); + let mut already_existing_pids = HashSet::new(); // change process entries of processes that have existed before store.iter::().flatten().for_each(|object| { let item_pid = object.pid(); - // filter out processes that have existed before but don't anymore - if apps.get_process(item_pid).is_none() { + if let Some(process) = apps_context.get_process(item_pid) { + object.update(process); if let Some((dialog_pid, dialog)) = dialog_opt { if *dialog_pid == item_pid { - dialog.close(); - dialog_opt = &None; + dialog.update(&object); } } - *imp.popped_over_process.borrow_mut() = None; - pids_to_remove.insert(item_pid); - } - if let Some((_, new_item)) = new_items.remove_entry(&item_pid) { + already_existing_pids.insert(item_pid); + } else { + // filter out processes that have existed before but don't anymore if let Some((dialog_pid, dialog)) = dialog_opt { if *dialog_pid == item_pid { - dialog.update(&new_item); + dialog.close(); + dialog_opt = &None; } } - object.update(new_item); + *imp.popped_over_process.borrow_mut() = None; + pids_to_remove.insert(item_pid); } }); @@ -598,9 +589,13 @@ impl ResProcesses { }); // add the newly started process to the store - let items: Vec = new_items - .drain() - .map(|(_, new_item)| ProcessEntry::new(new_item)) + let items: Vec = apps_context + .processes_iter() + .filter(|process| { + !already_existing_pids.contains(&process.data.pid) + && !pids_to_remove.contains(&process.data.pid) + }) + .map(ProcessEntry::new) .collect(); store.extend_from_slice(&items); @@ -614,13 +609,15 @@ impl ResProcesses { ); } - pub fn execute_process_action_dialog(&self, process: ProcessItem, action: ProcessAction) { + pub fn execute_process_action_dialog(&self, process: &ProcessEntry, action: ProcessAction) { // Nothing too bad can happen on Continue so dont show the dialog if action == ProcessAction::CONT { let main_context = MainContext::default(); main_context.spawn_local(clone!( #[weak(rename_to = this)] self, + #[weak] + process, async move { let imp = this.imp(); let _ = imp @@ -629,8 +626,8 @@ impl ResProcesses { .unwrap() .send(Action::ManipulateProcess( action, - process.pid, - process.clone().display_name, + process.pid(), + process.name().to_string(), imp.toast_overlay.get(), )) .await; @@ -641,7 +638,7 @@ impl ResProcesses { // Confirmation dialog & warning let dialog = adw::AlertDialog::builder() - .heading(get_action_name(action, &[&process.display_name])) + .heading(get_action_name(action, &[&process.name()])) .body(get_process_action_warning(action)) .build(); @@ -658,6 +655,8 @@ impl ResProcesses { clone!( #[weak(rename_to = this)] self, + #[weak] + process, move |_, response| { if response == "yes" { let main_context = MainContext::default(); @@ -674,8 +673,8 @@ impl ResProcesses { .unwrap() .send(Action::ManipulateProcess( action, - process.pid, - process.clone().display_name, + process.pid(), + process.name().to_string(), imp.toast_overlay.get(), )) .await; @@ -719,6 +718,11 @@ impl ResProcesses { .bind(&row, "tooltip", Widget::NONE); }); + name_col_factory.connect_teardown(move |_factory, item| { + let item = item.downcast_ref::(); + item.unwrap().set_child(None::<&ResProcessNameCell>); + }); + let name_col_sorter = StringSorter::builder() .ignore_case(true) .expression(gtk::PropertyExpression::new( @@ -754,6 +758,11 @@ impl ResProcesses { .bind(&row, "text", Widget::NONE); }); + pid_col_factory.connect_teardown(move |_factory, item| { + let item = item.downcast_ref::(); + item.unwrap().set_child(None::<>k::Inscription>); + }); + let pid_col_sorter = NumericSorter::builder() .sort_order(SortType::Ascending) .expression(gtk::PropertyExpression::new( @@ -797,6 +806,11 @@ impl ResProcesses { .bind(&row, "text", Widget::NONE); }); + user_col_factory.connect_teardown(move |_factory, item| { + let item = item.downcast_ref::(); + item.unwrap().set_child(None::<>k::Inscription>); + }); + let user_col_sorter = StringSorter::builder() .ignore_case(true) .expression(gtk::PropertyExpression::new( @@ -844,6 +858,11 @@ impl ResProcesses { .bind(&row, "text", Widget::NONE); }); + memory_col_factory.connect_teardown(move |_factory, item| { + let item = item.downcast_ref::(); + item.unwrap().set_child(None::<>k::Inscription>); + }); + let memory_col_sorter = NumericSorter::builder() .sort_order(SortType::Ascending) .expression(gtk::PropertyExpression::new( @@ -896,6 +915,11 @@ impl ResProcesses { .bind(&row, "text", Widget::NONE); }); + cpu_col_factory.connect_teardown(move |_factory, item| { + let item = item.downcast_ref::(); + item.unwrap().set_child(None::<>k::Inscription>); + }); + let cpu_col_sorter = NumericSorter::builder() .sort_order(SortType::Ascending) .expression(gtk::PropertyExpression::new( @@ -949,6 +973,11 @@ impl ResProcesses { .bind(&row, "text", Widget::NONE); }); + read_speed_col_factory.connect_teardown(move |_factory, item| { + let item = item.downcast_ref::(); + item.unwrap().set_child(None::<>k::Inscription>); + }); + let read_speed_col_sorter = NumericSorter::builder() .sort_order(SortType::Ascending) .expression(gtk::PropertyExpression::new( @@ -1004,6 +1033,11 @@ impl ResProcesses { .bind(&row, "text", Widget::NONE); }); + read_total_col_factory.connect_teardown(move |_factory, item| { + let item = item.downcast_ref::(); + item.unwrap().set_child(None::<>k::Inscription>); + }); + let read_total_col_sorter = NumericSorter::builder() .sort_order(SortType::Ascending) .expression(gtk::PropertyExpression::new( @@ -1059,6 +1093,11 @@ impl ResProcesses { .bind(&row, "text", Widget::NONE); }); + write_speed_col_factory.connect_teardown(move |_factory, item| { + let item = item.downcast_ref::(); + item.unwrap().set_child(None::<>k::Inscription>); + }); + let write_speed_col_sorter = NumericSorter::builder() .sort_order(SortType::Ascending) .expression(gtk::PropertyExpression::new( @@ -1114,6 +1153,11 @@ impl ResProcesses { .bind(&row, "text", Widget::NONE); }); + write_total_col_factory.connect_teardown(move |_factory, item| { + let item = item.downcast_ref::(); + item.unwrap().set_child(None::<>k::Inscription>); + }); + let write_total_col_sorter = NumericSorter::builder() .sort_order(SortType::Ascending) .expression(gtk::PropertyExpression::new( @@ -1162,6 +1206,11 @@ impl ResProcesses { .bind(&row, "text", Widget::NONE); }); + gpu_col_factory.connect_teardown(move |_factory, item| { + let item = item.downcast_ref::(); + item.unwrap().set_child(None::<>k::Inscription>); + }); + let gpu_col_sorter = NumericSorter::builder() .sort_order(SortType::Ascending) .expression(gtk::PropertyExpression::new( @@ -1211,6 +1260,11 @@ impl ResProcesses { .bind(&row, "text", Widget::NONE); }); + encoder_col_factory.connect_teardown(move |_factory, item| { + let item = item.downcast_ref::(); + item.unwrap().set_child(None::<>k::Inscription>); + }); + let encoder_col_sorter = NumericSorter::builder() .sort_order(SortType::Ascending) .expression(gtk::PropertyExpression::new( @@ -1260,6 +1314,11 @@ impl ResProcesses { .bind(&row, "text", Widget::NONE); }); + decoder_col_factory.connect_teardown(move |_factory, item| { + let item = item.downcast_ref::(); + item.unwrap().set_child(None::<>k::Inscription>); + }); + let decoder_col_sorter = NumericSorter::builder() .sort_order(SortType::Ascending) .expression(gtk::PropertyExpression::new( @@ -1308,6 +1367,11 @@ impl ResProcesses { .bind(&row, "text", Widget::NONE); }); + gpu_mem_col_factory.connect_teardown(move |_factory, item| { + let item = item.downcast_ref::(); + item.unwrap().set_child(None::<>k::Inscription>); + }); + let gpu_mem_col_sorter = NumericSorter::builder() .sort_order(SortType::Ascending) .expression(gtk::PropertyExpression::new( @@ -1356,6 +1420,11 @@ impl ResProcesses { .bind(&row, "text", Widget::NONE); }); + total_cpu_time_col_factory.connect_teardown(move |_factory, item| { + let item = item.downcast_ref::(); + item.unwrap().set_child(None::<>k::Inscription>); + }); + let total_cpu_time_col_sorter = NumericSorter::builder() .sort_order(SortType::Ascending) .expression(gtk::PropertyExpression::new( @@ -1404,6 +1473,11 @@ impl ResProcesses { .bind(&row, "text", Widget::NONE); }); + user_cpu_time_col_factory.connect_teardown(move |_factory, item| { + let item = item.downcast_ref::(); + item.unwrap().set_child(None::<>k::Inscription>); + }); + let user_cpu_time_col_sorter = NumericSorter::builder() .sort_order(SortType::Ascending) .expression(gtk::PropertyExpression::new( @@ -1452,6 +1526,11 @@ impl ResProcesses { .bind(&row, "text", Widget::NONE); }); + system_cpu_time_col_factory.connect_teardown(move |_factory, item| { + let item = item.downcast_ref::(); + item.unwrap().set_child(None::<>k::Inscription>); + }); + let system_cpu_time_col_sorter = NumericSorter::builder() .sort_order(SortType::Ascending) .expression(gtk::PropertyExpression::new( diff --git a/src/ui/pages/processes/process_entry.rs b/src/ui/pages/processes/process_entry.rs index 9ed2466a..2ca38214 100644 --- a/src/ui/pages/processes/process_entry.rs +++ b/src/ui/pages/processes/process_entry.rs @@ -1,12 +1,13 @@ -use gtk::{ - glib::{self}, - subclass::prelude::ObjectSubclassIsExt, -}; +use gtk::glib::{self, GString}; +use process_data::Containerization; -use crate::utils::process::ProcessItem; +use crate::{ + i18n::i18n, + utils::{process::Process, TICK_RATE}, +}; mod imp { - use std::cell::{Cell, RefCell}; + use std::cell::Cell; use gtk::{ gio::{Icon, ThemedIcon}, @@ -74,7 +75,14 @@ mod imp { #[property(get, set)] system_cpu_time: Cell, - pub process_item: RefCell>, + #[property(get = Self::cgroup, set = Self::set_cgroup)] + cgroup: Cell>, + + #[property(get = Self::containerization, set = Self::set_containerization)] + containerization: Cell, + + #[property(get = Self::running_since, set = Self::set_running_since)] + running_since: Cell>, } impl Default for ProcessEntry { @@ -87,7 +95,6 @@ mod imp { pid: Cell::new(0), cpu_usage: Cell::new(0.0), memory_usage: Cell::new(0), - process_item: RefCell::new(None), read_speed: Cell::new(0.0), read_total: Cell::new(0), write_speed: Cell::new(0.0), @@ -99,40 +106,16 @@ mod imp { total_cpu_time: Cell::new(0.0), user_cpu_time: Cell::new(0.0), system_cpu_time: Cell::new(0.0), + cgroup: Cell::new(None), + containerization: Cell::new(glib::GString::default()), + running_since: Cell::new(None), } } } impl ProcessEntry { - pub fn name(&self) -> glib::GString { - let name = self.name.take(); - self.name.set(name.clone()); - name - } - - pub fn set_name(&self, name: &str) { - self.name.set(glib::GString::from(name)); - } - - pub fn commandline(&self) -> glib::GString { - let commandline = self.commandline.take(); - self.commandline.set(commandline.clone()); - commandline - } - - pub fn set_commandline(&self, commandline: &str) { - self.commandline.set(glib::GString::from(commandline)); - } - - pub fn user(&self) -> glib::GString { - let user = self.user.take(); - self.user.set(user.clone()); - user - } - - pub fn set_user(&self, user: &str) { - self.user.set(glib::GString::from(user)); - } + gstring_getter_setter!(user, commandline, name, containerization); + gstring_option_getter_setter!(cgroup, running_since); pub fn icon(&self) -> Icon { let icon = self.icon.replace(ThemedIcon::new("generic-process").into()); @@ -175,48 +158,56 @@ glib::wrapper! { } impl ProcessEntry { - pub fn new(process_item: ProcessItem) -> Self { + pub fn new(process: &Process) -> Self { + let display_name = if process.executable_name.starts_with(&process.data.comm) { + process.executable_name.clone() + } else { + process.data.comm.clone() + }; + + let containerization = match process.data.containerization { + Containerization::None => i18n("No"), + Containerization::Flatpak => i18n("Yes (Flatpak)"), + Containerization::Snap => i18n("Yes (Snap)"), + }; + let this: Self = glib::Object::builder() - .property("name", &process_item.display_name) - .property("commandline", &process_item.commandline) - .property("user", &process_item.user) - .property("icon", &process_item.icon) - .property("pid", process_item.pid) + .property("name", &display_name) + .property("commandline", process.data.commandline.replace('\0', " ")) + .property("user", &process.data.user) + .property("icon", &process.icon) + .property("pid", process.data.pid) + .property("cgroup", process.data.cgroup.clone().map(GString::from)) + .property("containerization", containerization) + .property("running_since", process.running_since().ok()) .build(); - this.update(process_item); + this.update(process); this } - pub fn update(&self, process_item: ProcessItem) { - self.set_cpu_usage(process_item.cpu_time_ratio); - self.set_memory_usage(process_item.memory_usage as u64); - self.set_read_speed(process_item.read_speed.unwrap_or(-1.0)); + pub fn update(&self, process: &Process) { + self.set_cpu_usage(process.cpu_time_ratio()); + self.set_memory_usage(process.data.memory_usage as u64); + self.set_read_speed(process.read_speed().unwrap_or(-1.0)); self.set_read_total( - process_item - .read_total + process + .data + .read_bytes .map_or(-1, |read_total| read_total as i64), ); - self.set_write_speed(process_item.write_speed.unwrap_or(-1.0)); + self.set_write_speed(process.write_speed().unwrap_or(-1.0)); self.set_write_total( - process_item - .write_total + process + .data + .write_bytes .map_or(-1, |write_total| write_total as i64), ); - self.set_gpu_usage(process_item.gpu_usage); - self.set_enc_usage(process_item.enc_usage); - self.set_dec_usage(process_item.dec_usage); - self.set_gpu_mem_usage(process_item.gpu_mem_usage); - self.set_total_cpu_time(process_item.user_cpu_time + process_item.system_cpu_time); - self.set_user_cpu_time(process_item.user_cpu_time); - self.set_system_cpu_time(process_item.system_cpu_time); - - self.imp().process_item.replace(Some(process_item)); - } - - pub fn process_item(&self) -> Option { - let imp = self.imp(); - let item = imp.process_item.take(); - imp.process_item.replace(item.clone()); - item + self.set_gpu_usage(process.gpu_usage()); + self.set_enc_usage(process.enc_usage()); + self.set_dec_usage(process.dec_usage()); + self.set_gpu_mem_usage(process.gpu_mem_usage()); + self.set_user_cpu_time((process.data.user_cpu_time as f64) / (*TICK_RATE as f64)); + self.set_system_cpu_time((process.data.system_cpu_time as f64) / (*TICK_RATE as f64)); + self.set_total_cpu_time(self.user_cpu_time() + self.system_cpu_time()); } } diff --git a/src/ui/window.rs b/src/ui/window.rs index 4856de71..56653269 100644 --- a/src/ui/window.rs +++ b/src/ui/window.rs @@ -1,4 +1,3 @@ -use hashbrown::HashMap; use process_data::ProcessData; use std::path::PathBuf; use std::time::Duration; @@ -38,7 +37,7 @@ pub enum Action { } mod imp { - use std::cell::RefCell; + use std::{cell::RefCell, collections::HashMap}; use crate::{ ui::{ @@ -247,14 +246,14 @@ impl MainWindow { let selected_page = self.get_selected_page().unwrap(); if selected_page.is::() { - if let Some(app_item) = imp.applications.get_selected_app_item() { + if let Some(app_item) = imp.applications.get_selected_app_entry() { imp.applications - .execute_process_action_dialog(app_item, process_action); + .execute_process_action_dialog(&app_item, process_action); } } else if selected_page.is::() { - if let Some(process_item) = imp.processes.get_selected_process_item() { + if let Some(process_item) = imp.processes.get_selected_process_entry() { imp.processes - .execute_process_action_dialog(process_item, process_action); + .execute_process_action_dialog(&process_item, process_action); } } } @@ -265,11 +264,11 @@ impl MainWindow { let selected_page = self.get_selected_page().unwrap(); if selected_page.is::() { - if let Some(app_item) = imp.applications.get_selected_app_item() { + if let Some(app_item) = imp.applications.get_selected_app_entry() { imp.applications.open_information_dialog(&app_item); } } else if selected_page.is::() { - if let Some(process_item) = imp.processes.get_selected_process_item() { + if let Some(process_item) = imp.processes.get_selected_process_entry() { imp.processes.open_information_dialog(&process_item); } } @@ -835,7 +834,7 @@ impl MainWindow { } Action::ManipulateApp(action, id, toast_overlay) => { - let app = apps_context.get_app(&id).unwrap(); + let app = apps_context.get_app(&Some(id.clone())).unwrap(); let res = app.execute_process_action(&apps_context, action); for r in &res { diff --git a/src/utils/app.rs b/src/utils/app.rs index 65d1d62e..5457bdc6 100644 --- a/src/utils/app.rs +++ b/src/utils/app.rs @@ -1,4 +1,5 @@ use std::{ + collections::{HashMap, HashSet}, path::{Path, PathBuf}, sync::LazyLock, time::Instant, @@ -9,7 +10,6 @@ use gtk::{ gio::{File, FileIcon, Icon, ThemedIcon}, glib::GString, }; -use hashbrown::{HashMap, HashSet}; use log::{debug, info}; use process_data::{pci_slot::PciSlot, Containerization, ProcessData}; use regex::Regex; @@ -18,19 +18,32 @@ use crate::i18n::i18n; use super::{ boot_time, - process::{Process, ProcessAction, ProcessItem}, - FiniteOr, TICK_RATE, + process::{Process, ProcessAction}, + FiniteOr, }; // This contains executable names that are blacklisted from being recognized as applications const DESKTOP_EXEC_BLOCKLIST: &[&str] = &["bash", "zsh", "fish", "sh", "ksh", "flatpak"]; // This contains IDs of desktop files that shouldn't be counted as applications for whatever reason -const APP_ID_BLOCKLIST: &[&str] = &[ - "org.gnome.Terminal.Preferences", // Prevents the actual Terminal app "org.gnome.Terminal" from being shown - "org.freedesktop.IBus.Panel.Extension.Gtk3", // Technical application - "org.gnome.RemoteDesktop.Handover", // Technical application -]; +static APP_ID_BLOCKLIST: LazyLock> = LazyLock::new(|| { + HashMap::from([ + ( + "org.gnome.Terminal.Preferences", + "Prevents the actual Terminal app \"org.gnome.Terminal\" from being shown", + ), + ( + "org.freedesktop.IBus.Panel.Extension.Gtk3", + "Technical application", + ), + ("org.gnome.RemoteDesktop.Handover", "Technical application"), + ( + "gnome-software-local-file-packagekit", + "Technical application", + ), + ("snap-handle-link", "Technical application"), + ]) +}); static RE_ENV_FILTER: LazyLock = LazyLock::new(|| Regex::new(r"env\s*\S*=\S*\s*(.*)").unwrap()); @@ -67,16 +80,17 @@ pub static DATA_DIRS: LazyLock> = LazyLock::new(|| { // The HashMap is used like this: // Key: The name of the executable of the process // Value: What it should be replaced with when finding out to which app it belongs -static KNOWN_EXECUTABLE_NAME_EXCEPTIONS: LazyLock> = LazyLock::new(|| { - HashMap::from([ - ("firefox-bin".into(), "firefox".into()), - ("oosplash".into(), "libreoffice".into()), - ("soffice.bin".into(), "libreoffice".into()), - ("resources-processes".into(), "resources".into()), - ("gnome-terminal-server".into(), "gnome-terminal".into()), - ("chrome".into(), "google-chrome-stable".into()), - ]) -}); +static KNOWN_EXECUTABLE_NAME_EXCEPTIONS: LazyLock> = + LazyLock::new(|| { + HashMap::from([ + ("firefox-bin", "firefox"), + ("oosplash", "libreoffice"), + ("soffice.bin", "libreoffice"), + ("resources-processes", "resources"), + ("gnome-terminal-server", "gnome-terminal"), + ("chrome", "google-chrome-stable"), + ]) + }); static MESSAGE_LOCALES: LazyLock> = LazyLock::new(|| { let envs = ["LC_MESSAGES", "LANGUAGE", "LANG", "LC_ALL"]; @@ -119,34 +133,8 @@ static MESSAGE_LOCALES: LazyLock> = LazyLock::new(|| { #[derive(Debug, Clone, Default)] pub struct AppsContext { - apps: HashMap, + apps: HashMap, App>, processes: HashMap, - processes_assigned_to_apps: HashSet, - read_bytes_from_dead_system_processes: u64, - write_bytes_from_dead_system_processes: u64, -} - -/// Convenience struct for displaying running applications and -/// displaying a "System Processes" item. -#[derive(Debug, Clone)] -pub struct AppItem { - pub id: Option, - pub display_name: String, - pub icon: Icon, - pub description: Option, - pub memory_usage: usize, - pub cpu_time_ratio: f32, - pub processes_amount: usize, - pub containerization: Containerization, - pub running_since: GString, - pub read_speed: f64, - pub read_total: u64, - pub write_speed: f64, - pub write_total: u64, - pub gpu_usage: f32, - pub enc_usage: f32, - pub dec_usage: f32, - pub gpu_mem_usage: u64, } /// Represents an application installed on the system. It doesn't @@ -159,9 +147,10 @@ pub struct App { pub display_name: String, pub description: Option, pub icon: Icon, - pub id: String, + pub id: Option, pub read_bytes_from_dead_processes: u64, pub write_bytes_from_dead_processes: u64, + pub containerization: Containerization, } impl App { @@ -180,7 +169,7 @@ impl App { applications_dir ); - let apps: Vec<_> = applications_dir + let mut apps: Vec<_> = applications_dir .iter() .flat_map(|applications_path| { applications_path.read_dir().ok().map(|read| { @@ -196,10 +185,20 @@ impl App { let elapsed = start.elapsed(); - info!( - "Detected {} apps within {elapsed:.2?}", - applications_dir.len() - ); + info!("Detected {} apps within {elapsed:.2?}", apps.len()); + + apps.push(App { + processes: Vec::new(), + commandline: None, + executable_name: None, + display_name: i18n("System Processes"), + description: None, + icon: ThemedIcon::new("system-processes").into(), + id: None, + read_bytes_from_dead_processes: 0, + write_bytes_from_dead_processes: 0, + containerization: Containerization::None, + }); apps } @@ -223,12 +222,15 @@ impl App { }) .context("unable to get ID of desktop file")?; - if APP_ID_BLOCKLIST.contains(&id.as_str()) { - debug!("Skipping {id} because it's blocklisted…"); - bail!("{id} is blocklisted") + if let Some(reason) = APP_ID_BLOCKLIST.get(id.as_str()) { + debug!("Skipping {id} because it's blocklisted (reason: {reason})"); + bail!("{id} is blocklisted (reason: {reason})") } let exec = desktop_entry.get("Exec"); + let is_flatpak = exec + .map(|exec| exec.starts_with("/usr/bin/flatpak run")) + .unwrap_or_default(); let commandline = exec .and_then(|exec| { RE_ENV_FILTER @@ -297,12 +299,35 @@ impl App { .or_else(|| desktop_entry.get("Comment")) .map(str::to_string); - debug!( - "Found app \"{display_name}\" (ID: {id}) at {} with commandline `{}` (detected executable name: {})", - file_path.to_string_lossy(), - commandline.as_ref().unwrap_or(&"".into()), - executable_name.as_ref().unwrap_or(&"".into()), - ); + let is_snap = desktop_entry.get("X-SnapInstanceName").is_some(); + + let containerization = if is_flatpak { + debug!( + "Found Flatpak app \"{display_name}\" (ID: {id}) at {} with commandline `{}` (detected executable name: {})", + file_path.to_string_lossy(), + commandline.as_ref().unwrap_or(&"".into()), + executable_name.as_ref().unwrap_or(&"".into()), + ); + Containerization::Flatpak + } else if is_snap { + debug!( + "Found Snap app \"{display_name}\" (ID: {id}) at {} with commandline `{}` (detected executable name: {})", + file_path.to_string_lossy(), + commandline.as_ref().unwrap_or(&"".into()), + executable_name.as_ref().unwrap_or(&"".into()), + ); + Containerization::Snap + } else { + debug!( + "Found native app \"{display_name}\" (ID: {id}) at {} with commandline `{}` (detected executable name: {})", + file_path.to_string_lossy(), + commandline.as_ref().unwrap_or(&"".into()), + executable_name.as_ref().unwrap_or(&"".into()), + ); + Containerization::None + }; + + let id = Some(id); Ok(App { processes: Vec::new(), @@ -314,6 +339,7 @@ impl App { id, read_bytes_from_dead_processes: 0, write_bytes_from_dead_processes: 0, + containerization, }) } @@ -335,7 +361,7 @@ impl App { } pub fn processes_iter<'a>(&'a self, apps: &'a AppsContext) -> impl Iterator { - apps.all_processes() + apps.processes_iter() .filter(move |process| self.processes.contains(&process.data.pid)) } @@ -343,7 +369,7 @@ impl App { &'a mut self, apps: &'a mut AppsContext, ) -> impl Iterator { - apps.all_processes_mut() + apps.processes_iter_mut() .filter(move |process| self.processes.contains(&process.data.pid)) } @@ -428,6 +454,20 @@ impl App { .map(|process| process.execute_process_action(action)) .collect() } + + pub fn running_since(&self, apps: &AppsContext) -> Result { + boot_time() + .and_then(|boot_time| { + boot_time + .add_seconds(self.starttime(apps)) + .context("unable to add seconds to boot time") + }) + .and_then(|time| time.format("%c").context("unable to format running_since")) + } + + pub fn running_processes(&self) -> usize { + self.processes.len() + } } impl AppsContext { @@ -435,7 +475,7 @@ impl AppsContext { /// so try to do it only one time during the lifetime of the program. /// Please call refresh() immediately after this function. pub fn new() -> AppsContext { - let apps: HashMap = App::all() + let apps: HashMap, App> = App::all() .into_iter() .map(|app| (app.id.clone(), app)) .collect(); @@ -443,14 +483,11 @@ impl AppsContext { AppsContext { apps, processes: HashMap::new(), - processes_assigned_to_apps: HashSet::new(), - read_bytes_from_dead_system_processes: 0, - write_bytes_from_dead_system_processes: 0, } } pub fn gpu_fraction(&self, pci_slot: PciSlot) -> f32 { - self.all_processes() + self.processes_iter() .map(|process| { ( &process.data.gpu_usage_stats, @@ -488,7 +525,7 @@ impl AppsContext { } pub fn encoder_fraction(&self, pci_slot: PciSlot) -> f32 { - self.all_processes() + self.processes_iter() .map(|process| { ( &process.data.gpu_usage_stats, @@ -526,7 +563,7 @@ impl AppsContext { } pub fn decoder_fraction(&self, pci_slot: PciSlot) -> f32 { - self.all_processes() + self.processes_iter() .map(|process| { ( &process.data.gpu_usage_stats, @@ -568,27 +605,27 @@ impl AppsContext { // ↓ look for whether we can find an ID in the cgroup if let Some(app) = self .apps - .get(process.data.cgroup.as_deref().unwrap_or_default()) + .get(&Some(process.data.cgroup.clone().unwrap_or_default())) { debug!( "Associating process {} with app \"{}\" (ID: {}) based on process cgroup matching with app ID", - process.data.pid, app.display_name, app.id + process.data.pid, app.display_name, app.id.as_deref().unwrap_or_default() ); - Some(app.id.clone()) - } else if let Some(app) = self.apps.get(&process.executable_path) { + app.id.clone() + } else if let Some(app) = self.apps.get(&Some(process.executable_path.clone())) { // ↑ look for whether we can find an ID in the executable path of the process debug!( "Associating process {} with app \"{}\" (ID: {}) based on process executable path matching with app ID", - process.data.pid, app.display_name, app.id + process.data.pid, app.display_name, app.id.as_deref().unwrap_or_default() ); - Some(app.id.clone()) - } else if let Some(app) = self.apps.get(&process.executable_name) { + app.id.clone() + } else if let Some(app) = self.apps.get(&Some(process.executable_name.clone())) { // ↑ look for whether we can find an ID in the executable name of the process debug!( "Associating process {} with app \"{}\" (ID: {}) based on process executable name matching with app ID", - process.data.pid, app.display_name, app.id + process.data.pid, app.display_name, app.id.as_deref().unwrap_or_default() ); - Some(app.id.clone()) + app.id.clone() } else { self.apps .values() @@ -602,7 +639,7 @@ impl AppsContext { { debug!( "Associating process {} with app \"{}\" (ID: {}) based on process executable pathmatching with app commandline ({})", - process.data.pid, app.display_name, app.id, process.executable_path + process.data.pid, app.display_name, app.id.as_deref().unwrap_or_default(), process.executable_path ); true } else if app @@ -612,7 +649,7 @@ impl AppsContext { { debug!( "Associating process {} with app \"{}\" (ID: {}) based on process executable name matching with app executable name ({})", - process.data.pid, app.display_name, app.id, process.executable_name + process.data.pid, app.display_name, app.id.as_deref().unwrap_or_default(), process.executable_name ); true } else if app @@ -620,7 +657,7 @@ impl AppsContext { .as_ref() .and_then(|executable_name| { KNOWN_EXECUTABLE_NAME_EXCEPTIONS - .get(&process.executable_name) + .get(process.executable_name.as_str()) .map(|substituted_executable_name| { substituted_executable_name == executable_name }) @@ -629,14 +666,14 @@ impl AppsContext { { debug!( "Associating process {} with app \"{}\" (ID: {}) based on match in KNOWN_EXECUTABLE_NAME_EXCEPTIONS", - process.data.pid, app.display_name, app.id + process.data.pid, app.display_name, app.id.as_deref().unwrap_or_default() ); true } else { false } }) - .map(|app| app.id.clone()) + .and_then(|app| app.id.clone()) } } @@ -644,215 +681,35 @@ impl AppsContext { self.processes.get(&pid) } - pub fn get_app(&self, id: &str) -> Option<&App> { + pub fn get_app(&self, id: &Option) -> Option<&App> { self.apps.get(id) } #[must_use] - pub fn all_processes(&self) -> impl Iterator { + pub fn processes_iter(&self) -> impl Iterator { self.processes.values() } #[must_use] - pub fn all_processes_mut(&mut self) -> impl Iterator { + pub fn processes_iter_mut(&mut self) -> impl Iterator { self.processes.values_mut() } - /// Returns a `HashMap` of running processes. For more info, refer to - /// `ProcessItem`. - pub fn process_items(&self) -> HashMap { - self.all_processes() - .map(|process| (process.data.pid, self.process_item(process.data.pid))) - .filter_map(|(pid, process_opt)| process_opt.map(|process| (pid, process))) - .collect() + pub fn apps_iter(&self) -> impl Iterator { + self.apps.values() } - pub fn process_item(&self, pid: i32) -> Option { - self.get_process(pid).map(|process| { - let full_comm = if process.executable_name.starts_with(&process.data.comm) { - process.executable_name.clone() - } else { - process.data.comm.clone() - }; - ProcessItem { - pid: process.data.pid, - user: process.data.user.clone(), - display_name: full_comm.clone(), - icon: process.icon.clone(), - memory_usage: process.data.memory_usage, - cpu_time_ratio: process.cpu_time_ratio(), - user_cpu_time: ((process.data.user_cpu_time) as f64 / (*TICK_RATE) as f64), - system_cpu_time: ((process.data.system_cpu_time) as f64 / (*TICK_RATE) as f64), - commandline: Process::sanitize_cmdline(process.data.commandline.clone()) - .unwrap_or(full_comm), - containerization: process.data.containerization, - starttime: process.starttime(), - cgroup: process.data.cgroup.clone(), - read_speed: process.read_speed(), - read_total: process.data.read_bytes, - write_speed: process.write_speed(), - write_total: process.data.write_bytes, - gpu_usage: process.gpu_usage(), - enc_usage: process.enc_usage(), - dec_usage: process.dec_usage(), - gpu_mem_usage: process.gpu_mem_usage(), - } + pub fn running_apps_iter(&self) -> impl Iterator { + self.apps_iter().filter(|app| { + app.is_running() + && !app + .id + .as_ref() + .map(|id| id.starts_with("xdg-desktop-portal")) + .unwrap_or_default() }) } - /// Returns a `HashMap` of running graphical applications. For more info, - /// refer to `AppItem`. - #[must_use] - pub fn app_items(&self) -> HashMap, AppItem> { - let mut app_pids = HashSet::new(); - - let mut return_map = self - .apps - .iter() - .filter(|(_, app)| app.is_running() && !app.id.starts_with("xdg-desktop-portal")) - .map(|(_, app)| { - app.processes_iter(self).for_each(|process| { - app_pids.insert(process.data.pid); - }); - - let is_flatpak = app - .processes_iter(self) - .filter(|process| { - !process.data.commandline.starts_with("bwrap") - && !process.data.commandline.is_empty() - }) - .any(|process| process.data.containerization == Containerization::Flatpak); - - let is_snap = app - .processes_iter(self) - .filter(|process| { - !process.data.commandline.starts_with("bwrap") - && !process.data.commandline.is_empty() - }) - .any(|process| process.data.containerization == Containerization::Snap); - - let containerization = if is_flatpak { - Containerization::Flatpak - } else if is_snap { - Containerization::Snap - } else { - Containerization::None - }; - - let running_since = boot_time() - .and_then(|boot_time| { - boot_time - .add_seconds(app.starttime(self)) - .context("unable to add seconds to boot time") - }) - .and_then(|time| time.format("%c").context("unable to format running_since")) - .unwrap_or_else(|_| GString::from(i18n("N/A"))); - - ( - Some(app.id.clone()), - AppItem { - id: Some(app.id.clone()), - display_name: app.display_name.clone(), - icon: app.icon.clone(), - description: app.description.clone(), - memory_usage: app.memory_usage(self), - cpu_time_ratio: app.cpu_time_ratio(self), - processes_amount: app.processes_iter(self).count(), - containerization, - running_since, - read_speed: app.read_speed(self), - read_total: app.read_total(self), - write_speed: app.write_speed(self), - write_total: app.write_total(self), - gpu_usage: app.gpu_usage(self), - enc_usage: app.enc_usage(self), - dec_usage: app.dec_usage(self), - gpu_mem_usage: app.gpu_mem_usage(self), - }, - ) - }) - .collect::, AppItem>>(); - - let system_cpu_ratio = self - .system_processes_iter() - .map(Process::cpu_time_ratio) - .sum(); - - let system_memory_usage: usize = self - .system_processes_iter() - .map(|process| process.data.memory_usage) - .sum(); - - let system_read_speed = self - .system_processes_iter() - .filter_map(Process::read_speed) - .sum(); - - let system_read_total = self.read_bytes_from_dead_system_processes - + self - .system_processes_iter() - .filter_map(|process| process.data.read_bytes) - .sum::(); - - let system_write_speed = self - .system_processes_iter() - .filter_map(Process::write_speed) - .sum(); - - let system_write_total = self.write_bytes_from_dead_system_processes - + self - .system_processes_iter() - .filter_map(|process| process.data.write_bytes) - .sum::(); - - let system_gpu_usage = self.system_processes_iter().map(Process::gpu_usage).sum(); - - let system_enc_usage = self.system_processes_iter().map(Process::enc_usage).sum(); - - let system_dec_usage = self.system_processes_iter().map(Process::dec_usage).sum(); - - let system_gpu_mem_usage = self - .system_processes_iter() - .map(Process::gpu_mem_usage) - .sum(); - - let system_running_since = boot_time() - .and_then(|boot_time| { - boot_time - .format("%c") - .context("unable to format running_time") - }) - .unwrap_or_else(|_| GString::from(i18n("N/A"))); - - return_map.insert( - None, - AppItem { - id: None, - display_name: i18n("System Processes"), - icon: ThemedIcon::new("system-processes").into(), - description: None, - memory_usage: system_memory_usage, - cpu_time_ratio: system_cpu_ratio, - processes_amount: self - .processes - .len() - .saturating_sub(self.processes_assigned_to_apps.len()), - containerization: Containerization::None, - running_since: system_running_since, - read_speed: system_read_speed, - read_total: system_read_total, - write_speed: system_write_speed, - write_total: system_write_total, - gpu_usage: system_gpu_usage, - enc_usage: system_enc_usage, - dec_usage: system_dec_usage, - gpu_mem_usage: system_gpu_mem_usage, - }, - ); - - return_map - } - /// Refreshes the statistics about the running applications and processes. pub fn refresh(&mut self, new_process_data: Vec) { let mut updated_processes = HashSet::new(); @@ -876,20 +733,15 @@ impl AppsContext { let mut new_process = Process::from_process_data(process_data); - if let Some(app_id) = self.app_associated_with_process(&new_process) { - self.processes_assigned_to_apps.insert(new_process.data.pid); - self.apps - .get_mut(&app_id) - .unwrap() - .add_process(&mut new_process); - } + self.apps + .get_mut(&self.app_associated_with_process(&new_process)) + .unwrap() + .add_process(&mut new_process); self.processes.insert(new_process.data.pid, new_process); } } - // all the not-updated processes have unfortunately died, probably - // collect the I/O stats for died app processes so an app doesn't suddenly have less total disk I/O self.apps.values_mut().for_each(|app| { let (read_dead, write_dead) = app @@ -919,36 +771,8 @@ impl AppsContext { } }); - // same as above but for system processes - let (read_dead, write_dead) = self - .processes - .iter() - .filter(|(pid, _)| { - !self.processes_assigned_to_apps.contains(*pid) && !updated_processes.contains(*pid) - }) - .map(|(_, process)| (process.data.read_bytes, process.data.write_bytes)) - .filter_map( - |(read_bytes, write_bytes)| match (read_bytes, write_bytes) { - (Some(read), Some(write)) => Some((read, write)), - _ => None, - }, - ) - .reduce(|sum, current| (sum.0 + current.0, sum.1 + current.1)) - .unwrap_or((0, 0)); - self.read_bytes_from_dead_system_processes += read_dead; - self.write_bytes_from_dead_system_processes += write_dead; - - // remove the dead process from our process map + // all the not-updated processes have unfortunately died, probably self.processes .retain(|pid, _| updated_processes.contains(pid)); - - // remove the dead process from out list of app processes - self.processes_assigned_to_apps - .retain(|pid| updated_processes.contains(pid)); - } - - pub fn system_processes_iter(&self) -> impl Iterator { - self.all_processes() - .filter(|process| !self.processes_assigned_to_apps.contains(&process.data.pid)) } } diff --git a/src/utils/gpu/amd.rs b/src/utils/gpu/amd.rs index 1d2a190a..9fdbd553 100644 --- a/src/utils/gpu/amd.rs +++ b/src/utils/gpu/amd.rs @@ -1,10 +1,9 @@ use anyhow::{bail, Result}; -use hashbrown::HashMap; use log::{debug, warn}; use process_data::pci_slot::PciSlot; use regex::Regex; -use std::{path::PathBuf, sync::LazyLock, time::Instant}; +use std::{collections::HashMap, path::PathBuf, sync::LazyLock, time::Instant}; use crate::utils::{ pci::{self, Device}, diff --git a/src/utils/mod.rs b/src/utils/mod.rs index 204da785..0c191ea7 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -40,12 +40,12 @@ static BOOT_TIMESTAMP: LazyLock> = LazyLock::new(|| { .ok() }); -static TICK_RATE: LazyLock = - LazyLock::new(|| sysconf::sysconf(sysconf::SysconfVariable::ScClkTck).unwrap_or(100) as usize); - static FLATPAK_APP_PATH: LazyLock = LazyLock::new(|| flatpak_app_path().unwrap_or_else(|_| String::new())); +pub static TICK_RATE: LazyLock = + LazyLock::new(|| sysconf::sysconf(sysconf::SysconfVariable::ScClkTck).unwrap_or(100) as usize); + pub static NUM_CPUS: LazyLock = LazyLock::new(num_cpus::get); // Adapted from Mission Center: https://gitlab.com/mission-center-devs/mission-center/ diff --git a/src/utils/pci.rs b/src/utils/pci.rs index 9fdf3535..eb55e09d 100644 --- a/src/utils/pci.rs +++ b/src/utils/pci.rs @@ -93,6 +93,8 @@ fn parse_pci_ids() -> Result> { let mut seen: BTreeMap = BTreeMap::new(); + let (mut vendors_count, mut devices_count, mut subdevices_count) = (0, 0, 0); + for line in reader.lines().map_while(Result::ok) { if line.starts_with('C') { // case 1: we've reached the classes, time to stop @@ -130,6 +132,8 @@ fn parse_pci_ids() -> Result> { name, }; + subdevices_count += 1; + seen.values_mut() .last() .and_then(|vendor| vendor.devices.values_mut().last()) @@ -164,6 +168,8 @@ fn parse_pci_ids() -> Result> { sub_devices: Vec::new(), }; + devices_count += 1; + seen.values_mut() .last() .with_context(|| format!("no preceding device (line: {line})"))? @@ -191,13 +197,15 @@ fn parse_pci_ids() -> Result> { devices: BTreeMap::new(), }; + vendors_count += 1; + seen.insert(vid, vendor); } } let elapsed = start.elapsed(); - info!("Successfully parsed pci.ids within {elapsed:.2?}"); + info!("Successfully parsed pci.ids within {elapsed:.2?} (vendors: {vendors_count}, devices: {devices_count}, subdevices: {subdevices_count})"); Ok(seen) } diff --git a/src/utils/process.rs b/src/utils/process.rs index 619f3406..8de41993 100644 --- a/src/utils/process.rs +++ b/src/utils/process.rs @@ -1,7 +1,7 @@ use anyhow::{bail, Context, Result}; use config::LIBEXECDIR; use log::debug; -use process_data::{pci_slot::PciSlot, Containerization, GpuUsageStats, ProcessData}; +use process_data::{pci_slot::PciSlot, GpuUsageStats, ProcessData}; use std::{ collections::BTreeMap, io::{Read, Write}, @@ -10,11 +10,16 @@ use std::{ }; use strum_macros::Display; -use gtk::gio::{Icon, ThemedIcon}; +use gtk::{ + gio::{Icon, ThemedIcon}, + glib::GString, +}; use crate::config; -use super::{FiniteOr, FLATPAK_APP_PATH, FLATPAK_SPAWN, IS_FLATPAK, NUM_CPUS, TICK_RATE}; +use super::{ + boot_time, FiniteOr, FLATPAK_APP_PATH, FLATPAK_SPAWN, IS_FLATPAK, NUM_CPUS, TICK_RATE, +}; static OTHER_PROCESS: LazyLock> = LazyLock::new(|| { let proxy_path = if *IS_FLATPAK { @@ -73,30 +78,6 @@ pub enum ProcessAction { KILL, CONT, } -/// Convenience struct for displaying running processes -#[derive(Debug, Clone)] -pub struct ProcessItem { - pub pid: i32, - pub user: String, - pub display_name: String, - pub icon: Icon, - pub memory_usage: usize, - pub cpu_time_ratio: f32, - pub user_cpu_time: f64, - pub system_cpu_time: f64, - pub commandline: String, - pub containerization: Containerization, - pub starttime: f64, - pub cgroup: Option, - pub read_speed: Option, - pub read_total: Option, - pub write_speed: Option, - pub write_total: Option, - pub gpu_usage: f32, - pub enc_usage: f32, - pub dec_usage: f32, - pub gpu_mem_usage: u64, -} impl Process { /// Returns a `Vec` containing all currently running processes. @@ -417,6 +398,16 @@ impl Process { self.data.starttime as f64 / *TICK_RATE as f64 } + pub fn running_since(&self) -> Result { + boot_time() + .and_then(|boot_time| { + boot_time + .add_seconds(self.starttime()) + .context("unable to add seconds to boot time") + }) + .and_then(|time| time.format("%c").context("unable to format running_since")) + } + pub fn sanitize_cmdline>(cmdline: S) -> Option { let cmdline = cmdline.as_ref(); if cmdline.is_empty() {