| // Take a look at the license at the top of the repository in the LICENSE file. |
| |
| use crate::sys::{ffi, macos::utils::IOReleaser}; |
| use crate::ComponentExt; |
| |
| use libc::{c_char, c_int, c_void}; |
| |
| use std::mem; |
| |
| const COMPONENTS_TEMPERATURE_IDS: &[(&str, &[i8])] = &[ |
| ("PECI CPU", &['T' as i8, 'C' as i8, 'X' as i8, 'C' as i8]), // PECI CPU "TCXC" |
| ("PECI CPU", &['T' as i8, 'C' as i8, 'X' as i8, 'c' as i8]), // PECI CPU "TCXc" |
| ( |
| "CPU Proximity", |
| &['T' as i8, 'C' as i8, '0' as i8, 'P' as i8], |
| ), // CPU Proximity (heat spreader) "TC0P" |
| ("GPU", &['T' as i8, 'G' as i8, '0' as i8, 'P' as i8]), // GPU "TG0P" |
| ("Battery", &['T' as i8, 'B' as i8, '0' as i8, 'T' as i8]), // Battery "TB0T" |
| ]; |
| |
| pub(crate) struct ComponentFFI { |
| input_structure: ffi::KeyData_t, |
| val: ffi::Val_t, |
| /// It is the `System::connection`. We need it to not require an extra argument |
| /// in `ComponentExt::refresh`. |
| connection: ffi::io_connect_t, |
| } |
| |
| impl ComponentFFI { |
| fn new(key: &[i8], connection: ffi::io_connect_t) -> Option<ComponentFFI> { |
| unsafe { |
| get_key_size(connection, key) |
| .ok() |
| .map(|(input_structure, val)| ComponentFFI { |
| input_structure, |
| val, |
| connection, |
| }) |
| } |
| } |
| |
| fn temperature(&self) -> Option<f32> { |
| get_temperature_inner(self.connection, &self.input_structure, &self.val) |
| } |
| } |
| |
| /// Used to get CPU information, not supported on iOS, or inside the default macOS sandbox. |
| pub(crate) struct Components { |
| pub inner: Vec<Component>, |
| connection: Option<IoService>, |
| } |
| |
| impl Components { |
| pub(crate) fn new() -> Self { |
| Self { |
| inner: Vec::with_capacity(2), |
| connection: IoService::new_connection(), |
| } |
| } |
| |
| pub(crate) fn refresh(&mut self) { |
| if let Some(ref connection) = self.connection { |
| let connection = connection.inner(); |
| self.inner.clear(); |
| // getting CPU critical temperature |
| let critical_temp = |
| get_temperature(connection, &['T' as i8, 'C' as i8, '0' as i8, 'D' as i8, 0]); |
| |
| for (id, v) in COMPONENTS_TEMPERATURE_IDS.iter() { |
| if let Some(c) = |
| Component::new((*id).to_owned(), None, critical_temp, v, connection) |
| { |
| self.inner.push(c); |
| } |
| } |
| } |
| } |
| } |
| |
| #[doc = include_str!("../../../../md_doc/component.md")] |
| pub struct Component { |
| temperature: f32, |
| max: f32, |
| critical: Option<f32>, |
| label: String, |
| ffi_part: ComponentFFI, |
| } |
| |
| impl Component { |
| /// Creates a new `Component` with the given information. |
| pub(crate) fn new( |
| label: String, |
| max: Option<f32>, |
| critical: Option<f32>, |
| key: &[i8], |
| connection: ffi::io_connect_t, |
| ) -> Option<Component> { |
| let ffi_part = ComponentFFI::new(key, connection)?; |
| ffi_part.temperature().map(|temperature| Component { |
| temperature, |
| label, |
| max: max.unwrap_or(temperature), |
| critical, |
| ffi_part, |
| }) |
| } |
| } |
| |
| impl ComponentExt for Component { |
| fn temperature(&self) -> f32 { |
| self.temperature |
| } |
| |
| fn max(&self) -> f32 { |
| self.max |
| } |
| |
| fn critical(&self) -> Option<f32> { |
| self.critical |
| } |
| |
| fn label(&self) -> &str { |
| &self.label |
| } |
| |
| fn refresh(&mut self) { |
| if let Some(temp) = self.ffi_part.temperature() { |
| self.temperature = temp; |
| if self.temperature > self.max { |
| self.max = self.temperature; |
| } |
| } |
| } |
| } |
| |
| unsafe fn perform_call( |
| conn: ffi::io_connect_t, |
| index: c_int, |
| input_structure: *const ffi::KeyData_t, |
| output_structure: *mut ffi::KeyData_t, |
| ) -> i32 { |
| let mut structure_output_size = mem::size_of::<ffi::KeyData_t>(); |
| |
| ffi::IOConnectCallStructMethod( |
| conn, |
| index as u32, |
| input_structure, |
| mem::size_of::<ffi::KeyData_t>(), |
| output_structure, |
| &mut structure_output_size, |
| ) |
| } |
| |
| // Adapted from https://github.com/lavoiesl/osx-cpu-temp/blob/master/smc.c#L28 |
| #[inline] |
| fn strtoul(s: &[i8]) -> u32 { |
| unsafe { |
| ((*s.get_unchecked(0) as u32) << (3u32 << 3)) |
| + ((*s.get_unchecked(1) as u32) << (2u32 << 3)) |
| + ((*s.get_unchecked(2) as u32) << (1u32 << 3)) |
| + (*s.get_unchecked(3) as u32) |
| } |
| } |
| |
| #[inline] |
| unsafe fn ultostr(s: *mut c_char, val: u32) { |
| *s.offset(0) = ((val >> 24) % 128) as i8; |
| *s.offset(1) = ((val >> 16) % 128) as i8; |
| *s.offset(2) = ((val >> 8) % 128) as i8; |
| *s.offset(3) = (val % 128) as i8; |
| *s.offset(4) = 0; |
| } |
| |
| unsafe fn get_key_size( |
| con: ffi::io_connect_t, |
| key: &[i8], |
| ) -> Result<(ffi::KeyData_t, ffi::Val_t), i32> { |
| let mut input_structure: ffi::KeyData_t = mem::zeroed::<ffi::KeyData_t>(); |
| let mut output_structure: ffi::KeyData_t = mem::zeroed::<ffi::KeyData_t>(); |
| let mut val: ffi::Val_t = mem::zeroed::<ffi::Val_t>(); |
| |
| input_structure.key = strtoul(key); |
| input_structure.data8 = ffi::SMC_CMD_READ_KEYINFO; |
| |
| let result = perform_call( |
| con, |
| ffi::KERNEL_INDEX_SMC, |
| &input_structure, |
| &mut output_structure, |
| ); |
| if result != ffi::KIO_RETURN_SUCCESS { |
| return Err(result); |
| } |
| |
| val.data_size = output_structure.key_info.data_size; |
| ultostr( |
| val.data_type.as_mut_ptr(), |
| output_structure.key_info.data_type, |
| ); |
| input_structure.key_info.data_size = val.data_size; |
| input_structure.data8 = ffi::SMC_CMD_READ_BYTES; |
| Ok((input_structure, val)) |
| } |
| |
| unsafe fn read_key( |
| con: ffi::io_connect_t, |
| input_structure: &ffi::KeyData_t, |
| mut val: ffi::Val_t, |
| ) -> Result<ffi::Val_t, i32> { |
| let mut output_structure: ffi::KeyData_t = mem::zeroed::<ffi::KeyData_t>(); |
| |
| match perform_call( |
| con, |
| ffi::KERNEL_INDEX_SMC, |
| input_structure, |
| &mut output_structure, |
| ) { |
| ffi::KIO_RETURN_SUCCESS => { |
| libc::memcpy( |
| val.bytes.as_mut_ptr() as *mut c_void, |
| output_structure.bytes.as_mut_ptr() as *mut c_void, |
| mem::size_of::<[u8; 32]>(), |
| ); |
| Ok(val) |
| } |
| result => Err(result), |
| } |
| } |
| |
| fn get_temperature_inner( |
| con: ffi::io_connect_t, |
| input_structure: &ffi::KeyData_t, |
| original_val: &ffi::Val_t, |
| ) -> Option<f32> { |
| unsafe { |
| if let Ok(val) = read_key(con, input_structure, (*original_val).clone()) { |
| if val.data_size > 0 |
| && libc::strcmp(val.data_type.as_ptr(), b"sp78\0".as_ptr() as *const i8) == 0 |
| { |
| // convert sp78 value to temperature |
| let x = (i32::from(val.bytes[0]) << 6) + (i32::from(val.bytes[1]) >> 2); |
| return Some(x as f32 / 64f32); |
| } |
| } |
| } |
| None |
| } |
| |
| fn get_temperature(con: ffi::io_connect_t, key: &[i8]) -> Option<f32> { |
| unsafe { |
| let (input_structure, val) = get_key_size(con, key).ok()?; |
| get_temperature_inner(con, &input_structure, &val) |
| } |
| } |
| |
| pub(crate) struct IoService(ffi::io_connect_t); |
| |
| impl IoService { |
| fn new(obj: ffi::io_connect_t) -> Option<Self> { |
| if obj == 0 { |
| None |
| } else { |
| Some(Self(obj)) |
| } |
| } |
| |
| pub(crate) fn inner(&self) -> ffi::io_connect_t { |
| self.0 |
| } |
| |
| // code from https://github.com/Chris911/iStats |
| // Not supported on iOS, or in the default macOS |
| pub(crate) fn new_connection() -> Option<Self> { |
| let mut iterator: ffi::io_iterator_t = 0; |
| |
| unsafe { |
| let matching_dictionary = ffi::IOServiceMatching(b"AppleSMC\0".as_ptr() as *const i8); |
| let result = ffi::IOServiceGetMatchingServices( |
| ffi::kIOMasterPortDefault, |
| matching_dictionary, |
| &mut iterator, |
| ); |
| if result != ffi::KIO_RETURN_SUCCESS { |
| sysinfo_debug!("Error: IOServiceGetMatchingServices() = {}", result); |
| return None; |
| } |
| let iterator = match IOReleaser::new(iterator) { |
| Some(i) => i, |
| None => { |
| sysinfo_debug!("Error: IOServiceGetMatchingServices() succeeded but returned invalid descriptor"); |
| return None; |
| } |
| }; |
| |
| let device = match IOReleaser::new(ffi::IOIteratorNext(iterator.inner())) { |
| Some(d) => d, |
| None => { |
| sysinfo_debug!("Error: no SMC found"); |
| return None; |
| } |
| }; |
| |
| let mut conn = 0; |
| let result = ffi::IOServiceOpen(device.inner(), libc::mach_task_self(), 0, &mut conn); |
| if result != ffi::KIO_RETURN_SUCCESS { |
| sysinfo_debug!("Error: IOServiceOpen() = {}", result); |
| return None; |
| } |
| let conn = IoService::new(conn); |
| if conn.is_none() { |
| sysinfo_debug!( |
| "Error: IOServiceOpen() succeeded but returned invalid descriptor..." |
| ); |
| } |
| conn |
| } |
| } |
| } |
| |
| impl Drop for IoService { |
| fn drop(&mut self) { |
| unsafe { |
| ffi::IOServiceClose(self.0); |
| } |
| } |
| } |