Skip to content

Instantly share code, notes, and snippets.

@extrawurst
Last active May 15, 2025 21:22
Show Gist options
  • Save extrawurst/b5aab1550f08fdb6722749a9fde087bf to your computer and use it in GitHub Desktop.
Save extrawurst/b5aab1550f08fdb6722749a9fde087bf to your computer and use it in GitHub Desktop.
swizzle_bevy_app_delegate_openURL
use bevy::prelude::*;
use objc2::{
ClassType, MainThreadMarker, MainThreadOnly, define_class, msg_send,
rc::{Allocated, Retained},
runtime::{AnyObject, Imp, NSObjectProtocol, ProtocolObject, Sel},
sel,
};
use objc2_foundation::{NSDictionary, NSURL};
use objc2_ui_kit::{
UIApplication, UIApplicationDelegate, UIApplicationOpenURLOptionsKey, UIResponder,
};
use std::mem;
define_class!(
#[unsafe(super(UIResponder))]
#[thread_kind = MainThreadOnly]
struct AppDelegate;
unsafe impl NSObjectProtocol for AppDelegate {}
unsafe impl UIApplicationDelegate for AppDelegate {
#[allow(non_snake_case)]
#[unsafe(method(application:openURL:options:))]
unsafe fn application_openURL_options(
&self,
_app: &UIApplication,
url: &NSURL,
_options: &NSDictionary<UIApplicationOpenURLOptionsKey, AnyObject>,
) -> bool {
let url_string = unsafe { url.absoluteString().unwrap().to_string() };
info!("url open: {url_string}");
true
}
}
);
define_class!(
#[unsafe(super(UIApplication))]
#[name = "SpecialAppNameThatGoesInPlist"]
struct IosApp;
impl IosApp {
#[unsafe(method_id(init))]
fn init(this: Allocated<Self>) -> Retained<Self> {
let this = this.set_ivars(());
let app: Retained<Self> = unsafe { objc2::msg_send![super(this), init] };
install_global_app_delegate(&app);
app
}
}
);
fn install_global_app_delegate(app: &UIApplication) {
let mtm = MainThreadMarker::new()
.expect("install_global_app_delegate must be called on the main thread");
let app_delegate = AppDelegate::new(mtm);
unsafe {
app.setDelegate(Some(ProtocolObject::from_ref(&*app_delegate)));
}
// We need the app delegate not to be deallocated.
// It will live for the lifetime of the application.
std::mem::forget(app_delegate);
debug!("AppDelegate registererd");
}
impl AppDelegate {
fn new(mtm: MainThreadMarker) -> Retained<Self> {
unsafe { msg_send![super(mtm.alloc().set_ivars(())), init] }
}
}
/// Swizzle `-[UIApplication init]` to install our application delegate in there.
fn swizzle() {
type SetDelegate = extern "C-unwind" fn(*mut UIApplication, Sel) -> *mut UIApplication;
static mut ORIGINAL: Option<SetDelegate> = None;
extern "C-unwind" fn init(this: *mut UIApplication, sel: Sel) -> *mut UIApplication {
let original = unsafe { ORIGINAL.unwrap() };
let this = original(this, sel);
let app = unsafe { &*this };
install_global_app_delegate(&app);
this
}
let _mtm = MainThreadMarker::new().unwrap();
let class = UIApplication::class();
let method = class
.instance_method(sel!(init))
.expect("UIApplication must have init method");
let overridden = unsafe { mem::transmute::<SetDelegate, Imp>(init) };
let original = unsafe { method.set_implementation(overridden) };
let original = unsafe { mem::transmute::<Imp, SetDelegate>(original) };
unsafe { ORIGINAL = Some(original) };
}
pub fn plugin(_app: &mut App) {
swizzle();
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment