use async_lock::OnceCell;
use slab::Slab;
use windows_sys::core::BOOL;
use windows_sys::Win32::System::Console::{SetConsoleCtrlHandler, CTRL_C_EVENT};
use std::io::Result;
use std::mem;
use std::os::raw::c_int;
use std::sync::Mutex;
use super::signum::SIGINT;
pub(crate) type SigId = usize;
pub(crate) unsafe fn register(
signal: c_int,
handler: impl Fn() + Send + Sync + 'static,
) -> Result<SigId> {
if signal != SIGINT {
return Err(std::io::Error::other("unsupported signal"));
}
Ok(Registry::get()?.register(handler))
}
pub fn unregister(id: SigId) {
if let Ok(registry) = Registry::get() {
registry.unregister(id)
}
}
struct Registry {
handlers: Mutex<Slab<Handler>>,
}
type Handler = Box<dyn Fn() + Send + Sync + 'static>;
impl Registry {
fn get() -> Result<&'static Self> {
static REGISTRY: OnceCell<Registry> = OnceCell::new();
REGISTRY.get_or_try_init_blocking(|| {
let res = unsafe { SetConsoleCtrlHandler(Some(Self::handle_event), true as _) };
if res == 0 {
return Err(std::io::Error::last_os_error());
}
Ok(Registry {
handlers: Mutex::new(Slab::new()),
})
})
}
unsafe extern "system" fn handle_event(event: u32) -> BOOL {
struct AbortOnDrop;
impl Drop for AbortOnDrop {
fn drop(&mut self) {
std::process::abort();
}
}
let _abort_on_drop = AbortOnDrop;
if event == CTRL_C_EVENT {
let registry = match Self::get() {
Ok(registry) => registry,
Err(_) => return false as BOOL,
};
let handlers = registry.handlers.lock().unwrap_or_else(|e| e.into_inner());
for handler in handlers.iter() {
(handler.1)();
}
mem::forget(_abort_on_drop);
return true as BOOL;
}
mem::forget(_abort_on_drop);
false as BOOL
}
fn register(&self, handler: impl Fn() + Send + Sync + 'static) -> usize {
self.handlers
.lock()
.unwrap_or_else(|e| e.into_inner())
.insert(Box::new(handler))
}
fn unregister(&self, id: usize) {
self.handlers
.lock()
.unwrap_or_else(|e| e.into_inner())
.try_remove(id);
}
}