mod iokit_ffi;
use std::ffi::{c_void, CString};
use std::sync::OnceLock;
pub fn load_avg() -> Option<f64> {
let mut loads: [f64; 3] = [0.0; 3];
let got = unsafe { libc::getloadavg(loads.as_mut_ptr(), 3) };
if got <= 0 {
None
} else {
Some(loads[0])
}
}
pub fn cpu_percent() -> Option<f32> {
None
}
#[repr(C)]
struct XswUsage {
xsu_total: u64,
xsu_avail: u64,
xsu_used: u64,
xsu_pagesize: u32,
xsu_encrypted: i32,
}
pub fn swap_info() -> Option<(u64, u64)> {
let name = CString::new("vm.swapusage").ok()?;
let mut usage = XswUsage { xsu_total: 0, xsu_avail: 0, xsu_used: 0, xsu_pagesize: 0, xsu_encrypted: 0 };
let mut size = std::mem::size_of::<XswUsage>();
let rc = unsafe {
libc::sysctlbyname(
name.as_ptr(),
&mut usage as *mut XswUsage as *mut c_void,
&mut size,
std::ptr::null_mut(),
0,
)
};
if rc != 0 {
return None;
}
const MB: u64 = 1024 * 1024;
Some((usage.xsu_used / MB, usage.xsu_total / MB))
}
pub fn local_time_string() -> Option<String> {
let now = unsafe { libc::time(std::ptr::null_mut()) };
let mut tm: libc::tm = unsafe { std::mem::zeroed() };
let rc = unsafe { libc::localtime_r(&now, &mut tm) };
if rc.is_null() {
return None;
}
let offset_mins = tm.tm_gmtoff / 60;
let sign = if offset_mins >= 0 { '+' } else { '-' };
Some(format!(
"{:02}:{:02}:{:02} (UTC{sign}{:02}:{:02})",
tm.tm_hour,
tm.tm_min,
tm.tm_sec,
(offset_mins.abs()) / 60,
(offset_mins.abs()) % 60,
))
}
use iokit_ffi::*;
fn cf_string(s: &str) -> Option<CFStringRef> {
let c = CString::new(s).ok()?;
let cf = unsafe { CFStringCreateWithCString(kCFAllocatorDefault, c.as_ptr(), K_CF_STRING_ENCODING_UTF8) };
if cf.is_null() { None } else { Some(cf) }
}
fn dict_get_i32(dict: CFDictionaryRef, key: &str) -> Option<i32> {
let cf_key = cf_string(key)?;
let value = unsafe { CFDictionaryGetValue(dict, cf_key) };
unsafe { CFRelease(cf_key) };
if value.is_null() {
return None;
}
let mut out: i32 = 0;
let ok = unsafe { CFNumberGetValue(value, K_CF_NUMBER_SINT32_TYPE, &mut out as *mut i32 as *mut c_void) };
if ok != 0 { Some(out) } else { None }
}
fn dict_get_bool(dict: CFDictionaryRef, key: &str) -> Option<bool> {
let cf_key = cf_string(key)?;
let value = unsafe { CFDictionaryGetValue(dict, cf_key) };
unsafe { CFRelease(cf_key) };
if value.is_null() {
return None;
}
Some(unsafe { CFBooleanGetValue(value) } != 0)
}
pub fn battery() -> Option<(u8, bool)> {
let blob = unsafe { IOPSCopyPowerSourcesInfo() };
if blob.is_null() {
return None;
}
let list = unsafe { IOPSCopyPowerSourcesList(blob) };
if list.is_null() {
unsafe { CFRelease(blob) };
return None;
}
let count = unsafe { CFArrayGetCount(list) };
let mut result = None;
for i in 0..count {
let ps = unsafe { CFArrayGetValueAtIndex(list, i) };
if ps.is_null() {
continue;
}
let desc = unsafe { IOPSGetPowerSourceDescription(blob, ps) };
if desc.is_null() {
continue;
}
let current = dict_get_i32(desc, "Current Capacity");
let max = dict_get_i32(desc, "Max Capacity");
if let (Some(current), Some(max)) = (current, max)
&& max > 0
{
let pct = ((current as f64 / max as f64) * 100.0).round().clamp(0.0, 100.0) as u8;
let charging = dict_get_bool(desc, "Is Charging").unwrap_or(false);
result = Some((pct, charging));
break;
}
}
unsafe {
CFRelease(list);
CFRelease(blob);
}
result
}
pub fn gpu_name() -> Option<String> {
static CACHE: OnceLock<Option<String>> = OnceLock::new();
CACHE
.get_or_init(|| {
std::process::Command::new("system_profiler")
.args(["SPDisplaysDataType", "-json"])
.output()
.ok()
.filter(|out| out.status.success())
.and_then(|out| parse_gpu_name_from_system_profiler(&String::from_utf8_lossy(&out.stdout)))
})
.clone()
}
fn parse_gpu_name_from_system_profiler(json: &str) -> Option<String> {
for key in ["\"sppci_model\"", "\"_name\""] {
if let Some(pos) = json.find(key) {
let after_key = &json[pos + key.len()..];
let colon = after_key.find(':')?;
let rest = after_key[colon + 1..].trim_start();
if let Some(rest) = rest.strip_prefix('"') {
let end = rest.find('"')?;
let name = rest[..end].trim();
if !name.is_empty() {
return Some(name.to_string());
}
}
}
}
None
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parses_gpu_model_from_system_profiler_json() {
let json = r#"{"SPDisplaysDataType":[{"sppci_model":"Apple M4 Pro","_name":"Apple M4 Pro"}]}"#;
assert_eq!(parse_gpu_name_from_system_profiler(json).as_deref(), Some("Apple M4 Pro"));
}
#[test]
fn missing_fields_yield_none() {
assert_eq!(parse_gpu_name_from_system_profiler("{}"), None);
}
}