app_lib/
tray_popover_escape_macos.rs

1//! Tray popover: Escape closes the window.
2//!
3//! On macOS the popover is an undecorated `NSPanel` + `WKWebView`. The webview often never
4//! becomes first responder, so JS `keydown` never runs. `NSEvent` local monitoring runs on the
5//! main thread for the active app only (not a system-wide global shortcut).
6
7#[cfg(target_os = "macos")]
8pub fn install(app: tauri::AppHandle) {
9    use block2::RcBlock;
10    use objc2_app_kit::{NSEvent, NSEventMask};
11    use std::ops::Deref;
12    use std::ptr::NonNull;
13    use tauri::Manager;
14
15    let app = app.clone();
16    let block = RcBlock::new(move |event: NonNull<NSEvent>| -> *mut NSEvent {
17        let e = unsafe { event.as_ref() };
18        if e.keyCode() != 53 {
19            return event.as_ptr();
20        }
21        let Some(popover) = app.get_webview_window("tray-popover") else {
22            return event.as_ptr();
23        };
24        if !popover.is_visible().unwrap_or(false) {
25            return event.as_ptr();
26        }
27        let _ = popover.hide();
28        std::ptr::null_mut()
29    });
30
31    let handler: &block2::DynBlock<dyn Fn(NonNull<NSEvent>) -> *mut NSEvent> = block.deref();
32    unsafe {
33        if let Some(monitor) = NSEvent::addLocalMonitorForEventsMatchingMask_handler(
34            NSEventMask::KeyDown,
35            handler,
36        ) {
37            /* Monitor must stay alive; we never call `removeMonitor`. */
38            std::mem::forget(monitor);
39        }
40    }
41}
42
43#[cfg(not(target_os = "macos"))]
44pub fn install(_app: tauri::AppHandle) {}