use objc2::encode::{Encode, Encoding, RefEncode};
use objc2::ffi::NSInteger;
use objc2::runtime::AnyObject;
use objc2::{class, msg_send};
use flexaudio_core::types::{Error, Result};
const MIN_MAJOR: i64 = 14;
const MIN_MINOR: i64 = 4;
#[repr(C)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
struct NSOperatingSystemVersion {
major: NSInteger,
minor: NSInteger,
patch: NSInteger,
}
unsafe impl Encode for NSOperatingSystemVersion {
const ENCODING: Encoding = Encoding::Struct(
"?",
&[
<NSInteger>::ENCODING,
<NSInteger>::ENCODING,
<NSInteger>::ENCODING,
],
);
}
unsafe impl RefEncode for NSOperatingSystemVersion {
const ENCODING_REF: Encoding = Encoding::Pointer(&Self::ENCODING);
}
fn meets_min_version(major: i64, minor: i64) -> bool {
major > MIN_MAJOR || (major == MIN_MAJOR && minor >= MIN_MINOR)
}
fn current_os_version() -> (i64, i64) {
unsafe {
let cls = class!(NSProcessInfo);
let process_info: *mut AnyObject = msg_send![cls, processInfo];
let version: NSOperatingSystemVersion = msg_send![process_info, operatingSystemVersion];
(version.major as i64, version.minor as i64)
}
}
pub(crate) fn ensure_process_tap_supported() -> Result<()> {
let (major, minor) = current_os_version();
if meets_min_version(major, minor) {
Ok(())
} else {
Err(Error::UnsupportedOsVersion)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn version_14_3_is_unsupported() {
assert!(!meets_min_version(14, 3));
assert!(!meets_min_version(14, 0));
assert!(!meets_min_version(13, 9));
}
#[test]
fn version_14_4_is_supported() {
assert!(meets_min_version(14, 4));
}
#[test]
fn newer_versions_are_supported() {
assert!(meets_min_version(14, 5));
assert!(meets_min_version(15, 0));
assert!(meets_min_version(26, 6));
}
#[test]
fn higher_major_with_low_minor_is_supported() {
assert!(meets_min_version(15, 0));
assert!(meets_min_version(99, 0));
}
#[test]
fn current_os_version_is_sane() {
let (major, minor) = current_os_version();
assert!(major >= 10, "unexpected macOS major version: {major}");
assert!(minor >= 0, "unexpected macOS minor version: {minor}");
}
}