use std::path::PathBuf;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use objc2::rc::Retained;
use objc2::runtime::{NSObject, NSObjectProtocol};
use objc2::{define_class, msg_send, AllocAnyThread, DefinedClass};
use objc2_foundation::NSError;
use objc2_virtualization::{VZVirtualMachine, VZVirtualMachineDelegate};
use crate::session::{EndSlot, SessionEnd};
pub(crate) struct DelegateState {
pub exit_code_file: Option<PathBuf>,
pub timed_out: Arc<AtomicBool>,
pub end: Arc<EndSlot>,
}
define_class!(
#[unsafe(super(NSObject))]
#[ivars = DelegateState]
#[name = "VmetteDelegate"]
pub(crate) struct VmetteDelegate;
unsafe impl NSObjectProtocol for VmetteDelegate {}
unsafe impl VZVirtualMachineDelegate for VmetteDelegate {
#[unsafe(method(guestDidStopVirtualMachine:))]
fn guest_did_stop(&self, _vm: &VZVirtualMachine) {
let state = self.ivars();
if state.timed_out.load(Ordering::SeqCst) {
state.end.set(SessionEnd::TimedOut);
return;
}
let code = read_exit_file(state.exit_code_file.as_deref());
state.end.set(SessionEnd::Exited(code));
}
#[unsafe(method(virtualMachine:didStopWithError:))]
fn did_stop_with_error(&self, _vm: &VZVirtualMachine, err: &NSError) {
let state = self.ivars();
state
.end
.set(SessionEnd::Error(err.localizedDescription().to_string()));
}
}
);
impl VmetteDelegate {
pub(crate) fn new(state: DelegateState) -> Retained<Self> {
let this = Self::alloc().set_ivars(state);
unsafe { msg_send![super(this), init] }
}
}
fn read_exit_file(path: Option<&std::path::Path>) -> i32 {
let Some(p) = path else { return 0 };
match std::fs::read_to_string(p) {
Ok(s) => match s.trim().parse() {
Ok(n) => n,
Err(_) => {
eprintln!(
"\r\n[vmette] warning: .vmette-exit unparseable ({:?}); reporting 1\r",
s.trim()
);
1
}
},
Err(_) => {
eprintln!(
"\r\n[vmette] warning: .vmette-exit missing (guest likely crashed); reporting 1\r"
);
1
}
}
}