use std::os::raw::c_int;
use std::sync::{Mutex, OnceLock};
use dispatch2::DispatchQueue;
use objc2::rc::Retained;
use objc2::runtime::{NSObject, NSObjectProtocol, ProtocolObject};
use objc2::{AnyThread, DefinedClass, MainThreadOnly, MainThreadMarker, define_class, msg_send};
use objc2_app_kit::{NSApplication, NSApplicationActivationPolicy, NSApplicationDelegate};
use super::install_main_state;
use crate::PlatformEvent;
const K_PROCESS_TRANSFORM_TO_UI_ELEMENT: u32 = 4;
#[repr(C)]
#[derive(Clone, Copy)]
struct ProcessSerialNumber {
high_long_of_psn: u32,
low_long_of_psn: u32,
}
const K_CURRENT_PROCESS: u32 = 2;
#[link(name = "ApplicationServices", kind = "framework")]
unsafe extern "C" {
fn TransformProcessType(psn: *const ProcessSerialNumber, transform_type: u32) -> c_int;
fn GetCurrentProcess(psn: *mut ProcessSerialNumber) -> c_int;
}
fn transform_to_ui_element() {
let mut psn = ProcessSerialNumber {
high_long_of_psn: 0,
low_long_of_psn: K_CURRENT_PROCESS,
};
let status = unsafe { TransformProcessType(&psn, K_PROCESS_TRANSFORM_TO_UI_ELEMENT) };
if status != 0 {
log::warn!("macos: TransformProcessType returned {status}");
} else {
log::info!("macos: process transformed to UIElement");
}
let _ = unsafe { GetCurrentProcess(&mut psn) };
}
pub fn bootstrap_main<F>(daemon_body: F) -> !
where
F: FnOnce() + Send + 'static,
{
let mtm = MainThreadMarker::new()
.expect("vernier_platform::bootstrap_main must be called on the main thread");
install_main_state();
transform_to_ui_element();
let app = NSApplication::sharedApplication(mtm);
app.setActivationPolicy(NSApplicationActivationPolicy::Accessory);
unsafe { app.finishLaunching() };
log::info!("macos: NSApp finished launching, activation=Accessory");
let delegate = VernierAppDelegate::new(mtm);
app.setDelegate(Some(ProtocolObject::from_ref(&*delegate)));
APP_DELEGATE.set(SendableDelegate(delegate)).ok();
NSAPP.set(SendableApp(app.clone())).ok();
std::thread::Builder::new()
.name("vernier-daemon".into())
.spawn(move || {
daemon_body();
terminate_nsapp();
})
.expect("spawn vernier daemon worker thread");
unsafe { app.run() };
std::process::exit(0)
}
struct SendableApp(objc2::rc::Retained<NSApplication>);
unsafe impl Send for SendableApp {}
unsafe impl Sync for SendableApp {}
static NSAPP: OnceLock<SendableApp> = OnceLock::new();
fn terminate_nsapp() {
run_on_main_async(|| {
let Some(SendableApp(app)) = NSAPP.get() else {
return;
};
unsafe { app.stop(None) };
wake_main_event_loop();
});
}
fn wake_main_event_loop() {
use objc2_app_kit::{NSEvent, NSEventModifierFlags, NSEventType};
use objc2_foundation::NSPoint;
let Some(SendableApp(app)) = NSAPP.get() else {
return;
};
let _ = MainThreadMarker::new().expect("main");
unsafe {
let event = NSEvent::otherEventWithType_location_modifierFlags_timestamp_windowNumber_context_subtype_data1_data2(
NSEventType::ApplicationDefined,
NSPoint { x: 0.0, y: 0.0 },
NSEventModifierFlags(0),
0.0,
0,
None,
0,
0,
0,
);
if let Some(event) = event {
app.postEvent_atStart(&event, true);
}
}
}
pub(crate) fn run_on_main_sync<F, R>(f: F) -> R
where
F: FnOnce() -> R + Send,
R: Send,
{
let result: Mutex<Option<R>> = Mutex::new(None);
DispatchQueue::main().exec_sync(|| {
let r = f();
*result.lock().expect("dispatch sync result lock") = Some(r);
});
result
.into_inner()
.expect("dispatch sync result mutex")
.expect("dispatch sync closure did not produce a result")
}
pub(crate) fn run_on_main_async<F>(f: F)
where
F: FnOnce() + Send + 'static,
{
DispatchQueue::main().exec_async(f);
}
define_class!(
#[unsafe(super(NSObject))]
#[thread_kind = MainThreadOnly]
#[name = "VernierAppDelegate"]
pub(crate) struct VernierAppDelegate;
unsafe impl NSObjectProtocol for VernierAppDelegate {}
unsafe impl NSApplicationDelegate for VernierAppDelegate {
#[unsafe(method(applicationShouldHandleReopen:hasVisibleWindows:))]
fn application_should_handle_reopen(
&self,
_sender: &NSApplication,
_has_visible_windows: bool,
) -> bool {
if let Some(tx) = super::event_tx() {
let _ = tx.send(PlatformEvent::TrayMenuActivated {
id: "open_prefs".to_string(),
});
log::info!("macos: reopen Apple Event → open_prefs");
} else {
log::warn!("macos: reopen Apple Event arrived before event channel ready");
}
true
}
}
);
impl VernierAppDelegate {
fn new(mtm: MainThreadMarker) -> Retained<Self> {
unsafe { msg_send![mtm.alloc::<Self>(), init] }
}
}
struct SendableDelegate(Retained<VernierAppDelegate>);
unsafe impl Send for SendableDelegate {}
unsafe impl Sync for SendableDelegate {}
static APP_DELEGATE: OnceLock<SendableDelegate> = OnceLock::new();