use std::ptr::NonNull;
use std::sync::{Arc, Mutex};
use std::time::Instant;
use block2::RcBlock;
use objc2_app_kit::{NSEvent, NSEventMask};
use tauri::{AppHandle, Emitter, Runtime};
pub fn install<R: Runtime>(app: AppHandle<R>) {
let mask = NSEventMask::Swipe
| NSEventMask::OtherMouseDown
| NSEventMask::OtherMouseUp
| NSEventMask::SystemDefined;
const X1_BACK_BIT: isize = 1 << 3; const X2_FWD_BIT: isize = 1 << 4;
let last_emit: Arc<Mutex<Option<Instant>>> = Arc::new(Mutex::new(None));
let emit_once = move |payload: &'static str| {
let app = app.clone();
let last = last_emit.clone();
move || {
let mut guard = last.lock().expect("mouse-nav debounce mutex");
let now = Instant::now();
let fresh = guard.map_or(true, |t| now.duration_since(t).as_millis() > 200);
if fresh {
let _ = app.emit("mouse-nav", payload);
*guard = Some(now);
}
}
};
let emit_back = emit_once("back");
let emit_forward = emit_once("forward");
let block = RcBlock::new(move |event: NonNull<NSEvent>| -> *mut NSEvent {
let ev = unsafe { event.as_ref() };
match ev.r#type().0 {
14 => {
if ev.subtype().0 == 7 {
let action = match ev.data1() {
X1_BACK_BIT => Some(false), X2_FWD_BIT => Some(true), _ => None,
};
if let Some(is_forward) = action {
if ev.data2() != 0 {
if is_forward {
emit_forward()
} else {
emit_back()
}
}
return std::ptr::null_mut();
}
}
}
25 => match ev.buttonNumber() {
3 => {
emit_back();
return std::ptr::null_mut();
}
4 => {
emit_forward();
return std::ptr::null_mut();
}
_ => {}
},
26 => {
if matches!(ev.buttonNumber(), 3 | 4) {
return std::ptr::null_mut();
}
}
31 => {
let dx = ev.deltaX();
if dx > 0.5 {
emit_back();
return std::ptr::null_mut();
} else if dx < -0.5 {
emit_forward();
return std::ptr::null_mut();
}
}
_ => {}
}
event.as_ptr()
});
let registration = unsafe { NSEvent::addLocalMonitorForEventsMatchingMask_handler(mask, &block) };
std::mem::forget(block);
std::mem::forget(registration);
}