// 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 { unsafe { get_key_size(connection, key) .ok() .map(|(input_structure, val)| ComponentFFI { input_structure, val, connection, }) } } fn temperature(&self) -> Option { 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, connection: Option, } 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, label: String, ffi_part: ComponentFFI, } impl Component { /// Creates a new `Component` with the given information. pub(crate) fn new( label: String, max: Option, critical: Option, key: &[i8], connection: ffi::io_connect_t, ) -> Option { 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 { 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::IOConnectCallStructMethod( conn, index as u32, input_structure, mem::size_of::(), 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::(); let mut output_structure: ffi::KeyData_t = mem::zeroed::(); let mut val: ffi::Val_t = mem::zeroed::(); 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 { let mut output_structure: ffi::KeyData_t = mem::zeroed::(); 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 { 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 { 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 { 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 { 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); } } }