use gpui::{App, AppContext, Application, RequestFrameOptions, WindowOptions};
use std::ffi::c_void;
use std::rc::Rc;
use std::sync::OnceLock;
static IOS_APP_STATE: OnceLock<IosAppState> = OnceLock::new();
struct IosAppState {
finish_launching: std::cell::UnsafeCell<Option<Box<dyn FnOnce()>>>,
}
unsafe impl Send for IosAppState {}
unsafe impl Sync for IosAppState {}
struct WindowListWrapper(std::cell::UnsafeCell<Vec<*const super::window::IosWindow>>);
unsafe impl Send for WindowListWrapper {}
unsafe impl Sync for WindowListWrapper {}
static IOS_WINDOW_LIST: OnceLock<WindowListWrapper> = OnceLock::new();
#[unsafe(no_mangle)]
pub extern "C" fn gpui_ios_initialize() -> *mut c_void {
#[cfg(all(debug_assertions, feature = "test-support"))]
{
let _ = env_logger::try_init();
}
log::info!("GPUI iOS: Initializing");
let state = IosAppState {
finish_launching: std::cell::UnsafeCell::new(None),
};
if IOS_APP_STATE.set(state).is_err() {
log::error!("GPUI iOS: Already initialized");
return std::ptr::null_mut();
}
let _ = IOS_WINDOW_LIST.set(WindowListWrapper(std::cell::UnsafeCell::new(Vec::new())));
1 as *mut c_void
}
pub(crate) fn register_window(window: *const super::window::IosWindow) {
if let Some(wrapper) = IOS_WINDOW_LIST.get() {
unsafe {
(*wrapper.0.get()).push(window);
log::info!("GPUI iOS: Registered window {:p}", window);
}
}
}
#[unsafe(no_mangle)]
pub extern "C" fn gpui_ios_get_window() -> *mut c_void {
if let Some(wrapper) = IOS_WINDOW_LIST.get() {
unsafe {
let windows = &*wrapper.0.get();
if let Some(&window) = windows.last() {
log::info!("GPUI iOS: Returning window {:p}", window);
return window as *mut c_void;
}
}
}
log::warn!("GPUI iOS: No windows registered");
std::ptr::null_mut()
}
pub(crate) fn set_finish_launching_callback(callback: Box<dyn FnOnce()>) {
if let Some(state) = IOS_APP_STATE.get() {
unsafe {
*state.finish_launching.get() = Some(callback);
}
}
}
#[unsafe(no_mangle)]
pub extern "C" fn gpui_ios_did_finish_launching(_app_ptr: *mut c_void) {
log::info!("GPUI iOS: Did finish launching");
if let Some(state) = IOS_APP_STATE.get() {
let callback = unsafe { (*state.finish_launching.get()).take() };
if let Some(callback) = callback {
log::info!("GPUI iOS: Invoking finish launching callback");
callback();
} else {
log::warn!("GPUI iOS: No finish launching callback registered");
}
} else {
log::error!("GPUI iOS: Not initialized");
}
}
#[unsafe(no_mangle)]
pub extern "C" fn gpui_ios_will_enter_foreground(_app_ptr: *mut c_void) {
log::info!("GPUI iOS: Will enter foreground");
if let Some(wrapper) = IOS_WINDOW_LIST.get() {
unsafe {
let windows = &*wrapper.0.get();
for &window_ptr in windows.iter() {
if !window_ptr.is_null() {
let window = &*window_ptr;
window.notify_active_status_change(true);
}
}
}
}
}
#[unsafe(no_mangle)]
pub extern "C" fn gpui_ios_did_become_active(_app_ptr: *mut c_void) {
log::info!("GPUI iOS: Did become active");
if let Some(wrapper) = IOS_WINDOW_LIST.get() {
unsafe {
let windows = &*wrapper.0.get();
for &window_ptr in windows.iter() {
if !window_ptr.is_null() {
let window = &*window_ptr;
window.notify_active_status_change(true);
}
}
}
}
}
#[unsafe(no_mangle)]
pub extern "C" fn gpui_ios_will_resign_active(_app_ptr: *mut c_void) {
log::info!("GPUI iOS: Will resign active");
if let Some(wrapper) = IOS_WINDOW_LIST.get() {
unsafe {
let windows = &*wrapper.0.get();
for &window_ptr in windows.iter() {
if !window_ptr.is_null() {
let window = &*window_ptr;
window.notify_active_status_change(false);
}
}
}
}
}
#[unsafe(no_mangle)]
pub extern "C" fn gpui_ios_did_enter_background(_app_ptr: *mut c_void) {
log::info!("GPUI iOS: Did enter background");
if let Some(wrapper) = IOS_WINDOW_LIST.get() {
unsafe {
let windows = &*wrapper.0.get();
for &window_ptr in windows.iter() {
if !window_ptr.is_null() {
let window = &*window_ptr;
window.notify_active_status_change(false);
}
}
}
}
}
#[unsafe(no_mangle)]
pub extern "C" fn gpui_ios_will_terminate(_app_ptr: *mut c_void) {
log::info!("GPUI iOS: Will terminate");
}
#[unsafe(no_mangle)]
pub extern "C" fn gpui_ios_handle_touch(
window_ptr: *mut c_void,
touch_ptr: *mut c_void,
event_ptr: *mut c_void,
) {
if window_ptr.is_null() || touch_ptr.is_null() {
return;
}
let window = unsafe { &*(window_ptr as *const super::window::IosWindow) };
window.handle_touch(
touch_ptr as *mut objc::runtime::Object,
event_ptr as *mut objc::runtime::Object,
);
}
#[unsafe(no_mangle)]
pub extern "C" fn gpui_ios_request_frame(window_ptr: *mut c_void) {
if window_ptr.is_null() {
return;
}
let window = unsafe { &*(window_ptr as *const super::window::IosWindow) };
window.pump_momentum();
let callback = window.request_frame_callback.borrow_mut().take();
if let Some(mut cb) = callback {
cb(RequestFrameOptions::default());
window.request_frame_callback.borrow_mut().replace(cb);
}
}
#[unsafe(no_mangle)]
pub extern "C" fn gpui_ios_show_keyboard(window_ptr: *mut c_void) {
if window_ptr.is_null() {
return;
}
log::info!("GPUI iOS: Show keyboard requested");
let window = unsafe { &*(window_ptr as *const super::window::IosWindow) };
window.show_keyboard();
}
#[unsafe(no_mangle)]
pub extern "C" fn gpui_ios_hide_keyboard(window_ptr: *mut c_void) {
if window_ptr.is_null() {
return;
}
log::info!("GPUI iOS: Hide keyboard requested");
let window = unsafe { &*(window_ptr as *const super::window::IosWindow) };
window.hide_keyboard();
}
#[unsafe(no_mangle)]
pub extern "C" fn gpui_ios_handle_text_input(window_ptr: *mut c_void, text_ptr: *mut c_void) {
if window_ptr.is_null() || text_ptr.is_null() {
return;
}
log::info!("GPUI iOS: Handle text input");
let window = unsafe { &*(window_ptr as *const super::window::IosWindow) };
window.handle_text_input(text_ptr as *mut objc::runtime::Object);
}
#[unsafe(no_mangle)]
pub extern "C" fn gpui_ios_handle_key_event(
window_ptr: *mut c_void,
key_code: u32,
modifiers: u32,
is_key_down: bool,
) {
if window_ptr.is_null() {
return;
}
log::info!(
"GPUI iOS: Handle key event - code: {}, modifiers: {}, down: {}",
key_code,
modifiers,
is_key_down
);
let window = unsafe { &*(window_ptr as *const super::window::IosWindow) };
window.handle_key_event(key_code, modifiers, is_key_down);
}
struct AppCallbackCell(std::cell::UnsafeCell<Option<Box<dyn FnOnce(&mut App)>>>);
unsafe impl Send for AppCallbackCell {}
unsafe impl Sync for AppCallbackCell {}
static APP_CALLBACK: OnceLock<AppCallbackCell> = OnceLock::new();
pub fn set_app_callback(cb: Box<dyn FnOnce(&mut App)>) {
let cell = APP_CALLBACK.get_or_init(|| AppCallbackCell(std::cell::UnsafeCell::new(None)));
unsafe {
*cell.0.get() = Some(cb);
}
}
fn take_app_callback() -> Option<Box<dyn FnOnce(&mut App)>> {
APP_CALLBACK
.get()
.and_then(|cell| unsafe { (*cell.0.get()).take() })
}
#[unsafe(no_mangle)]
pub extern "C" fn gpui_ios_run_demo() {
run_app();
}
pub fn run_app() {
log::info!("GPUI iOS: Starting application");
if IOS_APP_STATE.get().is_none() {
let state = IosAppState {
finish_launching: std::cell::UnsafeCell::new(None),
};
let _ = IOS_APP_STATE.set(state);
let _ = IOS_WINDOW_LIST.set(WindowListWrapper(std::cell::UnsafeCell::new(Vec::new())));
}
let platform = Rc::new(super::IosPlatform::new());
Application::with_platform(platform).run(|cx: &mut App| {
if let Some(cb) = take_app_callback() {
log::info!("GPUI iOS: Invoking user-provided app callback");
cb(cx);
} else {
log::warn!("GPUI iOS: No app callback registered — opening default empty window");
cx.open_window(
WindowOptions {
window_bounds: None,
..Default::default()
},
|_, cx| cx.new(|_| gpui::Empty),
)
.expect("Failed to open default window");
cx.activate(true);
}
});
if let Some(state) = IOS_APP_STATE.get() {
let callback = unsafe { (*state.finish_launching.get()).take() };
if let Some(callback) = callback {
log::info!("GPUI iOS: Invoking Application::run callback");
callback();
}
}
}