use std::sync::{Arc, OnceLock};
use parking_lot::{Condvar, Mutex};
use windows_sys::Win32::{
Foundation::{BOOL, FALSE, TRUE},
System::Console::{
SetConsoleCtrlHandler, CTRL_BREAK_EVENT, CTRL_CLOSE_EVENT, CTRL_C_EVENT
}
};
use crate::{err::Error, SigType};
static CELL: OnceLock<Arc<Shared>> = OnceLock::new();
#[derive(Default)]
pub struct ReadWrite {
term: bool,
handler: Option<Box<dyn FnOnce(SigType) + Send + Sync>>
}
#[derive(Default)]
pub struct Shared {
rw: Mutex<ReadWrite>,
signal: Condvar
}
pub struct KillWait(Arc<Shared>);
impl KillWait {
#[allow(clippy::significant_drop_tightening)]
pub fn wait(self) {
let mut g = self.0.rw.lock();
while !g.term {
self.0.signal.wait(&mut g);
}
}
}
pub fn init() -> Result<KillWait, Error> {
let kw = Shared::default();
let kw = Arc::new(kw);
CELL
.set(Arc::clone(&kw))
.map_err(|_| Error::internal("Unable to set global cell"))?;
Ok(KillWait(kw))
}
pub fn register(
handler: impl FnOnce(SigType) + Send + Sync + 'static
) -> Result<(), Error> {
let Some(sh) = CELL.get() else {
return Err(Error::internal("cell not initialized"));
};
let mut g = sh.rw.lock();
g.handler = Some(Box::new(handler));
drop(g);
let rc = unsafe { SetConsoleCtrlHandler(Some(ctrlhandler), 1) };
(rc != 0)
.then_some(())
.ok_or_else(|| Error::internal("SetConsoleCtrlHandler failed"))?;
Ok(())
}
unsafe extern "system" fn ctrlhandler(ty: u32) -> BOOL {
let Some(sigtype) = ty2sig(ty) else {
return FALSE;
};
let Some(sh) = CELL.get() else {
return FALSE;
};
let mut g = sh.rw.lock();
let Some(handler) = g.handler.take() else {
return FALSE;
};
drop(g);
handler(sigtype);
let mut g = sh.rw.lock();
g.term = true;
sh.signal.notify_all();
drop(g);
TRUE
}
const fn ty2sig(ty: u32) -> Option<SigType> {
match ty {
CTRL_C_EVENT => Some(SigType::Int),
CTRL_BREAK_EVENT | CTRL_CLOSE_EVENT => Some(SigType::Term),
_ => None
}
}