1use core::ffi::{c_char, c_void};
12
13unsafe extern "C" {
14 fn dlsym(handle: *mut c_void, symbol: *const c_char) -> *mut c_void;
15 fn objc_msgSend(receiver: *mut c_void, sel: *const c_void, ...) -> *mut c_void;
16 fn sel_registerName(name: *const c_char) -> *const c_void;
17
18 fn objc_allocateClassPair(
19 superclass: *mut c_void,
20 name: *const c_char,
21 extra_bytes: usize,
22 ) -> *mut c_void;
23 fn objc_registerClassPair(class: *mut c_void);
24 fn objc_getClass(name: *const c_char) -> *mut c_void;
25 fn objc_getProtocol(name: *const c_char) -> *mut c_void;
26 fn class_addMethod(
27 class: *mut c_void,
28 name: *const c_void,
29 imp: *const c_void,
30 types: *const c_char,
31 ) -> bool;
32 fn class_addProtocol(class: *mut c_void, protocol: *mut c_void) -> bool;
33}
34
35const RTLD_DEFAULT: *mut c_void = -2isize as *mut c_void;
36
37#[repr(C)]
38#[derive(Clone, Copy, Debug, PartialEq)]
39pub struct NSPoint {
40 pub x: f64,
41 pub y: f64,
42}
43
44#[repr(C)]
45#[derive(Clone, Copy, Debug, PartialEq)]
46pub struct NSSize {
47 pub width: f64,
48 pub height: f64,
49}
50
51#[repr(C)]
52#[derive(Clone, Copy, Debug, PartialEq)]
53pub struct NSRect {
54 pub origin: NSPoint,
55 pub size: NSSize,
56}
57
58impl NSRect {
59 pub fn new(x: f64, y: f64, width: f64, height: f64) -> Self {
60 Self { origin: NSPoint { x, y }, size: NSSize { width, height } }
61 }
62}
63
64pub struct AutoreleasePool {
85 context: *mut c_void,
86}
87
88impl AutoreleasePool {
89 pub fn new() -> Self {
90 unsafe extern "C" {
91 fn objc_autoreleasePoolPush() -> *mut c_void;
92 }
93 Self { context: unsafe { objc_autoreleasePoolPush() } }
94 }
95}
96
97impl Default for AutoreleasePool {
98 fn default() -> Self {
99 Self::new()
100 }
101}
102
103impl Drop for AutoreleasePool {
104 fn drop(&mut self) {
105 unsafe extern "C" {
106 fn objc_autoreleasePoolPop(context: *mut c_void);
107 }
108 unsafe { objc_autoreleasePoolPop(self.context) };
109 }
110}
111
112pub fn msg_send_addr() -> *const c_void {
118 objc_msgSend as *const c_void
119}
120
121pub fn sel(name: &core::ffi::CStr) -> *const c_void {
123 unsafe { sel_registerName(name.as_ptr().cast()) }
124}
125
126pub fn class(symbol: &core::ffi::CStr) -> *mut c_void {
128 let c = unsafe { dlsym(RTLD_DEFAULT, symbol.as_ptr()) };
129 assert!(!c.is_null(), "Objective-C class not found: {symbol:?}");
130 c
131}
132
133macro_rules! send {
134 ($name:ident, ($($arg:ident: $ty:ty),*) -> $ret:ty) => {
135 pub unsafe fn $name(recv: *mut c_void, s: *const c_void $(, $arg: $ty)*) -> $ret {
141 type F = unsafe extern "C" fn(*mut c_void, *const c_void $(, $ty)*) -> $ret;
142 let f: F = unsafe { core::mem::transmute(objc_msgSend as *const c_void) };
143 unsafe { f(recv, s $(, $arg)*) }
144 }
145 };
146}
147
148send!(send, () -> *mut c_void);
149send!(send_id, (a: *mut c_void) -> *mut c_void);
150send!(send_bool, (a: bool) -> *mut c_void);
151send!(send_i64, (a: i64) -> *mut c_void);
152send!(send_u64, (a: u64) -> *mut c_void);
153send!(send_f64, (a: f64) -> *mut c_void);
154send!(send_rect, (a: NSRect) -> *mut c_void);
155send!(send_rect_id, (a: NSRect, b: *mut c_void) -> *mut c_void);
156send!(send_u64_id, (a: u64, b: *mut c_void) -> *mut c_void);
157send!(send_id_bool, (a: *mut c_void, b: bool) -> bool);
158send!(send_ret_size, () -> NSSize);
159send!(send_ret_rect, () -> NSRect);
160send!(send_ret_u64, () -> u64);
161send!(send_ret_f64, () -> f64);
162send!(send_ret_ptr, () -> *const u8);
163send!(send_bool_id, (a: bool, b: *mut c_void) -> *mut c_void);
164send!(send_i64_id, (a: i64, b: *mut c_void) -> *mut c_void);
165send!(send_f64_id, (a: f64, b: *mut c_void) -> *mut c_void);
166send!(send_id_id, (a: *mut c_void, b: *mut c_void) -> *mut c_void);
167send!(send_id_ret_bool, (a: *mut c_void) -> bool);
168send!(send_id_ret_i64, (a: *mut c_void) -> i64);
169send!(send_id_ret_f64, (a: *mut c_void) -> f64);
170send!(send_ret_bool, () -> bool);
171
172pub unsafe fn responds(receiver: *mut c_void, selector: *const c_void) -> bool {
180 unsafe { send_id(receiver, sel(c"respondsToSelector:"), selector as *mut c_void) as usize & 1 == 1 }
181}
182
183pub unsafe fn nsstring(s: &str) -> *mut c_void {
187 let alloc = unsafe { send(class(c"OBJC_CLASS_$_NSString"), sel(c"alloc")) };
188 type InitBytes =
189 unsafe extern "C" fn(*mut c_void, *const c_void, *const u8, u64, u64) -> *mut c_void;
190 let f: InitBytes = unsafe { core::mem::transmute(objc_msgSend as *const c_void) };
191 let out = unsafe {
193 f(alloc, sel(c"initWithBytes:length:encoding:"), s.as_ptr(), s.len() as u64, 4)
194 };
195 assert!(!out.is_null(), "NSString creation failed for {s:?}");
196 out
197}
198
199#[cfg(target_os = "macos")]
202pub unsafe fn shared_app() -> *mut c_void {
206 let app = unsafe { send(class(c"OBJC_CLASS_$_NSApplication"), sel(c"sharedApplication")) };
207 unsafe {
208 let name = nsstring("NSAppearanceNameAqua");
209 let appearance = send_id(
210 class(c"OBJC_CLASS_$_NSAppearance"),
211 sel(c"appearanceNamed:"),
212 name,
213 );
214 if !appearance.is_null() {
215 send_id(app, sel(c"setAppearance:"), appearance);
216 }
217 }
218 app
219}
220
221#[cfg(not(target_os = "macos"))]
230pub unsafe fn shared_app() -> *mut c_void {
231 unsafe { send(class(c"OBJC_CLASS_$_UIApplication"), sel(c"sharedApplication")) }
232}
233
234pub struct ClassBuilder {
245 class: *mut c_void,
246}
247
248impl ClassBuilder {
249 pub unsafe fn new(superclass: &core::ffi::CStr, name: &core::ffi::CStr) -> Option<Self> {
254 unsafe {
255 let sup = objc_getClass(superclass.as_ptr().cast());
256 assert!(!sup.is_null(), "superclass not found: {superclass:?}");
257 let class = objc_allocateClassPair(sup, name.as_ptr(), 0);
258 if class.is_null() {
259 return None;
260 }
261 Some(Self { class })
262 }
263 }
264
265 pub unsafe fn method(
274 self,
275 selector: &core::ffi::CStr,
276 imp: *const c_void,
277 types: &core::ffi::CStr,
278 ) -> Self {
279 unsafe {
280 let ok = class_addMethod(self.class, sel(selector), imp, types.as_ptr());
281 assert!(ok, "could not add {selector:?}");
282 }
283 self
284 }
285
286 pub unsafe fn conforms(self, protocol: &core::ffi::CStr) -> Self {
291 unsafe {
292 let p = objc_getProtocol(protocol.as_ptr());
293 if !p.is_null() {
294 class_addProtocol(self.class, p);
295 }
296 }
297 self
298 }
299
300 pub fn register(self) -> *mut c_void {
302 unsafe { objc_registerClassPair(self.class) };
303 self.class
304 }
305}
306
307pub fn class_named(name: &core::ffi::CStr) -> *mut c_void {
309 unsafe { objc_getClass(name.as_ptr().cast()) }
310}
311
312#[repr(C)]
320pub struct GlobalBlock {
321 isa: *const c_void,
322 flags: i32,
323 reserved: i32,
324 invoke: *const c_void,
325 descriptor: *const BlockDescriptor,
326}
327
328#[repr(C)]
330pub struct BlockDescriptor {
331 reserved: u64,
332 size: u64,
333}
334
335unsafe impl Sync for GlobalBlock {}
337unsafe impl Sync for BlockDescriptor {}
338
339const BLOCK_IS_GLOBAL: i32 = 1 << 28;
341
342impl GlobalBlock {
343 pub unsafe fn new(invoke: *const c_void, descriptor: &'static BlockDescriptor) -> Self {
350 let isa = unsafe { dlsym(RTLD_DEFAULT, c"_NSConcreteGlobalBlock".as_ptr()) };
351 assert!(!isa.is_null(), "_NSConcreteGlobalBlock");
352 GlobalBlock { isa, flags: BLOCK_IS_GLOBAL, reserved: 0, invoke, descriptor }
353 }
354}
355
356impl BlockDescriptor {
357 pub const fn empty() -> Self {
359 BlockDescriptor { reserved: 0, size: core::mem::size_of::<GlobalBlock>() as u64 }
360 }
361}
362
363#[repr(C)]
375pub struct CapturingBlock {
376 isa: *const c_void,
377 flags: i32,
378 reserved: i32,
379 invoke: *const c_void,
380 descriptor: *const BlockDescriptor,
381 pub capture: *mut c_void,
383}
384
385impl CapturingBlock {
386 pub unsafe fn new(invoke: *const c_void, capture: *mut c_void) -> Self {
392 static DESCRIPTOR: BlockDescriptor = BlockDescriptor {
393 reserved: 0,
394 size: core::mem::size_of::<CapturingBlock>() as u64,
395 };
396 let isa = unsafe { dlsym(RTLD_DEFAULT, c"_NSConcreteStackBlock".as_ptr()) };
397 assert!(!isa.is_null(), "_NSConcreteStackBlock");
398 CapturingBlock {
399 isa,
400 flags: 0,
401 reserved: 0,
402 invoke,
403 descriptor: &DESCRIPTOR,
404 capture,
405 }
406 }
407}