├── src
├── bin
│ ├── resources.rs
│ ├── resources-kill.rs
│ ├── resources-processes.rs
│ └── resources-adjust.rs
├── ui
│ ├── dialogs
│ │ └── mod.rs
│ ├── widgets
│ │ ├── mod.rs
│ │ ├── graph_box.rs
│ │ └── double_graph_box.rs
│ ├── mod.rs
│ └── pages
│ │ ├── mod.rs
│ │ ├── processes
│ │ └── process_name_cell.rs
│ │ └── applications
│ │ └── application_name_cell.rs
├── lib.rs
├── config.rs.in
├── utils
│ ├── os.rs
│ ├── link
│ │ ├── mod.rs
│ │ ├── sata.rs
│ │ └── usb.rs
│ ├── npu
│ │ ├── other.rs
│ │ └── intel.rs
│ └── gpu
│ │ ├── other.rs
│ │ ├── v3d.rs
│ │ └── amd.rs
├── meson.build
├── gui.rs
└── i18n.rs
├── data
├── resources
│ ├── screenshots
│ │ ├── 1.png
│ │ ├── 2.png
│ │ ├── 3.png
│ │ ├── 4.png
│ │ ├── 5.png
│ │ ├── 6.png
│ │ ├── 7.png
│ │ └── 8.png
│ ├── meson.build
│ ├── icons
│ │ ├── info-symbolic.svg
│ │ ├── app-symbolic.svg
│ │ ├── flash-storage-symbolic.svg
│ │ ├── wwan-symbolic.svg
│ │ ├── floppy-symbolic.svg
│ │ ├── memory-symbolic.svg
│ │ ├── system-processes-symbolic.svg
│ │ ├── search-symbolic.svg
│ │ ├── slip-symbolic.svg
│ │ ├── ethernet-symbolic.svg
│ │ ├── infiniband-symbolic.svg
│ │ ├── virtual-ethernet-symbolic.svg
│ │ ├── unknown-network-type-symbolic.svg
│ │ ├── gpu-symbolic.svg
│ │ ├── options-symbolic.svg
│ │ ├── hdd-symbolic.svg
│ │ ├── battery-symbolic.svg
│ │ ├── unknown-drive-type-symbolic.svg
│ │ ├── nvme-symbolic.svg
│ │ ├── ssd-symbolic.svg
│ │ ├── processor-symbolic.svg
│ │ ├── bluetooth-symbolic.svg
│ │ ├── shell-symbolic.svg
│ │ ├── cd-dvd-bluray-symbolic.svg
│ │ ├── zram-symbolic.svg
│ │ ├── wlan-symbolic.svg
│ │ ├── vpn-symbolic.svg
│ │ ├── emmc-symbolic.svg
│ │ ├── select-all-symbolic.svg
│ │ ├── bridge-symbolic.svg
│ │ ├── vm-bridge-symbolic.svg
│ │ ├── docker-bridge-symbolic.svg
│ │ ├── loop-device-symbolic.svg
│ │ ├── ram-disk-symbolic.svg
│ │ ├── device-settings-symbolic.svg
│ │ ├── generic-process-symbolic.svg
│ │ ├── raid-symbolic.svg
│ │ ├── npu-symbolic.svg
│ │ ├── generic-settings-symbolic.svg
│ │ ├── zfs-symbolic.svg
│ │ └── mapped-device-symbolic.svg
│ ├── style.css
│ └── ui
│ │ ├── widgets
│ │ ├── stack_sidebar.ui
│ │ ├── process_name_cell.ui
│ │ ├── application_name_cell.ui
│ │ ├── graph_box.ui
│ │ ├── stack_sidebar_item.ui
│ │ └── double_graph_box.ui
│ │ ├── shortcuts.ui
│ │ └── pages
│ │ ├── applications.ui
│ │ ├── memory.ui
│ │ ├── battery.ui
│ │ ├── drive.ui
│ │ ├── npu.ui
│ │ ├── network.ui
│ │ └── gpu.ui
├── icons
│ ├── meson.build
│ └── net.nokyan.Resources-symbolic.svg
├── net.nokyan.Resources.desktop.in.in
├── net.nokyan.Resources.policy.in.in
└── meson.build
├── .typos.toml
├── .gitignore
├── po
├── its
│ ├── polkit.loc
│ └── polkit.its
├── meson.build
├── LINGUAS
└── POTFILES.in
├── meson_options.txt
├── lib
└── process_data
│ ├── src
│ └── meson.build
│ └── Cargo.toml
├── .github
├── workflows
│ └── flatpak.yml
└── ISSUE_TEMPLATE
│ ├── feature_request.yaml
│ └── bug_report.yaml
├── resources.doap
├── Cargo.toml
├── hooks
└── pre-commit.hook
├── meson.build
└── README.md
/src/bin/resources.rs:
--------------------------------------------------------------------------------
1 | fn main() {
2 | resources::gui::main();
3 | }
4 |
--------------------------------------------------------------------------------
/data/resources/screenshots/1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nokyan/resources/HEAD/data/resources/screenshots/1.png
--------------------------------------------------------------------------------
/data/resources/screenshots/2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nokyan/resources/HEAD/data/resources/screenshots/2.png
--------------------------------------------------------------------------------
/data/resources/screenshots/3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nokyan/resources/HEAD/data/resources/screenshots/3.png
--------------------------------------------------------------------------------
/data/resources/screenshots/4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nokyan/resources/HEAD/data/resources/screenshots/4.png
--------------------------------------------------------------------------------
/data/resources/screenshots/5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nokyan/resources/HEAD/data/resources/screenshots/5.png
--------------------------------------------------------------------------------
/data/resources/screenshots/6.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nokyan/resources/HEAD/data/resources/screenshots/6.png
--------------------------------------------------------------------------------
/data/resources/screenshots/7.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nokyan/resources/HEAD/data/resources/screenshots/7.png
--------------------------------------------------------------------------------
/data/resources/screenshots/8.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nokyan/resources/HEAD/data/resources/screenshots/8.png
--------------------------------------------------------------------------------
/src/ui/dialogs/mod.rs:
--------------------------------------------------------------------------------
1 | pub mod app_dialog;
2 | pub mod process_dialog;
3 | pub mod process_options_dialog;
4 | pub mod settings_dialog;
5 |
--------------------------------------------------------------------------------
/src/lib.rs:
--------------------------------------------------------------------------------
1 | pub mod application;
2 | #[rustfmt::skip]
3 | pub mod config;
4 | pub mod gui;
5 | pub mod i18n;
6 | pub mod ui;
7 | pub mod utils;
8 |
--------------------------------------------------------------------------------
/.typos.toml:
--------------------------------------------------------------------------------
1 | # SPDX-FileCopyrightText: 2022 Emmanuele Bassi
2 | # SPDX-License-Identifier: CC0-1.0
3 | [files]
4 | extend-exclude = ["LICENSES/*", "po/*"]
--------------------------------------------------------------------------------
/src/ui/widgets/mod.rs:
--------------------------------------------------------------------------------
1 | pub mod double_graph_box;
2 | pub mod graph;
3 | pub mod graph_box;
4 | pub mod stack_sidebar;
5 | pub mod stack_sidebar_item;
6 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | target/
2 | build/
3 | _build/
4 | builddir/
5 | build-aux/app
6 | build-aux/.flatpak-builder/
7 | src/config.rs
8 | *.ui.in~
9 | *.ui~
10 | .flatpak/
11 | .flatpak-builder/
12 | .vscode/
13 | vendor
14 |
--------------------------------------------------------------------------------
/po/its/polkit.loc:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/meson_options.txt:
--------------------------------------------------------------------------------
1 | option(
2 | 'profile',
3 | type: 'combo',
4 | choices: [
5 | 'default',
6 | 'development'
7 | ],
8 | value: 'development',
9 | description: 'The build profile for Resources. One of "default" or "development".'
10 | )
11 |
--------------------------------------------------------------------------------
/data/resources/meson.build:
--------------------------------------------------------------------------------
1 | # Resources
2 | resources = gnome.compile_resources(
3 | 'resources',
4 | 'resources.gresource.xml',
5 | gresource_bundle: true,
6 | source_dir: meson.current_build_dir(),
7 | install: true,
8 | install_dir: pkgdatadir,
9 | )
10 |
--------------------------------------------------------------------------------
/po/meson.build:
--------------------------------------------------------------------------------
1 | i18n.gettext(
2 | 'resources',
3 | args: [
4 | '--keyword=i18n',
5 | '--keyword=i18n_f',
6 | '--keyword=i18n_k',
7 | '--keyword=ni18n:1,2',
8 | '--keyword=ni18n_f:1,2',
9 | '--keyword=ni18n_k:1,2',
10 | ],
11 | preset: 'glib',
12 | )
13 |
--------------------------------------------------------------------------------
/data/resources/icons/info-symbolic.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/po/LINGUAS:
--------------------------------------------------------------------------------
1 | # Please keep this list sorted alphabetically
2 | #
3 | ar
4 | bg
5 | bn
6 | ca
7 | cs
8 | de
9 | el
10 | en_GB
11 | es
12 | eu
13 | fa
14 | fi
15 | fr
16 | he
17 | hi
18 | hu
19 | it
20 | ja
21 | ka
22 | nb
23 | nl
24 | oc
25 | pl
26 | pt_BR
27 | ru
28 | sl
29 | sv
30 | tr
31 | uk
32 | uz
33 | zh_CN
34 | zh_TW
35 |
--------------------------------------------------------------------------------
/data/icons/meson.build:
--------------------------------------------------------------------------------
1 | install_data(
2 | '@0@.svg'.format(application_id),
3 | install_dir: iconsdir / 'hicolor' / 'scalable' / 'apps'
4 | )
5 |
6 | install_data(
7 | '@0@-symbolic.svg'.format(base_id),
8 | install_dir: iconsdir / 'hicolor' / 'symbolic' / 'apps',
9 | rename: '@0@-symbolic.svg'.format(application_id)
10 | )
11 |
--------------------------------------------------------------------------------
/po/its/polkit.its:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
8 |
9 |
--------------------------------------------------------------------------------
/src/config.rs.in:
--------------------------------------------------------------------------------
1 | pub const APP_ID: &str = @APP_ID@;
2 | pub const GETTEXT_PACKAGE: &str = @GETTEXT_PACKAGE@;
3 | pub const LOCALEDIR: &str = @LOCALEDIR@;
4 | pub const PKGDATADIR: &str = @PKGDATADIR@;
5 | pub const PROFILE: &str = @PROFILE@;
6 | pub const RESOURCES_FILE: &str = concat!(@PKGDATADIR@, "/resources.gresource");
7 | pub const VERSION: &str = @VERSION@;
8 | pub const LIBEXECDIR: &str = @LIBEXECDIR@;
--------------------------------------------------------------------------------
/lib/process_data/src/meson.build:
--------------------------------------------------------------------------------
1 | cargo_options = [
2 | '--manifest-path', meson.project_source_root() / 'lib' / 'process_data' / 'Cargo.toml',
3 | ]
4 | cargo_options += [
5 | '--target-dir', meson.project_build_root() / 'lib' / 'process_data' / 'src',
6 | ]
7 |
8 | test(
9 | 'Cargo tests (process_data)',
10 | cargo,
11 | args: ['test', cargo_options],
12 | timeout: 3600,
13 | env: cargo_env,
14 | )
--------------------------------------------------------------------------------
/data/resources/icons/app-symbolic.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/data/resources/icons/flash-storage-symbolic.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/data/resources/icons/wwan-symbolic.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/data/net.nokyan.Resources.desktop.in.in:
--------------------------------------------------------------------------------
1 | [Desktop Entry]
2 | Name=Resources
3 | Comment=Keep an eye on system resources
4 | Type=Application
5 | Exec=resources
6 | Terminal=false
7 | Categories=System;
8 | # Translators: Search terms to find this application. Do NOT translate or localize the semicolons! The list MUST also end with a semicolon!
9 | Keywords=System;Resources;Monitor;Processes;Usage;Task;Manager;CPU;RAM;Memory;GPU;
10 | # Translators: Do NOT translate or transliterate this text (this is an icon file name)!
11 | Icon=@icon@
12 | StartupNotify=true
13 | X-Purism-FormFactor=Workstation;Mobile;
14 |
--------------------------------------------------------------------------------
/data/resources/icons/floppy-symbolic.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/data/resources/style.css:
--------------------------------------------------------------------------------
1 | progressbar.slim > trough, progressbar.slim > trough > progress {
2 | min-height: 4px;
3 | }
4 |
5 | .resources-columnview {
6 | background-color: @window_bg_color;
7 | }
8 |
9 | .bubble {
10 | background-color: alpha(currentColor, 0.08);
11 | min-width: 32px;
12 | min-height: 32px;
13 | border-radius: 50%;
14 | }
15 |
16 | .big-bubble {
17 | background-color: alpha(currentColor, 0.08);
18 | min-width: 128px;
19 | min-height: 128px;
20 | border-radius: 50%;
21 | }
22 |
23 | .small-graph {
24 | border-radius: 4px;
25 | }
26 |
27 | .graph {
28 | border-radius: 8px;
29 | }
30 |
--------------------------------------------------------------------------------
/data/resources/icons/memory-symbolic.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/data/resources/icons/system-processes-symbolic.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/.github/workflows/flatpak.yml:
--------------------------------------------------------------------------------
1 | name: Flatpak Build Test
2 |
3 | on:
4 | push:
5 | branches: [main]
6 | pull_request:
7 |
8 | env:
9 | CARGO_TERM_COLOR: always
10 |
11 | jobs:
12 | flatpak:
13 | runs-on: ubuntu-latest
14 | container:
15 | image: ghcr.io/flathub-infra/flatpak-github-actions:gnome-48
16 | options: --privileged
17 | steps:
18 | - uses: actions/checkout@v4
19 | - uses: flatpak/flatpak-github-actions/flatpak-builder@v6
20 | with:
21 | manifest-path: build-aux/net.nokyan.Resources.Devel.json
22 | run-tests: true
23 | cache-key: flatpak-builder-${{ github.sha }}
24 | upload-artifact: false
25 |
--------------------------------------------------------------------------------
/data/resources/icons/search-symbolic.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/data/resources/icons/slip-symbolic.svg:
--------------------------------------------------------------------------------
1 |
2 |
5 |
--------------------------------------------------------------------------------
/data/resources/icons/ethernet-symbolic.svg:
--------------------------------------------------------------------------------
1 |
2 |
5 |
--------------------------------------------------------------------------------
/data/resources/icons/infiniband-symbolic.svg:
--------------------------------------------------------------------------------
1 |
2 |
5 |
--------------------------------------------------------------------------------
/data/resources/icons/virtual-ethernet-symbolic.svg:
--------------------------------------------------------------------------------
1 |
2 |
5 |
--------------------------------------------------------------------------------
/data/resources/icons/unknown-network-type-symbolic.svg:
--------------------------------------------------------------------------------
1 |
2 |
5 |
--------------------------------------------------------------------------------
/data/resources/icons/gpu-symbolic.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/data/resources/icons/options-symbolic.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/data/resources/ui/widgets/stack_sidebar.ui:
--------------------------------------------------------------------------------
1 |
2 |
3 |
22 |
--------------------------------------------------------------------------------
/data/net.nokyan.Resources.policy.in.in:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 | nokyan
7 | net.nokyan.Resources
8 |
9 | Control Process
10 | Authentication is required to control superuser’s or other users’ processes
11 |
12 | no
13 | no
14 | auth_admin_keep
15 |
16 | @libexecdir@/resources-kill
17 |
18 |
--------------------------------------------------------------------------------
/data/resources/icons/hdd-symbolic.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/data/resources/icons/battery-symbolic.svg:
--------------------------------------------------------------------------------
1 |
2 |
5 |
--------------------------------------------------------------------------------
/data/resources/icons/unknown-drive-type-symbolic.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/lib/process_data/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "process-data"
3 | version = "1.9.1"
4 | authors = ["nokyan "]
5 | edition = "2024"
6 | rust-version = "1.85.0"
7 | homepage = "https://apps.gnome.org/app/net.nokyan.Resources/"
8 | license = "GPL-3.0-or-later"
9 |
10 | [profile.dev]
11 | opt-level = 1
12 |
13 | [profile.release]
14 | codegen-units = 1
15 | lto = true
16 | strip = true
17 | opt-level = 3
18 |
19 | [dependencies]
20 | anyhow = "1.0.100"
21 | glob = "0.3.3"
22 | lazy-regex = "3.4.2"
23 | libc = "0.2.177"
24 | num_cpus = "1.17.0"
25 | nutype = { version = "0.6.2", features = ["serde"] }
26 | nvml-wrapper = "0.11.0"
27 | serde = { version = "1.0.228", features = ["serde_derive"] }
28 | syscalls = { version = "0.7.0", features = ["all"] }
29 | thiserror = "2.0.17"
30 | unescape = "0.1.0"
31 | uzers = "0.12.1"
32 |
33 | [dev-dependencies]
34 | pretty_assertions = "1.4.1"
35 |
--------------------------------------------------------------------------------
/data/resources/icons/nvme-symbolic.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/data/resources/icons/ssd-symbolic.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/data/resources/ui/widgets/process_name_cell.ui:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 12
5 |
6 |
12 |
13 |
14 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/data/resources/ui/widgets/application_name_cell.ui:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 12
5 |
6 |
12 |
13 |
14 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/data/resources/icons/processor-symbolic.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/src/bin/resources-kill.rs:
--------------------------------------------------------------------------------
1 | use std::env;
2 |
3 | use nix::{sys::signal, unistd::Pid};
4 |
5 | fn main() {
6 | if let Some(pid) = env::args().nth(1).and_then(|s| s.trim().parse().ok()) {
7 | if let Some(arg) = env::args().nth(2) {
8 | let signal = match arg.as_str() {
9 | "STOP" => signal::Signal::SIGSTOP,
10 | "CONT" => signal::Signal::SIGCONT,
11 | "TERM" => signal::Signal::SIGTERM,
12 | "KILL" => signal::Signal::SIGKILL,
13 | _ => std::process::exit(254),
14 | };
15 | let result = signal::kill(Pid::from_raw(pid), Some(signal));
16 | if let Err(errno) = result {
17 | match errno {
18 | nix::errno::Errno::UnknownErrno => std::process::exit(253),
19 | _ => std::process::exit(errno as i32),
20 | };
21 | }
22 | std::process::exit(0);
23 | }
24 | }
25 | std::process::exit(255);
26 | }
27 |
--------------------------------------------------------------------------------
/data/resources/icons/bluetooth-symbolic.svg:
--------------------------------------------------------------------------------
1 |
2 |
5 |
--------------------------------------------------------------------------------
/data/resources/icons/shell-symbolic.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/resources.doap:
--------------------------------------------------------------------------------
1 |
2 |
3 | Resources
4 | Keep an eye on system resources
5 |
6 |
7 | Rust
8 | GTK 4
9 | Libadwaita
10 |
11 |
12 | nokyan
13 |
14 |
15 |
16 |
17 | nokyan
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/data/resources/icons/cd-dvd-bluray-symbolic.svg:
--------------------------------------------------------------------------------
1 |
2 |
5 |
--------------------------------------------------------------------------------
/src/utils/os.rs:
--------------------------------------------------------------------------------
1 | use lazy_regex::{Lazy, Regex, lazy_regex};
2 | use log::trace;
3 |
4 | use crate::utils::read_parsed;
5 |
6 | use super::IS_FLATPAK;
7 |
8 | const PATH_OS_RELEASE: &str = "/etc/os-release";
9 | const PATH_OS_RELEASE_FLATPAK: &str = "/run/host/etc/os-release";
10 | const PATH_KERNEL_VERSION: &str = "/proc/sys/kernel/osrelease";
11 |
12 | static RE_PRETTY_NAME: Lazy = lazy_regex!("PRETTY_NAME=\"(.*)\"");
13 |
14 | pub struct OsInfo {
15 | pub name: Option,
16 | pub kernel_version: Option,
17 | }
18 |
19 | impl OsInfo {
20 | pub fn get() -> Self {
21 | let os_path = if *IS_FLATPAK {
22 | PATH_OS_RELEASE_FLATPAK
23 | } else {
24 | PATH_OS_RELEASE
25 | };
26 |
27 | trace!("Path for the os-release file is determined to be `{os_path}`");
28 |
29 | let name = RE_PRETTY_NAME
30 | .captures(&read_parsed::(os_path).unwrap_or_default())
31 | .and_then(|captures| captures.get(1))
32 | .map(|capture| capture.as_str().trim().to_string());
33 |
34 | let kernel_version = read_parsed(PATH_KERNEL_VERSION).ok();
35 |
36 | OsInfo {
37 | name,
38 | kernel_version,
39 | }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/data/resources/icons/zram-symbolic.svg:
--------------------------------------------------------------------------------
1 |
2 |
9 |
--------------------------------------------------------------------------------
/src/ui/mod.rs:
--------------------------------------------------------------------------------
1 | extern crate paste;
2 |
3 | #[macro_export]
4 | macro_rules! gstring_getter_setter {
5 | ($($gstring_name:ident),*) => {
6 | $(
7 | pub fn $gstring_name(&self) -> glib::GString {
8 | let $gstring_name = self.$gstring_name.take();
9 | self.$gstring_name.set($gstring_name.clone());
10 | $gstring_name
11 | }
12 |
13 | paste::paste! {
14 | pub fn [](&self, $gstring_name: &str) {
15 | self.$gstring_name.set(glib::GString::from($gstring_name));
16 | }
17 | }
18 | )*
19 | };
20 | }
21 |
22 | #[macro_export]
23 | macro_rules! gstring_option_getter_setter {
24 | ($($gstring_name:ident),*) => {
25 | $(
26 | pub fn $gstring_name(&self) -> Option {
27 | let $gstring_name = self.$gstring_name.take();
28 | self.$gstring_name.set($gstring_name.clone());
29 | $gstring_name
30 | }
31 |
32 | paste::paste! {
33 | pub fn [](&self, $gstring_name: Option<&str>) {
34 | self.$gstring_name.set($gstring_name.map(glib::GString::from));
35 | }
36 | }
37 | )*
38 | };
39 | }
40 |
41 | pub mod dialogs;
42 | pub mod pages;
43 | pub mod widgets;
44 | pub mod window;
45 |
--------------------------------------------------------------------------------
/src/bin/resources-processes.rs:
--------------------------------------------------------------------------------
1 | use anyhow::Result;
2 | use process_data::ProcessData;
3 | use ron::ser::PrettyConfig;
4 | use std::io::{Read, Write};
5 |
6 | use clap::Parser;
7 |
8 | #[derive(Parser, Debug)]
9 | #[command(version, about)]
10 | struct Args {
11 | /// Output once and then exit
12 | #[arg(short, long, default_value_t = false)]
13 | once: bool,
14 |
15 | /// Use Rusty Object Notation (use this only for debugging this binary on its own, Resources won't be able to decode RON)
16 | #[arg(short, long, default_value_t = false)]
17 | ron: bool,
18 | }
19 |
20 | fn main() -> Result<()> {
21 | let args = Args::parse();
22 |
23 | if args.once {
24 | output(args.ron)?;
25 | return Ok(());
26 | }
27 |
28 | loop {
29 | let mut buffer = [0; 1];
30 |
31 | std::io::stdin().read_exact(&mut buffer)?;
32 |
33 | output(args.ron)?;
34 | }
35 | }
36 |
37 | fn output(ron: bool) -> Result<()> {
38 | let data = ProcessData::all_process_data()?;
39 |
40 | let encoded = if ron {
41 | ron::ser::to_string_pretty(&data, PrettyConfig::default())?
42 | .as_bytes()
43 | .to_vec()
44 | } else {
45 | rmp_serde::to_vec(&data)?
46 | };
47 |
48 | let len_byte_array = encoded.len().to_le_bytes();
49 |
50 | let stdout = std::io::stdout();
51 | let mut handle = stdout.lock();
52 |
53 | handle.write_all(&len_byte_array)?;
54 |
55 | handle.write_all(&encoded)?;
56 |
57 | handle.flush()?;
58 | Ok(())
59 | }
60 |
--------------------------------------------------------------------------------
/po/POTFILES.in:
--------------------------------------------------------------------------------
1 | # List of source files containing translatable strings.
2 | # Please keep this file sorted alphabetically.
3 |
4 | data/net.nokyan.Resources.desktop.in.in
5 | data/net.nokyan.Resources.metainfo.xml.in.in
6 | data/net.nokyan.Resources.policy.in.in
7 |
8 | data/resources/ui/dialogs/app_dialog.ui
9 | data/resources/ui/dialogs/process_dialog.ui
10 | data/resources/ui/dialogs/process_options_dialog.ui
11 | data/resources/ui/dialogs/settings_dialog.ui
12 | data/resources/ui/pages/applications.ui
13 | data/resources/ui/pages/battery.ui
14 | data/resources/ui/pages/cpu.ui
15 | data/resources/ui/pages/drive.ui
16 | data/resources/ui/pages/gpu.ui
17 | data/resources/ui/pages/memory.ui
18 | data/resources/ui/pages/network.ui
19 | data/resources/ui/pages/npu.ui
20 | data/resources/ui/pages/processes.ui
21 | data/resources/ui/shortcuts.ui
22 | data/resources/ui/window.ui
23 |
24 | src/application.rs
25 | src/ui/dialogs/app_dialog.rs
26 | src/ui/dialogs/process_dialog.rs
27 | src/ui/dialogs/process_options_dialog.rs
28 | src/ui/pages/applications/application_entry.rs
29 | src/ui/pages/applications/mod.rs
30 | src/ui/pages/battery.rs
31 | src/ui/pages/cpu.rs
32 | src/ui/pages/drive.rs
33 | src/ui/pages/gpu.rs
34 | src/ui/pages/memory.rs
35 | src/ui/pages/mod.rs
36 | src/ui/pages/network.rs
37 | src/ui/pages/processes/mod.rs
38 | src/ui/pages/processes/process_entry.rs
39 | src/ui/window.rs
40 | src/utils/app.rs
41 | src/utils/battery.rs
42 | src/utils/units.rs
43 | src/utils/drive.rs
44 | src/utils/gpu/mod.rs
45 | src/utils/link/mod.rs
46 | src/utils/network.rs
47 | src/utils/npu/mod.rs
48 | src/utils/units.rs
49 |
--------------------------------------------------------------------------------
/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "resources"
3 | version = "1.9.1"
4 | authors = ["nokyan "]
5 | edition = "2024"
6 | rust-version = "1.85.0"
7 | homepage = "https://apps.gnome.org/app/net.nokyan.Resources/"
8 | license = "GPL-3.0-or-later"
9 |
10 | [profile.dev]
11 | opt-level = 1
12 |
13 | [profile.release]
14 | codegen-units = 1
15 | lto = true
16 | strip = true
17 | opt-level = 3
18 |
19 | [dependencies]
20 | adw = { version = "0.8.0", features = ["v1_8"], package = "libadwaita" }
21 | anyhow = { version = "1.0.100", features = ["backtrace"] }
22 | async-channel = "2.5.0"
23 | clap = { version = "4.5.51", features = ["derive"] }
24 | gettext-rs = { version = "0.7.7", features = ["gettext-system"] }
25 | glob = "0.3.3"
26 | gtk = { version = "0.10.2", features = ["v4_12"], package = "gtk4" }
27 | lazy-regex = "3.4.1"
28 | libc = { version = "0.2.177", features = ["extra_traits"] }
29 | log = "0.4.28"
30 | neli-wifi = { version = "0.6.1", features = ["default"] }
31 | nix = { version = "0.30.0", default-features = false, features = [
32 | "signal",
33 | "sched",
34 | ] }
35 | num_cpus = "1.17.0"
36 | nvml-wrapper = "0.11.0"
37 | paste = "1.0.15"
38 | path-dedot = "3.1.1"
39 | plotters = { version = "0.3.7", default-features = false, features = [
40 | "area_series",
41 | ] }
42 | plotters-cairo = "0.8.0"
43 | pretty_env_logger = "0.5"
44 | process-data = { path = "lib/process_data" }
45 | rmp-serde = "1.3.0"
46 | ron = "0.12.0"
47 | rust-ini = "0.21.3"
48 | strum = "0.27.2"
49 | strum_macros = "0.27.2"
50 | sysconf = "0.3.4"
51 |
52 | [dev-dependencies]
53 | pretty_assertions = "1.4.1"
54 |
--------------------------------------------------------------------------------
/data/resources/icons/wlan-symbolic.svg:
--------------------------------------------------------------------------------
1 |
2 |
5 |
--------------------------------------------------------------------------------
/src/bin/resources-adjust.rs:
--------------------------------------------------------------------------------
1 | use std::{env, path::PathBuf};
2 |
3 | use nix::{
4 | sched::{CpuSet, sched_setaffinity},
5 | unistd::Pid,
6 | };
7 |
8 | fn main() {
9 | if let Some(pid) = env::args().nth(1).and_then(|s| s.trim().parse().ok()) {
10 | if let Some(nice) = env::args().nth(2).and_then(|s| s.trim().parse().ok()) {
11 | if let Some(mask) = env::args().nth(3) {
12 | let mut cpu_set = CpuSet::new();
13 |
14 | for (i, c) in mask.chars().enumerate() {
15 | if c == '1' {
16 | cpu_set.set(i).unwrap_or_default();
17 | }
18 | }
19 |
20 | adjust(pid, nice, &cpu_set);
21 |
22 | // find tasks that belong to this process
23 | let tasks_path = PathBuf::from("/proc/").join(pid.to_string()).join("task");
24 | for entry in std::fs::read_dir(tasks_path).unwrap().flatten() {
25 | let thread_id = entry.file_name().to_string_lossy().parse().unwrap();
26 |
27 | adjust(thread_id, nice, &cpu_set);
28 | }
29 |
30 | std::process::exit(0)
31 | }
32 | }
33 | }
34 | std::process::exit(255);
35 | }
36 |
37 | fn adjust(id: i32, nice: i32, cpu_set: &CpuSet) {
38 | unsafe {
39 | libc::setpriority(libc::PRIO_PROCESS, id as u32, nice);
40 | };
41 |
42 | let error = std::io::Error::last_os_error()
43 | .raw_os_error()
44 | .unwrap_or_default();
45 |
46 | if error != 0 {
47 | std::process::exit(error)
48 | }
49 |
50 | let _ = sched_setaffinity(Pid::from_raw(id), cpu_set);
51 | }
52 |
--------------------------------------------------------------------------------
/data/resources/icons/vpn-symbolic.svg:
--------------------------------------------------------------------------------
1 |
2 |
8 |
--------------------------------------------------------------------------------
/src/ui/pages/mod.rs:
--------------------------------------------------------------------------------
1 | use std::{collections::HashMap, sync::LazyLock};
2 |
3 | use process_data::Niceness;
4 |
5 | use crate::i18n::pi18n;
6 |
7 | pub mod applications;
8 | pub mod battery;
9 | pub mod cpu;
10 | pub mod drive;
11 | pub mod gpu;
12 | pub mod memory;
13 | pub mod network;
14 | pub mod npu;
15 | pub mod processes;
16 |
17 | const APPLICATIONS_PRIMARY_ORD: u32 = 0;
18 | const PROCESSES_PRIMARY_ORD: u32 = 1;
19 | const CPU_PRIMARY_ORD: u32 = 2;
20 | const MEMORY_PRIMARY_ORD: u32 = 3;
21 | const GPU_PRIMARY_ORD: u32 = 4;
22 | const NPU_PRIMARY_ORD: u32 = 5;
23 | const DRIVE_PRIMARY_ORD: u32 = 6;
24 | const NETWORK_PRIMARY_ORD: u32 = 7;
25 | const BATTERY_PRIMARY_ORD: u32 = 8;
26 |
27 | pub static NICE_TO_LABEL: LazyLock> = LazyLock::new(|| {
28 | let mut hash_map = HashMap::new();
29 |
30 | for i in -20..=-8 {
31 | hash_map.insert(
32 | Niceness::try_new(i).unwrap(),
33 | (pi18n("process priority", "Very High"), 0),
34 | );
35 | }
36 |
37 | for i in -7..=-3 {
38 | hash_map.insert(
39 | Niceness::try_new(i).unwrap(),
40 | (pi18n("process priority", "High"), 1),
41 | );
42 | }
43 |
44 | for i in -2..=2 {
45 | hash_map.insert(
46 | Niceness::try_new(i).unwrap(),
47 | (pi18n("process priority", "Normal"), 2),
48 | );
49 | }
50 |
51 | for i in 3..=6 {
52 | hash_map.insert(
53 | Niceness::try_new(i).unwrap(),
54 | (pi18n("process priority", "Low"), 3),
55 | );
56 | }
57 |
58 | for i in 7..=19 {
59 | hash_map.insert(
60 | Niceness::try_new(i).unwrap(),
61 | (pi18n("process priority", "Very Low"), 4),
62 | );
63 | }
64 |
65 | hash_map
66 | });
67 |
--------------------------------------------------------------------------------
/src/utils/link/mod.rs:
--------------------------------------------------------------------------------
1 | mod pcie;
2 | mod sata;
3 | mod usb;
4 | mod wifi;
5 |
6 | use crate::i18n::i18n;
7 | use crate::utils::link::pcie::PcieLinkData;
8 | use crate::utils::link::sata::SataSpeed;
9 | use crate::utils::link::usb::UsbSpeed;
10 | use crate::utils::link::wifi::WifiGeneration;
11 | use anyhow::Result;
12 | use std::fmt::{Display, Formatter};
13 |
14 | #[derive(Debug, Default)]
15 | pub enum Link {
16 | Pcie(LinkData),
17 | Sata(LinkData),
18 | Usb(LinkData),
19 | Wifi(LinkData),
20 | #[default]
21 | Unknown,
22 | }
23 |
24 | #[derive(Debug)]
25 | pub struct LinkData {
26 | pub current: T,
27 | pub max: Result,
28 | }
29 |
30 | impl Display for LinkData
31 | where
32 | T: Display,
33 | T: PartialEq,
34 | {
35 | fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
36 | let has_different_max = {
37 | if let Ok(max) = self.max.as_ref() {
38 | self.current != *max
39 | } else {
40 | false
41 | }
42 | };
43 | if has_different_max {
44 | write!(f, "{} / {}", self.current, self.max.as_ref().unwrap())
45 | } else {
46 | write!(f, "{}", self.current)
47 | }
48 | }
49 | }
50 |
51 | impl Display for Link {
52 | fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
53 | write!(
54 | f,
55 | "{}",
56 | match self {
57 | Link::Pcie(data) => data.to_string(),
58 | Link::Sata(data) => data.to_string(),
59 | Link::Usb(data) => data.to_string(),
60 | Link::Wifi(data) => data.to_string(),
61 | Link::Unknown => i18n("N/A"),
62 | }
63 | )
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/hooks/pre-commit.hook:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | # Source: https://gitlab.gnome.org/GNOME/fractal/blob/master/hooks/pre-commit.hook
3 |
4 | install_rustfmt() {
5 | if ! which rustup &> /dev/null; then
6 | curl https://sh.rustup.rs -sSf | sh -s -- -y
7 | export PATH=$PATH:$HOME/.cargo/bin
8 | if ! which rustup &> /dev/null; then
9 | echo "Failed to install rustup. Performing the commit without style checking."
10 | exit 0
11 | fi
12 | fi
13 |
14 | if ! rustup component list|grep rustfmt &> /dev/null; then
15 | echo "Installing rustfmt…"
16 | rustup component add rustfmt
17 | fi
18 | }
19 |
20 | if ! which cargo >/dev/null 2>&1 || ! cargo fmt --help >/dev/null 2>&1; then
21 | echo "Unable to check the project’s code style, because rustfmt could not be run."
22 |
23 | if [ ! -t 1 ]; then
24 | # No input is possible
25 | echo "Performing commit."
26 | exit 0
27 | fi
28 |
29 | echo ""
30 | echo "y: Install rustfmt via rustup"
31 | echo "n: Don't install rustfmt and perform the commit"
32 | echo "Q: Don't install rustfmt and abort the commit"
33 |
34 | echo ""
35 | while true
36 | do
37 | echo -n "Install rustfmt via rustup? [y/n/Q]: "; read yn < /dev/tty
38 | case $yn in
39 | [Yy]* ) install_rustfmt; break;;
40 | [Nn]* ) echo "Performing commit."; exit 0;;
41 | [Qq]* | "" ) echo "Aborting commit."; exit -1 >/dev/null 2>&1;;
42 | * ) echo "Invalid input";;
43 | esac
44 | done
45 |
46 | fi
47 |
48 | echo "--Checking style--"
49 | cargo fmt --all -- --check
50 | if test $? != 0; then
51 | echo "--Checking style fail--"
52 | echo "Please fix the above issues, either manually or by running: cargo fmt --all"
53 |
54 | exit -1
55 | else
56 | echo "--Checking style pass--"
57 | fi
58 |
--------------------------------------------------------------------------------
/data/resources/icons/emmc-symbolic.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.yaml:
--------------------------------------------------------------------------------
1 | name: "\U0001F680 Feature Request"
2 | description: Suggest an idea for this project
3 | labels: ["enhancement"]
4 | body:
5 | - type: checkboxes
6 | attributes:
7 | label: Is there an existing issue for this?
8 | description: Please search to see if an issue already exists for the bug you encountered.
9 | options:
10 | - label: I have searched the existing issues
11 | required: true
12 | - type: textarea
13 | attributes:
14 | label: Is your feature request related to a problem? Please describe.
15 | description: A clear and concise description of what the problem is.
16 | placeholder: I'm always frustrated when I have to press this small button.
17 | validations:
18 | required: false
19 | - type: textarea
20 | attributes:
21 | label: Describe the solution you'd like
22 | description: A clear and concise description of what you want to happen.
23 | placeholder: Make this button bigger
24 | validations:
25 | required: false
26 | - type: textarea
27 | attributes:
28 | label: Describe alternatives you've considered
29 | description: A clear and concise description of any alternative solutions or features you've considered.
30 | placeholder: I wanted to but bigger display, but it would be too expensive.
31 | validations:
32 | required: false
33 | - type: textarea
34 | attributes:
35 | label: Additional context
36 | description: |
37 | Add any other context or screenshots about the feature request here.
38 |
39 | Tip: You can attach images or log files by clicking this area to highlight it and then dragging files in.
40 | validations:
41 | required: false
42 | - type: markdown
43 | attributes:
44 | value: |
45 | ---
46 | The more thumbs up 👍 your idea gets, the more likely it is that we'll implement it. ✨
47 |
--------------------------------------------------------------------------------
/data/resources/icons/select-all-symbolic.svg:
--------------------------------------------------------------------------------
1 |
2 |
46 |
--------------------------------------------------------------------------------
/data/resources/icons/bridge-symbolic.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/data/resources/icons/vm-bridge-symbolic.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/data/resources/icons/docker-bridge-symbolic.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/data/resources/icons/loop-device-symbolic.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/data/resources/icons/ram-disk-symbolic.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/data/resources/ui/widgets/graph_box.ui:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | true
5 | hidden
6 | false
7 | 160
8 |
9 |
53 |
54 |
55 |
--------------------------------------------------------------------------------
/src/utils/npu/other.rs:
--------------------------------------------------------------------------------
1 | use anyhow::Result;
2 | use process_data::pci_slot::PciSlot;
3 |
4 | use std::path::{Path, PathBuf};
5 |
6 | use crate::utils::pci::Device;
7 |
8 | use super::NpuImpl;
9 |
10 | #[derive(Debug, Clone, Default)]
11 |
12 | pub struct OtherNpu {
13 | pub device: Option<&'static Device>,
14 | pub pci_slot: PciSlot,
15 | pub driver: String,
16 | sysfs_path: PathBuf,
17 | first_hwmon_path: Option,
18 | }
19 |
20 | impl OtherNpu {
21 | pub fn new(
22 | device: Option<&'static Device>,
23 | pci_slot: PciSlot,
24 | driver: String,
25 | sysfs_path: PathBuf,
26 | first_hwmon_path: Option,
27 | ) -> Self {
28 | Self {
29 | device,
30 | pci_slot,
31 | driver,
32 | sysfs_path,
33 | first_hwmon_path,
34 | }
35 | }
36 | }
37 |
38 | impl NpuImpl for OtherNpu {
39 | fn device(&self) -> Option<&'static Device> {
40 | self.device
41 | }
42 |
43 | fn pci_slot(&self) -> PciSlot {
44 | self.pci_slot
45 | }
46 |
47 | fn driver(&self) -> &str {
48 | &self.driver
49 | }
50 |
51 | fn sysfs_path(&self) -> &Path {
52 | &self.sysfs_path
53 | }
54 |
55 | fn first_hwmon(&self) -> Option<&Path> {
56 | self.first_hwmon_path.as_deref()
57 | }
58 |
59 | fn name(&self) -> Result {
60 | self.drm_name()
61 | }
62 |
63 | fn usage(&self) -> Result {
64 | self.drm_usage().map(|usage| usage as f64 / 100.0)
65 | }
66 |
67 | fn used_vram(&self) -> Result {
68 | self.drm_used_memory().map(|usage| usage as usize)
69 | }
70 |
71 | fn total_vram(&self) -> Result {
72 | self.drm_total_memory().map(|usage| usage as usize)
73 | }
74 |
75 | fn temperature(&self) -> Result {
76 | self.hwmon_temperature()
77 | }
78 |
79 | fn power_usage(&self) -> Result {
80 | self.hwmon_power_usage()
81 | }
82 |
83 | fn core_frequency(&self) -> Result {
84 | self.hwmon_core_frequency()
85 | }
86 |
87 | fn memory_frequency(&self) -> Result {
88 | self.hwmon_memory_frequency()
89 | }
90 |
91 | fn power_cap(&self) -> Result {
92 | self.hwmon_power_cap()
93 | }
94 |
95 | fn power_cap_max(&self) -> Result {
96 | self.hwmon_power_cap_max()
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/data/resources/icons/device-settings-symbolic.svg:
--------------------------------------------------------------------------------
1 |
9 |
--------------------------------------------------------------------------------
/data/resources/icons/generic-process-symbolic.svg:
--------------------------------------------------------------------------------
1 |
2 |
5 |
--------------------------------------------------------------------------------
/meson.build:
--------------------------------------------------------------------------------
1 | project(
2 | 'resources',
3 | 'rust',
4 | version: '1.9.1',
5 | meson_version: '>= 0.59',
6 | )
7 |
8 | i18n = import('i18n')
9 | gnome = import('gnome')
10 |
11 | base_id = 'net.nokyan.Resources'
12 |
13 | dependency('glib-2.0', version: '>= 2.66')
14 | dependency('gio-2.0', version: '>= 2.66')
15 | dependency('gtk4', version: '>= 4.12.0')
16 | dependency('libadwaita-1', version: '>= 1.8.0')
17 |
18 | glib_compile_resources = find_program('glib-compile-resources', required: true)
19 | glib_compile_schemas = find_program('glib-compile-schemas', required: true)
20 | desktop_file_validate = find_program('desktop-file-validate', required: false)
21 | appstreamcli = find_program('appstreamcli', required: false)
22 | cargo = find_program('cargo', required: true)
23 |
24 | version = meson.project_version()
25 |
26 | prefix = get_option('prefix')
27 | bindir = prefix / get_option('bindir')
28 | localedir = prefix / get_option('localedir')
29 | libexecdir = prefix / get_option('libexecdir') / meson.project_name()
30 |
31 | datadir = prefix / get_option('datadir')
32 | pkgdatadir = datadir / meson.project_name()
33 | iconsdir = datadir / 'icons'
34 | podir = meson.project_source_root() / 'po'
35 | gettext_package = meson.project_name()
36 |
37 | if get_option('profile') == 'development'
38 | profile = 'Devel'
39 | vcs_tag = run_command('git', 'rev-parse', '--short', 'HEAD').stdout().strip()
40 | vcs_branch = run_command('git', 'rev-parse', '--abbrev-ref', 'HEAD').stdout().strip()
41 | if vcs_tag == '' or vcs_branch == ''
42 | version_suffix = '-devel'
43 | else
44 | version_suffix = '-@0@@@1@'.format(vcs_branch, vcs_tag)
45 | endif
46 | application_id = '@0@.@1@'.format(base_id, profile)
47 | else
48 | profile = ''
49 | version_suffix = ''
50 | application_id = base_id
51 | endif
52 |
53 | meson.add_dist_script(
54 | 'build-aux/dist-vendor.sh',
55 | meson.project_build_root() / 'meson-dist' / meson.project_name()
56 | + '-'
57 | + version,
58 | meson.project_source_root(),
59 | )
60 |
61 | if get_option('profile') == 'development'
62 | # Setup pre-commit hook for ensuring coding style is always consistent
63 | message('Setting up git pre-commit hook..')
64 | run_command('cp', '-f', 'hooks/pre-commit.hook', '.git/hooks/pre-commit')
65 | endif
66 |
67 | subdir('data')
68 | subdir('po')
69 | subdir('src')
70 | subdir('lib/process_data/src')
71 |
72 | gnome.post_install(
73 | gtk_update_icon_cache: true,
74 | glib_compile_schemas: true,
75 | update_desktop_database: true,
76 | )
--------------------------------------------------------------------------------
/data/resources/ui/widgets/stack_sidebar_item.ui:
--------------------------------------------------------------------------------
1 |
2 |
3 |
72 |
--------------------------------------------------------------------------------
/data/resources/icons/raid-symbolic.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/src/utils/gpu/other.rs:
--------------------------------------------------------------------------------
1 | use anyhow::{Result, bail};
2 | use process_data::gpu_usage::GpuIdentifier;
3 |
4 | use std::path::{Path, PathBuf};
5 |
6 | use crate::utils::pci::Device;
7 |
8 | use super::GpuImpl;
9 |
10 | #[derive(Debug, Clone, Default)]
11 |
12 | pub struct OtherGpu {
13 | pub device: Option<&'static Device>,
14 | pub gpu_identifier: GpuIdentifier,
15 | pub driver: String,
16 | sysfs_path: PathBuf,
17 | first_hwmon_path: Option,
18 | }
19 |
20 | impl OtherGpu {
21 | pub fn new(
22 | device: Option<&'static Device>,
23 | gpu_identifier: GpuIdentifier,
24 | driver: String,
25 | sysfs_path: PathBuf,
26 | first_hwmon_path: Option,
27 | ) -> Self {
28 | Self {
29 | device,
30 | gpu_identifier,
31 | driver,
32 | sysfs_path,
33 | first_hwmon_path,
34 | }
35 | }
36 | }
37 |
38 | impl GpuImpl for OtherGpu {
39 | fn device(&self) -> Option<&'static Device> {
40 | self.device
41 | }
42 |
43 | fn gpu_identifier(&self) -> GpuIdentifier {
44 | self.gpu_identifier
45 | }
46 |
47 | fn driver(&self) -> &str {
48 | &self.driver
49 | }
50 |
51 | fn sysfs_path(&self) -> &Path {
52 | &self.sysfs_path
53 | }
54 |
55 | fn first_hwmon(&self) -> Option<&Path> {
56 | self.first_hwmon_path.as_deref()
57 | }
58 |
59 | fn name(&self) -> Result {
60 | self.drm_name()
61 | }
62 |
63 | fn usage(&self) -> Result {
64 | self.drm_usage().map(|usage| usage as f64 / 100.0)
65 | }
66 |
67 | fn encode_usage(&self) -> Result {
68 | bail!("encode usage not implemented for other")
69 | }
70 |
71 | fn decode_usage(&self) -> Result {
72 | bail!("decode usage not implemented for other")
73 | }
74 |
75 | fn combined_media_engine(&self) -> Result {
76 | bail!("can't know for other GPUs")
77 | }
78 |
79 | fn used_vram(&self) -> Result {
80 | self.drm_used_vram().map(|usage| usage as u64)
81 | }
82 |
83 | fn total_vram(&self) -> Result {
84 | self.drm_total_vram().map(|usage| usage as u64)
85 | }
86 |
87 | fn temperature(&self) -> Result {
88 | self.hwmon_temperature()
89 | }
90 |
91 | fn power_usage(&self) -> Result {
92 | self.hwmon_power_usage()
93 | }
94 |
95 | fn core_frequency(&self) -> Result {
96 | self.hwmon_core_frequency()
97 | }
98 |
99 | fn vram_frequency(&self) -> Result {
100 | self.hwmon_vram_frequency()
101 | }
102 |
103 | fn power_cap(&self) -> Result {
104 | self.hwmon_power_cap()
105 | }
106 |
107 | fn power_cap_max(&self) -> Result {
108 | self.hwmon_power_cap_max()
109 | }
110 | }
111 |
--------------------------------------------------------------------------------
/src/ui/widgets/graph_box.rs:
--------------------------------------------------------------------------------
1 | use adw::{prelude::*, subclass::prelude::*};
2 | use gtk::glib;
3 | use log::trace;
4 |
5 | use crate::config::PROFILE;
6 |
7 | use super::graph::ResGraph;
8 |
9 | mod imp {
10 | use crate::ui::widgets::graph::ResGraph;
11 |
12 | use super::*;
13 |
14 | use gtk::CompositeTemplate;
15 |
16 | #[derive(Debug, CompositeTemplate, Default)]
17 | #[template(resource = "/net/nokyan/Resources/ui/widgets/graph_box.ui")]
18 | pub struct ResGraphBox {
19 | #[template_child]
20 | pub graph: TemplateChild,
21 | #[template_child]
22 | pub title_label: TemplateChild,
23 | #[template_child]
24 | pub info_label: TemplateChild,
25 | }
26 |
27 | #[glib::object_subclass]
28 | impl ObjectSubclass for ResGraphBox {
29 | const NAME: &'static str = "ResGraphBox";
30 | type Type = super::ResGraphBox;
31 | type ParentType = adw::PreferencesRow;
32 |
33 | fn class_init(klass: &mut Self::Class) {
34 | Self::bind_template(klass);
35 | }
36 |
37 | // You must call `Widget`'s `init_template()` within `instance_init()`.
38 | fn instance_init(obj: &glib::subclass::InitializingObject) {
39 | obj.init_template();
40 | }
41 | }
42 |
43 | impl ObjectImpl for ResGraphBox {
44 | fn constructed(&self) {
45 | self.parent_constructed();
46 | let obj = self.obj();
47 |
48 | // Devel Profile
49 | if PROFILE == "Devel" {
50 | obj.add_css_class("devel");
51 | }
52 | }
53 | }
54 |
55 | impl WidgetImpl for ResGraphBox {}
56 |
57 | impl ListBoxRowImpl for ResGraphBox {}
58 |
59 | impl PreferencesRowImpl for ResGraphBox {}
60 | }
61 |
62 | glib::wrapper! {
63 | pub struct ResGraphBox(ObjectSubclass)
64 | @extends gtk::Widget, gtk::ListBoxRow, adw::PreferencesRow,
65 | @implements gtk::Buildable, gtk::ConstraintTarget, gtk::Accessible, gtk::Actionable;
66 | }
67 |
68 | impl Default for ResGraphBox {
69 | fn default() -> Self {
70 | Self::new()
71 | }
72 | }
73 |
74 | impl ResGraphBox {
75 | pub fn new() -> Self {
76 | trace!("Creating ResGraphBox GObject…");
77 |
78 | glib::Object::new::()
79 | }
80 |
81 | pub fn graph(&self) -> ResGraph {
82 | self.imp().graph.get()
83 | }
84 |
85 | pub fn set_title_label(&self, str: &str) {
86 | let imp = self.imp();
87 | imp.title_label.set_label(str);
88 | }
89 |
90 | pub fn set_subtitle(&self, str: &str) {
91 | let imp = self.imp();
92 | imp.info_label.set_label(str);
93 | }
94 |
95 | pub fn set_tooltip(&self, str: Option<&str>) {
96 | let imp = self.imp();
97 | imp.info_label.set_tooltip_text(str);
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/src/utils/gpu/v3d.rs:
--------------------------------------------------------------------------------
1 | use anyhow::{Result, bail};
2 | use process_data::gpu_usage::GpuIdentifier;
3 |
4 | use std::path::{Path, PathBuf};
5 |
6 | use crate::utils::{pci::Device, read_parsed};
7 |
8 | use super::GpuImpl;
9 |
10 | #[derive(Debug, Clone, Default)]
11 |
12 | pub struct V3dGpu {
13 | pub device: Option<&'static Device>,
14 | pub gpu_identifier: GpuIdentifier,
15 | pub driver: String,
16 | sysfs_path: PathBuf,
17 | first_hwmon_path: Option,
18 | }
19 |
20 | impl V3dGpu {
21 | pub fn new(
22 | device: Option<&'static Device>,
23 | gpu_identifier: GpuIdentifier,
24 | driver: String,
25 | sysfs_path: PathBuf,
26 | first_hwmon_path: Option,
27 | ) -> Self {
28 | Self {
29 | device,
30 | gpu_identifier,
31 | driver,
32 | sysfs_path,
33 | first_hwmon_path,
34 | }
35 | }
36 | }
37 |
38 | impl GpuImpl for V3dGpu {
39 | fn device(&self) -> Option<&'static Device> {
40 | self.device
41 | }
42 |
43 | fn gpu_identifier(&self) -> GpuIdentifier {
44 | self.gpu_identifier
45 | }
46 |
47 | fn driver(&self) -> &str {
48 | &self.driver
49 | }
50 |
51 | fn sysfs_path(&self) -> &Path {
52 | &self.sysfs_path
53 | }
54 |
55 | fn first_hwmon(&self) -> Option<&Path> {
56 | self.first_hwmon_path.as_deref()
57 | }
58 |
59 | fn name(&self) -> Result {
60 | self.drm_name()
61 | }
62 |
63 | fn usage(&self) -> Result {
64 | self.drm_usage().map(|usage| usage as f64 / 100.0)
65 | }
66 |
67 | fn encode_usage(&self) -> Result {
68 | bail!("encode usage not implemented for v3d")
69 | }
70 |
71 | fn decode_usage(&self) -> Result {
72 | bail!("decode usage not implemented for v3d")
73 | }
74 |
75 | fn combined_media_engine(&self) -> Result {
76 | Ok(true)
77 | }
78 |
79 | fn used_vram(&self) -> Result {
80 | self.drm_used_vram().map(|usage| usage as u64)
81 | }
82 |
83 | fn total_vram(&self) -> Result {
84 | self.drm_total_vram().map(|usage| usage as u64)
85 | }
86 |
87 | fn temperature(&self) -> Result {
88 | self.hwmon_temperature()
89 | }
90 |
91 | fn power_usage(&self) -> Result {
92 | self.hwmon_power_usage()
93 | }
94 |
95 | fn core_frequency(&self) -> Result {
96 | read_parsed::(self.sysfs_path().join("gt_cur_freq_mhz")).map(|freq| freq * 1_000_000.0)
97 | }
98 |
99 | fn vram_frequency(&self) -> Result {
100 | self.hwmon_vram_frequency()
101 | }
102 |
103 | fn power_cap(&self) -> Result {
104 | self.hwmon_power_cap()
105 | }
106 |
107 | fn power_cap_max(&self) -> Result {
108 | self.hwmon_power_cap_max()
109 | }
110 | }
111 |
--------------------------------------------------------------------------------
/data/resources/icons/npu-symbolic.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/data/meson.build:
--------------------------------------------------------------------------------
1 | subdir('icons')
2 | subdir('resources')
3 | # Desktop file
4 | desktop_conf = configuration_data()
5 | desktop_conf.set('icon', application_id)
6 | desktop_file = i18n.merge_file(
7 | type: 'desktop',
8 | input: configure_file(
9 | input: '@0@.desktop.in.in'.format(base_id),
10 | output: '@BASENAME@',
11 | configuration: desktop_conf
12 | ),
13 | output: '@0@.desktop'.format(application_id),
14 | po_dir: podir,
15 | install: true,
16 | install_dir: datadir / 'applications'
17 | )
18 | # Validate Desktop file
19 | if desktop_file_validate.found()
20 | test(
21 | 'validate-desktop',
22 | desktop_file_validate,
23 | args: [
24 | desktop_file.full_path()
25 | ],
26 | depends: desktop_file,
27 | )
28 | endif
29 |
30 | # Appdata
31 | appdata_conf = configuration_data()
32 | appdata_conf.set('app-id', application_id)
33 | appdata_conf.set('gettext-package', gettext_package)
34 | appdata_file = i18n.merge_file(
35 | input: configure_file(
36 | input: '@0@.metainfo.xml.in.in'.format(base_id),
37 | output: '@BASENAME@',
38 | configuration: appdata_conf
39 | ),
40 | output: '@0@.metainfo.xml'.format(application_id),
41 | po_dir: podir,
42 | install: true,
43 | install_dir: datadir / 'metainfo'
44 | )
45 | # Validate Appdata
46 | if appstreamcli.found()
47 | test(
48 | 'validate-appdata', appstreamcli,
49 | args: [
50 | 'validate', '--no-net', '--explain', appdata_file.full_path()
51 | ],
52 | depends: appdata_file,
53 | )
54 | endif
55 |
56 | # GSchema
57 | gschema_conf = configuration_data()
58 | gschema_conf.set('app-id', application_id)
59 | gschema_conf.set('gettext-package', gettext_package)
60 | configure_file(
61 | input: '@0@.gschema.xml.in'.format(base_id),
62 | output: '@0@.gschema.xml'.format(application_id),
63 | configuration: gschema_conf,
64 | install: true,
65 | install_dir: datadir / 'glib-2.0' / 'schemas'
66 | )
67 |
68 | # Validate GSchema
69 | if glib_compile_schemas.found()
70 | test(
71 | 'validate-gschema', glib_compile_schemas,
72 | args: [
73 | '--strict', '--dry-run', meson.current_build_dir()
74 | ],
75 | )
76 | endif
77 |
78 | # DBus service
79 | # install_data('net.nokyan.Resources.conf', install_dir : datadir / 'dbus-1' / 'system.d')
80 |
81 | # systemd Service file
82 | #service_conf = configuration_data()
83 | #service_conf.set('bindir', bindir)
84 | #configure_file(
85 | # input: '@0@.service.in'.format(base_id),
86 | # output: '@0@.service'.format(application_id),
87 | # configuration: service_conf,
88 | # install: true,
89 | # install_dir: prefix / 'lib' / 'systemd' / 'system'
90 | #)
91 |
92 | # polkit Policy file
93 | policy_conf = configuration_data()
94 | policy_conf.set('libexecdir', libexecdir)
95 | policy_conf.set('gettext-package', gettext_package)
96 | i18n.merge_file(
97 | input: configure_file(
98 | input: '@0@.policy.in.in'.format(base_id),
99 | output: '@BASENAME@',
100 | configuration: policy_conf,
101 | ),
102 | output: '@0@.policy'.format(application_id),
103 | po_dir: podir,
104 | data_dirs: podir,
105 | install: true,
106 | install_dir: datadir / 'polkit-1' / 'actions'
107 | )
108 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.yaml:
--------------------------------------------------------------------------------
1 | name: "\U0001F41E Bug Report"
2 | description: Create a bug report to help us fix it
3 | labels: ["bug"]
4 | body:
5 | - type: checkboxes
6 | attributes:
7 | label: Is there an existing issue for this?
8 | description: Please search to see if an issue already exists for the bug you encountered.
9 | options:
10 | - label: I searched the existing issues and did not find anything similar.
11 | required: true
12 | - type: textarea
13 | attributes:
14 | label: Current Behavior
15 | description: A concise description of what you're experiencing.
16 | validations:
17 | required: false
18 | - type: textarea
19 | attributes:
20 | label: Expected Behavior
21 | description: A concise description of what you expected to happen.
22 | validations:
23 | required: false
24 | - type: textarea
25 | attributes:
26 | label: Steps to Reproduce
27 | description: Steps to reproduce the behavior.
28 | placeholder: |
29 | 1. Go to '…'
30 | 2. Click on '…'
31 | 3. Scroll down to '…'
32 | 4. See error
33 | validations:
34 | required: false
35 | - type: textarea
36 | attributes:
37 | label: Debug Logs
38 | description: |
39 | Please run Resources once with debug logs enabled (if possible) and include the terminal output here. This helps us to get hardware and software information in a streamlined way.
40 | You can do this by running `flatpak run --env=RUST_LOG=resources=debug net.nokyan.Resources` in your terminal if you've installed Resources using Flatpak. Otherwise run `RUST_LOG=resources=debug resources`.
41 | Especially during process and app detection, personally identifiable information may be printed in the debug logs, please double-check that there's nothing inside that you don't want to be public. If your issue is unrelated to process/app detection, you can safely omit any messages that start with `DEBUG resources::utils::app`.
42 | value: |
43 |
44 | Expand logs
45 |
46 |
47 | ```
48 | REPLACE THIS SENTENCE WITH THE TERMINAL OUTPUT OF THE AFOREMENTIONED COMMAND.
49 | ```
50 |
51 | validations:
52 | required: true
53 | - type: textarea
54 | attributes:
55 | label: Environment
56 | description: |
57 | Please provide information about your environment if you were not able to include debug logs as described above.
58 | placeholder: |
59 | Resources version: 1.9.1 (you can find this in the 'About' dialog)
60 | Package type: Flatpak
61 | Operating system: Ubuntu 22.04
62 | Hardware info: Intel i7-7700k, Nvidia GTX 1080, …
63 | …
64 | render: markdown
65 | validations:
66 | required: false
67 | - type: textarea
68 | attributes:
69 | label: Anything Else?
70 | description: |
71 | Links? References? Anything that will give us more context about the issue you are encountering!
72 |
73 | Tip: You can attach images or log files by clicking this area to highlight it and then dragging files in.
74 | validations:
75 | required: false
76 |
--------------------------------------------------------------------------------
/src/meson.build:
--------------------------------------------------------------------------------
1 | global_conf = configuration_data()
2 | global_conf.set_quoted('APP_ID', application_id)
3 | global_conf.set_quoted('PKGDATADIR', pkgdatadir)
4 | global_conf.set_quoted('PROFILE', profile)
5 | global_conf.set_quoted('VERSION', version + version_suffix)
6 | global_conf.set_quoted('GETTEXT_PACKAGE', gettext_package)
7 | global_conf.set_quoted('LOCALEDIR', localedir)
8 | global_conf.set_quoted('LIBEXECDIR', libexecdir)
9 | config = configure_file(input: 'config.rs.in', output: 'config.rs', configuration: global_conf)
10 | # Copy the config.rs output to the source directory.
11 | run_command(
12 | 'cp',
13 | meson.project_build_root() / 'src' / 'config.rs',
14 | meson.project_source_root() / 'src' / 'config.rs',
15 | check: true,
16 | )
17 |
18 | cargo_options = ['--manifest-path', meson.project_source_root() / 'Cargo.toml']
19 | cargo_options += ['--target-dir', meson.project_build_root() / 'src']
20 |
21 | if get_option('profile') == 'default'
22 | cargo_options += ['--release']
23 | rust_target = 'release'
24 | message('Building in release mode')
25 | else
26 | rust_target = 'debug'
27 | message('Building in debug mode')
28 | endif
29 |
30 | cargo_env = ['CARGO_HOME=' + meson.project_build_root() / 'cargo']
31 |
32 | test(
33 | 'Cargo tests (main application)',
34 | cargo,
35 | args: ['test', cargo_options],
36 | timeout: 3600,
37 | env: cargo_env,
38 | )
39 |
40 | cargo_build = custom_target(
41 | 'cargo-build',
42 | depends: resources,
43 | build_by_default: true,
44 | build_always_stale: true,
45 | output: rust_target,
46 | console: true,
47 | command: [
48 | 'env',
49 | cargo_env,
50 | cargo,
51 | 'build',
52 | cargo_options,
53 | ],
54 | )
55 |
56 | copy_binary = custom_target(
57 | 'cp-binary',
58 | depends: cargo_build,
59 | build_by_default: true,
60 | build_always_stale: true,
61 | install: true,
62 | install_dir: bindir,
63 | output: meson.project_name(),
64 | command: [
65 | 'cp',
66 | 'src' / rust_target / meson.project_name(),
67 | '@OUTPUT@',
68 | ],
69 | )
70 |
71 | copy_kill_binary = custom_target(
72 | 'cp-kill-binary',
73 | depends: cargo_build,
74 | build_by_default: true,
75 | build_always_stale: true,
76 | install: true,
77 | install_dir: libexecdir,
78 | output: meson.project_name() + '-kill',
79 | command: [
80 | 'cp',
81 | 'src' / rust_target / meson.project_name() + '-kill',
82 | '@OUTPUT@',
83 | ],
84 | )
85 |
86 | copy_processes_binary = custom_target(
87 | 'cp-processes-binary',
88 | depends: cargo_build,
89 | build_by_default: true,
90 | build_always_stale: true,
91 | install: true,
92 | install_dir: libexecdir,
93 | output: meson.project_name() + '-processes',
94 | command: [
95 | 'cp',
96 | 'src' / rust_target / meson.project_name() + '-processes',
97 | '@OUTPUT@',
98 | ],
99 | )
100 |
101 | copy_adjust_binary = custom_target(
102 | 'cp-adjust-binary',
103 | depends: cargo_build,
104 | build_by_default: true,
105 | build_always_stale: true,
106 | install: true,
107 | install_dir: libexecdir,
108 | output: meson.project_name() + '-adjust',
109 | command: [
110 | 'cp',
111 | 'src' / rust_target / meson.project_name() + '-adjust',
112 | '@OUTPUT@',
113 | ],
114 | )
--------------------------------------------------------------------------------
/src/utils/npu/intel.rs:
--------------------------------------------------------------------------------
1 | use anyhow::Result;
2 | use process_data::{pci_slot::PciSlot, unix_as_secs_f64};
3 |
4 | use std::{
5 | cell::Cell,
6 | path::{Path, PathBuf},
7 | };
8 |
9 | use crate::utils::{pci::Device, read_parsed};
10 |
11 | use super::NpuImpl;
12 |
13 | #[derive(Debug, Clone, Default)]
14 |
15 | pub struct IntelNpu {
16 | pub device: Option<&'static Device>,
17 | pub pci_slot: PciSlot,
18 | pub driver: String,
19 | sysfs_path: PathBuf,
20 | first_hwmon_path: Option,
21 | last_busy_time_us: Cell,
22 | last_busy_time_timestamp: Cell,
23 | }
24 |
25 | impl IntelNpu {
26 | pub fn new(
27 | device: Option<&'static Device>,
28 | pci_slot: PciSlot,
29 | driver: String,
30 | sysfs_path: PathBuf,
31 | first_hwmon_path: Option,
32 | ) -> Self {
33 | Self {
34 | device,
35 | pci_slot,
36 | driver,
37 | sysfs_path,
38 | first_hwmon_path,
39 | last_busy_time_us: Cell::default(),
40 | last_busy_time_timestamp: Cell::default(),
41 | }
42 | }
43 | }
44 |
45 | impl NpuImpl for IntelNpu {
46 | fn device(&self) -> Option<&'static Device> {
47 | self.device
48 | }
49 |
50 | fn pci_slot(&self) -> PciSlot {
51 | self.pci_slot
52 | }
53 |
54 | fn driver(&self) -> &str {
55 | &self.driver
56 | }
57 |
58 | fn sysfs_path(&self) -> &Path {
59 | &self.sysfs_path
60 | }
61 |
62 | fn first_hwmon(&self) -> Option<&Path> {
63 | self.first_hwmon_path.as_deref()
64 | }
65 |
66 | fn name(&self) -> Result {
67 | self.drm_name()
68 | }
69 |
70 | fn usage(&self) -> Result {
71 | let last_timestamp = self.last_busy_time_timestamp.get();
72 | let last_busy_time = self.last_busy_time_us.get();
73 |
74 | let new_timestamp = unix_as_secs_f64();
75 | let new_busy_time = read_parsed(self.sysfs_path().join("device/npu_busy_time_us"))?;
76 |
77 | self.last_busy_time_timestamp.set(new_timestamp);
78 | self.last_busy_time_us.set(new_busy_time);
79 |
80 | let delta_timestamp = new_timestamp - last_timestamp;
81 | let delta_busy_time = new_busy_time.saturating_sub(last_busy_time) as f64;
82 |
83 | Ok(delta_busy_time / delta_timestamp)
84 | }
85 |
86 | fn used_vram(&self) -> Result {
87 | self.drm_used_memory().map(|usage| usage as usize)
88 | }
89 |
90 | fn total_vram(&self) -> Result {
91 | self.drm_total_memory().map(|usage| usage as usize)
92 | }
93 |
94 | fn temperature(&self) -> Result {
95 | self.hwmon_temperature()
96 | }
97 |
98 | fn power_usage(&self) -> Result {
99 | self.hwmon_power_usage()
100 | }
101 |
102 | fn core_frequency(&self) -> Result {
103 | self.hwmon_core_frequency()
104 | }
105 |
106 | fn memory_frequency(&self) -> Result {
107 | self.hwmon_memory_frequency()
108 | }
109 |
110 | fn power_cap(&self) -> Result {
111 | self.hwmon_power_cap()
112 | }
113 |
114 | fn power_cap_max(&self) -> Result {
115 | self.hwmon_power_cap_max()
116 | }
117 | }
118 |
--------------------------------------------------------------------------------
/data/icons/net.nokyan.Resources-symbolic.svg:
--------------------------------------------------------------------------------
1 |
14 |
--------------------------------------------------------------------------------
/data/resources/icons/generic-settings-symbolic.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/data/resources/ui/shortcuts.ui:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | General
7 |
8 |
9 | Preferences
10 | app.settings
11 |
12 |
13 |
14 |
15 | Show Shortcuts
16 | app.shortcuts
17 |
18 |
19 |
20 |
21 | Quit
22 | app.quit
23 |
24 |
25 |
26 |
27 |
28 |
29 | Apps/Processes
30 |
31 |
32 | Toggle Search Field
33 | app.toggle-search
34 |
35 |
36 |
37 |
38 | End App/Process
39 | app.end-app-process
40 |
41 |
42 |
43 |
44 | Kill App/Process
45 | app.kill-app-process
46 |
47 |
48 |
49 |
50 | Halt App/Process
51 | app.halt-app-process
52 |
53 |
54 |
55 |
56 | Continue App/Process
57 | app.continue-app-process
58 |
59 |
60 |
61 |
62 | Show Information for App/Process
63 | app.information-app-process
64 |
65 |
66 |
67 |
68 | Show Process Options
69 | app.process-options
70 |
71 |
72 |
73 |
74 |
75 |
--------------------------------------------------------------------------------
/src/gui.rs:
--------------------------------------------------------------------------------
1 | use std::ffi::OsString;
2 | use std::sync::LazyLock;
3 |
4 | use crate::application;
5 | #[rustfmt::skip]
6 | use crate::config;
7 | use crate::utils::IS_FLATPAK;
8 | use crate::utils::app::DATA_DIRS;
9 |
10 | use clap::{Parser, command};
11 | use gettextrs::{LocaleCategory, gettext};
12 | use gtk::{gio, glib};
13 | use log::trace;
14 |
15 | use self::application::Application;
16 | use self::config::{GETTEXT_PACKAGE, LOCALEDIR, RESOURCES_FILE};
17 |
18 | pub static ARGS: LazyLock = LazyLock::new(Args::parse);
19 |
20 | #[derive(Parser, Debug)]
21 | #[command(version, about)]
22 | pub struct Args {
23 | /// Disable GPU monitoring
24 | #[arg(short = 'g', long, default_value_t = false)]
25 | pub disable_gpu_monitoring: bool,
26 |
27 | /// Disable network interface monitoring
28 | #[arg(short = 'n', long, default_value_t = false)]
29 | pub disable_network_interface_monitoring: bool,
30 |
31 | /// Disable drive monitoring
32 | #[arg(short = 'd', long, default_value_t = false)]
33 | pub disable_drive_monitoring: bool,
34 |
35 | /// Disable battery monitoring
36 | #[arg(short = 'b', long, default_value_t = false)]
37 | pub disable_battery_monitoring: bool,
38 |
39 | /// Disable CPU monitoring
40 | #[arg(short = 'c', long, default_value_t = false)]
41 | pub disable_cpu_monitoring: bool,
42 |
43 | /// Disable memory monitoring
44 | #[arg(short = 'm', long, default_value_t = false)]
45 | pub disable_memory_monitoring: bool,
46 |
47 | /// Disable NPU monitoring
48 | #[arg(short = 'v', long, default_value_t = false)]
49 | pub disable_npu_monitoring: bool,
50 |
51 | /// Disable process monitoring
52 | #[arg(short = 'p', long, default_value_t = false)]
53 | pub disable_process_monitoring: bool,
54 |
55 | /// Open tab specified by ID.
56 | /// Valid IDs are: "applications", "processes", "cpu", "memory", "gpu-$PCI_SLOT$",
57 | /// "drive-$MODEL_NAME_OR_DEVICE_NAME$", "network-$INTERFACE_NAME$",
58 | /// "battery-$MANUFACTURER$-$MODEL_NAME$-$DEVICE_NAME$"
59 | #[arg(short = 't', long)]
60 | pub open_tab_id: Option,
61 | }
62 |
63 | pub fn main() {
64 | // Force args parsing here so we don't start printing logs before printing the help page
65 | std::hint::black_box(ARGS.disable_battery_monitoring);
66 |
67 | // Initialize logger
68 | pretty_env_logger::init();
69 | trace!("Trace logs activated. Brace yourself for *lots* of logs. Slowdowns may occur.");
70 |
71 | // reset XDG_DATA_DIRS to use absolute paths instead of relative paths because Flatpak seemingly cannot resolve them
72 | // this must happen now because once the GTK app is loaded, it's too late
73 | if *IS_FLATPAK {
74 | unsafe {
75 | std::env::set_var(
76 | "XDG_DATA_DIRS",
77 | DATA_DIRS
78 | .iter()
79 | .map(|pathbuf| pathbuf.as_os_str().to_owned())
80 | .collect::>()
81 | .join(&OsString::from(":")),
82 | );
83 | }
84 | }
85 |
86 | // Prepare i18n
87 | gettextrs::setlocale(LocaleCategory::LcAll, "");
88 | gettextrs::bindtextdomain(GETTEXT_PACKAGE, LOCALEDIR).expect("Unable to bind the text domain");
89 | gettextrs::textdomain(GETTEXT_PACKAGE).expect("Unable to switch to the text domain");
90 |
91 | glib::set_application_name(&gettext("Resources"));
92 |
93 | let res = gio::Resource::load(RESOURCES_FILE).expect("Could not load gresource file");
94 | gio::resources_register(&res);
95 |
96 | let app = Application::new();
97 | app.run();
98 | }
99 |
--------------------------------------------------------------------------------
/data/resources/icons/zfs-symbolic.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/src/utils/link/sata.rs:
--------------------------------------------------------------------------------
1 | use crate::utils::drive::AtaSlot;
2 | use crate::utils::link::LinkData;
3 | use crate::utils::link::sata::SataSpeed::{Sata150, Sata300, Sata600};
4 | use anyhow::{Context, Error, anyhow};
5 | use log::trace;
6 | use std::fmt::{Display, Formatter};
7 | use std::path::Path;
8 | use std::str::FromStr;
9 |
10 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
11 | pub enum SataSpeed {
12 | Sata150,
13 | Sata300,
14 | Sata600,
15 | }
16 |
17 | impl LinkData {
18 | pub fn from_ata_slot(ata_slot: &AtaSlot) -> anyhow::Result {
19 | trace!("Reading ATA link data for {ata_slot:?}…");
20 |
21 | let ata_link_path =
22 | Path::new("/sys/class/ata_link").join(format!("link{}", ata_slot.ata_link));
23 |
24 | let current_sata_speed_raw = std::fs::read_to_string(ata_link_path.join("sata_spd"))
25 | .map(|x| x.trim().to_string())
26 | .context("Could not read sata_spd")?;
27 |
28 | let max_sata_speed_raw = std::fs::read_to_string(ata_link_path.join("sata_spd_max"))
29 | .map(|x| x.trim().to_string())
30 | .context("Could not read sata_spd_max");
31 |
32 | let current = SataSpeed::from_str(¤t_sata_speed_raw)
33 | .context("Could not parse current sata speed")?;
34 | let max = max_sata_speed_raw.and_then(|raw| SataSpeed::from_str(&raw));
35 |
36 | Ok(Self { current, max })
37 | }
38 | }
39 |
40 | impl FromStr for SataSpeed {
41 | type Err = Error;
42 |
43 | fn from_str(s: &str) -> std::result::Result {
44 | match s {
45 | // https://en.wikipedia.org/wiki/SATA
46 | "1.5 Gbps" => Ok(Sata150),
47 | "3.0 Gbps" => Ok(Sata300),
48 | "6.0 Gbps" => Ok(Sata600),
49 | _ => Err(anyhow!("Could not parse SATA speed: '{s}'")),
50 | }
51 | }
52 | }
53 |
54 | impl Display for SataSpeed {
55 | fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
56 | write!(
57 | f,
58 | "{}",
59 | match self {
60 | Sata150 => "SATA-150",
61 | Sata300 => "SATA-300",
62 | Sata600 => "SATA-600",
63 | }
64 | )
65 | }
66 | }
67 |
68 | #[cfg(test)]
69 | mod test {
70 | use crate::utils::link::sata::SataSpeed;
71 | use std::collections::HashMap;
72 | use std::str::FromStr;
73 |
74 | #[test]
75 | fn parse_sata_link_speeds() {
76 | let map = HashMap::from([
77 | ("1.5 Gbps", SataSpeed::Sata150),
78 | ("3.0 Gbps", SataSpeed::Sata300),
79 | ("6.0 Gbps", SataSpeed::Sata600),
80 | ]);
81 |
82 | for input in map.keys() {
83 | let result = SataSpeed::from_str(input);
84 | assert!(result.is_ok(), "Could not parse SATA speed for '{input}'");
85 | let expected = map[input];
86 | pretty_assertions::assert_eq!(expected, result.unwrap());
87 | }
88 | }
89 |
90 | #[test]
91 | fn parse_sata_link_speeds_failure() {
92 | let invalid = vec!["4.0 Gbps", "SOMETHING_ELSE", ""];
93 |
94 | for input in invalid {
95 | let result = SataSpeed::from_str(input);
96 | assert!(
97 | result.is_err(),
98 | "Could parse SATA speed for '{input}' while we don't expect that"
99 | );
100 | }
101 | }
102 |
103 | #[test]
104 | fn display_sata_link_speeds() {
105 | let map = HashMap::from([
106 | (SataSpeed::Sata150, "SATA-150"),
107 | (SataSpeed::Sata300, "SATA-300"),
108 | (SataSpeed::Sata600, "SATA-600"),
109 | ]);
110 |
111 | for input in map.keys() {
112 | let result = input.to_string();
113 | let expected = map[input];
114 | pretty_assertions::assert_str_eq!(expected, result);
115 | }
116 | }
117 | }
118 |
--------------------------------------------------------------------------------
/src/i18n.rs:
--------------------------------------------------------------------------------
1 | // i18n.rs
2 | //
3 | // Copyright 2020 Christopher Davis
4 | //
5 | // This program is free software: you can redistribute it and/or modify
6 | // it under the terms of the GNU General Public License as published by
7 | // the Free Software Foundation, either version 3 of the License, or
8 | // (at your option) any later version.
9 | //
10 | // This program is distributed in the hope that it will be useful,
11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | // GNU General Public License for more details.
14 | //
15 | // You should have received a copy of the GNU General Public License
16 | // along with this program. If not, see .
17 | //
18 | // SPDX-License-Identifier: GPL-3.0-or-later
19 |
20 | use gettextrs::{gettext, ngettext, npgettext, pgettext};
21 | use lazy_regex::{Captures, Regex};
22 |
23 | #[allow(dead_code)]
24 | fn freplace(input: String, args: &[&str]) -> String {
25 | let mut parts = input.split("{}");
26 | let mut output = parts.next().unwrap_or_default().to_string();
27 | for (p, a) in parts.zip(args.iter()) {
28 | output += &((*a).to_string() + p);
29 | }
30 | output
31 | }
32 |
33 | #[allow(dead_code)]
34 | fn kreplace(input: String, kwargs: &[(&str, &str)]) -> String {
35 | let mut s = input;
36 | for (k, v) in kwargs {
37 | if let Ok(re) = Regex::new(&format!("\\{{{k}\\}}")) {
38 | s = re
39 | .replace_all(&s, |_: &Captures<'_>| (*v).to_string())
40 | .to_string();
41 | }
42 | }
43 |
44 | s
45 | }
46 |
47 | // Simple translations functions
48 |
49 | #[allow(dead_code)]
50 | pub fn i18n(format: &str) -> String {
51 | gettext(format)
52 | }
53 |
54 | #[allow(dead_code)]
55 | pub fn i18n_f(format: &str, args: &[&str]) -> String {
56 | let s = gettext(format);
57 | freplace(s, args)
58 | }
59 |
60 | #[allow(dead_code)]
61 | pub fn i18n_k(format: &str, kwargs: &[(&str, &str)]) -> String {
62 | let s = gettext(format);
63 | kreplace(s, kwargs)
64 | }
65 |
66 | // Singular and plural translations functions
67 |
68 | #[allow(dead_code)]
69 | pub fn ni18n(single: &str, multiple: &str, number: u32) -> String {
70 | ngettext(single, multiple, number)
71 | }
72 |
73 | #[allow(dead_code)]
74 | pub fn ni18n_f(single: &str, multiple: &str, number: u32, args: &[&str]) -> String {
75 | let s = ngettext(single, multiple, number);
76 | freplace(s, args)
77 | }
78 |
79 | #[allow(dead_code)]
80 | pub fn ni18n_k(single: &str, multiple: &str, number: u32, kwargs: &[(&str, &str)]) -> String {
81 | let s = ngettext(single, multiple, number);
82 | kreplace(s, kwargs)
83 | }
84 |
85 | // Translations with context functions
86 |
87 | #[allow(dead_code)]
88 | pub fn pi18n(ctx: &str, format: &str) -> String {
89 | pgettext(ctx, format)
90 | }
91 |
92 | #[allow(dead_code)]
93 | pub fn pi18n_f(ctx: &str, format: &str, args: &[&str]) -> String {
94 | let s = pgettext(ctx, format);
95 | freplace(s, args)
96 | }
97 |
98 | #[allow(dead_code)]
99 | pub fn pi18n_k(ctx: &str, format: &str, kwargs: &[(&str, &str)]) -> String {
100 | let s = pgettext(ctx, format);
101 | kreplace(s, kwargs)
102 | }
103 |
104 | // Singular and plural with context
105 |
106 | #[allow(dead_code)]
107 | pub fn pni18n(ctx: &str, single: &str, multiple: &str, number: u32) -> String {
108 | npgettext(ctx, single, multiple, number)
109 | }
110 |
111 | #[allow(dead_code)]
112 | pub fn pni18n_f(ctx: &str, single: &str, multiple: &str, number: u32, args: &[&str]) -> String {
113 | let s = npgettext(ctx, single, multiple, number);
114 | freplace(s, args)
115 | }
116 |
117 | #[allow(dead_code)]
118 | pub fn pni18n_k(
119 | ctx: &str,
120 | single: &str,
121 | multiple: &str,
122 | number: u32,
123 | kwargs: &[(&str, &str)],
124 | ) -> String {
125 | let s = npgettext(ctx, single, multiple, number);
126 | kreplace(s, kwargs)
127 | }
128 |
--------------------------------------------------------------------------------
/data/resources/icons/mapped-device-symbolic.svg:
--------------------------------------------------------------------------------
1 |
2 |
14 |
--------------------------------------------------------------------------------
/src/ui/widgets/double_graph_box.rs:
--------------------------------------------------------------------------------
1 | use adw::{prelude::*, subclass::prelude::*};
2 | use gtk::glib;
3 | use log::trace;
4 |
5 | use crate::config::PROFILE;
6 |
7 | use super::graph::ResGraph;
8 |
9 | mod imp {
10 | use crate::ui::widgets::graph::ResGraph;
11 |
12 | use super::*;
13 |
14 | use gtk::CompositeTemplate;
15 |
16 | #[derive(Debug, CompositeTemplate, Default)]
17 | #[template(resource = "/net/nokyan/Resources/ui/widgets/double_graph_box.ui")]
18 | pub struct ResDoubleGraphBox {
19 | #[template_child]
20 | pub start_graph: TemplateChild,
21 | #[template_child]
22 | pub start_title_label: TemplateChild,
23 | #[template_child]
24 | pub start_info_label: TemplateChild,
25 | #[template_child]
26 | pub end_graph: TemplateChild,
27 | #[template_child]
28 | pub end_title_label: TemplateChild,
29 | #[template_child]
30 | pub end_info_label: TemplateChild,
31 | }
32 |
33 | #[glib::object_subclass]
34 | impl ObjectSubclass for ResDoubleGraphBox {
35 | const NAME: &'static str = "ResDoubleGraphBox";
36 | type Type = super::ResDoubleGraphBox;
37 | type ParentType = adw::PreferencesRow;
38 |
39 | fn class_init(klass: &mut Self::Class) {
40 | Self::bind_template(klass);
41 | }
42 |
43 | // You must call `Widget`'s `init_template()` within `instance_init()`.
44 | fn instance_init(obj: &glib::subclass::InitializingObject) {
45 | obj.init_template();
46 | }
47 | }
48 |
49 | impl ObjectImpl for ResDoubleGraphBox {
50 | fn constructed(&self) {
51 | self.parent_constructed();
52 | let obj = self.obj();
53 |
54 | // Devel Profile
55 | if PROFILE == "Devel" {
56 | obj.add_css_class("devel");
57 | }
58 | }
59 | }
60 |
61 | impl WidgetImpl for ResDoubleGraphBox {}
62 |
63 | impl ListBoxRowImpl for ResDoubleGraphBox {}
64 |
65 | impl PreferencesRowImpl for ResDoubleGraphBox {}
66 | }
67 |
68 | glib::wrapper! {
69 | pub struct ResDoubleGraphBox(ObjectSubclass)
70 | @extends gtk::Widget, gtk::ListBoxRow, adw::PreferencesRow,
71 | @implements gtk::Buildable, gtk::ConstraintTarget, gtk::Accessible, gtk::Actionable;
72 | }
73 |
74 | impl Default for ResDoubleGraphBox {
75 | fn default() -> Self {
76 | Self::new()
77 | }
78 | }
79 |
80 | impl ResDoubleGraphBox {
81 | pub fn new() -> Self {
82 | trace!("Creating ResDoubleGraphBox GObject…");
83 |
84 | glib::Object::new::()
85 | }
86 |
87 | pub fn set_graphs_visible(&self, visible: bool) {
88 | let imp = self.imp();
89 | imp.start_graph.set_visible(visible);
90 | imp.end_graph.set_visible(visible);
91 | }
92 |
93 | pub fn start_graph(&self) -> ResGraph {
94 | self.imp().start_graph.get()
95 | }
96 |
97 | pub fn set_start_title_label(&self, str: &str) {
98 | let imp = self.imp();
99 | imp.start_title_label.set_label(str);
100 | }
101 |
102 | pub fn set_start_subtitle(&self, str: &str) {
103 | let imp = self.imp();
104 | imp.start_info_label.set_label(str);
105 | }
106 |
107 | pub fn set_start_tooltip(&self, str: Option<&str>) {
108 | let imp = self.imp();
109 | imp.start_info_label.set_tooltip_text(str);
110 | }
111 |
112 | pub fn end_graph(&self) -> ResGraph {
113 | self.imp().end_graph.get()
114 | }
115 |
116 | pub fn set_end_title_label(&self, str: &str) {
117 | let imp = self.imp();
118 | imp.end_title_label.set_label(str);
119 | }
120 |
121 | pub fn set_end_subtitle(&self, str: &str) {
122 | let imp = self.imp();
123 | imp.end_info_label.set_label(str);
124 | }
125 |
126 | pub fn set_end_tooltip(&self, str: Option<&str>) {
127 | let imp = self.imp();
128 | imp.end_info_label.set_tooltip_text(str);
129 | }
130 | }
131 |
--------------------------------------------------------------------------------
/data/resources/ui/widgets/double_graph_box.ui:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | true
5 | hidden
6 | false
7 | 160
8 |
9 |
10 | horizontal
11 | 12
12 | 12
13 | 12
14 | 6
15 | 12
16 | true
17 |
18 |
19 | vertical
20 | true
21 | 12
22 |
23 |
24 |
27 | hidden
28 | true
29 | 120
30 |
31 |
32 |
33 |
34 | vertical
35 | true
36 | 4
37 |
38 |
39 |
42 | true
43 | start
44 | 2
45 |
46 |
47 |
48 |
49 | true
50 | start
51 | true
52 | 2
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 | vertical
62 | true
63 | 12
64 |
65 |
66 |
69 | hidden
70 | true
71 | 120
72 |
73 |
74 |
75 |
76 | vertical
77 | true
78 | 4
79 |
80 |
81 |
84 | true
85 | start
86 | 2
87 |
88 |
89 |
90 |
91 | true
92 | start
93 | true
94 | 2
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
--------------------------------------------------------------------------------
/data/resources/ui/pages/applications.ui:
--------------------------------------------------------------------------------
1 |
2 |
3 |
19 |
45 |
46 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 | Search applications
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 | true
68 | true
69 | automatic
70 |
71 |
72 |
73 |
74 |
75 |
76 | 16
77 | 16
78 | 16
79 | 16
80 | true
81 | 16
82 | end
83 |
84 |
85 | info-symbolic
86 | false
87 | Show App Information
88 |
89 | Show App Information
90 |
91 |
94 |
95 |
96 |
97 |
98 | End App
99 | end_app_menu
100 | false
101 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
--------------------------------------------------------------------------------
/data/resources/ui/pages/memory.ui:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | vertical
7 |
8 |
9 | You need to authenticate to see memory properties
10 | Authenticate
11 |
12 |
13 |
14 |
15 |
16 |
17 | 768
18 |
19 |
20 | 24
21 | 24
22 | 16
23 | 16
24 | 24
25 | vertical
26 | true
27 | start
28 | true
29 |
30 |
31 | Usage
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 | Properties
43 |
44 |
45 | Slots Used
46 |
49 | true
50 |
51 |
52 |
53 |
54 | Speed
55 |
58 | true
59 |
60 |
61 |
62 |
63 | Form Factor
64 |
67 | true
68 |
69 |
70 |
71 |
72 | Type
73 |
76 | true
77 |
78 |
79 |
80 |
81 | Type Detail
82 |
85 | true
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
--------------------------------------------------------------------------------
/data/resources/ui/pages/battery.ui:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | 768
9 |
10 |
11 | 24
12 | 24
13 | 16
14 | 16
15 | 24
16 | vertical
17 | true
18 | start
19 | true
20 |
21 |
22 | Usage
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 | Properties
34 |
35 |
36 | Battery Health
37 |
40 | true
41 |
42 |
43 |
44 |
45 | Design Capacity
46 |
49 | true
50 |
51 |
52 |
53 |
54 | Charge Cycles
55 |
58 | true
59 |
60 |
61 |
62 |
63 | Technology
64 |
67 | true
68 |
69 |
70 |
71 |
72 | Manufacturer
73 |
76 | true
77 |
78 |
79 |
80 |
81 | Model Name
82 |
85 | true
86 |
87 |
88 |
89 |
90 | Device
91 |
94 | true
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
--------------------------------------------------------------------------------
/src/ui/pages/processes/process_name_cell.rs:
--------------------------------------------------------------------------------
1 | use adw::{glib::property::PropertySet, prelude::*, subclass::prelude::*};
2 | use gtk::{gio::Icon, glib};
3 | use log::trace;
4 |
5 | mod imp {
6 | use std::cell::{Cell, RefCell};
7 |
8 | use super::*;
9 |
10 | use gtk::{
11 | Box, CompositeTemplate,
12 | gio::ThemedIcon,
13 | glib::{ParamSpec, Properties, Value},
14 | };
15 |
16 | #[derive(CompositeTemplate, Properties)]
17 | #[template(resource = "/net/nokyan/Resources/ui/widgets/process_name_cell.ui")]
18 | #[properties(wrapper_type = super::ResProcessNameCell)]
19 | pub struct ResProcessNameCell {
20 | #[template_child]
21 | pub image: TemplateChild,
22 | #[template_child]
23 | pub inscription: TemplateChild,
24 |
25 | #[property(get = Self::name, set = Self::set_name, type = glib::GString)]
26 | name: Cell,
27 | #[property(get = Self::tooltip, set = Self::set_tooltip, type = glib::GString)]
28 | tooltip: Cell,
29 | #[property(get = Self::icon, set = Self::set_icon, type = Icon)]
30 | icon: RefCell,
31 | #[property(get, set = Self::set_symbolic)]
32 | symbolic: Cell,
33 | }
34 |
35 | impl Default for ResProcessNameCell {
36 | fn default() -> Self {
37 | Self {
38 | image: Default::default(),
39 | inscription: Default::default(),
40 | name: Default::default(),
41 | tooltip: Default::default(),
42 | icon: RefCell::new(ThemedIcon::new("generic-process").into()),
43 | symbolic: Default::default(),
44 | }
45 | }
46 | }
47 |
48 | impl ResProcessNameCell {
49 | pub fn name(&self) -> glib::GString {
50 | let name = self.name.take();
51 | self.name.set(name.clone());
52 | name
53 | }
54 |
55 | pub fn set_name(&self, name: &str) {
56 | self.name.set(glib::GString::from(name));
57 | self.inscription.set_text(Some(name));
58 | }
59 |
60 | pub fn tooltip(&self) -> glib::GString {
61 | let tooltip = self.tooltip.take();
62 | self.tooltip.set(tooltip.clone());
63 | tooltip
64 | }
65 |
66 | pub fn set_tooltip(&self, tooltip: &str) {
67 | self.tooltip.set(glib::GString::from(tooltip));
68 | self.inscription.set_tooltip_text(Some(tooltip));
69 | }
70 |
71 | pub fn icon(&self) -> Icon {
72 | let icon = self
73 | .icon
74 | .replace_with(|_| ThemedIcon::new("generic-process").into());
75 | self.icon.set(icon.clone());
76 | icon
77 | }
78 |
79 | pub fn set_icon(&self, icon: &Icon) {
80 | let current_icon = self
81 | .icon
82 | .replace_with(|_| ThemedIcon::new("generic-process").into());
83 |
84 | if ¤t_icon == icon {
85 | self.icon.set(current_icon);
86 | return;
87 | }
88 |
89 | self.image.set_from_gicon(icon);
90 |
91 | self.icon.set(icon.clone());
92 | }
93 |
94 | pub fn set_symbolic(&self, symbolic: bool) {
95 | self.symbolic.set(symbolic);
96 |
97 | if symbolic {
98 | self.image.set_css_classes(&[]);
99 | } else {
100 | self.image.set_css_classes(&["lowres-icon"]);
101 | }
102 | }
103 | }
104 |
105 | #[glib::object_subclass]
106 | impl ObjectSubclass for ResProcessNameCell {
107 | const NAME: &'static str = "ResProcessNameCell";
108 | type Type = super::ResProcessNameCell;
109 | type ParentType = Box;
110 |
111 | fn class_init(klass: &mut Self::Class) {
112 | Self::bind_template(klass);
113 | }
114 |
115 | // You must call `Widget`'s `init_template()` within `instance_init()`.
116 | fn instance_init(obj: &glib::subclass::InitializingObject) {
117 | obj.init_template();
118 | }
119 | }
120 |
121 | impl ObjectImpl for ResProcessNameCell {
122 | fn constructed(&self) {
123 | self.parent_constructed();
124 | }
125 |
126 | fn properties() -> &'static [ParamSpec] {
127 | Self::derived_properties()
128 | }
129 |
130 | fn set_property(&self, id: usize, value: &Value, pspec: &ParamSpec) {
131 | self.derived_set_property(id, value, pspec);
132 | }
133 |
134 | fn property(&self, id: usize, pspec: &ParamSpec) -> Value {
135 | self.derived_property(id, pspec)
136 | }
137 | }
138 |
139 | impl WidgetImpl for ResProcessNameCell {}
140 |
141 | impl BoxImpl for ResProcessNameCell {}
142 | }
143 |
144 | glib::wrapper! {
145 | pub struct ResProcessNameCell(ObjectSubclass)
146 | @extends gtk::Widget, gtk::Box,
147 | @implements gtk::Buildable, gtk::ConstraintTarget, gtk::Accessible;
148 | }
149 |
150 | impl Default for ResProcessNameCell {
151 | fn default() -> Self {
152 | Self::new()
153 | }
154 | }
155 |
156 | impl ResProcessNameCell {
157 | pub fn new() -> Self {
158 | trace!("Creating ResProcessNameCell GObject…");
159 |
160 | glib::Object::new::()
161 | }
162 | }
163 |
--------------------------------------------------------------------------------
/src/ui/pages/applications/application_name_cell.rs:
--------------------------------------------------------------------------------
1 | use adw::{glib::property::PropertySet, prelude::*, subclass::prelude::*};
2 | use gtk::{gio::Icon, glib};
3 | use log::trace;
4 |
5 | mod imp {
6 | use std::cell::{Cell, RefCell};
7 |
8 | use super::*;
9 |
10 | use gtk::{
11 | Box, CompositeTemplate,
12 | gio::ThemedIcon,
13 | glib::{ParamSpec, Properties, Value},
14 | };
15 |
16 | #[derive(CompositeTemplate, Properties)]
17 | #[template(resource = "/net/nokyan/Resources/ui/widgets/application_name_cell.ui")]
18 | #[properties(wrapper_type = super::ResApplicationNameCell)]
19 | pub struct ResApplicationNameCell {
20 | #[template_child]
21 | pub image: TemplateChild,
22 | #[template_child]
23 | pub inscription: TemplateChild,
24 |
25 | #[property(get = Self::name, set = Self::set_name, type = glib::GString)]
26 | name: Cell,
27 | #[property(get = Self::tooltip, set = Self::set_tooltip, type = glib::GString)]
28 | tooltip: Cell,
29 | #[property(get = Self::icon, set = Self::set_icon, type = Icon)]
30 | icon: RefCell,
31 | #[property(get, set = Self::set_symbolic)]
32 | symbolic: Cell,
33 | }
34 |
35 | impl Default for ResApplicationNameCell {
36 | fn default() -> Self {
37 | Self {
38 | image: Default::default(),
39 | inscription: Default::default(),
40 | name: Default::default(),
41 | tooltip: Default::default(),
42 | icon: RefCell::new(ThemedIcon::new("generic-process").into()),
43 | symbolic: Default::default(),
44 | }
45 | }
46 | }
47 |
48 | impl ResApplicationNameCell {
49 | pub fn name(&self) -> glib::GString {
50 | let name = self.name.take();
51 | let result = name.clone();
52 | self.name.set(name);
53 |
54 | result
55 | }
56 |
57 | pub fn set_name(&self, name: &str) {
58 | self.name.set(glib::GString::from(name));
59 | self.inscription.set_text(Some(name));
60 | }
61 |
62 | pub fn tooltip(&self) -> glib::GString {
63 | let tooltip = self.tooltip.take();
64 | let result = tooltip.clone();
65 | self.tooltip.set(tooltip);
66 |
67 | result
68 | }
69 |
70 | pub fn set_tooltip(&self, tooltip: &str) {
71 | self.tooltip.set(glib::GString::from(tooltip));
72 | self.inscription.set_tooltip_text(Some(tooltip));
73 | }
74 |
75 | pub fn icon(&self) -> Icon {
76 | let icon = self
77 | .icon
78 | .replace_with(|_| ThemedIcon::new("generic-process").into());
79 | self.icon.set(icon.clone());
80 |
81 | icon
82 | }
83 |
84 | pub fn set_icon(&self, icon: &Icon) {
85 | let current_icon = self
86 | .icon
87 | .replace_with(|_| ThemedIcon::new("generic-process").into());
88 |
89 | if ¤t_icon == icon {
90 | self.icon.set(current_icon);
91 | return;
92 | }
93 |
94 | self.image.set_from_gicon(icon);
95 |
96 | self.icon.set(icon.clone());
97 | }
98 |
99 | pub fn set_symbolic(&self, symbolic: bool) {
100 | self.symbolic.set(symbolic);
101 |
102 | if symbolic {
103 | self.image.set_css_classes(&["bubble"]);
104 | self.image.set_pixel_size(16);
105 | } else {
106 | self.image.set_css_classes(&["lowres-icon"]);
107 | self.image.set_pixel_size(32);
108 | }
109 | }
110 | }
111 |
112 | #[glib::object_subclass]
113 | impl ObjectSubclass for ResApplicationNameCell {
114 | const NAME: &'static str = "ResApplicationNameCell";
115 | type Type = super::ResApplicationNameCell;
116 | type ParentType = Box;
117 |
118 | fn class_init(klass: &mut Self::Class) {
119 | Self::bind_template(klass);
120 | }
121 |
122 | // You must call `Widget`'s `init_template()` within `instance_init()`.
123 | fn instance_init(obj: &glib::subclass::InitializingObject) {
124 | obj.init_template();
125 | }
126 | }
127 |
128 | impl ObjectImpl for ResApplicationNameCell {
129 | fn constructed(&self) {
130 | self.parent_constructed();
131 | }
132 |
133 | fn properties() -> &'static [ParamSpec] {
134 | Self::derived_properties()
135 | }
136 |
137 | fn set_property(&self, id: usize, value: &Value, pspec: &ParamSpec) {
138 | self.derived_set_property(id, value, pspec);
139 | }
140 |
141 | fn property(&self, id: usize, pspec: &ParamSpec) -> Value {
142 | self.derived_property(id, pspec)
143 | }
144 | }
145 |
146 | impl WidgetImpl for ResApplicationNameCell {}
147 |
148 | impl BoxImpl for ResApplicationNameCell {}
149 | }
150 |
151 | glib::wrapper! {
152 | pub struct ResApplicationNameCell(ObjectSubclass)
153 | @extends gtk::Widget, gtk::Box,
154 | @implements gtk::Buildable, gtk::ConstraintTarget, gtk::Accessible;
155 | }
156 |
157 | impl Default for ResApplicationNameCell {
158 | fn default() -> Self {
159 | Self::new()
160 | }
161 | }
162 |
163 | impl ResApplicationNameCell {
164 | pub fn new() -> Self {
165 | trace!("Creating ResApplicationNameCell GObject…");
166 |
167 | glib::Object::new::()
168 | }
169 | }
170 |
--------------------------------------------------------------------------------
/data/resources/ui/pages/drive.ui:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | 768
9 |
10 |
11 | 24
12 | 24
13 | 16
14 | 16
15 | 24
16 | vertical
17 | true
18 | start
19 | true
20 |
21 |
22 | Usage
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
37 | true
38 | Total Read
39 |
40 |
41 |
42 |
43 |
46 | true
47 | Total Written
48 |
49 |
50 |
51 |
52 |
53 |
54 | Properties
55 |
56 |
57 | Type
58 |
61 | true
62 |
63 |
64 |
65 |
66 | Device
67 |
70 | true
71 |
72 |
73 |
74 |
75 | Capacity
76 |
79 | true
80 |
81 |
82 |
83 |
84 | Writable
85 |
88 | true
89 |
90 |
91 |
92 |
93 | Removable
94 |
97 | true
98 |
99 |
100 |
101 |
102 | Link
103 |
106 | true
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
--------------------------------------------------------------------------------
/src/utils/gpu/amd.rs:
--------------------------------------------------------------------------------
1 | use anyhow::{Context, Result, bail};
2 | use lazy_regex::{Lazy, Regex, lazy_regex};
3 | use log::{debug, trace, warn};
4 | use process_data::gpu_usage::GpuIdentifier;
5 |
6 | use std::{
7 | collections::HashMap,
8 | path::{Path, PathBuf},
9 | sync::LazyLock,
10 | time::Instant,
11 | };
12 |
13 | use crate::utils::{
14 | IS_FLATPAK,
15 | pci::{self, Device},
16 | read_parsed,
17 | };
18 |
19 | use super::GpuImpl;
20 |
21 | static RE_AMDGPU_IDS: Lazy = lazy_regex!(r"([0-9A-F]{4}),\s*([0-9A-F]{2}),\s*(.*)");
22 |
23 | static AMDGPU_IDS: LazyLock> = LazyLock::new(|| {
24 | AmdGpu::read_libdrm_ids()
25 | .inspect_err(|e| warn!("Unable to parse amdgpu.ids!\n{e}\n{}", e.backtrace()))
26 | .unwrap_or_default()
27 | });
28 |
29 | #[derive(Debug, Clone, Default)]
30 |
31 | pub struct AmdGpu {
32 | pub device: Option<&'static Device>,
33 | pub gpu_identifier: GpuIdentifier,
34 | pub driver: String,
35 | sysfs_path: PathBuf,
36 | first_hwmon_path: Option,
37 | combined_media_engine: bool,
38 | }
39 |
40 | impl AmdGpu {
41 | pub fn new(
42 | device: Option<&'static Device>,
43 | gpu_identifier: GpuIdentifier,
44 | driver: String,
45 | sysfs_path: PathBuf,
46 | first_hwmon_path: Option,
47 | ) -> Self {
48 | let mut gpu = Self {
49 | device,
50 | gpu_identifier,
51 | driver,
52 | sysfs_path,
53 | first_hwmon_path,
54 | combined_media_engine: false,
55 | };
56 |
57 | if let Ok(vcn_version) = read_parsed::(
58 | gpu.sysfs_path()
59 | .join("device/ip_discovery/die/0/UVD/0/major"),
60 | ) {
61 | if vcn_version >= 4 {
62 | gpu.combined_media_engine = true;
63 | }
64 | }
65 |
66 | gpu
67 | }
68 |
69 | pub fn read_libdrm_ids() -> Result> {
70 | let path = if *IS_FLATPAK {
71 | PathBuf::from("/run/host/usr/share/libdrm/amdgpu.ids")
72 | } else {
73 | PathBuf::from("/usr/share/libdrm/amdgpu.ids")
74 | };
75 |
76 | debug!("Parsing {}…", path.to_string_lossy());
77 |
78 | let start = Instant::now();
79 |
80 | let mut map = HashMap::new();
81 |
82 | let amdgpu_ids_raw = read_parsed::(&path)?;
83 |
84 | for capture in RE_AMDGPU_IDS.captures_iter(&amdgpu_ids_raw) {
85 | if let (Some(device_id), Some(revision), Some(name)) =
86 | (capture.get(1), capture.get(2), capture.get(3))
87 | {
88 | let device_id = u16::from_str_radix(device_id.as_str().trim(), 16).unwrap();
89 | let revision = u8::from_str_radix(revision.as_str().trim(), 16).unwrap();
90 | let name = name.as_str().into();
91 | trace!("Found {name} ({device_id:04x}, rev {revision:02x})");
92 | map.insert((device_id, revision), name);
93 | }
94 | }
95 |
96 | let elapsed = start.elapsed();
97 |
98 | debug!(
99 | "Successfully parsed {} within {elapsed:.2?} ({} entries)",
100 | path.to_string_lossy(),
101 | map.len()
102 | );
103 |
104 | Ok(map)
105 | }
106 | }
107 |
108 | impl GpuImpl for AmdGpu {
109 | fn device(&self) -> Option<&'static Device> {
110 | self.device
111 | }
112 |
113 | fn gpu_identifier(&self) -> GpuIdentifier {
114 | self.gpu_identifier
115 | }
116 |
117 | fn driver(&self) -> &str {
118 | &self.driver
119 | }
120 |
121 | fn sysfs_path(&self) -> &Path {
122 | &self.sysfs_path
123 | }
124 |
125 | fn first_hwmon(&self) -> Option<&Path> {
126 | self.first_hwmon_path.as_deref()
127 | }
128 |
129 | fn name(&self) -> Result {
130 | let revision = u8::from_str_radix(
131 | read_parsed::(self.sysfs_path().join("device/revision"))?
132 | .strip_prefix("0x")
133 | .context("missing hex prefix")?,
134 | 16,
135 | )?;
136 | Ok((*AMDGPU_IDS)
137 | .get(&(self.device().map_or(0, pci::Device::pid), revision))
138 | .cloned()
139 | .unwrap_or_else(|| {
140 | if let Ok(drm_name) = self.drm_name() {
141 | format!("AMD Radeon Graphics ({drm_name})")
142 | } else {
143 | "AMD Radeon Graphics".into()
144 | }
145 | }))
146 | }
147 |
148 | fn usage(&self) -> Result {
149 | self.drm_usage().map(|usage| usage as f64 / 100.0)
150 | }
151 |
152 | fn encode_usage(&self) -> Result {
153 | bail!("encode usage not implemented for AMD")
154 | }
155 |
156 | fn decode_usage(&self) -> Result {
157 | bail!("decode usage not implemented for AMD")
158 | }
159 |
160 | fn combined_media_engine(&self) -> Result {
161 | Ok(self.combined_media_engine)
162 | }
163 |
164 | fn used_vram(&self) -> Result {
165 | self.drm_used_vram().map(|usage| usage as u64)
166 | }
167 |
168 | fn total_vram(&self) -> Result {
169 | self.drm_total_vram().map(|usage| usage as u64)
170 | }
171 |
172 | fn temperature(&self) -> Result {
173 | self.hwmon_temperature()
174 | }
175 |
176 | fn power_usage(&self) -> Result {
177 | self.hwmon_power_usage()
178 | }
179 |
180 | fn core_frequency(&self) -> Result {
181 | self.hwmon_core_frequency()
182 | }
183 |
184 | fn vram_frequency(&self) -> Result {
185 | self.hwmon_vram_frequency()
186 | }
187 |
188 | fn power_cap(&self) -> Result {
189 | self.hwmon_power_cap()
190 | }
191 |
192 | fn power_cap_max(&self) -> Result {
193 | self.hwmon_power_cap_max()
194 | }
195 | }
196 |
--------------------------------------------------------------------------------
/data/resources/ui/pages/npu.ui:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | 768
9 |
10 |
11 | 24
12 | 24
13 | 16
14 | 16
15 | 24
16 | vertical
17 | true
18 | start
19 | true
20 |
21 |
22 | Usage
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 | NPU Frequency
32 |
35 | true
36 |
37 |
38 |
39 |
40 |
43 | true
44 | Memory Frequency
45 |
46 |
47 |
48 |
49 | Power Usage
50 |
53 | true
54 |
55 |
56 |
57 |
58 |
59 |
60 | Sensors
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 | Properties
69 |
70 |
71 |
74 | true
75 | Manufacturer
76 |
77 |
78 |
79 |
80 |
83 | true
84 | PCI Slot
85 |
86 |
87 |
88 |
89 |
92 | true
93 | Driver Used
94 |
95 |
96 |
97 |
98 |
101 | true
102 | Max Power Cap
103 |
104 |
105 |
106 |
107 | Link
108 |
111 | true
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
--------------------------------------------------------------------------------
/data/resources/ui/pages/network.ui:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | 768
9 |
10 |
11 | 24
12 | 24
13 | 16
14 | 16
15 | 24
16 | vertical
17 | true
18 | start
19 | true
20 |
21 |
22 | Usage
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
34 | true
35 | Total Received
36 |
37 |
38 |
39 |
40 |
43 | true
44 | Total Sent
45 |
46 |
47 |
48 |
49 |
50 |
51 | Properties
52 |
53 |
54 |
57 | true
58 | Manufacturer
59 |
60 |
61 |
62 |
63 |
66 | true
67 | Driver Used
68 |
69 |
70 |
71 |
72 |
75 | true
76 | Interface
77 |
78 |
79 |
80 |
81 |
84 | true
85 | Hardware Address
86 |
87 |
88 |
89 |
90 |
93 | true
94 | Network Name
95 |
96 |
97 |
98 |
99 |
102 | true
103 | Link
104 |
105 |
106 |
107 |
108 |
111 | true
112 | Link Speed
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Resources
2 |
3 |
4 |
5 | [](https://apps.gnome.org/app/net.nokyan.Resources/) [](https://stopthemingmy.app)
7 |
8 | Resources is a simple yet powerful monitor for your system resources and processes, written in Rust and using GTK 4 and libadwaita for its GUI. It’s capable of displaying usage and details of your CPU, memory, GPUs, NPUs, network interfaces and block devices. It’s also capable of listing and terminating running graphical applications as well as processes.
9 |
10 | Resources is *not* a program that will try to display every single possible piece of information about each tiny part of your device. Instead, it aims to strike a balance between information richness, user-friendliness and a balanced user interface — showing you most of the information most of you need most of the time.
11 |
12 |
13 | Click me for screenshots!
14 |
15 | 
16 |
17 | 
18 |
19 | 
20 |
21 | 
22 |
23 | 
24 |
25 | 
26 |
27 | 
28 |
29 | 
30 |
31 |
32 |
33 | ## Installing
34 |
35 | The **official** and **only supported** way of installing Resources is using Flatpak. Simply use your graphical software manager like GNOME Software or Discover to install Resources from Flathub or type ``flatpak install flathub net.nokyan.Resources`` in your terminal.
36 | Please keep in mind that you need to have Flathub set up on your device. You can find out how to set up Flathub [here](https://flathub.org/setup).
37 |
38 | ### Unofficial Packages
39 |
40 | Resources has been packaged for some Linux distributions by volunteers. Keep in mind that these are not supported.
41 | If you’re packaging Resources for another distribution, feel free to send a pull request to add your package to this list!
42 |
43 | #### Arch Linux
44 |
45 | Unofficially packaged in the [extra](https://archlinux.org/packages/extra/x86_64/resources/) repository.
46 |
47 | You can install Resources using `pacman` with no further configuration required.
48 |
49 | ```sh
50 | pacman -S resources
51 | ```
52 |
53 | #### Fedora
54 |
55 | Unofficially packaged in [Copr](https://copr.fedorainfracloud.org/coprs/atim/resources/) for Fedora 39 and newer.
56 |
57 | You first need to enable the `atim/resources` Copr repository and then use `dnf` to install Resources.
58 |
59 | ```sh
60 | dnf copr enable atim/resources
61 | dnf install resources
62 | ```
63 |
64 | #### Nix
65 |
66 | Unofficially packaged for Nix/NixOS. The Flatpak version is [known to have issues](https://github.com/nokyan/resources/issues/76) with showing running apps and processes on NixOS, which the native package may resolve.
67 |
68 | In `configuration.nix`:
69 | ```
70 | environment.systemPackages = [
71 | pkgs.resources
72 | ];
73 | ```
74 |
75 | ## Building
76 |
77 | You can also build Resources yourself using either Meson directly or preferably using Flatpak Builder.
78 |
79 | ### Build Dependencies
80 |
81 | - `glib-2.0` ≥ 2.66
82 | - `gio-2.0` ≥ 2.66
83 | - `gtk-4` ≥ 4.12
84 | - `libadwaita-1` ≥ 1.8
85 | - `cargo`
86 |
87 | Other dependencies are handled by `cargo`.
88 | Resources’ minimum supported Rust version (MSRV) is **1.85.0**.
89 |
90 | ### Runtime Dependencies
91 |
92 | These dependencies are not needed to build Resources but Resources may lack certain functionalities when they are not present.
93 |
94 | - `systemd` (needed for app detection using cgroups)
95 | - `polkit` (needed for executing privileged actions like killing certain processes)
96 |
97 | ### Building Using Flatpak Builder
98 |
99 | ```sh
100 | flatpak install org.gnome.Sdk//49 org.freedesktop.Sdk.Extension.rust-stable//25.08 org.gnome.Platform//49 org.freedesktop.Sdk.Extension.llvm21//25.08
101 | flatpak-builder --user flatpak_app build-aux/net.nokyan.Resources.Devel.json
102 | ```
103 |
104 | If you use [GNOME Builder](https://apps.gnome.org/app/org.gnome.Builder/) or Visual Studio Code with the [Flatpak extension](https://marketplace.visualstudio.com/items?itemName=bilelmoussaoui.flatpak-vscode), Resources can be built and run automatically.
105 |
106 | ### Building Natively Using Meson
107 |
108 | ```sh
109 | meson . build --prefix=/usr/local
110 | ninja -C build install
111 | ```
112 |
113 | ## Running
114 |
115 | Running Resources is as simple as typing `flatpak run net.nokyan.Resources` into a terminal or running it from your app launcher.
116 | If you’ve built Resources natively or installed it from a traditional package manager such as `apt` or `dnf`, or if you’ve built Resources yourself, typing `resources` in a terminal will start Resources.
117 | If you’ve built Resources as a Flatpak, type `flatpak-builder --run flatpak_app build-aux/net.nokyan.Resources.Devel.json resources` into your terminal or use one of the aforementioned IDEs to do that automatically.
118 |
119 | ## Contributing
120 |
121 | If you have an idea, bug report, question or something else, don’t hesitate to [open an issue](https://github.com/nokyan/resources/issues)! Translations are always welcome but need to go through [GNOME Damned Lies](https://l10n.gnome.org/module/resources/), ordinary pull requests for translation changes cannot be accepted anymore.
122 |
123 | ## Code of Conduct
124 |
125 | Resources follows the [GNOME Code of Conduct](/CODE_OF_CONDUCT.md).
126 | All communications in project spaces are expected to follow it.
127 |
--------------------------------------------------------------------------------
/data/resources/ui/pages/gpu.ui:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | 768
9 |
10 |
11 | 24
12 | 24
13 | 16
14 | 16
15 | 24
16 | vertical
17 | true
18 | start
19 | true
20 |
21 |
22 | Usage
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 | GPU Frequency
38 |
41 | true
42 |
43 |
44 |
45 |
46 |
49 | true
50 | Video Memory Frequency
51 |
52 |
53 |
54 |
55 | Power Usage
56 |
59 | true
60 |
61 |
62 |
63 |
64 |
65 |
66 | Sensors
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 | Properties
75 |
76 |
77 |
80 | true
81 | Manufacturer
82 |
83 |
84 |
85 |
86 |
89 | true
90 | PCI Slot
91 |
92 |
93 |
94 |
95 |
98 | true
99 | Driver Used
100 |
101 |
102 |
103 |
104 |
107 | true
108 | Max Power Cap
109 |
110 |
111 |
112 |
113 | Link
114 |
117 | true
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
--------------------------------------------------------------------------------
/src/utils/link/usb.rs:
--------------------------------------------------------------------------------
1 | use crate::utils::drive::UsbSlot;
2 | use crate::utils::link::LinkData;
3 | use crate::utils::units::convert_speed_bits_decimal_with_places;
4 | use anyhow::{Context, Error, anyhow};
5 | use log::trace;
6 | use std::fmt::{Display, Formatter};
7 | use std::path::Path;
8 | use std::str::FromStr;
9 |
10 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
11 | pub enum UsbSpeed {
12 | // https://en.wikipedia.org/wiki/USB#Release_versions
13 | Usb1_0,
14 | Usb1_1(usize),
15 | Usb2_0(usize),
16 | Usb3_0(usize),
17 | Usb3_1(usize),
18 | Usb3_2(usize),
19 | Usb4(usize),
20 | Usb4_2_0(usize),
21 | }
22 |
23 | impl LinkData {
24 | pub fn from_usb_slot(usb_slot: &UsbSlot) -> anyhow::Result {
25 | trace!("Reading USB link data for {usb_slot:?}…");
26 |
27 | let usb_bus_path =
28 | Path::new("/sys/bus/usb/devices/").join(format!("usb{}", usb_slot.usb_bus));
29 |
30 | let max_usb_port_speed_raw = std::fs::read_to_string(usb_bus_path.join("speed"))
31 | .map(|x| x.trim().to_string())
32 | .context("Could not read usb port speed");
33 |
34 | let usb_device_speed =
35 | std::fs::read_to_string(usb_bus_path.join(&usb_slot.usb_device).join("speed"))
36 | .map(|x| x.trim().to_string())
37 | .context("Could not read usb device speed")?;
38 |
39 | let usb_port_speed = max_usb_port_speed_raw.and_then(|x| UsbSpeed::from_str(&x));
40 |
41 | let usb_device_speed =
42 | UsbSpeed::from_str(&usb_device_speed).context("Could not parse USB device speed")?;
43 |
44 | Ok(LinkData {
45 | current: usb_device_speed,
46 | max: usb_port_speed,
47 | })
48 | }
49 | }
50 |
51 | impl Display for UsbSpeed {
52 | fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
53 | write!(
54 | f,
55 | "{} ({})",
56 | // https://en.wikipedia.org/wiki/USB#Release_versions
57 | match self {
58 | UsbSpeed::Usb1_0 => "USB 1.0",
59 | UsbSpeed::Usb1_1(_) => "USB 1.1",
60 | UsbSpeed::Usb2_0(_) => "USB 2.0",
61 | UsbSpeed::Usb3_0(_) => "USB 3.0",
62 | UsbSpeed::Usb3_1(_) => "USB 3.1",
63 | UsbSpeed::Usb3_2(_) => "USB 3.2",
64 | UsbSpeed::Usb4(_) => "USB4",
65 | UsbSpeed::Usb4_2_0(_) => "USB4 2.0",
66 | },
67 | match self {
68 | UsbSpeed::Usb1_0 => convert_speed_bits_decimal_with_places(1.5 * 1_000_000.0, 1),
69 | UsbSpeed::Usb1_1(mbit)
70 | | UsbSpeed::Usb2_0(mbit)
71 | | UsbSpeed::Usb3_0(mbit)
72 | | UsbSpeed::Usb3_1(mbit)
73 | | UsbSpeed::Usb3_2(mbit)
74 | | UsbSpeed::Usb4(mbit)
75 | | UsbSpeed::Usb4_2_0(mbit) =>
76 | convert_speed_bits_decimal_with_places(*mbit as f64 * 1_000_000.0, 0),
77 | }
78 | )
79 | }
80 | }
81 |
82 | impl FromStr for UsbSpeed {
83 | type Err = Error;
84 | fn from_str(s: &str) -> std::result::Result {
85 | match s {
86 | // https://en.wikipedia.org/wiki/USB#Release_versions
87 | //https://www.kernel.org/doc/Documentation/ABI/stable/sysfs-bus-usb
88 | "1.5" => Ok(UsbSpeed::Usb1_0),
89 | "12" => Ok(UsbSpeed::Usb1_1(12)),
90 | "480" => Ok(UsbSpeed::Usb2_0(480)),
91 | "5000" => Ok(UsbSpeed::Usb3_0(5_000)),
92 | "10000" => Ok(UsbSpeed::Usb3_1(10_000)),
93 | "20000" => Ok(UsbSpeed::Usb3_2(20_000)),
94 | "40000" => Ok(UsbSpeed::Usb4(40_000)),
95 | "80000" => Ok(UsbSpeed::Usb4_2_0(80_000)),
96 | "120000" => Ok(UsbSpeed::Usb4_2_0(120_000)),
97 | _ => Err(anyhow!("Could not parse USB speed: '{s}'")),
98 | }
99 | }
100 | }
101 |
102 | #[cfg(test)]
103 | mod test {
104 | use crate::utils::link::usb::UsbSpeed;
105 | use std::collections::HashMap;
106 | use std::str::FromStr;
107 |
108 | #[test]
109 | fn parse_usb_link_speeds() {
110 | let map = HashMap::from([
111 | ("1.5", UsbSpeed::Usb1_0),
112 | ("12", UsbSpeed::Usb1_1(12)),
113 | ("480", UsbSpeed::Usb2_0(480)),
114 | ("5000", UsbSpeed::Usb3_0(5_000)),
115 | ("10000", UsbSpeed::Usb3_1(10_000)),
116 | ("20000", UsbSpeed::Usb3_2(20_000)),
117 | ("40000", UsbSpeed::Usb4(40_000)),
118 | ("80000", UsbSpeed::Usb4_2_0(80_000)),
119 | ("120000", UsbSpeed::Usb4_2_0(120_000)),
120 | ]);
121 |
122 | for input in map.keys() {
123 | let result = UsbSpeed::from_str(input);
124 | assert!(result.is_ok(), "Could not parse USB speed for '{input}'");
125 | let expected = map[input];
126 | pretty_assertions::assert_eq!(expected, result.unwrap());
127 | }
128 | }
129 |
130 | #[test]
131 | fn parse_usb_link_speeds_failure() {
132 | let invalid = vec!["4000", "160000", "SOMETHING_ELSE", ""];
133 |
134 | for input in invalid {
135 | let result = UsbSpeed::from_str(input);
136 | assert!(
137 | result.is_err(),
138 | "Could parse USB speed for '{input}' while we don't expect that"
139 | );
140 | }
141 | }
142 |
143 | #[test]
144 | fn display_usb_link_speeds() {
145 | let map = HashMap::from([
146 | (UsbSpeed::Usb1_0, "USB 1.0 (1.5 Mb/s)"),
147 | (UsbSpeed::Usb1_1(12), "USB 1.1 (12 Mb/s)"),
148 | (UsbSpeed::Usb2_0(480), "USB 2.0 (480 Mb/s)"),
149 | (UsbSpeed::Usb3_0(5_000), "USB 3.0 (5 Gb/s)"),
150 | (UsbSpeed::Usb3_1(10_000), "USB 3.1 (10 Gb/s)"),
151 | (UsbSpeed::Usb3_2(20_000), "USB 3.2 (20 Gb/s)"),
152 | (UsbSpeed::Usb4(40_000), "USB4 (40 Gb/s)"),
153 | (UsbSpeed::Usb4_2_0(80_000), "USB4 2.0 (80 Gb/s)"),
154 | (UsbSpeed::Usb4_2_0(120_000), "USB4 2.0 (120 Gb/s)"),
155 | ]);
156 |
157 | for input in map.keys() {
158 | let result = input.to_string();
159 | let expected = map[input];
160 | pretty_assertions::assert_str_eq!(expected, result);
161 | }
162 | }
163 | }
164 |
--------------------------------------------------------------------------------