diff --git a/Cargo.lock b/Cargo.lock index 0b65560..49511af 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -198,6 +198,7 @@ version = "0.1.0" dependencies = [ "colored", "display-info", + "libc", "objc2", "objc2-app-kit", "objc2-foundation", diff --git a/Cargo.toml b/Cargo.toml index f624248..d234f04 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,6 +6,7 @@ edition = "2024" [dependencies] colored = "3.0.0" display-info = "0.5.7" +libc = "0.2.179" objc2 = "0.6.3" objc2-app-kit = "0.3.2" objc2-foundation = "0.3.2" diff --git a/TODO.md b/TODO.md new file mode 100644 index 0000000..5ccd33f --- /dev/null +++ b/TODO.md @@ -0,0 +1,5 @@ +[ ] Fix [display.rs](src/helpers/display.rs) to match fastfetch +[ ] Fix [terminal.rs](src/helpers/terminal.rs) to match fastfetch +[ ] Fix [wm.rs](src/helpers/wm.rs) to match fastfetch +[ ] Get rid of sysinfo crate entirely use native methods +[ ] Get rid of unsafe 😭 diff --git a/src/helpers/battery.rs b/src/helpers/battery.rs new file mode 100644 index 0000000..0ea0aa3 --- /dev/null +++ b/src/helpers/battery.rs @@ -0,0 +1,100 @@ +use std::process::Command; + +pub fn get_battery_info() -> String { + let output = Command::new("ioreg") + .args(["-l", "-w0", "-r", "-c", "AppleSmartBattery"]) + .output(); + + let stdout = match output { + Ok(out) => String::from_utf8_lossy(&out.stdout).to_string(), + Err(_) => return "".to_string(), + }; + + let mut device_name = "Built-in".to_string(); + let mut current_capacity: Option = None; + let mut external_connected = false; + let mut is_charging = false; + let mut avg_time_to_empty: Option = None; + + for line in stdout.lines() { + if line.contains("\"DeviceName\"") { + if let Some(equals_pos) = line.find('=') { + let value_part = &line[equals_pos + 1..].trim(); + let value = value_part.trim_matches('"').trim_matches(';').trim(); + if !value.is_empty() { + device_name = value.to_string(); + } + } + } else if line.contains("\"CurrentCapacity\"") { + if let Some(equals_pos) = line.find('=') { + let value_part = &line[equals_pos + 1..].trim(); + let value = value_part.trim_matches(';').trim(); + if let Ok(capacity) = value.parse::() { + current_capacity = Some(capacity); + } + } + } else if line.contains("\"ExternalConnected\"") { + if let Some(equals_pos) = line.find('=') { + let value_part = &line[equals_pos + 1..].trim(); + let value = value_part.trim_matches(';').trim(); + external_connected = value == "Yes"; + } + } else if line.contains("\"IsCharging\"") { + if let Some(equals_pos) = line.find('=') { + let value_part = &line[equals_pos + 1..].trim(); + let value = value_part.trim_matches(';').trim(); + is_charging = value == "Yes"; + } + } else if line.contains("\"AvgTimeToEmpty\"") { + if let Some(equals_pos) = line.find('=') { + let value_part = &line[equals_pos + 1..].trim(); + let value = value_part.trim_matches(';').trim(); + if let Ok(time) = value.parse::() { + avg_time_to_empty = Some(time); + } + } + } + } + + let percentage = if let Some(capacity) = current_capacity { + if capacity >= 0 && capacity <= 100 { + capacity as u32 + } else { + return "".to_string(); + } + } else { + return "".to_string(); + }; + + let mut status = String::new(); + if external_connected { + status.push_str("AC connected"); + } else if is_charging { + status.push_str("Charging"); + } else { + status.push_str("Discharging"); + } + + let mut result = format!("Battery ({}): {}%", device_name, percentage); + + if !external_connected && !is_charging { + if let Some(time_mins) = avg_time_to_empty { + if time_mins > 0 && time_mins < 0xFFFF { + let hours = time_mins / 60; + let mins = time_mins % 60; + + if hours > 0 && mins > 0 { + result.push_str(&format!(" ({} hours, {} mins remaining)", hours, mins)); + } else if hours > 0 { + result.push_str(&format!(" ({} hours remaining)", hours)); + } else if mins > 0 { + result.push_str(&format!(" ({} mins remaining)", mins)); + } + } + } + } + + result.push_str(&format!(" [{}]", status)); + + result +} diff --git a/src/helpers/cpu.rs b/src/helpers/cpu.rs new file mode 100644 index 0000000..a5d8a52 --- /dev/null +++ b/src/helpers/cpu.rs @@ -0,0 +1,63 @@ +// https://github.com/fastfetch-cli/fastfetch/blob/dev/src/detection/cpu/cpu_apple.c + +use std::process::Command; + +pub fn get_cpu_info() -> String { + let brand = Command::new("sysctl") + .args(["-n", "machdep.cpu.brand_string"]) + .output() + .map(|o| String::from_utf8_lossy(&o.stdout).trim().to_string()) + .unwrap_or_else(|_| "Unknown CPU".to_string()); + + let cores = Command::new("sysctl") + .args(["-n", "hw.physicalcpu"]) + .output() + .map(|o| String::from_utf8_lossy(&o.stdout).trim().to_string()) + .unwrap_or_else(|_| "0".to_string()); + + let ioreg_output = Command::new("ioreg") + .args(["-p", "IODeviceTree", "-n", "pmgr", "-l"]) + .output(); + + let mut max_freq = 0u64; + + if let Ok(output) = ioreg_output { + let stdout = String::from_utf8_lossy(&output.stdout); + for line in stdout.lines() { + if line.contains("voltage-states5-sram") { + let hex_data = line + .split('<') + .nth(1) + .and_then(|s| s.split('>').next()) + .unwrap_or(""); + for i in (0..hex_data.len()).step_by(16) { + if i + 8 <= hex_data.len() { + let chunk = &hex_data[i..i + 8]; + let mut bytes = [0u8; 4]; + for j in 0..4 { + if let Ok(byte) = u8::from_str_radix(&chunk[j * 2..j * 2 + 2], 16) { + bytes[j] = byte; + } + } + let freq = u32::from_le_bytes(bytes) as u64; + if freq > max_freq { + max_freq = freq; + } + } + } + } + } + } + + if max_freq == 0 { + return format!("{} ({})", brand, cores); + } + + let ghz = if max_freq > 100_000_000 { + max_freq as f64 / 1_000_000_000.0 + } else { + max_freq as f64 / 1_000_000.0 + }; + + format!("{} ({}) @ {:.2} GHz", brand, cores, ghz) +} diff --git a/src/helpers/cursor.rs b/src/helpers/cursor.rs index e2be9d4..6c70b38 100644 --- a/src/helpers/cursor.rs +++ b/src/helpers/cursor.rs @@ -4,25 +4,6 @@ use plist::Value; use std::env; use std::path::PathBuf; -fn format_color(dict: &plist::dictionary::Dictionary) -> String { - let r = (dict.get("red").and_then(|v| v.as_real()).unwrap_or(0.0) * 255.0 + 0.5) as u32; - let g = (dict.get("green").and_then(|v| v.as_real()).unwrap_or(0.0) * 255.0 + 0.5) as u32; - let b = (dict.get("blue").and_then(|v| v.as_real()).unwrap_or(0.0) * 255.0 + 0.5) as u32; - let a = (dict.get("alpha").and_then(|v| v.as_real()).unwrap_or(1.0) * 255.0 + 0.5) as u32; - - let color_hex = (r << 24) | (g << 16) | (b << 8) | a; - - match color_hex { - 0x000000FF => "Black".to_string(), - 0xFFFFFFFF => "White".to_string(), - 0xFF2600FF => "Red".to_string(), - 0x0433FFFF => "Blue".to_string(), - 0x00F900FF => "Green".to_string(), - 0xFFFB00FF => "Yellow".to_string(), - _ => format!("#{:08X}", color_hex), - } -} - pub fn get_cursor_info() -> String { let mut path = PathBuf::from(env::var("HOME").unwrap_or_default()); path.push("Library/Preferences/com.apple.universalaccess.plist"); @@ -34,10 +15,37 @@ pub fn get_cursor_info() -> String { if let Ok(value) = Value::from_file(path) { if let Some(dict) = value.as_dictionary() { if let Some(f_dict) = dict.get("cursorFill").and_then(|v| v.as_dictionary()) { - fill = format_color(f_dict); + let r = (f_dict.get("red").and_then(|v| v.as_real()).unwrap_or(0.0) * 255.0 + 0.5) as u32; + let g = (f_dict.get("green").and_then(|v| v.as_real()).unwrap_or(0.0) * 255.0 + 0.5) as u32; + let b = (f_dict.get("blue").and_then(|v| v.as_real()).unwrap_or(0.0) * 255.0 + 0.5) as u32; + let a = (f_dict.get("alpha").and_then(|v| v.as_real()).unwrap_or(1.0) * 255.0 + 0.5) as u32; + let color_hex = (r << 24) | (g << 16) | (b << 8) | a; + fill = match color_hex { + 0x000000FF => "Black".to_string(), + 0xFFFFFFFF => "White".to_string(), + 0xFF2600FF => "Red".to_string(), + 0x0433FFFF => "Blue".to_string(), + 0x00F900FF => "Green".to_string(), + 0xFFFB00FF => "Yellow".to_string(), + _ => format!("#{:08X}", color_hex), + }; } + if let Some(o_dict) = dict.get("cursorOutline").and_then(|v| v.as_dictionary()) { - outline = format_color(o_dict); + let r = (o_dict.get("red").and_then(|v| v.as_real()).unwrap_or(0.0) * 255.0 + 0.5) as u32; + let g = (o_dict.get("green").and_then(|v| v.as_real()).unwrap_or(0.0) * 255.0 + 0.5) as u32; + let b = (o_dict.get("blue").and_then(|v| v.as_real()).unwrap_or(0.0) * 255.0 + 0.5) as u32; + let a = (o_dict.get("alpha").and_then(|v| v.as_real()).unwrap_or(1.0) * 255.0 + 0.5) as u32; + let color_hex = (r << 24) | (g << 16) | (b << 8) | a; + outline = match color_hex { + 0x000000FF => "Black".to_string(), + 0xFFFFFFFF => "White".to_string(), + 0xFF2600FF => "Red".to_string(), + 0x0433FFFF => "Blue".to_string(), + 0x00F900FF => "Green".to_string(), + 0xFFFB00FF => "Yellow".to_string(), + _ => format!("#{:08X}", color_hex), + }; } if let Some(s_val) = dict.get("mouseDriverCursorSize").and_then(|v| v.as_real()) { diff --git a/src/helpers/gpu.rs b/src/helpers/gpu.rs new file mode 100644 index 0000000..3eaa770 --- /dev/null +++ b/src/helpers/gpu.rs @@ -0,0 +1,100 @@ +// https://github.com/fastfetch-cli/fastfetch/blob/dev/src/detection/gpu/gpu_apple.c + +use std::process::Command; + +pub fn get_gpu_info() -> String { + let ioreg_accel = Command::new("ioreg") + .args(["-rc", "IOAccelerator", "-d", "1"]) + .output(); + + let mut model = String::new(); + let mut cores = String::new(); + let mut vendor_id = String::new(); + + if let Ok(output) = ioreg_accel { + let stdout = String::from_utf8_lossy(&output.stdout); + for line in stdout.lines() { + if line.contains("\"model\"") { + model = line + .split('=') + .nth(1) + .unwrap_or("") + .trim() + .replace('"', "") + .replace('<', "") + .replace('>', ""); + } + if line.contains("\"gpu-core-count\"") { + cores = line.split('=').nth(1).unwrap_or("").trim().to_string(); + } + if line.contains("\"vendor-id\"") { + vendor_id = line.split('=').nth(1).unwrap_or("").trim().to_string(); + } + } + } + + // Apple (0x106b) or Intel (0x8086) = Integrated + let type_str = match vendor_id.to_lowercase().as_str() { + "0x106b" | "4203" => "[Integrated]", + "0x8086" | "32902" => "[Integrated]", + "0x1002" | "4098" => "[Discrete]", + "0x10de" | "4318" => "[Discrete]", + _ => "[Integrated]", // Default for Apple Silicon if vendor-id is weird + }; + + let ioreg_pmgr = Command::new("ioreg") + .args(["-p", "IODeviceTree", "-n", "pmgr", "-l"]) + .output(); + + let mut max_freq = 0u64; + if let Ok(output) = ioreg_pmgr { + let stdout = String::from_utf8_lossy(&output.stdout); + for line in stdout.lines() { + if line.contains("voltage-states9-sram") { + let hex_data = line + .split('<') + .nth(1) + .and_then(|s| s.split('>').next()) + .unwrap_or(""); + for i in (0..hex_data.len()).step_by(16) { + if i + 8 <= hex_data.len() { + let chunk = &hex_data[i..i + 8]; + let mut bytes = [0u8; 4]; + for j in 0..4 { + if let Ok(byte) = u8::from_str_radix(&chunk[j * 2..j * 2 + 2], 16) { + bytes[j] = byte; + } + } + let freq = u32::from_le_bytes(bytes) as u64; + if freq > max_freq { + max_freq = freq; + } + } + } + } + } + } + + let freq_str = if max_freq > 0 { + let ghz = if max_freq > 100_000_000 { + max_freq as f64 / 1_000_000_000.0 + } else { + max_freq as f64 / 1_000_000.0 + }; + format!(" @ {:.2} GHz", ghz) + } else { + "".to_string() + }; + + let core_str = if !cores.is_empty() { + format!(" ({})", cores) + } else { + "".to_string() + }; + + if model.is_empty() { + "Unknown GPU".to_string() + } else { + format!("{}{} {} {}", model, core_str, freq_str, type_str).replace(" ", " ") + } +} diff --git a/src/helpers/ip.rs b/src/helpers/ip.rs new file mode 100644 index 0000000..bbdaff8 --- /dev/null +++ b/src/helpers/ip.rs @@ -0,0 +1,55 @@ +use std::process::Command; + +pub fn get_ip_info() -> String { + let mut interface = "en0".to_string(); + + let route_output = Command::new("route") + .args(["get", "default"]) + .output(); + + if let Ok(output) = route_output { + let stdout = String::from_utf8_lossy(&output.stdout); + for line in stdout.lines() { + if line.trim().starts_with("interface:") { + if let Some(iface) = line.split_whitespace().nth(1) { + interface = iface.to_string(); + break; + } + } + } + } + + let ifconfig_output = Command::new("ifconfig") + .arg(&interface) + .output(); + + if let Ok(output) = ifconfig_output { + let stdout = String::from_utf8_lossy(&output.stdout); + for line in stdout.lines() { + if line.contains("inet ") && !line.contains("127.0.0.1") { + let parts: Vec<&str> = line.split_whitespace().collect(); + if parts.len() >= 2 { + let ip = parts[1]; + let mut ip_with_cidr = ip.to_string(); + + if let Some(netmask_idx) = parts.iter().position(|&x| x == "netmask") { + if netmask_idx + 1 < parts.len() { + let netmask_hex = parts[netmask_idx + 1]; + if netmask_hex.starts_with("0x") { + if let Ok(num) = u32::from_str_radix(&netmask_hex[2..], 16) { + let cidr = num.count_ones(); + ip_with_cidr = format!("{}/{}", ip, cidr); + } + } + } + } + + return format!("Local IP ({}): {}", interface, ip_with_cidr); + } + } + } + } + + "".to_string() +} + diff --git a/src/helpers/locale.rs b/src/helpers/locale.rs new file mode 100644 index 0000000..5bb82f0 --- /dev/null +++ b/src/helpers/locale.rs @@ -0,0 +1,18 @@ +use std::env; + +pub fn get_locale_info() -> String { + if let Ok(locale) = env::var("LC_ALL") { + if !locale.is_empty() { + return format!("Locale: {}", locale); + } + } + + if let Ok(locale) = env::var("LANG") { + if !locale.is_empty() { + return format!("Locale: {}", locale); + } + } + + "".to_string() +} + diff --git a/src/helpers/memory.rs b/src/helpers/memory.rs new file mode 100644 index 0000000..a2e38ec --- /dev/null +++ b/src/helpers/memory.rs @@ -0,0 +1,79 @@ +// https://github.com/fastfetch-cli/fastfetch/blob/dev/src/detection/memory/memory_apple.c + +use libc::{ + HOST_VM_INFO64, HOST_VM_INFO64_COUNT, host_statistics64, mach_host_self, vm_statistics64_data_t, +}; +use std::mem; +use std::process::Command; + +pub fn get_memory_info() -> String { + let total_bytes = Command::new("sysctl") + .args(["-n", "hw.memsize"]) + .output() + .map(|o| { + String::from_utf8_lossy(&o.stdout) + .trim() + .parse::() + .unwrap_or(0) + }) + .unwrap_or(0); + + let usable_bytes = Command::new("sysctl") + .args(["-n", "hw.memsize_usable"]) + .output() + .map(|o| { + String::from_utf8_lossy(&o.stdout) + .trim() + .parse::() + .unwrap_or(total_bytes) + }) + .unwrap_or(total_bytes); + + let mut used_bytes: u64 = 0; + + // mach_host_self, and HOST_VM_INFO64 are macos c functions (ffi), so we HAVE to use unsafe + // i needa learn a better way to do this tbh + // learned smth new lol + unsafe { + let mut count = HOST_VM_INFO64_COUNT; + let mut vmstat: vm_statistics64_data_t = mem::zeroed(); // this this is unsafe we have to manually zero it + + if host_statistics64( + mach_host_self(), + HOST_VM_INFO64, + &mut vmstat as *mut _ as *mut _, + &mut count, + ) == 0 + { + let page_size = Command::new("pagesize") + .output() + .map(|o| { + String::from_utf8_lossy(&o.stdout) + .trim() + .parse::() + .unwrap_or(4096) + }) + .unwrap_or(4096); + + let app_memory = (vmstat.internal_page_count as u64) * page_size; + let wired_memory = (vmstat.wire_count as u64) * page_size; + let compressed_memory = (vmstat.compressor_page_count as u64) * page_size; + let reserved_memory = total_bytes.saturating_sub(usable_bytes); + + used_bytes = app_memory + wired_memory + compressed_memory + reserved_memory; + } + } + + let total_gib = total_bytes as f64 / 1073741824.0; + let used_gib = used_bytes as f64 / 1073741824.0; + let percentage = if total_bytes > 0 { + (used_bytes as f64 / total_bytes as f64) * 100.0 + } else { + 0.0 + }; + + format!( + "{:.2} GiB / {:.2} GiB ({:.0}%)", + used_gib, total_gib, percentage + ) +} diff --git a/src/helpers/mod.rs b/src/helpers/mod.rs index 0d8bba4..69795f4 100644 --- a/src/helpers/mod.rs +++ b/src/helpers/mod.rs @@ -1,9 +1,17 @@ +pub mod battery; +pub mod cpu; pub mod cursor; pub mod desktop_env; pub mod display; pub mod font; +pub mod gpu; +pub mod ip; +pub mod locale; +pub mod memory; pub mod packages; pub mod shell; +pub mod storage; +pub mod swap; pub mod terminal; pub mod terminal_font; pub mod uptime; diff --git a/src/helpers/storage.rs b/src/helpers/storage.rs new file mode 100644 index 0000000..83841a0 --- /dev/null +++ b/src/helpers/storage.rs @@ -0,0 +1,100 @@ +// https://github.com/fastfetch-cli/fastfetch/blob/dev/src/detection/disk/disk.c + +use libc::{c_int, c_char}; +use std::ffi::CStr; + +#[repr(C)] +#[derive(Clone, Copy)] +struct Statfs { + f_bsize: u32, + f_iosize: c_int, + f_blocks: u64, + f_bfree: u64, + f_bavail: u64, + f_files: u64, + f_ffree: u64, + f_fsid: [u32; 2], + f_owner: u32, + f_type: u32, + f_flags: u32, + f_fssubtype: u32, + f_fstypename: [c_char; 16], + f_mntonname: [c_char; 1024], + f_mntfromname: [c_char; 1024], + f_reserved: [u32; 8], +} + +unsafe extern "C" { + fn getfsstat(buf: *mut Statfs, bufsize: c_int, flags: c_int) -> c_int; +} + +const MNT_WAIT: c_int = 1; +const MNT_NOWAIT: c_int = 2; +const MNT_RDONLY: u32 = 0x00000001; + +pub fn get_storage_info() -> String { + unsafe { + let size = getfsstat(std::ptr::null_mut(), 0, MNT_WAIT); + if size <= 0 { + return "".to_string(); + } + + let statfs_size = std::mem::size_of::(); + if statfs_size != 2168 { + return "".to_string(); + } + + let mut buf = vec![std::mem::zeroed::(); size as usize]; + let bufsize = (statfs_size * size as usize) as c_int; + + let result = getfsstat(buf.as_mut_ptr(), bufsize, MNT_NOWAIT); + if result <= 0 { + return "".to_string(); + } + + for fs in &buf { + let mountpoint = CStr::from_ptr(fs.f_mntonname.as_ptr() as *const c_char) + .to_string_lossy() + .to_string(); + + if mountpoint == "/" { + let filesystem = CStr::from_ptr(fs.f_fstypename.as_ptr() as *const c_char) + .to_string_lossy() + .to_string(); + + let block_size = fs.f_bsize as u64; + let total_bytes = fs.f_blocks * block_size; + let available_bytes = fs.f_bavail * block_size; + let used_bytes = total_bytes.saturating_sub(available_bytes); + + let total_gib = total_bytes as f64 / 1073741824.0; + let used_gib = used_bytes as f64 / 1073741824.0; + let percentage = if total_bytes > 0 { + (used_bytes as f64 / total_bytes as f64) * 100.0 + } else { + 0.0 + }; + + let read_only = (fs.f_flags & MNT_RDONLY) != 0; + + let mut result = format!( + "{:.2} GiB / {:.2} GiB ({:.0}%)", + used_gib, total_gib, percentage + ); + + if !filesystem.is_empty() { + result.push_str(&format!(" - {}", filesystem)); + } + + if read_only { + result.push_str(" [Read-only]"); + } + + return result; + } + } + } + + "".to_string() +} + diff --git a/src/helpers/swap.rs b/src/helpers/swap.rs new file mode 100644 index 0000000..b97031b --- /dev/null +++ b/src/helpers/swap.rs @@ -0,0 +1,41 @@ +// https://github.com/fastfetch-cli/fastfetch/blob/dev/src/detection/swap/swap_apple.c + +use std::process::Command; + +pub fn get_swap_info() -> String { + let output = Command::new("sysctl").args(["-n", "vm.swapusage"]).output(); + + if let Ok(out) = output { + let stdout = String::from_utf8_lossy(&out.stdout); + + let mut total_mb = 0.0; + let mut used_mb = 0.0; + + for part in stdout.split_whitespace() { + if let Some(val) = part.strip_suffix('M') { + if let Ok(num) = val.parse::() { + if stdout.contains(&format!("total = {}", part)) { + total_mb = num; + } else if stdout.contains(&format!("used = {}", part)) { + used_mb = num; + } + } + } + } + + let total_gib = total_mb / 1024.0; + let used_gib = used_mb / 1024.0; + let percentage = if total_mb > 0.0 { + (used_mb / total_mb) * 100.0 + } else { + 0.0 + }; + + return format!( + "{:.2} GiB / {:.2} GiB ({:.0}%)", + used_gib, total_gib, percentage + ); + } + + "0.00 GiB / 0.00 GiB (0%)".to_string() +} diff --git a/src/main.rs b/src/main.rs index 5f9c8f9..074a51d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -25,6 +25,14 @@ struct Stats { cursor: String, terminal: String, terminal_font: String, + cpu: String, + gpu: String, + memory: String, + swap: String, + storage: String, + ip: String, + battery: String, + locale: String, // Extra fields architecture: String, @@ -54,6 +62,14 @@ fn main() { cursor: helpers::cursor::get_cursor_info(), terminal: helpers::terminal::get_terminal_info(), terminal_font: helpers::terminal_font::get_terminal_font_info(), + cpu: helpers::cpu::get_cpu_info(), + gpu: helpers::gpu::get_gpu_info(), + memory: helpers::memory::get_memory_info(), + swap: helpers::swap::get_swap_info(), + storage: helpers::storage::get_storage_info(), + ip: helpers::ip::get_ip_info(), + battery: helpers::battery::get_battery_info(), + locale: helpers::locale::get_locale_info(), }; // TODO: Add ascii art support later @@ -76,5 +92,13 @@ fn main() { println!("{}", stats.cursor); println!("{}", stats.terminal); println!("{}", stats.terminal_font); + println!("{}", stats.cpu); + println!("{}", stats.gpu); + println!("{}", stats.memory); + println!("{}", stats.swap); + println!("{}", stats.storage); + println!("{}", stats.ip); + println!("{}", stats.battery); + println!("{}", stats.locale); } }