pub use openlogi_core::binding::ButtonId;
#[derive(Clone, Debug)]
pub enum MouseEvent {
Button {
id: ButtonId,
pressed: bool,
},
Scroll {
delta_x: f32,
delta_y: f32,
},
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum EventDisposition {
PassThrough,
Suppress,
}
#[derive(Debug, thiserror::Error)]
pub enum HookError {
#[error("mouse event hook is not supported on this platform")]
Unsupported,
#[error(
"macOS Accessibility permission is required to capture mouse events; \
grant it in System Settings → Privacy & Security → Accessibility"
)]
AccessibilityDenied,
#[error("CGEventTap setup failed: {0}")]
MacOsTap(String),
#[cfg(target_os = "linux")]
#[error(
"no mouse device found under /dev/input; \
ensure a pointing device is connected and the process has read permission \
(add user to the `input` group or add a udev rule)"
)]
NoDeviceFound,
#[cfg(target_os = "linux")]
#[error("Linux input error: {0}")]
Linux(#[source] std::io::Error),
}
pub struct Hook {
#[cfg(target_os = "macos")]
inner: Option<macos::HookInner>,
#[cfg(target_os = "linux")]
inner: Option<linux::HookInner>,
#[cfg(not(any(target_os = "macos", target_os = "linux")))]
never: std::convert::Infallible,
}
impl Drop for Hook {
fn drop(&mut self) {
#[cfg(target_os = "macos")]
if let Some(inner) = self.inner.take() {
macos::stop(inner);
}
#[cfg(target_os = "linux")]
if let Some(inner) = self.inner.take() {
linux::stop(inner);
}
#[cfg(not(any(target_os = "macos", target_os = "linux")))]
{}
}
}
impl Hook {
pub fn start(
cb: impl Fn(MouseEvent) -> EventDisposition + Send + Sync + 'static,
) -> Result<Self, HookError> {
#[cfg(target_os = "macos")]
{
macos::start(cb).map(|inner| Self { inner: Some(inner) })
}
#[cfg(target_os = "linux")]
{
linux::start(cb).map(|inner| Self { inner: Some(inner) })
}
#[cfg(not(any(target_os = "macos", target_os = "linux")))]
{
let _ = cb;
Err(HookError::Unsupported)
}
}
#[cfg_attr(
not(any(target_os = "macos", target_os = "linux")),
allow(
unused_mut,
reason = "`mut self` is only consumed by macOS and Linux teardown paths"
)
)]
pub fn stop(mut self) {
#[cfg(target_os = "macos")]
if let Some(inner) = self.inner.take() {
macos::stop(inner);
}
#[cfg(target_os = "linux")]
if let Some(inner) = self.inner.take() {
linux::stop(inner);
}
#[cfg(not(any(target_os = "macos", target_os = "linux")))]
match self.never {}
}
#[must_use]
pub fn has_accessibility() -> bool {
#[cfg(target_os = "macos")]
{
macos::has_accessibility()
}
#[cfg(not(target_os = "macos"))]
{
true
}
}
pub fn prompt_accessibility() {
#[cfg(target_os = "macos")]
{
macos::prompt_accessibility();
}
}
}
#[must_use]
pub fn frontmost_bundle_id() -> Option<String> {
#[cfg(target_os = "macos")]
{
macos::frontmost_bundle_id()
}
#[cfg(target_os = "linux")]
{
linux::frontmost_bundle_id()
}
#[cfg(not(any(target_os = "macos", target_os = "linux")))]
{
None
}
}
#[cfg(target_os = "macos")]
mod macos;
#[cfg(target_os = "linux")]
mod linux;
#[cfg(test)]
mod tests;