Initial macOS detect implementation
This commit is contained in:
		
							parent
							
								
									18515319a8
								
							
						
					
					
						commit
						afb64df17c
					
				
							
								
								
									
										1
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										1
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							| 
						 | 
				
			
			@ -145,6 +145,7 @@ dependencies = [
 | 
			
		|||
 "anyhow",
 | 
			
		||||
 "cc",
 | 
			
		||||
 "enum-as-inner",
 | 
			
		||||
 "lazy_static",
 | 
			
		||||
 "lazycell",
 | 
			
		||||
 "libc",
 | 
			
		||||
 "log",
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -18,6 +18,9 @@ widestring = "0.4.3"
 | 
			
		|||
libc = "0.2.85"
 | 
			
		||||
scopeguard = "1.1.0"
 | 
			
		||||
 | 
			
		||||
[target.'cfg(target_os="macos")'.dependencies]
 | 
			
		||||
lazy_static = "1.4.0"
 | 
			
		||||
 | 
			
		||||
[build-dependencies]
 | 
			
		||||
cc = "1.0.66"
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -59,7 +59,16 @@ fn cc_config() {
 | 
			
		|||
 | 
			
		||||
#[cfg(target_os = "macos")]
 | 
			
		||||
fn cc_config() {
 | 
			
		||||
  // TODO
 | 
			
		||||
  println!("cargo:rerun-if-changed=src/mac/native.mm");
 | 
			
		||||
  println!("cargo:rerun-if-changed=src/mac/native.h");
 | 
			
		||||
  cc::Build::new()
 | 
			
		||||
    .cpp(true)
 | 
			
		||||
    .include("src/mac/native.h")
 | 
			
		||||
    .file("src/mac/native.mm")
 | 
			
		||||
    .compile("espansodetect");
 | 
			
		||||
  println!("cargo:rustc-link-lib=dylib=c++");
 | 
			
		||||
  println!("cargo:rustc-link-lib=static=espansodetect");
 | 
			
		||||
  println!("cargo:rustc-link-lib=framework=Cocoa");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn main() {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -227,6 +227,9 @@ fn key_sym_to_key(key_sym: i32) -> (Key, Option<Variant>) {
 | 
			
		|||
    0xFF56 => (PageDown, None),
 | 
			
		||||
    0xFF55 => (PageUp, None),
 | 
			
		||||
 | 
			
		||||
    // UI
 | 
			
		||||
    0xFF1B => (Escape, None),
 | 
			
		||||
 | 
			
		||||
    // Editing keys
 | 
			
		||||
    0xFF08 => (Backspace, None),
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -90,6 +90,9 @@ pub enum Key {
 | 
			
		|||
  PageDown,
 | 
			
		||||
  PageUp,
 | 
			
		||||
 | 
			
		||||
  // UI
 | 
			
		||||
  Escape,
 | 
			
		||||
 | 
			
		||||
  // Editing keys
 | 
			
		||||
  Backspace,
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -27,3 +27,10 @@ pub mod x11;
 | 
			
		|||
 | 
			
		||||
#[cfg(target_os = "linux")]
 | 
			
		||||
pub mod evdev;
 | 
			
		||||
 | 
			
		||||
#[cfg(target_os = "macos")]
 | 
			
		||||
pub mod mac;
 | 
			
		||||
 | 
			
		||||
#[cfg(target_os = "macos")]
 | 
			
		||||
#[macro_use]
 | 
			
		||||
extern crate lazy_static;
 | 
			
		||||
							
								
								
									
										343
									
								
								espanso-detect/src/mac/mod.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										343
									
								
								espanso-detect/src/mac/mod.rs
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,343 @@
 | 
			
		|||
/*
 | 
			
		||||
 * 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 <https://www.gnu.org/licenses/>.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
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::Status::*;
 | 
			
		||||
use crate::event::Variant::*;
 | 
			
		||||
use crate::event::{InputEvent, Key, KeyboardEvent, Variant};
 | 
			
		||||
use crate::event::{Key::*, MouseButton, MouseEvent};
 | 
			
		||||
 | 
			
		||||
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<Mutex<Option<Sender<InputEvent>>>> = 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<InputEvent> = 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 type CocoaSourceCallback = Box<dyn Fn(InputEvent)>;
 | 
			
		||||
pub struct CocoaSource {
 | 
			
		||||
  receiver: LazyCell<Receiver<InputEvent>>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[allow(clippy::new_without_default)]
 | 
			
		||||
impl CocoaSource {
 | 
			
		||||
  pub fn new() -> CocoaSource {
 | 
			
		||||
    Self {
 | 
			
		||||
      receiver: LazyCell::new(),
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  pub 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(())
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  pub fn eventloop(&self, event_callback: CocoaSourceCallback) {
 | 
			
		||||
    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 {
 | 
			
		||||
      panic!("Unable to start event loop if CocoaSource receiver is null");
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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<RawInputEvent> for Option<InputEvent> {
 | 
			
		||||
  fn from(raw: RawInputEvent) -> Option<InputEvent> {
 | 
			
		||||
    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<Variant>) {
 | 
			
		||||
  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<MouseButton> {
 | 
			
		||||
  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<InputEvent> = 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<InputEvent> = raw.into();
 | 
			
		||||
    assert_eq!(
 | 
			
		||||
      result.unwrap(),
 | 
			
		||||
      InputEvent::Mouse(MouseEvent {
 | 
			
		||||
        status: Released,
 | 
			
		||||
        button: MouseButton::Right,
 | 
			
		||||
      })
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										61
									
								
								espanso-detect/src/mac/native.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										61
									
								
								espanso-detect/src/mac/native.h
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,61 @@
 | 
			
		|||
/*
 | 
			
		||||
 * 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 <https://www.gnu.org/licenses/>.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#ifndef ESPANSO_DETECT_H
 | 
			
		||||
#define ESPANSO_DETECT_H
 | 
			
		||||
 | 
			
		||||
#include <stdint.h>
 | 
			
		||||
 | 
			
		||||
#define INPUT_EVENT_TYPE_KEYBOARD 1
 | 
			
		||||
#define INPUT_EVENT_TYPE_MOUSE    2
 | 
			
		||||
 | 
			
		||||
#define INPUT_STATUS_PRESSED      1
 | 
			
		||||
#define INPUT_STATUS_RELEASED     2
 | 
			
		||||
 | 
			
		||||
#define INPUT_LEFT_VARIANT 1
 | 
			
		||||
#define INPUT_RIGHT_VARIANT 2
 | 
			
		||||
 | 
			
		||||
#define INPUT_MOUSE_LEFT_BUTTON   1
 | 
			
		||||
#define INPUT_MOUSE_RIGHT_BUTTON  2
 | 
			
		||||
#define INPUT_MOUSE_MIDDLE_BUTTON 3
 | 
			
		||||
 | 
			
		||||
typedef struct {
 | 
			
		||||
  // Keyboard or Mouse event
 | 
			
		||||
  int32_t event_type;
 | 
			
		||||
 | 
			
		||||
  // Contains the string corresponding to the key, if any
 | 
			
		||||
  char buffer[24];
 | 
			
		||||
  // Length of the extracted string. Equals 0 if no string is extracted
 | 
			
		||||
  int32_t buffer_len;
 | 
			
		||||
  
 | 
			
		||||
  // Virtual key code of the pressed key in case of keyboard events
 | 
			
		||||
  // Mouse button code otherwise.
 | 
			
		||||
  int32_t key_code;
 | 
			
		||||
  
 | 
			
		||||
  // Pressed or Released status
 | 
			
		||||
  int32_t status;
 | 
			
		||||
} InputEvent;
 | 
			
		||||
 | 
			
		||||
typedef void (*EventCallback)(InputEvent data);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
// Initialize the event global monitor
 | 
			
		||||
extern "C" void * detect_initialize(EventCallback callback);
 | 
			
		||||
 | 
			
		||||
#endif //ESPANSO_DETECT_H
 | 
			
		||||
							
								
								
									
										78
									
								
								espanso-detect/src/mac/native.mm
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										78
									
								
								espanso-detect/src/mac/native.mm
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,78 @@
 | 
			
		|||
/*
 | 
			
		||||
 * 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 <https://www.gnu.org/licenses/>.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#include "native.h"
 | 
			
		||||
#import <AppKit/AppKit.h>
 | 
			
		||||
#import <Foundation/Foundation.h>
 | 
			
		||||
#include <Carbon/Carbon.h>
 | 
			
		||||
 | 
			
		||||
#include <string.h>
 | 
			
		||||
 | 
			
		||||
const unsigned long long FLAGS = NSEventMaskKeyDown | NSEventMaskKeyUp | NSEventMaskFlagsChanged | NSEventMaskLeftMouseDown | 
 | 
			
		||||
                                 NSEventMaskLeftMouseUp | NSEventMaskRightMouseDown | NSEventMaskRightMouseUp | 
 | 
			
		||||
                                 NSEventMaskOtherMouseDown | NSEventMaskOtherMouseUp;
 | 
			
		||||
 | 
			
		||||
void * detect_initialize(EventCallback callback) {
 | 
			
		||||
  dispatch_async(dispatch_get_main_queue(), ^(void) {
 | 
			
		||||
    [NSEvent addGlobalMonitorForEventsMatchingMask:FLAGS handler:^(NSEvent *event){
 | 
			
		||||
        InputEvent inputEvent = {};
 | 
			
		||||
        if (event.type == NSEventTypeKeyDown || event.type == NSEventTypeKeyUp ) {
 | 
			
		||||
          inputEvent.event_type = INPUT_EVENT_TYPE_KEYBOARD;
 | 
			
		||||
          inputEvent.status = (event.type == NSEventTypeKeyDown) ? INPUT_STATUS_PRESSED : INPUT_STATUS_RELEASED;
 | 
			
		||||
          inputEvent.key_code = event.keyCode;
 | 
			
		||||
 | 
			
		||||
          const char *chars = [event.characters UTF8String];
 | 
			
		||||
          strncpy(inputEvent.buffer, chars, 23);
 | 
			
		||||
          inputEvent.buffer_len = event.characters.length;
 | 
			
		||||
 | 
			
		||||
          callback(inputEvent);
 | 
			
		||||
        }else if (event.type == NSEventTypeLeftMouseDown || event.type == NSEventTypeRightMouseDown || event.type == NSEventTypeOtherMouseDown ||
 | 
			
		||||
                  event.type == NSEventTypeLeftMouseUp || event.type == NSEventTypeRightMouseUp || event.type == NSEventTypeOtherMouseUp) {
 | 
			
		||||
          inputEvent.event_type = INPUT_EVENT_TYPE_MOUSE;
 | 
			
		||||
          inputEvent.status = (event.type == NSEventTypeLeftMouseDown || event.type == NSEventTypeRightMouseDown ||
 | 
			
		||||
                               event.type == NSEventTypeOtherMouseDown) ? INPUT_STATUS_PRESSED : INPUT_STATUS_RELEASED;
 | 
			
		||||
          if (event.type == NSEventTypeLeftMouseDown || event.type == NSEventTypeLeftMouseUp) {
 | 
			
		||||
            inputEvent.key_code = INPUT_MOUSE_LEFT_BUTTON;
 | 
			
		||||
          } else if (event.type == NSEventTypeRightMouseDown || event.type == NSEventTypeRightMouseUp) {
 | 
			
		||||
            inputEvent.key_code = INPUT_MOUSE_RIGHT_BUTTON;
 | 
			
		||||
          } else if (event.type == NSEventTypeOtherMouseDown || event.type == NSEventTypeOtherMouseUp) {
 | 
			
		||||
            inputEvent.key_code = INPUT_MOUSE_MIDDLE_BUTTON;
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          callback(inputEvent);
 | 
			
		||||
        }else{
 | 
			
		||||
          // Modifier keys (SHIFT, CTRL, ecc) are handled as a separate case on macOS
 | 
			
		||||
          inputEvent.event_type = INPUT_EVENT_TYPE_KEYBOARD;
 | 
			
		||||
          inputEvent.key_code = event.keyCode;
 | 
			
		||||
 | 
			
		||||
          // To determine whether these keys are pressed or released, we have to analyze each case
 | 
			
		||||
          if (event.keyCode == kVK_Shift || event.keyCode == kVK_RightShift) {
 | 
			
		||||
            inputEvent.status = (([event modifierFlags] & NSEventModifierFlagShift) == 0) ? INPUT_STATUS_RELEASED : INPUT_STATUS_PRESSED;
 | 
			
		||||
          } else if (event.keyCode == kVK_Command || event.keyCode == kVK_RightCommand) {
 | 
			
		||||
            inputEvent.status = (([event modifierFlags] & NSEventModifierFlagCommand) == 0) ? INPUT_STATUS_RELEASED : INPUT_STATUS_PRESSED;
 | 
			
		||||
          } else if (event.keyCode == kVK_Control || event.keyCode == kVK_RightControl) {
 | 
			
		||||
            inputEvent.status = (([event modifierFlags] & NSEventModifierFlagControl) == 0) ? INPUT_STATUS_RELEASED : INPUT_STATUS_PRESSED;
 | 
			
		||||
          } else if (event.keyCode == kVK_Option || event.keyCode == kVK_RightOption) {
 | 
			
		||||
            inputEvent.status = (([event modifierFlags] & NSEventModifierFlagOption) == 0) ? INPUT_STATUS_RELEASED : INPUT_STATUS_PRESSED;
 | 
			
		||||
          }
 | 
			
		||||
          callback(inputEvent);
 | 
			
		||||
        }
 | 
			
		||||
    }];
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -260,6 +260,9 @@ fn key_code_to_key(key_code: i32) -> (Key, Option<Variant>) {
 | 
			
		|||
    0x22 => (PageDown, None),
 | 
			
		||||
    0x21 => (PageUp, None),
 | 
			
		||||
 | 
			
		||||
    // UI
 | 
			
		||||
    0x1B => (Escape, None),
 | 
			
		||||
 | 
			
		||||
    // Editing keys
 | 
			
		||||
    0x08 => (Backspace, None),
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -260,6 +260,9 @@ fn key_sym_to_key(key_sym: i32) -> (Key, Option<Variant>) {
 | 
			
		|||
    0xFF56 => (PageDown, None),
 | 
			
		||||
    0xFF55 => (PageUp, None),
 | 
			
		||||
 | 
			
		||||
    // UI keys
 | 
			
		||||
    0xFF1B => (Escape, None),
 | 
			
		||||
 | 
			
		||||
    // Editing keys
 | 
			
		||||
    0xFF08 => (Backspace, None),
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -46,26 +46,27 @@ fn main() {
 | 
			
		|||
    icon_paths: &icon_paths,
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  let handle = std::thread::spawn(move || {
 | 
			
		||||
  eventloop.initialize();
 | 
			
		||||
 | 
			
		||||
  let handle = std::thread::spawn(move || {
 | 
			
		||||
    //let mut source = espanso_detect::win32::Win32Source::new();
 | 
			
		||||
    //let mut source = espanso_detect::x11::X11Source::new();
 | 
			
		||||
    // source.initialize();
 | 
			
		||||
    // source.eventloop(Box::new(move |event: InputEvent| {
 | 
			
		||||
    //   println!("ev {:?}", event);
 | 
			
		||||
    //   match event {
 | 
			
		||||
    //     InputEvent::Mouse(_) => {}
 | 
			
		||||
    //     InputEvent::Keyboard(evt) => {
 | 
			
		||||
    //       if evt.key == espanso_detect::event::Key::Shift && evt.status == Status::Pressed {
 | 
			
		||||
    //         //remote.update_tray_icon(espanso_ui::icons::TrayIcon::Disabled);
 | 
			
		||||
    //         remote.show_notification("Espanso is running!");
 | 
			
		||||
    //       }
 | 
			
		||||
    //     }
 | 
			
		||||
    //   }
 | 
			
		||||
    // }));
 | 
			
		||||
    let mut source = espanso_detect::mac::CocoaSource::new();
 | 
			
		||||
    source.initialize();
 | 
			
		||||
    source.eventloop(Box::new(move |event: InputEvent| {
 | 
			
		||||
      println!("ev {:?}", event);
 | 
			
		||||
      match event {
 | 
			
		||||
        InputEvent::Mouse(_) => {}
 | 
			
		||||
        InputEvent::Keyboard(evt) => {
 | 
			
		||||
          if evt.key == espanso_detect::event::Key::Shift && evt.status == Status::Pressed {
 | 
			
		||||
            //remote.update_tray_icon(espanso_ui::icons::TrayIcon::Disabled);
 | 
			
		||||
            //remote.show_notification("Espanso is running!");
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }));
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  eventloop.initialize();
 | 
			
		||||
  eventloop.run(Box::new(move |event| {
 | 
			
		||||
    println!("ui {:?}", event);
 | 
			
		||||
    let menu = Menu::from(vec![
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue
	
	Block a user