use core::ffi::{CStr, c_char, c_void};
pub(super) type Id = *mut c_void;
pub(super) type Sel = *const c_void;
type ObjcBool = i8;
pub(super) type NSUInteger = usize;
#[link(name = "objc")]
unsafe extern "C" {
fn objc_getClass(name: *const c_char) -> Id;
fn sel_registerName(name: *const c_char) -> Sel;
fn objc_msgSend();
fn objc_autoreleasePoolPush() -> *mut c_void;
fn objc_autoreleasePoolPop(pool: *mut c_void);
}
#[link(name = "GameController", kind = "framework")]
unsafe extern "C" {
#[cfg(test)]
pub(super) static GCProductCategoryDualShock4: Id;
#[cfg(test)]
pub(super) static GCProductCategoryDualSense: Id;
#[cfg(test)]
pub(super) static GCProductCategoryXboxOne: Id;
#[cfg(test)]
pub(super) static GCProductCategoryMFi: Id;
}
pub(super) fn class(name: &CStr) -> Option<Id> {
let class = unsafe { objc_getClass(name.as_ptr()) };
(!class.is_null()).then_some(class)
}
pub(super) fn sel(name: &CStr) -> Sel {
unsafe { sel_registerName(name.as_ptr()) }
}
#[derive(Debug)]
pub(super) struct Pool(*mut c_void);
impl Pool {
#[must_use]
pub(super) fn push() -> Self {
Self(unsafe { objc_autoreleasePoolPush() })
}
}
impl Drop for Pool {
fn drop(&mut self) {
unsafe { objc_autoreleasePoolPop(self.0) };
}
}
#[inline]
#[must_use]
unsafe fn msg_send<F: Copy>() -> F {
assert!(
size_of::<F>() == size_of::<*const c_void>(),
"msg_send's type parameter must be a function pointer"
);
unsafe { core::mem::transmute_copy(&(objc_msgSend as *const c_void)) }
}
pub(super) unsafe fn msg(receiver: Id, selector: Sel) -> Id {
let send: unsafe extern "C" fn(Id, Sel) -> Id = unsafe { msg_send() };
unsafe { send(receiver, selector) }
}
pub(super) unsafe fn msg_void(receiver: Id, selector: Sel) {
let send: unsafe extern "C" fn(Id, Sel) = unsafe { msg_send() };
unsafe { send(receiver, selector) };
}
pub(super) unsafe fn msg_bool(receiver: Id, selector: Sel) -> bool {
let send: unsafe extern "C" fn(Id, Sel) -> ObjcBool = unsafe { msg_send() };
unsafe { send(receiver, selector) != 0 }
}
pub(super) unsafe fn msg_f32(receiver: Id, selector: Sel) -> f32 {
let send: unsafe extern "C" fn(Id, Sel) -> f32 = unsafe { msg_send() };
unsafe { send(receiver, selector) }
}
pub(super) unsafe fn msg_usize(receiver: Id, selector: Sel) -> NSUInteger {
let send: unsafe extern "C" fn(Id, Sel) -> NSUInteger = unsafe { msg_send() };
unsafe { send(receiver, selector) }
}
pub(super) unsafe fn object_at(array: Id, index: NSUInteger) -> Id {
let send: unsafe extern "C" fn(Id, Sel, NSUInteger) -> Id = unsafe { msg_send() };
unsafe { send(array, sel(c"objectAtIndex:"), index) }
}
pub(super) unsafe fn responds_to(receiver: Id, selector: Sel) -> bool {
let send: unsafe extern "C" fn(Id, Sel, Sel) -> ObjcBool = unsafe { msg_send() };
unsafe { send(receiver, sel(c"respondsToSelector:"), selector) != 0 }
}
#[cfg(test)]
pub(super) unsafe fn is_kind_of(object: Id, class: Id) -> bool {
let send: unsafe extern "C" fn(Id, Sel, Id) -> ObjcBool = unsafe { msg_send() };
unsafe { send(object, sel(c"isKindOfClass:"), class) != 0 }
}
#[cfg(test)]
pub(super) unsafe fn instances_respond_to(class: Id, selector: Sel) -> bool {
let send: unsafe extern "C" fn(Id, Sel, Sel) -> ObjcBool = unsafe { msg_send() };
unsafe { send(class, sel(c"instancesRespondToSelector:"), selector) != 0 }
}
pub(super) unsafe fn string(string: Id) -> Option<String> {
if string.is_null() {
return None;
}
let send: unsafe extern "C" fn(Id, Sel) -> *const c_char = unsafe { msg_send() };
let utf8 = unsafe { send(string, sel(c"UTF8String")) };
if utf8.is_null() {
return None;
}
Some(
unsafe { CStr::from_ptr(utf8) }
.to_string_lossy()
.into_owned(),
)
}