#![cfg_attr(any(target_os = "ios", target_os = "android"), allow(dead_code))]
use std::sync::Arc;
use tauri::Runtime;
use crate::error::{Error, Result};
pub(crate) type Hook = Arc<dyn Fn() + Send + Sync>;
pub(crate) fn watch_right_clicks<R: Runtime>(window: &tauri::Window<R>, label: &str, hook: Hook) -> Result<()> {
platform::watch_right_clicks(window, label, hook)
}
pub(crate) fn forget(label: &str) {
platform::forget(label);
}
pub(crate) fn cover_menu_bar<R: Runtime>(window: &tauri::Window<R>) -> Result<()> {
platform::cover_menu_bar(window)
}
fn native_error(what: &str, error: impl std::fmt::Display) -> Error {
Error::Render {
reason: format!("{what}: {error}"),
}
}
#[cfg(target_os = "macos")]
mod platform {
use std::ptr::NonNull;
use std::sync::{Arc, Mutex, Once, PoisonError};
use block2::RcBlock;
use objc2_app_kit::{NSEvent, NSEventMask, NSStatusWindowLevel, NSWindow};
use tauri::Runtime;
use super::{native_error, Hook};
use crate::error::Result;
struct Watched {
label: String,
number: isize,
hook: Hook,
}
static WATCHED: Mutex<Vec<Watched>> = Mutex::new(Vec::new());
fn watched() -> std::sync::MutexGuard<'static, Vec<Watched>> {
WATCHED.lock().unwrap_or_else(PoisonError::into_inner)
}
fn ns_window<R: Runtime>(window: &tauri::Window<R>) -> Result<NonNull<NSWindow>> {
let pointer = window.ns_window().map_err(|error| native_error("no NSWindow for an output", error))?;
NonNull::new(pointer.cast::<NSWindow>()).ok_or_else(|| native_error("no NSWindow for an output", "null"))
}
pub(super) fn cover_menu_bar<R: Runtime>(window: &tauri::Window<R>) -> Result<()> {
let ns_window = ns_window(window)?;
unsafe { ns_window.as_ref() }.setLevel(NSStatusWindowLevel);
Ok(())
}
pub(super) fn watch_right_clicks<R: Runtime>(window: &tauri::Window<R>, label: &str, hook: Hook) -> Result<()> {
let ns_window = ns_window(window)?;
let number = unsafe { ns_window.as_ref() }.windowNumber();
watched().push(Watched {
label: label.to_string(),
number,
hook,
});
install_monitor();
Ok(())
}
pub(super) fn forget(label: &str) {
watched().retain(|watched| watched.label != label);
}
fn install_monitor() {
static INSTALLED: Once = Once::new();
INSTALLED.call_once(|| {
let block = RcBlock::new(|event: NonNull<NSEvent>| -> *mut NSEvent {
let number = unsafe { event.as_ref() }.windowNumber();
let hook = watched().iter().find(|watched| watched.number == number).map(|watched| Arc::clone(&watched.hook));
if let Some(hook) = hook {
hook();
}
event.as_ptr()
});
let monitor = unsafe { NSEvent::addLocalMonitorForEventsMatchingMask_handler(NSEventMask::RightMouseDown, &block) };
std::mem::forget(monitor);
});
}
}
#[cfg(windows)]
mod platform {
use raw_window_handle::{HasWindowHandle, RawWindowHandle};
use tauri::Runtime;
use windows::Win32::Foundation::{HWND, LPARAM, LRESULT, WPARAM};
use windows::Win32::UI::Shell::{DefSubclassProc, RemoveWindowSubclass, SetWindowSubclass};
use windows::Win32::UI::WindowsAndMessaging::{WM_NCDESTROY, WM_RBUTTONUP};
use super::{native_error, Hook};
use crate::error::Result;
const SUBCLASS: usize = 0x7674_6f6d;
unsafe extern "system" fn subclass(hwnd: HWND, message: u32, wparam: WPARAM, lparam: LPARAM, id: usize, data: usize) -> LRESULT {
let hook = data as *const Hook;
match message {
WM_RBUTTONUP => (unsafe { &*hook })(),
WM_NCDESTROY => {
unsafe {
let _ = RemoveWindowSubclass(hwnd, Some(subclass), id);
drop(Box::from_raw(hook.cast_mut()));
}
}
_ => {}
}
unsafe { DefSubclassProc(hwnd, message, wparam, lparam) }
}
pub(super) fn watch_right_clicks<R: Runtime>(window: &tauri::Window<R>, _label: &str, hook: Hook) -> Result<()> {
let handle = window.window_handle().map_err(|error| native_error("no native handle for an output", error))?;
let RawWindowHandle::Win32(win32) = handle.as_raw() else {
return Err(native_error("an output window", "not a Win32 window"));
};
let hwnd = HWND(win32.hwnd.get() as *mut std::ffi::c_void);
let data = Box::into_raw(Box::new(hook));
let installed = unsafe { SetWindowSubclass(hwnd, Some(subclass), SUBCLASS, data as usize) };
if !installed.as_bool() {
drop(unsafe { Box::from_raw(data) });
return Err(native_error("an output window", "Windows would not subclass it"));
}
Ok(())
}
pub(super) fn forget(_label: &str) {}
pub(super) fn cover_menu_bar<R: Runtime>(_window: &tauri::Window<R>) -> Result<()> {
Ok(())
}
}
#[cfg(target_os = "linux")]
mod platform {
use gtk::prelude::*;
use tauri::Runtime;
use super::{native_error, Hook};
use crate::error::Result;
const SECONDARY: u32 = 3;
pub(super) fn watch_right_clicks<R: Runtime>(window: &tauri::Window<R>, _label: &str, hook: Hook) -> Result<()> {
let gtk_window = window.gtk_window().map_err(|error| native_error("no GTK window for an output", error))?;
gtk_window.add_events(gtk::gdk::EventMask::BUTTON_PRESS_MASK);
gtk_window.connect_button_press_event(move |_, event| {
if event.button() == SECONDARY && event.event_type() == gtk::gdk::EventType::ButtonPress {
hook();
}
gtk::glib::Propagation::Proceed
});
Ok(())
}
pub(super) fn forget(_label: &str) {}
pub(super) fn cover_menu_bar<R: Runtime>(_window: &tauri::Window<R>) -> Result<()> {
Ok(())
}
}
#[cfg(not(any(target_os = "macos", windows, target_os = "linux")))]
mod platform {
use tauri::Runtime;
use super::Hook;
use crate::error::Result;
pub(super) fn watch_right_clicks<R: Runtime>(_window: &tauri::Window<R>, _label: &str, _hook: Hook) -> Result<()> {
Ok(())
}
pub(super) fn forget(_label: &str) {}
pub(super) fn cover_menu_bar<R: Runtime>(_window: &tauri::Window<R>) -> Result<()> {
Ok(())
}
}