use core_foundation::array::{CFArray, CFArrayRef};
use core_foundation::base::{
kCFAllocatorDefault, CFAllocatorRef, CFGetTypeID, CFRelease, CFTypeRef, TCFType,
};
use core_foundation::dictionary::{CFDictionary, CFDictionaryRef};
use core_foundation::number::CFNumber;
use core_foundation::string::{CFString, CFStringRef};
type IOHIDEventSystemClientRef = *mut libc::c_void;
type IOHIDServiceClientRef = *mut libc::c_void;
type IOHIDEventRef = *mut libc::c_void;
const K_HID_PAGE_APPLE_VENDOR: i64 = 0xff00;
const K_HID_USAGE_TEMPERATURE_SENSOR: i64 = 5;
const K_IOHID_EVENT_TYPE_TEMPERATURE: i64 = 15;
#[link(name = "IOKit", kind = "framework")]
extern "C" {
fn IOHIDEventSystemClientCreate(allocator: CFAllocatorRef) -> IOHIDEventSystemClientRef;
fn IOHIDEventSystemClientSetMatching(
client: IOHIDEventSystemClientRef,
matching: CFDictionaryRef,
);
fn IOHIDEventSystemClientCopyServices(client: IOHIDEventSystemClientRef) -> CFArrayRef;
fn IOHIDServiceClientCopyProperty(
service: IOHIDServiceClientRef,
key: CFStringRef,
) -> CFTypeRef;
fn IOHIDServiceClientCopyEvent(
service: IOHIDServiceClientRef,
event_type: i64,
options: i32,
timestamp: i64,
) -> IOHIDEventRef;
fn IOHIDEventGetFloatValue(event: IOHIDEventRef, field: i32) -> f64;
}
pub struct TempSensor {
client: IOHIDEventSystemClientRef,
}
unsafe impl Send for TempSensor {}
impl TempSensor {
pub fn new() -> Option<Self> {
unsafe {
let client = IOHIDEventSystemClientCreate(kCFAllocatorDefault);
if client.is_null() {
return None;
}
let matching = CFDictionary::from_CFType_pairs(&[
(
CFString::new("PrimaryUsagePage").as_CFType(),
CFNumber::from(K_HID_PAGE_APPLE_VENDOR).as_CFType(),
),
(
CFString::new("PrimaryUsage").as_CFType(),
CFNumber::from(K_HID_USAGE_TEMPERATURE_SENSOR).as_CFType(),
),
]);
IOHIDEventSystemClientSetMatching(client, matching.as_concrete_TypeRef() as _);
Some(Self { client })
}
}
pub fn list(&self) -> Vec<(String, f32)> {
let mut out = Vec::new();
unsafe {
let services = IOHIDEventSystemClientCopyServices(self.client);
if services.is_null() {
return out;
}
let arr: CFArray = CFArray::wrap_under_create_rule(services);
let product_key = CFString::new("Product");
let field = (K_IOHID_EVENT_TYPE_TEMPERATURE << 16) as i32;
for i in 0..arr.len() {
let Some(svc) = arr.get(i) else { continue };
let svc = *svc as IOHIDServiceClientRef;
let name_ref =
IOHIDServiceClientCopyProperty(svc, product_key.as_concrete_TypeRef());
if name_ref.is_null() {
continue;
}
if CFGetTypeID(name_ref) != CFString::type_id() {
CFRelease(name_ref);
continue;
}
let name = CFString::wrap_under_create_rule(name_ref as _).to_string();
let event = IOHIDServiceClientCopyEvent(svc, K_IOHID_EVENT_TYPE_TEMPERATURE, 0, 0);
if event.is_null() {
continue;
}
let value = IOHIDEventGetFloatValue(event, field) as f32;
CFRelease(event as CFTypeRef);
if value > -40.0 && value < 150.0 {
out.push((name, value));
}
}
}
out
}
pub fn read(&self) -> Option<f32> {
let all = self.list();
let pick = |pred: &dyn Fn(&str) -> bool| -> Vec<f32> {
all.iter()
.filter(|(n, _)| pred(&n.to_lowercase()))
.map(|&(_, v)| v)
.collect()
};
let mut vals = pick(&|n| n.contains("tdie"));
if vals.is_empty() {
vals = pick(&|n| n.contains("cpu"));
}
if vals.is_empty() {
return None;
}
Some(vals.iter().sum::<f32>() / vals.len() as f32)
}
}
#[cfg(test)]
mod tests {
#[test]
fn cpu_temp_plausible() {
crate::mac::needs_real_hardware!();
let t = super::TempSensor::new().expect("HID client");
let c = t.read().expect("at least one CPU temp sensor");
assert!((10.0..120.0).contains(&c), "got {c}");
}
}