use objc2_core_foundation::{
CFArray, CFDictionary, CFNumber, CFRetained, CFString, kCFAllocatorDefault,
};
use objc2_io_kit::{IOHIDEventSystemClient, IOHIDServiceClient};
use crate::Component;
use crate::sys::inner::ffi::{
HID_DEVICE_PROPERTY_PRIMARY_USAGE, HID_DEVICE_PROPERTY_PRIMARY_USAGE_PAGE,
HID_DEVICE_PROPERTY_PRODUCT, IOHIDEventFieldBase, IOHIDEventGetFloatValue,
IOHIDEventSystemClientCreate, IOHIDEventSystemClientSetMatching, IOHIDServiceClientCopyEvent,
kHIDPage_AppleVendor, kHIDUsage_AppleVendor_TemperatureSensor, kIOHIDEventTypeTemperature,
kIOHIDSerialNumberKey,
};
pub(crate) struct ComponentsInner {
pub(crate) components: Vec<Component>,
client: Option<CFRetained<IOHIDEventSystemClient>>,
}
unsafe impl Send for ComponentsInner {}
unsafe impl Sync for ComponentsInner {}
impl ComponentsInner {
pub(crate) fn new() -> Self {
Self {
components: vec![],
client: None,
}
}
pub(crate) fn from_vec(components: Vec<Component>) -> Self {
Self {
components,
client: None,
}
}
pub(crate) fn into_vec(self) -> Vec<Component> {
self.components
}
pub(crate) fn list(&self) -> &[Component] {
&self.components
}
pub(crate) fn list_mut(&mut self) -> &mut [Component] {
&mut self.components
}
#[allow(unreachable_code)]
pub(crate) fn refresh(&mut self) {
let keys = [
&*CFString::from_static_str(HID_DEVICE_PROPERTY_PRIMARY_USAGE_PAGE),
&*CFString::from_static_str(HID_DEVICE_PROPERTY_PRIMARY_USAGE),
];
let nums = [
&*CFNumber::new_i32(kHIDPage_AppleVendor),
&*CFNumber::new_i32(kHIDUsage_AppleVendor_TemperatureSensor),
];
let matches = CFDictionary::from_slices(&keys, &nums);
let matches = matches.as_opaque();
unsafe {
if self.client.is_none() {
let client = match IOHIDEventSystemClientCreate(kCFAllocatorDefault) {
Some(c) => CFRetained::from_raw(c),
None => return,
};
self.client = Some(client);
}
let Some(client) = self.client.as_ref() else {
return;
};
let _ = IOHIDEventSystemClientSetMatching(client, matches);
let services = match client.services() {
Some(s) => s,
None => return,
};
let services = CFRetained::cast_unchecked::<CFArray<IOHIDServiceClient>>(services);
let key = CFString::from_static_str(HID_DEVICE_PROPERTY_PRODUCT);
let serial_key = CFString::from_static_str(kIOHIDSerialNumberKey);
for service in services {
let Some(name) = service.property(&key) else {
continue;
};
let serial = service
.property(&serial_key)
.and_then(|value| value.downcast::<CFString>().ok())
.as_deref()
.map(CFString::to_string);
let Ok(name) = name.downcast::<CFString>() else {
continue;
};
let name_str = name.to_string();
if let Some(c) = self
.components
.iter_mut()
.find(|c| c.inner.label == name_str)
{
c.refresh();
c.inner.updated = true;
continue;
}
let mut component = ComponentInner::new(serial, name_str, None, None, service);
component.refresh();
self.components.push(Component { inner: component });
}
}
}
}
pub(crate) struct ComponentInner {
id: Option<String>,
service: CFRetained<IOHIDServiceClient>,
temperature: Option<f32>,
label: String,
max: f32,
critical: Option<f32>,
pub(crate) updated: bool,
}
unsafe impl Send for ComponentInner {}
unsafe impl Sync for ComponentInner {}
impl ComponentInner {
pub(crate) fn new(
id: Option<String>,
label: String,
max: Option<f32>,
critical: Option<f32>,
service: CFRetained<IOHIDServiceClient>,
) -> Self {
Self {
id,
service,
label,
max: max.unwrap_or(0.),
critical,
temperature: None,
updated: true,
}
}
pub(crate) fn temperature(&self) -> Option<f32> {
self.temperature
}
pub(crate) fn max(&self) -> Option<f32> {
Some(self.max)
}
pub(crate) fn critical(&self) -> Option<f32> {
self.critical
}
pub(crate) fn label(&self) -> &str {
&self.label
}
pub(crate) fn id(&self) -> Option<&str> {
self.id.as_deref()
}
pub(crate) fn refresh(&mut self) {
unsafe {
let Some(event) =
IOHIDServiceClientCopyEvent(&self.service, kIOHIDEventTypeTemperature, 0, 0)
else {
self.temperature = None;
return;
};
let event = CFRetained::from_raw(event);
let temperature =
IOHIDEventGetFloatValue(&event, IOHIDEventFieldBase(kIOHIDEventTypeTemperature))
as _;
self.temperature = Some(temperature);
if temperature > self.max {
self.max = temperature;
}
}
}
}