#[cfg(not(target_os = "openbsd"))]
pub use native::*;
#[cfg(target_os = "openbsd")]
pub use openbsd::*;
pub fn window_manager() -> Option<String> {
#[cfg(any(target_os = "macos", target_os = "windows"))]
{
None
}
#[cfg(not(any(target_os = "macos", target_os = "windows")))]
{
match (wm_name(), windowing_system()) {
(Some(name), Some(system)) => Some(format!("{name} ({system})")),
(Some(name), None) => Some(name),
(None, Some(system)) => Some(format!("({system})")),
(None, None) => None,
}
}
}
#[cfg(not(any(target_os = "macos", target_os = "windows")))]
fn windowing_system() -> Option<String> {
fn nonempty(key: &str) -> Option<String> {
std::env::var(key).ok().filter(|v| !v.is_empty())
}
if nonempty("WAYLAND_DISPLAY").is_some() {
return Some("Wayland".to_string());
}
if nonempty("DISPLAY").is_some() {
return Some("X11".to_string());
}
match nonempty("XDG_SESSION_TYPE").as_deref() {
Some("wayland") => Some("Wayland".to_string()),
Some("x11") => Some("X11".to_string()),
_ => None,
}
}
#[cfg(not(any(target_os = "macos", target_os = "windows")))]
fn wm_name() -> Option<String> {
std::env::var("XDG_CURRENT_DESKTOP")
.ok()
.and_then(|v| normalize_desktop_name(&v))
.or_else(|| {
std::env::var("DESKTOP_SESSION")
.ok()
.and_then(|v| normalize_desktop_name(&v))
})
.or_else(wm_from_process_tree)
}
#[cfg(not(any(target_os = "macos", target_os = "windows")))]
fn wm_from_process_tree() -> Option<String> {
use sysinfo::{Pid, ProcessRefreshKind, ProcessesToUpdate, System, UpdateKind};
let mut sys = System::new();
sys.refresh_processes_specifics(
ProcessesToUpdate::All,
true,
ProcessRefreshKind::nothing().with_exe(UpdateKind::OnlyIfNotSet),
);
let mut pid = Pid::from_u32(std::process::id());
let our_uid = sys.process(pid).and_then(|p| p.user_id().cloned());
for _ in 0..32 {
let Some(proc) = sys.process(pid) else { break };
if let Some(canonical) = match_compositor(&proc_basename(proc)) {
return Some(canonical.to_string());
}
let Some(parent) = proc.parent() else { break };
pid = parent;
}
let our_uid = our_uid?;
for proc in sys.processes().values() {
if proc.user_id() != Some(&our_uid) {
continue;
}
if let Some(canonical) = match_compositor(&proc_basename(proc)) {
return Some(canonical.to_string());
}
}
None
}
#[cfg(not(any(target_os = "macos", target_os = "windows")))]
fn proc_basename(proc: &sysinfo::Process) -> String {
if let Some(exe) = proc.exe()
&& let Some(file) = exe.file_name()
{
return file.to_string_lossy().into_owned();
}
proc.name().to_string_lossy().into_owned()
}
#[cfg(not(any(target_os = "macos", target_os = "windows")))]
fn match_compositor(raw: &str) -> Option<&'static str> {
let base = std::path::Path::new(raw)
.file_name()
.map(|n| n.to_string_lossy().into_owned())
.unwrap_or_else(|| raw.to_string());
let key = base.to_ascii_lowercase();
Some(match key.as_str() {
"sway" => "Sway",
"river" => "River",
"hyprland" => "Hyprland",
"labwc" => "Labwc",
"wayfire" => "Wayfire",
"niri" => "Niri",
"weston" => "Weston",
"cage" => "Cage",
"cosmic-comp" => "COSMIC",
"gnome-shell" | "mutter" => "GNOME",
"kwin_wayland" | "kwin_x11" | "kwin" => "KWin",
"plasmashell" => "Plasma",
"xfwm4" => "Xfwm",
"i3" => "i3",
"bspwm" => "bspwm",
"openbox" => "Openbox",
"awesome" => "awesome",
"fluxbox" => "Fluxbox",
"dwm" => "dwm",
"icewm" => "IceWM",
"jwm" => "JWM",
_ => return None,
})
}
#[cfg(not(any(target_os = "macos", target_os = "windows")))]
fn normalize_desktop_name(raw: &str) -> Option<String> {
let last = raw.rsplit(':').next().unwrap_or(raw).trim();
let name = last.strip_prefix("X-").unwrap_or(last).trim();
if name.is_empty() {
None
} else {
Some(name.to_string())
}
}
#[cfg(all(test, not(any(target_os = "macos", target_os = "windows"))))]
mod tests {
use super::{match_compositor, normalize_desktop_name};
#[test]
fn match_compositor_recognises_known_binaries() {
assert_eq!(match_compositor("river"), Some("River"));
assert_eq!(match_compositor("sway"), Some("Sway"));
assert_eq!(match_compositor("Hyprland"), Some("Hyprland"));
assert_eq!(match_compositor("/usr/local/bin/river"), Some("River"));
assert_eq!(match_compositor("bash"), None);
assert_eq!(match_compositor("alacritty"), None);
}
#[test]
fn normalize_desktop_name_picks_the_specific_entry() {
assert_eq!(normalize_desktop_name("River").as_deref(), Some("River"));
assert_eq!(
normalize_desktop_name("ubuntu:GNOME").as_deref(),
Some("GNOME")
);
assert_eq!(
normalize_desktop_name("pop:GNOME").as_deref(),
Some("GNOME")
);
assert_eq!(
normalize_desktop_name("X-Cinnamon").as_deref(),
Some("Cinnamon")
);
assert_eq!(normalize_desktop_name(""), None);
assert_eq!(normalize_desktop_name(" "), None);
assert_eq!(normalize_desktop_name(":"), None);
}
}
#[cfg(not(target_os = "openbsd"))]
mod native {
use sysinfo::{Product, System};
pub fn long_os_version() -> Option<String> {
#[cfg(target_os = "freebsd")]
{
let name = System::name()?;
let ver = System::os_version()?;
return Some(format!("{name} {ver}"));
}
#[cfg(not(target_os = "freebsd"))]
System::long_os_version()
}
pub fn os_version() -> Option<String> {
System::os_version()
}
pub fn host_name() -> Option<String> {
System::host_name()
}
pub fn kernel_long_version() -> String {
#[cfg(target_os = "freebsd")]
if let Some(v) = System::long_os_version() {
return v.trim().to_string();
}
System::kernel_long_version()
}
pub fn product_vendor_name() -> Option<String> {
Product::vendor_name()
}
pub fn product_name() -> Option<String> {
Product::name()
}
pub fn product_family() -> Option<String> {
Product::family()
}
#[cfg(windows)]
fn cpu_brand_from_registry() -> Option<String> {
use winreg::RegKey;
use winreg::enums::HKEY_LOCAL_MACHINE;
let hklm = RegKey::predef(HKEY_LOCAL_MACHINE);
let cpu_key = hklm
.open_subkey(r"HARDWARE\DESCRIPTION\System\CentralProcessor\0")
.ok()?;
let name: String = cpu_key.get_value("ProcessorNameString").ok()?;
let trimmed = name.trim();
if trimmed.is_empty() {
None
} else {
Some(trimmed.to_string())
}
}
#[cfg(target_os = "freebsd")]
fn sysctl(key: &str) -> Option<String> {
let out = std::process::Command::new("/sbin/sysctl")
.args(["-n", key])
.output()
.ok()?;
if !out.status.success() {
return None;
}
let s = String::from_utf8(out.stdout).ok()?;
let trimmed = s.trim();
if trimmed.is_empty() {
None
} else {
Some(trimmed.to_string())
}
}
pub fn cpu_brand(sys: &System) -> Option<String> {
#[cfg(windows)]
if let Some(brand) = cpu_brand_from_registry() {
return Some(brand);
}
#[cfg(target_os = "freebsd")]
if let Some(brand) = sysctl("hw.model") {
return Some(brand);
}
sys.cpus()
.first()
.map(|c| c.brand().to_string())
.filter(|s| !s.trim().is_empty())
}
pub fn cpu_frequency_mhz(sys: &System) -> Option<u64> {
#[cfg(target_os = "freebsd")]
{
let _ = sys;
None
}
#[cfg(not(target_os = "freebsd"))]
sys.cpus().first().map(|c| c.frequency())
}
pub fn total_memory_bytes(sys: &System) -> u64 {
sys.total_memory()
}
pub fn uptime_seconds() -> u64 {
System::uptime()
}
pub fn installed_package_count() -> Option<u32> {
#[cfg(target_os = "linux")]
{
dpkg_installed_count()
}
#[cfg(target_os = "freebsd")]
{
pkg_installed_count()
}
#[cfg(not(any(target_os = "linux", target_os = "freebsd")))]
{
None
}
}
#[cfg(target_os = "linux")]
fn dpkg_installed_count() -> Option<u32> {
let status = std::fs::read_to_string("/var/lib/dpkg/status").ok()?;
let n = status
.lines()
.filter(|line| {
line.strip_prefix("Status:")
.is_some_and(|s| s.trim() == "install ok installed")
})
.count();
Some(n as u32)
}
#[cfg(target_os = "freebsd")]
fn pkg_installed_count() -> Option<u32> {
let out = std::process::Command::new("/usr/sbin/pkg")
.args(["info", "-q"])
.stdin(std::process::Stdio::null())
.output()
.ok()?;
if !out.status.success() {
return None;
}
let s = std::str::from_utf8(&out.stdout).ok()?;
let n = s.lines().filter(|l| !l.trim().is_empty()).count();
Some(n as u32)
}
#[cfg(windows)]
fn physical_disk_total_bytes() -> u64 {
use std::ffi::OsStr;
use std::os::windows::ffi::OsStrExt;
use windows_sys::Win32::Foundation::{CloseHandle, INVALID_HANDLE_VALUE};
use windows_sys::Win32::Storage::FileSystem::{
CreateFileW, FILE_SHARE_READ, FILE_SHARE_WRITE, OPEN_EXISTING,
};
use windows_sys::Win32::System::IO::DeviceIoControl;
const IOCTL_DISK_GET_DRIVE_GEOMETRY_EX: u32 = 0x0007_00A0;
const IOCTL_STORAGE_QUERY_PROPERTY: u32 = 0x002D_1400;
const BUS_TYPE_USB: u32 = 0x07;
const MAX_DRIVES: u32 = 32;
let mut total: u64 = 0;
for n in 0..MAX_DRIVES {
let path = format!(r"\\.\PhysicalDrive{n}");
let wide: Vec<u16> = OsStr::new(&path)
.encode_wide()
.chain(std::iter::once(0))
.collect();
let handle = unsafe {
CreateFileW(
wide.as_ptr(),
0,
FILE_SHARE_READ | FILE_SHARE_WRITE,
std::ptr::null(),
OPEN_EXISTING,
0,
std::ptr::null_mut(),
)
};
if handle == INVALID_HANDLE_VALUE {
continue;
}
let mut returned: u32 = 0;
let query = [0u8; 12];
let mut desc = [0u8; 512];
let got_desc = unsafe {
DeviceIoControl(
handle,
IOCTL_STORAGE_QUERY_PROPERTY,
query.as_ptr() as *const _,
query.len() as u32,
desc.as_mut_ptr() as *mut _,
desc.len() as u32,
&mut returned,
std::ptr::null_mut(),
)
};
let skip = got_desc != 0 && returned >= 32 && {
let removable = desc[10] != 0;
let bus_type = u32::from_le_bytes([desc[28], desc[29], desc[30], desc[31]]);
removable || bus_type == BUS_TYPE_USB
};
if !skip {
let mut geo = [0u8; 64];
let got_geo = unsafe {
DeviceIoControl(
handle,
IOCTL_DISK_GET_DRIVE_GEOMETRY_EX,
std::ptr::null(),
0,
geo.as_mut_ptr() as *mut _,
geo.len() as u32,
&mut returned,
std::ptr::null_mut(),
)
};
if got_geo != 0 && returned >= 32 {
let size = i64::from_le_bytes([
geo[24], geo[25], geo[26], geo[27], geo[28], geo[29], geo[30], geo[31],
]);
if size > 0 {
total += size as u64;
}
}
}
unsafe { CloseHandle(handle) };
}
total
}
#[cfg(target_os = "linux")]
fn physical_disk_total_bytes() -> u64 {
let Ok(entries) = std::fs::read_dir("/sys/block") else {
return 0;
};
let mut total: u64 = 0;
for entry in entries.flatten() {
let name = entry.file_name();
let name = name.to_string_lossy();
if name.starts_with("loop")
|| name.starts_with("ram")
|| name.starts_with("zram")
|| name.starts_with("dm-")
|| name.starts_with("md")
|| name.starts_with("sr")
|| name.starts_with("fd")
{
continue;
}
let base = entry.path();
if std::fs::read_to_string(base.join("removable")).is_ok_and(|s| s.trim() == "1") {
continue;
}
if std::fs::canonicalize(&base).is_ok_and(|p| p.to_string_lossy().contains("/usb")) {
continue;
}
if let Some(sectors) = std::fs::read_to_string(base.join("size"))
.ok()
.and_then(|s| s.trim().parse::<u64>().ok())
{
total += sectors * 512;
}
}
total
}
#[cfg(target_os = "freebsd")]
fn physical_disk_total_bytes() -> u64 {
use std::collections::HashSet;
let usb_disks: HashSet<String> = std::fs::read_to_string("/var/run/dmesg.boot")
.map(|text| {
text.lines()
.filter_map(|line| {
let dev = line.split([' ', ':']).next()?;
if !dev.starts_with("da") || !line.contains(" at umass") {
return None;
}
Some(dev.to_string())
})
.collect()
})
.unwrap_or_default();
let Some(text) = sysctl("kern.geom.conftxt") else {
return 0;
};
let mut total: u64 = 0;
for line in text.lines() {
let mut parts = line.split_whitespace();
if parts.next() != Some("0") || parts.next() != Some("DISK") {
continue;
}
let Some(name) = parts.next() else { continue };
if name.starts_with("md")
|| name.starts_with("cd")
|| name.starts_with("acd")
|| name.starts_with("mmcsd")
|| usb_disks.contains(name)
{
continue;
}
if let Some(size) = parts.next().and_then(|s| s.parse::<u64>().ok()) {
total += size;
}
}
total
}
pub fn disk_totals() -> (u64, u64) {
use sysinfo::Disks;
let mut fs_total = 0u64;
let mut fs_avail = 0u64;
for disk in Disks::new_with_refreshed_list().list() {
fs_total += disk.total_space();
fs_avail += disk.available_space();
}
#[cfg(any(windows, target_os = "linux", target_os = "freebsd"))]
let total = {
let physical = physical_disk_total_bytes();
if physical > 0 { physical } else { fs_total }
};
#[cfg(not(any(windows, target_os = "linux", target_os = "freebsd")))]
let total = fs_total;
(total, fs_avail)
}
}
#[cfg(target_os = "openbsd")]
mod openbsd {
use std::process::Command;
fn run(cmd: &str, args: &[&str]) -> Option<String> {
let out = Command::new(cmd).args(args).output().ok()?;
if !out.status.success() {
return None;
}
let s = String::from_utf8(out.stdout).ok()?;
let trimmed = s.trim();
if trimmed.is_empty() {
None
} else {
Some(trimmed.to_string())
}
}
fn uname(flag: &str) -> Option<String> {
run("/usr/bin/uname", &[flag])
}
fn sysctl(key: &str) -> Option<String> {
run("/sbin/sysctl", &["-n", key])
}
pub fn long_os_version() -> Option<String> {
let sys = uname("-s")?;
let rel = uname("-r")?;
Some(format!("{sys} {rel}"))
}
pub fn os_version() -> Option<String> {
uname("-r")
}
pub fn host_name() -> Option<String> {
uname("-n")
}
pub fn kernel_long_version() -> String {
long_os_version().unwrap_or_else(|| "Unknown".to_string())
}
pub fn product_vendor_name() -> Option<String> {
sysctl("hw.vendor")
}
pub fn product_name() -> Option<String> {
sysctl("hw.product")
}
pub fn product_family() -> Option<String> {
sysctl("hw.version")
}
pub fn cpu_brand(_sys: &sysinfo::System) -> Option<String> {
sysctl("hw.model")
}
pub fn cpu_frequency_mhz(_sys: &sysinfo::System) -> Option<u64> {
sysctl("hw.cpuspeed")?.parse().ok()
}
pub fn total_memory_bytes(_sys: &sysinfo::System) -> u64 {
sysctl("hw.physmem")
.and_then(|s| s.parse().ok())
.unwrap_or(0)
}
pub fn installed_package_count() -> Option<u32> {
let entries = std::fs::read_dir("/var/db/pkg").ok()?;
let mut n: u32 = 0;
for entry in entries.flatten() {
if entry.file_name().to_string_lossy().starts_with('.') {
continue;
}
if entry.file_type().is_ok_and(|t| t.is_dir()) {
n += 1;
}
}
Some(n)
}
pub fn uptime_seconds() -> u64 {
let Some(boot) = sysctl("kern.boottime").and_then(|s| s.parse::<u64>().ok()) else {
return 0;
};
let Ok(now) = std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH) else {
return 0;
};
now.as_secs().saturating_sub(boot)
}
fn fs_disks() -> Vec<(u64, u64)> {
let Some(text) = run("/bin/df", &["-lkP"]) else {
return Vec::new();
};
text.lines()
.skip(1)
.filter_map(|line| {
let cols: Vec<&str> = line.split_whitespace().collect();
if cols.len() < 6 || !cols[0].starts_with("/dev/") {
return None;
}
let total: u64 = cols[1].parse().ok()?;
let avail: u64 = cols[3].parse().ok()?;
Some((total * 1024, avail * 1024))
})
.collect()
}
fn physical_disk_total_bytes() -> u64 {
use std::collections::{HashMap, HashSet};
let Ok(text) = std::fs::read_to_string("/var/run/dmesg.boot") else {
return 0;
};
let mut scsibus_parent: HashMap<String, String> = HashMap::new();
let mut disk_scsibus: HashMap<String, String> = HashMap::new();
let mut softraid: HashSet<String> = HashSet::new();
let mut sizes: HashMap<String, u64> = HashMap::new();
for line in text.lines() {
let dev = line.split([' ', ':']).next().unwrap_or("");
if dev.starts_with("scsibus")
&& let Some((_, after_at)) = line.split_once(" at ")
&& let Some(parent) = after_at.split([':', ' ']).next()
&& !parent.is_empty()
{
scsibus_parent.insert(dev.to_string(), parent.to_string());
}
if (dev.starts_with("sd") || dev.starts_with("wd"))
&& let Some((_, after_at)) = line.split_once(" at ")
{
if let Some(parent) = after_at.split([':', ' ']).next()
&& parent.starts_with("scsibus")
{
disk_scsibus.insert(dev.to_string(), parent.to_string());
}
if line.contains("<OPENBSD, SR ") {
softraid.insert(dev.to_string());
}
}
if (dev.starts_with("sd") || dev.starts_with("wd") || dev.starts_with("nvme"))
&& let Some((_, rest)) = line.split_once(": ")
&& rest.ends_with("sectors")
{
let parts: Vec<&str> = rest.split(", ").collect();
if parts.len() >= 3
&& let Some(bps) = parts[1].split_whitespace().next()
&& let Some(sec) = parts[2].split_whitespace().next()
&& let (Ok(bps), Ok(sec)) = (bps.parse::<u64>(), sec.parse::<u64>())
{
sizes.insert(dev.to_string(), bps * sec);
}
}
}
let is_usb = |dev: &str| -> bool {
disk_scsibus
.get(dev)
.and_then(|bus| scsibus_parent.get(bus))
.is_some_and(|parent| parent.starts_with("umass"))
};
sizes
.iter()
.filter(|(dev, _)| !softraid.contains(*dev) && !is_usb(dev))
.map(|(_, &size)| size)
.sum()
}
pub fn disk_totals() -> (u64, u64) {
let mut fs_total = 0u64;
let mut fs_avail = 0u64;
for (t, a) in fs_disks() {
fs_total += t;
fs_avail += a;
}
let physical = physical_disk_total_bytes();
let total = if physical > 0 { physical } else { fs_total };
(total, fs_avail)
}
}