use super::types::DetectionConfidence;
#[cfg(any(target_os = "macos", target_os = "ios"))]
use objc2_core_graphics as _;
#[derive(Debug, Clone)]
pub struct AppleDeviceInfo {
pub device_model: Option<String>,
pub has_neural_engine: bool,
pub confidence: DetectionConfidence,
}
pub fn detect_metal_with_confidence() -> (bool, DetectionConfidence) {
#[cfg(any(target_os = "macos", target_os = "ios"))]
{
let device = objc2_metal::MTLCreateSystemDefaultDevice();
(device.is_some(), DetectionConfidence::High)
}
#[cfg(not(any(target_os = "macos", target_os = "ios")))]
{
(false, DetectionConfidence::Unknown)
}
}
pub fn detect_metal_availability() -> bool {
detect_metal_with_confidence().0
}
pub fn detect_apple_device() -> AppleDeviceInfo {
let env_model = std::env::var("DEVICE_MODEL")
.or_else(|_| std::env::var("SIMULATOR_MODEL_IDENTIFIER"))
.or_else(|_| std::env::var("APPLE_DEVICE_MODEL"))
.ok();
if let Some(ref model_str) = env_model {
let has_ne = has_neural_engine_by_model(model_str);
return AppleDeviceInfo {
device_model: Some(model_str.clone()),
has_neural_engine: has_ne,
confidence: DetectionConfidence::Medium,
};
}
#[cfg(any(target_os = "macos", target_os = "ios"))]
if let Some(model) = read_hw_machine() {
let has_ne = has_neural_engine_by_model(&model);
return AppleDeviceInfo {
device_model: Some(model),
has_neural_engine: has_ne,
confidence: DetectionConfidence::High,
};
}
#[cfg(all(target_arch = "aarch64", target_os = "macos"))]
{
AppleDeviceInfo {
device_model: None,
has_neural_engine: true,
confidence: DetectionConfidence::High,
}
}
#[cfg(all(target_arch = "aarch64", target_os = "ios"))]
{
AppleDeviceInfo {
device_model: None,
has_neural_engine: false,
confidence: DetectionConfidence::Unknown,
}
}
#[cfg(not(target_arch = "aarch64"))]
{
AppleDeviceInfo {
device_model: None,
has_neural_engine: false,
confidence: DetectionConfidence::High,
}
}
#[cfg(all(
target_arch = "aarch64",
not(any(target_os = "macos", target_os = "ios"))
))]
{
AppleDeviceInfo {
device_model: None,
has_neural_engine: false,
confidence: DetectionConfidence::Unknown,
}
}
}
#[cfg(any(target_os = "macos", target_os = "ios"))]
fn read_hw_machine() -> Option<String> {
use std::ffi::CStr;
#[cfg(target_os = "macos")]
let key: &CStr = c"hw.model";
#[cfg(target_os = "ios")]
let key: &CStr = c"hw.machine";
let mut size: libc::size_t = 0;
let ret = unsafe {
libc::sysctlbyname(
key.as_ptr(),
std::ptr::null_mut(),
&mut size,
std::ptr::null_mut(),
0,
)
};
if ret != 0 || size == 0 {
return None;
}
let mut buf = vec![0u8; size];
let ret = unsafe {
libc::sysctlbyname(
key.as_ptr(),
buf.as_mut_ptr() as *mut _,
&mut size,
std::ptr::null_mut(),
0,
)
};
if ret != 0 {
return None;
}
CStr::from_bytes_until_nul(&buf)
.ok()?
.to_str()
.ok()
.map(String::from)
}
pub fn detect_neural_engine_with_confidence() -> (bool, DetectionConfidence) {
#[cfg(any(target_os = "macos", target_os = "ios"))]
{
use objc2::rc::Retained;
use objc2::runtime::{AnyClass, NSObjectProtocol, ProtocolObject};
use objc2_core_ml::MLComputeDeviceProtocol;
use objc2_foundation::NSArray;
let Some(ne_class) = AnyClass::get(c"MLNeuralEngineComputeDevice") else {
let info = detect_apple_device();
return (info.has_neural_engine, info.confidence);
};
type AllComputeDevicesFn = unsafe extern "C-unwind" fn() -> *mut NSArray<
ProtocolObject<dyn MLComputeDeviceProtocol>,
>;
let raw = unsafe {
libc::dlsym(
libc::RTLD_DEFAULT,
c"MLAllComputeDevices".as_ptr() as *const _,
)
};
if raw.is_null() {
let info = detect_apple_device();
return (info.has_neural_engine, info.confidence);
}
let all_compute_devices: AllComputeDevicesFn = unsafe { std::mem::transmute(raw) };
let raw_array = unsafe { all_compute_devices() };
let Some(devices) = (unsafe { Retained::retain_autoreleased(raw_array) }) else {
let info = detect_apple_device();
return (info.has_neural_engine, info.confidence);
};
let has_ne = devices.iter().any(|device| device.isKindOfClass(ne_class));
(has_ne, DetectionConfidence::High)
}
#[cfg(not(any(target_os = "macos", target_os = "ios")))]
{
(false, DetectionConfidence::Unknown)
}
}
pub fn has_neural_engine_by_model(model: &str) -> bool {
let model_lower = model.to_lowercase();
if model_lower.starts_with("iphone") {
if let Some(version_str) = model
.strip_prefix("iPhone")
.or_else(|| model.strip_prefix("iphone"))
{
if let Some((major_str, _)) = version_str.split_once(',') {
if let Ok(major) = major_str.parse::<u32>() {
return major >= 10;
}
}
}
return true;
}
if model_lower.starts_with("ipad") {
if let Some(version_str) = model
.strip_prefix("iPad")
.or_else(|| model.strip_prefix("ipad"))
{
if let Some((major_str, _)) = version_str.split_once(',') {
if let Ok(major) = major_str.parse::<u32>() {
return major >= 8;
}
}
}
return false;
}
if model_lower.contains("mac") {
let apple_silicon_patterns = [
"macbookpro17",
"macbookpro18",
"macbookpro19",
"macbookpro20",
"macbookair10",
"macbookair11",
"macbookair12",
"macmini9",
"macmini10",
"imac21",
"imac22",
"imac23",
"imac24",
"mac13",
"mac14",
"mac15", ];
for pattern in &apple_silicon_patterns {
if model_lower.contains(pattern) {
return true;
}
}
#[cfg(target_arch = "aarch64")]
return true; #[cfg(not(target_arch = "aarch64"))]
return false; }
if model_lower.starts_with("appletv") {
return false;
}
if model_lower.starts_with("watch") {
return false;
}
false
}
pub fn detect_coreml_availability() -> bool {
detect_neural_engine_with_confidence().0
}
#[cfg(test)]
mod tests {
use super::*;
use crate::device::types::DetectionConfidence;
#[cfg(any(target_os = "macos", target_os = "ios"))]
#[test]
fn test_metal_probe_returns_high_on_apple() {
let (_present, confidence) = detect_metal_with_confidence();
assert_eq!(confidence, DetectionConfidence::High);
}
#[cfg(not(any(target_os = "macos", target_os = "ios")))]
#[test]
fn test_metal_probe_returns_unknown_off_apple() {
let (present, confidence) = detect_metal_with_confidence();
assert!(!present);
assert_eq!(confidence, DetectionConfidence::Unknown);
}
#[cfg(any(target_os = "macos", target_os = "ios"))]
#[test]
fn test_neural_engine_probe_uses_runtime_class_lookup() {
use objc2::runtime::AnyClass;
let class_present = AnyClass::get(c"MLNeuralEngineComputeDevice").is_some();
let (_has_ne, confidence) = detect_neural_engine_with_confidence();
if class_present {
assert_eq!(
confidence,
DetectionConfidence::High,
"Class present → real Core ML probe ran → High confidence",
);
} else {
let fallback = detect_apple_device();
assert_eq!(
confidence, fallback.confidence,
"Class absent → fallback confidence should be preserved",
);
}
}
}