From 0c01a1fb535c80969d9d87a691cf7164c0a68ab2 Mon Sep 17 00:00:00 2001 From: Esen Date: Thu, 11 Sep 2025 20:35:44 +0300 Subject: [PATCH] Add "memory" and "cpu" modules --- Cargo.lock | 137 +++++++++++++++++++++-- Cargo.toml | 1 + share/tiny-dfr/memory.svg | 1 + src/config.rs | 4 + src/main.rs | 230 +++++++++++++++++++++++++++++++------- 5 files changed, 320 insertions(+), 53 deletions(-) create mode 100644 share/tiny-dfr/memory.svg diff --git a/Cargo.lock b/Cargo.lock index e4a84506..a74e2130 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -559,9 +559,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.172" +version = "0.2.175" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" +checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543" [[package]] name = "libredox" @@ -660,6 +660,15 @@ dependencies = [ "libc", ] +[[package]] +name = "ntapi" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8a3895c6391c39d7fe7ebc444a87eb2991b2a0bc718fdabd071eec617fc68e4" +dependencies = [ + "winapi", +] + [[package]] name = "num-traits" version = "0.2.19" @@ -669,6 +678,25 @@ dependencies = [ "autocfg", ] +[[package]] +name = "objc2-core-foundation" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c10c2894a6fed806ade6027bcd50662746363a9589d3ec9d9bef30a4e4bc166" +dependencies = [ + "bitflags 2.9.0", +] + +[[package]] +name = "objc2-io-kit" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71c1c64d6120e51cd86033f67176b1cb66780c2efe34dec55176f77befd93c0a" +dependencies = [ + "libc", + "objc2-core-foundation", +] + [[package]] name = "once_cell" version = "1.21.3" @@ -872,6 +900,20 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "sysinfo" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "252800745060e7b9ffb7b2badbd8b31cfa4aa2e61af879d0a3bf2a317c20217d" +dependencies = [ + "libc", + "memchr", + "ntapi", + "objc2-core-foundation", + "objc2-io-kit", + "windows", +] + [[package]] name = "system-deps" version = "7.0.3" @@ -931,6 +973,7 @@ dependencies = [ "privdrop", "rand", "serde", + "sysinfo", "toml", "udev 0.9.3", ] @@ -1106,11 +1149,55 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows" +version = "0.61.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893" +dependencies = [ + "windows-collections", + "windows-core", + "windows-future", + "windows-link", + "windows-numerics", +] + +[[package]] +name = "windows-collections" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8" +dependencies = [ + "windows-core", +] + [[package]] name = "windows-core" -version = "0.61.0" +version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4763c1de310c86d75a878046489e2e5ba02c649d185f21c67d4cf8a56d098980" +checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" dependencies = [ "windows-implement", "windows-interface", @@ -1119,6 +1206,17 @@ dependencies = [ "windows-strings", ] +[[package]] +name = "windows-future" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" +dependencies = [ + "windows-core", + "windows-link", + "windows-threading", +] + [[package]] name = "windows-implement" version = "0.60.0" @@ -1143,24 +1241,34 @@ dependencies = [ [[package]] name = "windows-link" -version = "0.1.1" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" + +[[package]] +name = "windows-numerics" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38" +checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" +dependencies = [ + "windows-core", + "windows-link", +] [[package]] name = "windows-result" -version = "0.3.2" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c64fd11a4fd95df68efcfee5f44a294fe71b8bc6a91993e2791938abcc712252" +checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" dependencies = [ "windows-link", ] [[package]] name = "windows-strings" -version = "0.4.0" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a2ba9642430ee452d5a7aa78d72907ebe8cfda358e8cb7918a2050581322f97" +checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" dependencies = [ "windows-link", ] @@ -1214,6 +1322,15 @@ dependencies = [ "windows_x86_64_msvc 0.52.6", ] +[[package]] +name = "windows-threading" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6" +dependencies = [ + "windows-link", +] + [[package]] name = "windows_aarch64_gnullvm" version = "0.48.5" diff --git a/Cargo.toml b/Cargo.toml index 3fde1b07..a96ff01b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,6 +27,7 @@ freetype-rs = "0.37" freedesktop-icons = "0.4.0" chrono = { version = "0.4", features = ["unstable-locales"] } udev = "0.9" +sysinfo = "0.36.1" [build-dependencies] pkg-config = "0.3" diff --git a/share/tiny-dfr/memory.svg b/share/tiny-dfr/memory.svg new file mode 100644 index 00000000..9948a883 --- /dev/null +++ b/share/tiny-dfr/memory.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/config.rs b/src/config.rs index ca02eecc..92a4f423 100644 --- a/src/config.rs +++ b/src/config.rs @@ -44,6 +44,8 @@ pub struct ButtonConfig { pub time: Option, pub battery: Option, pub locale: Option, + pub memory: Option, + pub cpu: Option, pub action: Key, pub stretch: Option, } @@ -95,6 +97,8 @@ fn load_config(width: u16) -> (Config, [FunctionLayer; 2]) { time: None, locale: None, battery: None, + memory: None, + cpu: None, }, ); } diff --git a/src/main.rs b/src/main.rs index 4d20b23e..6bd1c32a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -36,6 +36,7 @@ use std::{ path::{Path, PathBuf}, }; use udev::MonitorBuilder; +use sysinfo::{System, RefreshKind, MemoryRefreshKind, CpuRefreshKind}; mod backlight; mod config; @@ -69,31 +70,40 @@ struct BatteryImages { } #[derive(Eq, PartialEq, Copy, Clone)] -enum BatteryIconMode { +enum IconMode { Percentage, Icon, Both } -impl BatteryIconMode { +impl IconMode { fn should_draw_icon(self) -> bool { - self != BatteryIconMode::Percentage + self != IconMode::Percentage } fn should_draw_text(self) -> bool { - self != BatteryIconMode::Icon + self != IconMode::Icon } } enum ButtonImage { - Text(String), + Text, Svg(Handle), Bitmap(ImageSurface), - Time(Vec>, Locale), - Battery(String, BatteryIconMode, BatteryImages), + Time, + Battery, } +enum ButtonType { + Text(String), + Image, + Time(Vec>, Locale), + Battery(String, IconMode, BatteryImages), + Memory(IconMode), + Cpu(IconMode), +} struct Button { image: ButtonImage, + button_type: ButtonType, changed: bool, active: bool, action: Key, @@ -239,6 +249,27 @@ fn get_battery_state(battery: &str) -> (u32, BatteryState) { (capacity, status) } +fn get_memory_usage() -> u32 { + let mut s = System::new(); + s.refresh_memory_specifics(MemoryRefreshKind::nothing().with_ram()); + + ((s.used_memory() as f64 / s.total_memory() as f64) * 100.0).round() as u32 +} + +fn get_cpu_usage() -> u32 { + let mut s = System::new(); + s.refresh_cpu_usage(); + std::thread::sleep(sysinfo::MINIMUM_CPU_UPDATE_INTERVAL); + s.refresh_cpu_usage(); + + let mut total = 0.0; + for cpu in s.cpus() { + total += cpu.cpu_usage(); + } + + (total / s.cpus().len() as f32).round() as u32 +} + impl Button { fn with_config(cfg: ButtonConfig) -> Button { if let Some(text) = cfg.text { @@ -253,66 +284,111 @@ impl Button { } else { Button::new_text("Battery N/A".to_string(), cfg.action) } + } else if let Some(memory_mode) = cfg.memory { + Button::new_memory(cfg.action, memory_mode, cfg.theme) + } else if let Some(cpu_mode) = cfg.cpu { + Button::new_cpu(cfg.action, cpu_mode, cfg.theme) } else { - panic!("Invalid config, a button must have either Text, Icon or Time") + panic!("Invalid config, a button must have either Text, Icon, Time, Battery, Memory, or CPU") } } fn new_text(text: String, action: Key) -> Button { Button { + button_type: ButtonType::Text(text), action, active: false, changed: false, - image: ButtonImage::Text(text), + image: ButtonImage::Text, } } fn new_icon(path: impl AsRef, theme: Option>, action: Key) -> Button { let image = try_load_image(path, theme).expect("failed to load icon"); Button { + button_type: ButtonType::Image, action, image, active: false, changed: false, } } - fn load_battery_image(icon: &str, theme: Option>) -> Handle { + fn load_image(icon: &str, theme: Option>) -> Handle { if let ButtonImage::Svg(svg) = try_load_image(icon, theme).unwrap() { return svg; } panic!("failed to load icon"); } + fn new_battery(action: Key, battery: String, battery_mode: String, theme: Option>) -> Button { - let bolt = Self::load_battery_image("bolt", theme.as_ref()); + let bolt = Self::load_image("bolt", theme.as_ref()); let mut plain = Vec::new(); let mut charging = Vec::new(); for icon in [ "battery_0_bar", "battery_1_bar", "battery_2_bar", "battery_3_bar", "battery_4_bar", "battery_5_bar", "battery_6_bar", "battery_full", ] { - plain.push(Self::load_battery_image(icon, theme.as_ref())); + plain.push(Self::load_image(icon, theme.as_ref())); } for icon in [ "battery_charging_20", "battery_charging_30", "battery_charging_50", "battery_charging_60", "battery_charging_80", "battery_charging_90", "battery_charging_full", ] { - charging.push(Self::load_battery_image(icon, theme.as_ref())); + charging.push(Self::load_image(icon, theme.as_ref())); } let battery_mode = match battery_mode.as_str() { - "icon" => BatteryIconMode::Icon, - "percentage" => BatteryIconMode::Percentage, - "both" => BatteryIconMode::Both, + "icon" => IconMode::Icon, + "percentage" => IconMode::Percentage, + "both" => IconMode::Both, _ => panic!("invalid battery mode, accepted modes: icon, percentage, both"), }; Button { + button_type: ButtonType::Battery(battery, battery_mode, BatteryImages { + plain, bolt, charging + }), action, active: false, changed: false, - image: ButtonImage::Battery(battery, battery_mode, BatteryImages { - plain, bolt, charging - }), + image: ButtonImage::Battery, + } + } + + fn new_memory(action: Key, memory_mode: String, theme: Option>) -> Button { + let image = Self::load_image("memory", theme.as_ref()); + let memory_mode = match memory_mode.as_str() { + "icon" => IconMode::Icon, + "percentage" => IconMode::Percentage, + "both" => IconMode::Both, + _ => panic!("invalid battery mode, accepted modes: icon, percentage, both"), + }; + + Button { + button_type: ButtonType::Memory(memory_mode), + action, + active: false, + changed: false, + image: ButtonImage::Svg(image), } } + fn new_cpu(action: Key, cpu_mode: String, theme: Option>) -> Button { + let image = Self::load_image("memory", theme.as_ref()); + let cpu_mode = match cpu_mode.as_str() { + "icon" => IconMode::Icon, + "percentage" => IconMode::Percentage, + "both" => IconMode::Both, + _ => panic!("invalid battery mode, accepted modes: icon, percentage, both"), + }; + + Button { + button_type: ButtonType::Cpu(cpu_mode), + action, + active: false, + changed: false, + image: ButtonImage::Svg(image), + } + } + + fn new_time(action: Key, format: &str, locale_str: Option<&str>) -> Button { let format_str = if format == "24hr" { "%H:%M %a %-e %b" @@ -329,10 +405,11 @@ impl Button { let locale = locale_str.and_then(|l| Locale::try_from(l).ok()).unwrap_or(Locale::POSIX); Button { + button_type: ButtonType::Time(format_items, locale), action, active: false, changed: false, - image: ButtonImage::Time(format_items, locale), + image: ButtonImage::Time, } } fn render( @@ -343,8 +420,8 @@ impl Button { button_width: u64, y_shift: f64, ) { - match &self.image { - ButtonImage::Text(text) => { + match &self.button_type { + ButtonType::Text(text) => { let extents = c.text_extents(text).unwrap(); c.move_to( button_left_edge + (button_width as f64 / 2.0 - extents.width() / 2.0).round(), @@ -352,23 +429,30 @@ impl Button { ); c.show_text(text).unwrap(); } - ButtonImage::Svg(svg) => { - let x = - button_left_edge + (button_width as f64 / 2.0 - (ICON_SIZE / 2) as f64).round(); - let y = y_shift + ((height as f64 - ICON_SIZE as f64) / 2.0).round(); - - svg.render_document(c, &Rectangle::new(x, y, ICON_SIZE as f64, ICON_SIZE as f64)) - .unwrap(); - } - ButtonImage::Bitmap(surf) => { - let x = - button_left_edge + (button_width as f64 / 2.0 - (ICON_SIZE / 2) as f64).round(); - let y = y_shift + ((height as f64 - ICON_SIZE as f64) / 2.0).round(); - c.set_source_surface(surf, x, y).unwrap(); - c.rectangle(x, y, ICON_SIZE as f64, ICON_SIZE as f64); - c.fill().unwrap(); + ButtonType::Image => { + match &self.image { + ButtonImage::Svg(svg) => { + let x = + button_left_edge + (button_width as f64 / 2.0 - (ICON_SIZE / 2) as f64).round(); + let y = y_shift + ((height as f64 - ICON_SIZE as f64) / 2.0).round(); + + svg.render_document(c, &Rectangle::new(x, y, ICON_SIZE as f64, ICON_SIZE as f64)) + .unwrap(); + } + ButtonImage::Bitmap(surf) => { + let x = + button_left_edge + (button_width as f64 / 2.0 - (ICON_SIZE / 2) as f64).round(); + let y = y_shift + ((height as f64 - ICON_SIZE as f64) / 2.0).round(); + c.set_source_surface(surf, x, y).unwrap(); + c.rectangle(x, y, ICON_SIZE as f64, ICON_SIZE as f64); + c.fill().unwrap(); + } + _ => { + panic!("ButtonType is of Image but ButtonImage is not of Svg or Bitmap") + } + } } - ButtonImage::Time(format, locale) => { + ButtonType::Time(format, locale) => { let current_time = Local::now(); let formatted_time = current_time.format_localized_with_items(format.iter(), *locale).to_string(); let time_extents = c.text_extents(&formatted_time).unwrap(); @@ -378,7 +462,7 @@ impl Button { ); c.show_text(&formatted_time).unwrap(); } - ButtonImage::Battery(battery, battery_mode, icons) => { + ButtonType::Battery(battery, battery_mode, icons) => { let (capacity, state) = get_battery_state(battery); let icon = if battery_mode.should_draw_icon() { Some(match state { @@ -433,6 +517,65 @@ impl Button { c.show_text(&percent_str).unwrap(); } } + ButtonType::Memory(memory_mode) => { + let memory_usage = get_memory_usage(); + let icon = if memory_mode.should_draw_icon() { Some(&self.image) } else { None }; + let percent_str = format!("{:.0}%", memory_usage); + let extents = c.text_extents(&percent_str).unwrap(); + let mut width = extents.width(); + let mut text_offset = 0; + if let Some(ButtonImage::Svg(svg)) = icon { + if !memory_mode.should_draw_text() { + width = ICON_SIZE as f64; + } else { + width += ICON_SIZE as f64; + } + text_offset = ICON_SIZE; + let x = + button_left_edge + (button_width as f64 / 2.0 - width / 2.0).round(); + let y = y_shift + ((height as f64 - ICON_SIZE as f64) / 2.0).round(); + + svg.render_document(c, &Rectangle::new(x, y, ICON_SIZE as f64, ICON_SIZE as f64)) + .unwrap(); + } + if memory_mode.should_draw_text() { + c.move_to( + button_left_edge + (button_width as f64 / 2.0 - width / 2.0 + text_offset as f64).round(), + y_shift + (height as f64 / 2.0 + extents.height() / 2.0).round(), + ); + c.show_text(&percent_str).unwrap(); + } + } + ButtonType::Cpu(cpu_mode) => { + let cpu_usage = get_cpu_usage(); + let icon = if cpu_mode.should_draw_icon() { Some(&self.image) } else { None }; + let percent_str = format!("{:.0}%", cpu_usage); + let extents = c.text_extents(&percent_str).unwrap(); + let mut width = extents.width(); + let mut text_offset = 0; + if let Some(ButtonImage::Svg(svg)) = icon { + if !cpu_mode.should_draw_text() { + width = ICON_SIZE as f64; + } else { + width += ICON_SIZE as f64; + } + text_offset = ICON_SIZE; + let x = + button_left_edge + (button_width as f64 / 2.0 - width / 2.0).round(); + let y = y_shift + ((height as f64 - ICON_SIZE as f64) / 2.0).round(); + + svg.render_document(c, &Rectangle::new(x, y, ICON_SIZE as f64, ICON_SIZE as f64)) + .unwrap(); + } + if cpu_mode.should_draw_text() { + c.move_to( + button_left_edge + (button_width as f64 / 2.0 - width / 2.0 + text_offset as f64).round(), + y_shift + (height as f64 / 2.0 + extents.height() / 2.0).round(), + ); + c.show_text(&percent_str).unwrap(); + } + } + } } fn set_active(&mut self, uinput: &mut UInputHandle, active: bool) @@ -446,8 +589,9 @@ impl Button { toggle_key(uinput, self.action, active as i32); } } - fn set_backround_color(&self, c: &Context, color: f64) { - if let ButtonImage::Battery(battery, _, _) = &self.image { + + fn set_background_color(&self, c: &Context, color: f64) { + if let ButtonType::Battery(battery, _, _) = &self.button_type { let (_, state) = get_battery_state(battery); match state { BatteryState::NotCharging => c.set_source_rgb(color, color, color), @@ -571,7 +715,7 @@ impl FunctionLayer { ); c.fill().unwrap(); } - button.set_backround_color(&c, color); + button.set_background_color(&c, color); // draw box with rounded corners c.new_sub_path(); let left = left_edge + radius; @@ -847,7 +991,7 @@ fn real_main(drm: &mut DrmBackend) { } if layers[active_layer].displays_battery { for button in &mut layers[active_layer].buttons { - if let ButtonImage::Battery(_, _, _) = button.1.image { + if let ButtonImage::Battery = button.1.image { button.1.changed = true; } }