use core::ffi::{c_char, c_void};
unsafe extern "C" {
fn dlsym(handle: *mut c_void, symbol: *const c_char) -> *mut c_void;
fn objc_msgSend(receiver: *mut c_void, sel: *const c_void, ...) -> *mut c_void;
fn sel_registerName(name: *const c_char) -> *const c_void;
fn objc_allocateClassPair(
superclass: *mut c_void,
name: *const c_char,
extra_bytes: usize,
) -> *mut c_void;
fn objc_registerClassPair(class: *mut c_void);
fn objc_getClass(name: *const c_char) -> *mut c_void;
fn objc_getProtocol(name: *const c_char) -> *mut c_void;
fn class_addMethod(
class: *mut c_void,
name: *const c_void,
imp: *const c_void,
types: *const c_char,
) -> bool;
fn class_addProtocol(class: *mut c_void, protocol: *mut c_void) -> bool;
}
const RTLD_DEFAULT: *mut c_void = -2isize as *mut c_void;
#[repr(C)]
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct NSPoint {
pub x: f64,
pub y: f64,
}
#[repr(C)]
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct NSSize {
pub width: f64,
pub height: f64,
}
#[repr(C)]
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct NSRect {
pub origin: NSPoint,
pub size: NSSize,
}
impl NSRect {
pub fn new(x: f64, y: f64, width: f64, height: f64) -> Self {
Self { origin: NSPoint { x, y }, size: NSSize { width, height } }
}
}
pub struct AutoreleasePool {
context: *mut c_void,
}
impl AutoreleasePool {
pub fn new() -> Self {
unsafe extern "C" {
fn objc_autoreleasePoolPush() -> *mut c_void;
}
Self { context: unsafe { objc_autoreleasePoolPush() } }
}
}
impl Default for AutoreleasePool {
fn default() -> Self {
Self::new()
}
}
impl Drop for AutoreleasePool {
fn drop(&mut self) {
unsafe extern "C" {
fn objc_autoreleasePoolPop(context: *mut c_void);
}
unsafe { objc_autoreleasePoolPop(self.context) };
}
}
pub fn msg_send_addr() -> *const c_void {
objc_msgSend as *const c_void
}
pub fn sel(name: &core::ffi::CStr) -> *const c_void {
unsafe { sel_registerName(name.as_ptr().cast()) }
}
pub fn class(symbol: &core::ffi::CStr) -> *mut c_void {
let c = unsafe { dlsym(RTLD_DEFAULT, symbol.as_ptr()) };
assert!(!c.is_null(), "Objective-C class not found: {symbol:?}");
c
}
macro_rules! send {
($name:ident, ($($arg:ident: $ty:ty),*) -> $ret:ty) => {
pub unsafe fn $name(recv: *mut c_void, s: *const c_void $(, $arg: $ty)*) -> $ret {
type F = unsafe extern "C" fn(*mut c_void, *const c_void $(, $ty)*) -> $ret;
let f: F = unsafe { core::mem::transmute(objc_msgSend as *const c_void) };
unsafe { f(recv, s $(, $arg)*) }
}
};
}
send!(send, () -> *mut c_void);
send!(send_id, (a: *mut c_void) -> *mut c_void);
send!(send_bool, (a: bool) -> *mut c_void);
send!(send_i64, (a: i64) -> *mut c_void);
send!(send_u64, (a: u64) -> *mut c_void);
send!(send_f64, (a: f64) -> *mut c_void);
send!(send_rect, (a: NSRect) -> *mut c_void);
send!(send_rect_id, (a: NSRect, b: *mut c_void) -> *mut c_void);
send!(send_u64_id, (a: u64, b: *mut c_void) -> *mut c_void);
send!(send_id_bool, (a: *mut c_void, b: bool) -> bool);
send!(send_ret_size, () -> NSSize);
send!(send_ret_rect, () -> NSRect);
send!(send_ret_u64, () -> u64);
send!(send_ret_f64, () -> f64);
send!(send_ret_ptr, () -> *const u8);
send!(send_bool_id, (a: bool, b: *mut c_void) -> *mut c_void);
send!(send_i64_id, (a: i64, b: *mut c_void) -> *mut c_void);
send!(send_f64_id, (a: f64, b: *mut c_void) -> *mut c_void);
send!(send_id_id, (a: *mut c_void, b: *mut c_void) -> *mut c_void);
send!(send_id_ret_bool, (a: *mut c_void) -> bool);
send!(send_id_ret_i64, (a: *mut c_void) -> i64);
send!(send_id_ret_f64, (a: *mut c_void) -> f64);
send!(send_ret_bool, () -> bool);
pub unsafe fn responds(receiver: *mut c_void, selector: *const c_void) -> bool {
unsafe { send_id(receiver, sel(c"respondsToSelector:"), selector as *mut c_void) as usize & 1 == 1 }
}
pub unsafe fn nsstring(s: &str) -> *mut c_void {
let alloc = unsafe { send(class(c"OBJC_CLASS_$_NSString"), sel(c"alloc")) };
type InitBytes =
unsafe extern "C" fn(*mut c_void, *const c_void, *const u8, u64, u64) -> *mut c_void;
let f: InitBytes = unsafe { core::mem::transmute(objc_msgSend as *const c_void) };
let out = unsafe {
f(alloc, sel(c"initWithBytes:length:encoding:"), s.as_ptr(), s.len() as u64, 4)
};
assert!(!out.is_null(), "NSString creation failed for {s:?}");
out
}
#[cfg(target_os = "macos")]
pub unsafe fn shared_app() -> *mut c_void {
let app = unsafe { send(class(c"OBJC_CLASS_$_NSApplication"), sel(c"sharedApplication")) };
unsafe {
let name = nsstring("NSAppearanceNameAqua");
let appearance = send_id(
class(c"OBJC_CLASS_$_NSAppearance"),
sel(c"appearanceNamed:"),
name,
);
if !appearance.is_null() {
send_id(app, sel(c"setAppearance:"), appearance);
}
}
app
}
#[cfg(not(target_os = "macos"))]
pub unsafe fn shared_app() -> *mut c_void {
unsafe { send(class(c"OBJC_CLASS_$_UIApplication"), sel(c"sharedApplication")) }
}
pub struct ClassBuilder {
class: *mut c_void,
}
impl ClassBuilder {
pub unsafe fn new(superclass: &core::ffi::CStr, name: &core::ffi::CStr) -> Option<Self> {
unsafe {
let sup = objc_getClass(superclass.as_ptr().cast());
assert!(!sup.is_null(), "superclass not found: {superclass:?}");
let class = objc_allocateClassPair(sup, name.as_ptr(), 0);
if class.is_null() {
return None;
}
Some(Self { class })
}
}
pub unsafe fn method(
self,
selector: &core::ffi::CStr,
imp: *const c_void,
types: &core::ffi::CStr,
) -> Self {
unsafe {
let ok = class_addMethod(self.class, sel(selector), imp, types.as_ptr());
assert!(ok, "could not add {selector:?}");
}
self
}
pub unsafe fn conforms(self, protocol: &core::ffi::CStr) -> Self {
unsafe {
let p = objc_getProtocol(protocol.as_ptr());
if !p.is_null() {
class_addProtocol(self.class, p);
}
}
self
}
pub fn register(self) -> *mut c_void {
unsafe { objc_registerClassPair(self.class) };
self.class
}
}
pub fn class_named(name: &core::ffi::CStr) -> *mut c_void {
unsafe { objc_getClass(name.as_ptr().cast()) }
}
#[repr(C)]
pub struct GlobalBlock {
isa: *const c_void,
flags: i32,
reserved: i32,
invoke: *const c_void,
descriptor: *const BlockDescriptor,
}
#[repr(C)]
pub struct BlockDescriptor {
reserved: u64,
size: u64,
}
unsafe impl Sync for GlobalBlock {}
unsafe impl Sync for BlockDescriptor {}
const BLOCK_IS_GLOBAL: i32 = 1 << 28;
impl GlobalBlock {
pub unsafe fn new(invoke: *const c_void, descriptor: &'static BlockDescriptor) -> Self {
let isa = unsafe { dlsym(RTLD_DEFAULT, c"_NSConcreteGlobalBlock".as_ptr()) };
assert!(!isa.is_null(), "_NSConcreteGlobalBlock");
GlobalBlock { isa, flags: BLOCK_IS_GLOBAL, reserved: 0, invoke, descriptor }
}
}
impl BlockDescriptor {
pub const fn empty() -> Self {
BlockDescriptor { reserved: 0, size: core::mem::size_of::<GlobalBlock>() as u64 }
}
}
#[repr(C)]
pub struct CapturingBlock {
isa: *const c_void,
flags: i32,
reserved: i32,
invoke: *const c_void,
descriptor: *const BlockDescriptor,
pub capture: *mut c_void,
}
impl CapturingBlock {
pub unsafe fn new(invoke: *const c_void, capture: *mut c_void) -> Self {
static DESCRIPTOR: BlockDescriptor = BlockDescriptor {
reserved: 0,
size: core::mem::size_of::<CapturingBlock>() as u64,
};
let isa = unsafe { dlsym(RTLD_DEFAULT, c"_NSConcreteStackBlock".as_ptr()) };
assert!(!isa.is_null(), "_NSConcreteStackBlock");
CapturingBlock {
isa,
flags: 0,
reserved: 0,
invoke,
descriptor: &DESCRIPTOR,
capture,
}
}
}