/* * This file is part of espanso. * * Copyright (C) 2019-2021 Federico Terzi * * espanso is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * espanso is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with espanso. If not, see . */ use std::{ ffi::CStr, sync::{ mpsc::{channel, Receiver, Sender}, Arc, Mutex, }, }; use lazycell::LazyCell; use log::{error, trace, warn}; use anyhow::Result; use thiserror::Error; use crate::event::Variant::*; use crate::event::{InputEvent, Key, KeyboardEvent, Variant}; use crate::event::{Key::*, MouseButton, MouseEvent}; use crate::{event::Status::*, Source, SourceCallback}; const INPUT_EVENT_TYPE_KEYBOARD: i32 = 1; const INPUT_EVENT_TYPE_MOUSE: i32 = 2; const INPUT_STATUS_PRESSED: i32 = 1; const INPUT_STATUS_RELEASED: i32 = 2; const INPUT_MOUSE_LEFT_BUTTON: i32 = 1; const INPUT_MOUSE_RIGHT_BUTTON: i32 = 2; const INPUT_MOUSE_MIDDLE_BUTTON: i32 = 3; // Take a look at the native.h header file for an explanation of the fields #[repr(C)] pub struct RawInputEvent { pub event_type: i32, pub buffer: [u8; 24], pub buffer_len: i32, pub key_code: i32, pub status: i32, } #[allow(improper_ctypes)] #[link(name = "espansodetect", kind = "static")] extern "C" { pub fn detect_initialize(callback: extern "C" fn(event: RawInputEvent)); } lazy_static! { static ref CURRENT_SENDER: Arc>>> = Arc::new(Mutex::new(None)); } extern "C" fn native_callback(raw_event: RawInputEvent) { let lock = CURRENT_SENDER .lock() .expect("unable to acquire CocoaSource sender lock"); if let Some(sender) = lock.as_ref() { let event: Option = raw_event.into(); if let Some(event) = event { if let Err(error) = sender.send(event) { error!("Unable to send event to Cocoa Sender: {}", error); } } else { trace!("Unable to convert raw event to input event"); } } else { warn!("Lost raw event, as Cocoa Sender is not available"); } } pub struct CocoaSource { receiver: LazyCell>, } #[allow(clippy::new_without_default)] impl CocoaSource { pub fn new() -> CocoaSource { Self { receiver: LazyCell::new(), } } } impl Source for CocoaSource { fn initialize(&mut self) -> Result<()> { let (sender, receiver) = channel(); // Set the global sender { let mut lock = CURRENT_SENDER .lock() .expect("unable to acquire CocoaSource sender lock during initialization"); *lock = Some(sender); } unsafe { detect_initialize(native_callback) }; if self.receiver.fill(receiver).is_err() { error!("Unable to set CocoaSource receiver"); return Err(CocoaSourceError::Unknown().into()); } Ok(()) } fn eventloop(&self, event_callback: SourceCallback) -> Result<()> { if let Some(receiver) = self.receiver.borrow() { loop { let event = receiver.recv(); match event { Ok(event) => { event_callback(event); } Err(error) => { error!("CocoaSource receiver reported error: {}", error); break; } } } } else { error!("Unable to start event loop if CocoaSource receiver is null"); return Err(CocoaSourceError::Unknown().into()); } Ok(()) } } impl Drop for CocoaSource { fn drop(&mut self) { // Reset the global sender { let mut lock = CURRENT_SENDER .lock() .expect("unable to acquire CocoaSource sender lock during initialization"); *lock = None; } } } #[derive(Error, Debug)] pub enum CocoaSourceError { #[error("unknown error")] Unknown(), } impl From for Option { fn from(raw: RawInputEvent) -> Option { let status = match raw.status { INPUT_STATUS_RELEASED => Released, INPUT_STATUS_PRESSED => Pressed, _ => Pressed, }; match raw.event_type { // Keyboard events INPUT_EVENT_TYPE_KEYBOARD => { let (key, variant) = key_code_to_key(raw.key_code); let value = if raw.buffer_len > 0 { let raw_string_result = CStr::from_bytes_with_nul(&raw.buffer[..((raw.buffer_len + 1) as usize)]); match raw_string_result { Ok(c_string) => { let string_result = c_string.to_str(); match string_result { Ok(value) => Some(value.to_string()), Err(err) => { warn!("CocoaSource event utf8 conversion error: {}", err); None } } } Err(err) => { trace!("Received malformed event buffer: {}", err); None } } } else { None }; return Some(InputEvent::Keyboard(KeyboardEvent { key, value, status, variant, })); } // Mouse events INPUT_EVENT_TYPE_MOUSE => { let button = raw_to_mouse_button(raw.key_code); if let Some(button) = button { return Some(InputEvent::Mouse(MouseEvent { button, status })); } } _ => {} } None } } // Mappings from: https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key/Key_Values fn key_code_to_key(key_code: i32) -> (Key, Option) { match key_code { // Modifiers 0x3A => (Alt, Some(Left)), 0x3D => (Alt, Some(Right)), 0x39 => (CapsLock, None), // TODO 0x3B => (Control, Some(Left)), 0x3E => (Control, Some(Right)), 0x37 => (Meta, Some(Left)), 0x36 => (Meta, Some(Right)), 0x38 => (Shift, Some(Left)), 0x3C => (Shift, Some(Right)), // Whitespace 0x24 => (Enter, None), 0x30 => (Tab, None), 0x31 => (Space, None), // Navigation 0x7D => (ArrowDown, None), 0x7B => (ArrowLeft, None), 0x7C => (ArrowRight, None), 0x7E => (ArrowUp, None), 0x77 => (End, None), 0x73 => (Home, None), 0x79 => (PageDown, None), 0x74 => (PageUp, None), // UI 0x35 => (Escape, None), // Editing keys 0x33 => (Backspace, None), // Function keys 0x7A => (F1, None), 0x78 => (F2, None), 0x63 => (F3, None), 0x76 => (F4, None), 0x60 => (F5, None), 0x61 => (F6, None), 0x62 => (F7, None), 0x64 => (F8, None), 0x65 => (F9, None), 0x6D => (F10, None), 0x67 => (F11, None), 0x6F => (F12, None), 0x69 => (F13, None), 0x6B => (F14, None), 0x71 => (F15, None), 0x6A => (F16, None), 0x40 => (F17, None), 0x4F => (F18, None), 0x50 => (F19, None), 0x5A => (F20, None), // Other keys, includes the raw code provided by the operating system _ => (Other(key_code), None), } } fn raw_to_mouse_button(raw: i32) -> Option { match raw { INPUT_MOUSE_LEFT_BUTTON => Some(MouseButton::Left), INPUT_MOUSE_RIGHT_BUTTON => Some(MouseButton::Right), INPUT_MOUSE_MIDDLE_BUTTON => Some(MouseButton::Middle), _ => None, } } #[cfg(test)] mod tests { use std::ffi::CString; use super::*; fn default_raw_input_event() -> RawInputEvent { RawInputEvent { event_type: INPUT_EVENT_TYPE_KEYBOARD, buffer: [0; 24], buffer_len: 0, key_code: 0, status: INPUT_STATUS_PRESSED, } } #[test] fn raw_to_input_event_keyboard_works_correctly() { let c_string = CString::new("k".to_string()).unwrap(); let mut buffer: [u8; 24] = [0; 24]; buffer[..1].copy_from_slice(c_string.as_bytes()); let mut raw = default_raw_input_event(); raw.buffer = buffer; raw.buffer_len = 1; raw.status = INPUT_STATUS_RELEASED; raw.key_code = 40; let result: Option = raw.into(); assert_eq!( result.unwrap(), InputEvent::Keyboard(KeyboardEvent { key: Other(40), status: Released, value: Some("k".to_string()), variant: None, }) ); } #[test] fn raw_to_input_event_mouse_works_correctly() { let mut raw = default_raw_input_event(); raw.event_type = INPUT_EVENT_TYPE_MOUSE; raw.status = INPUT_STATUS_RELEASED; raw.key_code = INPUT_MOUSE_RIGHT_BUTTON; let result: Option = raw.into(); assert_eq!( result.unwrap(), InputEvent::Mouse(MouseEvent { status: Released, button: MouseButton::Right, }) ); } }