First draft of hotkey support on macOS
This commit is contained in:
		
							parent
							
								
									0ae9b60175
								
							
						
					
					
						commit
						89805a0248
					
				
							
								
								
									
										21
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										21
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							| 
						 | 
				
			
			@ -260,6 +260,7 @@ dependencies = [
 | 
			
		|||
 "lazycell",
 | 
			
		||||
 "libc",
 | 
			
		||||
 "log",
 | 
			
		||||
 "regex",
 | 
			
		||||
 "scopeguard",
 | 
			
		||||
 "thiserror",
 | 
			
		||||
 "widestring",
 | 
			
		||||
| 
						 | 
				
			
			@ -521,12 +522,6 @@ dependencies = [
 | 
			
		|||
 "objc",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "once_cell"
 | 
			
		||||
version = "1.5.2"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "13bd41f508810a131401606d54ac32a467c97172d74ba7662562ebba5ad07fa0"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "pkg-config"
 | 
			
		||||
version = "0.3.19"
 | 
			
		||||
| 
						 | 
				
			
			@ -613,14 +608,13 @@ dependencies = [
 | 
			
		|||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "regex"
 | 
			
		||||
version = "1.4.3"
 | 
			
		||||
version = "1.4.4"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "d9251239e129e16308e70d853559389de218ac275b515068abc96829d05b948a"
 | 
			
		||||
checksum = "54fd1046a3107eb58f42de31d656fee6853e5d276c455fd943742dce89fc3dd3"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "aho-corasick",
 | 
			
		||||
 "memchr",
 | 
			
		||||
 "regex-syntax",
 | 
			
		||||
 "thread_local",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
| 
						 | 
				
			
			@ -802,15 +796,6 @@ dependencies = [
 | 
			
		|||
 "syn 1.0.60",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "thread_local"
 | 
			
		||||
version = "1.1.3"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "8018d24e04c95ac8790716a5987d0fec4f8b27249ffa0f7d33f1369bdfb88cbd"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "once_cell",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "time"
 | 
			
		||||
version = "0.1.44"
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -15,6 +15,7 @@ log = "0.4.14"
 | 
			
		|||
lazycell = "1.3.0"
 | 
			
		||||
anyhow = "1.0.38"
 | 
			
		||||
thiserror = "1.0.23"
 | 
			
		||||
regex = "1.4.3"
 | 
			
		||||
 | 
			
		||||
[target.'cfg(windows)'.dependencies]
 | 
			
		||||
widestring = "0.4.3"
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -75,6 +75,7 @@ fn cc_config() {
 | 
			
		|||
  println!("cargo:rustc-link-lib=dylib=c++");
 | 
			
		||||
  println!("cargo:rustc-link-lib=static=espansodetect");
 | 
			
		||||
  println!("cargo:rustc-link-lib=framework=Cocoa");
 | 
			
		||||
  println!("cargo:rustc-link-lib=framework=Carbon");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn main() {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -24,6 +24,7 @@ use enum_as_inner::EnumAsInner;
 | 
			
		|||
pub enum InputEvent {
 | 
			
		||||
  Mouse(MouseEvent),
 | 
			
		||||
  Keyboard(KeyboardEvent),
 | 
			
		||||
  HotKey(HotKeyEvent),
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, PartialEq)]
 | 
			
		||||
| 
						 | 
				
			
			@ -121,3 +122,8 @@ pub enum Key {
 | 
			
		|||
  // Other keys, includes the raw code provided by the operating system
 | 
			
		||||
  Other(i32),
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, PartialEq)]
 | 
			
		||||
pub struct HotKeyEvent {
 | 
			
		||||
  pub hotkey_id: i32,
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										545
									
								
								espanso-detect/src/hotkey/keys.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										545
									
								
								espanso-detect/src/hotkey/keys.rs
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,545 @@
 | 
			
		|||
/*
 | 
			
		||||
 * 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::fmt::Display;
 | 
			
		||||
 | 
			
		||||
use regex::Regex;
 | 
			
		||||
 | 
			
		||||
lazy_static! {
 | 
			
		||||
  static ref RAW_PARSER: Regex = Regex::new(r"^RAW\((\d+)\)$").unwrap();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, PartialEq, Clone)]
 | 
			
		||||
pub enum ShortcutKey {
 | 
			
		||||
  Alt,
 | 
			
		||||
  Control,
 | 
			
		||||
  Meta,
 | 
			
		||||
  Shift,
 | 
			
		||||
 | 
			
		||||
  Enter,
 | 
			
		||||
  Tab,
 | 
			
		||||
  Space,
 | 
			
		||||
  Insert,
 | 
			
		||||
 | 
			
		||||
  // Navigation
 | 
			
		||||
  ArrowDown,
 | 
			
		||||
  ArrowLeft,
 | 
			
		||||
  ArrowRight,
 | 
			
		||||
  ArrowUp,
 | 
			
		||||
  End,
 | 
			
		||||
  Home,
 | 
			
		||||
  PageDown,
 | 
			
		||||
  PageUp,
 | 
			
		||||
 | 
			
		||||
  // Function ShortcutKeys
 | 
			
		||||
  F1,
 | 
			
		||||
  F2,
 | 
			
		||||
  F3,
 | 
			
		||||
  F4,
 | 
			
		||||
  F5,
 | 
			
		||||
  F6,
 | 
			
		||||
  F7,
 | 
			
		||||
  F8,
 | 
			
		||||
  F9,
 | 
			
		||||
  F10,
 | 
			
		||||
  F11,
 | 
			
		||||
  F12,
 | 
			
		||||
  F13,
 | 
			
		||||
  F14,
 | 
			
		||||
  F15,
 | 
			
		||||
  F16,
 | 
			
		||||
  F17,
 | 
			
		||||
  F18,
 | 
			
		||||
  F19,
 | 
			
		||||
  F20,
 | 
			
		||||
 | 
			
		||||
  // Alphabet
 | 
			
		||||
  A,
 | 
			
		||||
  B,
 | 
			
		||||
  C,
 | 
			
		||||
  D,
 | 
			
		||||
  E,
 | 
			
		||||
  F,
 | 
			
		||||
  G,
 | 
			
		||||
  H,
 | 
			
		||||
  I,
 | 
			
		||||
  J,
 | 
			
		||||
  K,
 | 
			
		||||
  L,
 | 
			
		||||
  M,
 | 
			
		||||
  N,
 | 
			
		||||
  O,
 | 
			
		||||
  P,
 | 
			
		||||
  Q,
 | 
			
		||||
  R,
 | 
			
		||||
  S,
 | 
			
		||||
  T,
 | 
			
		||||
  U,
 | 
			
		||||
  V,
 | 
			
		||||
  W,
 | 
			
		||||
  X,
 | 
			
		||||
  Y,
 | 
			
		||||
  Z,
 | 
			
		||||
 | 
			
		||||
  // Numbers
 | 
			
		||||
  N0,
 | 
			
		||||
  N1,
 | 
			
		||||
  N2,
 | 
			
		||||
  N3,
 | 
			
		||||
  N4,
 | 
			
		||||
  N5,
 | 
			
		||||
  N6,
 | 
			
		||||
  N7,
 | 
			
		||||
  N8,
 | 
			
		||||
  N9,
 | 
			
		||||
 | 
			
		||||
  // Numpad
 | 
			
		||||
  Numpad0,
 | 
			
		||||
  Numpad1,
 | 
			
		||||
  Numpad2,
 | 
			
		||||
  Numpad3,
 | 
			
		||||
  Numpad4,
 | 
			
		||||
  Numpad5,
 | 
			
		||||
  Numpad6,
 | 
			
		||||
  Numpad7,
 | 
			
		||||
  Numpad8,
 | 
			
		||||
  Numpad9,
 | 
			
		||||
 | 
			
		||||
  // Specify the raw platform-specific virtual key code.
 | 
			
		||||
  Raw(u32),
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Display for ShortcutKey {
 | 
			
		||||
  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
 | 
			
		||||
    match *self {
 | 
			
		||||
      ShortcutKey::Alt => write!(f, "ALT"),
 | 
			
		||||
      ShortcutKey::Control => write!(f, "CTRL"),
 | 
			
		||||
      ShortcutKey::Meta => write!(f, "META"),
 | 
			
		||||
      ShortcutKey::Shift => write!(f, "SHIFT"),
 | 
			
		||||
      ShortcutKey::Enter => write!(f, "ENTER"),
 | 
			
		||||
      ShortcutKey::Tab => write!(f, "TAB"),
 | 
			
		||||
      ShortcutKey::Space => write!(f, "SPACE"),
 | 
			
		||||
      ShortcutKey::Insert => write!(f, "INSERT"),
 | 
			
		||||
      ShortcutKey::ArrowDown => write!(f, "DOWN"),
 | 
			
		||||
      ShortcutKey::ArrowLeft => write!(f, "LEFT"),
 | 
			
		||||
      ShortcutKey::ArrowRight => write!(f, "RIGHT"),
 | 
			
		||||
      ShortcutKey::ArrowUp => write!(f, "UP"),
 | 
			
		||||
      ShortcutKey::End => write!(f, "END"),
 | 
			
		||||
      ShortcutKey::Home => write!(f, "HOME"),
 | 
			
		||||
      ShortcutKey::PageDown => write!(f, "PAGEDOWN"),
 | 
			
		||||
      ShortcutKey::PageUp => write!(f, "PAGEUP"),
 | 
			
		||||
      ShortcutKey::F1 => write!(f, "F1"),
 | 
			
		||||
      ShortcutKey::F2 => write!(f, "F2"),
 | 
			
		||||
      ShortcutKey::F3 => write!(f, "F3"),
 | 
			
		||||
      ShortcutKey::F4 => write!(f, "F4"),
 | 
			
		||||
      ShortcutKey::F5 => write!(f, "F5"),
 | 
			
		||||
      ShortcutKey::F6 => write!(f, "F6"),
 | 
			
		||||
      ShortcutKey::F7 => write!(f, "F7"),
 | 
			
		||||
      ShortcutKey::F8 => write!(f, "F8"),
 | 
			
		||||
      ShortcutKey::F9 => write!(f, "F9"),
 | 
			
		||||
      ShortcutKey::F10 => write!(f, "F10"),
 | 
			
		||||
      ShortcutKey::F11 => write!(f, "F11"),
 | 
			
		||||
      ShortcutKey::F12 => write!(f, "F12"),
 | 
			
		||||
      ShortcutKey::F13 => write!(f, "F13"),
 | 
			
		||||
      ShortcutKey::F14 => write!(f, "F14"),
 | 
			
		||||
      ShortcutKey::F15 => write!(f, "F15"),
 | 
			
		||||
      ShortcutKey::F16 => write!(f, "F16"),
 | 
			
		||||
      ShortcutKey::F17 => write!(f, "F17"),
 | 
			
		||||
      ShortcutKey::F18 => write!(f, "F18"),
 | 
			
		||||
      ShortcutKey::F19 => write!(f, "F19"),
 | 
			
		||||
      ShortcutKey::F20 => write!(f, "F20"),
 | 
			
		||||
      ShortcutKey::A => write!(f, "A"),
 | 
			
		||||
      ShortcutKey::B => write!(f, "B"),
 | 
			
		||||
      ShortcutKey::C => write!(f, "C"),
 | 
			
		||||
      ShortcutKey::D => write!(f, "D"),
 | 
			
		||||
      ShortcutKey::E => write!(f, "E"),
 | 
			
		||||
      ShortcutKey::F => write!(f, "F"),
 | 
			
		||||
      ShortcutKey::G => write!(f, "G"),
 | 
			
		||||
      ShortcutKey::H => write!(f, "H"),
 | 
			
		||||
      ShortcutKey::I => write!(f, "I"),
 | 
			
		||||
      ShortcutKey::J => write!(f, "J"),
 | 
			
		||||
      ShortcutKey::K => write!(f, "K"),
 | 
			
		||||
      ShortcutKey::L => write!(f, "L"),
 | 
			
		||||
      ShortcutKey::M => write!(f, "M"),
 | 
			
		||||
      ShortcutKey::N => write!(f, "N"),
 | 
			
		||||
      ShortcutKey::O => write!(f, "O"),
 | 
			
		||||
      ShortcutKey::P => write!(f, "P"),
 | 
			
		||||
      ShortcutKey::Q => write!(f, "Q"),
 | 
			
		||||
      ShortcutKey::R => write!(f, "R"),
 | 
			
		||||
      ShortcutKey::S => write!(f, "S"),
 | 
			
		||||
      ShortcutKey::T => write!(f, "T"),
 | 
			
		||||
      ShortcutKey::U => write!(f, "U"),
 | 
			
		||||
      ShortcutKey::V => write!(f, "V"),
 | 
			
		||||
      ShortcutKey::W => write!(f, "W"),
 | 
			
		||||
      ShortcutKey::X => write!(f, "X"),
 | 
			
		||||
      ShortcutKey::Y => write!(f, "Y"),
 | 
			
		||||
      ShortcutKey::Z => write!(f, "Z"),
 | 
			
		||||
      ShortcutKey::N0 => write!(f, "0"),
 | 
			
		||||
      ShortcutKey::N1 => write!(f, "1"),
 | 
			
		||||
      ShortcutKey::N2 => write!(f, "2"),
 | 
			
		||||
      ShortcutKey::N3 => write!(f, "3"),
 | 
			
		||||
      ShortcutKey::N4 => write!(f, "4"),
 | 
			
		||||
      ShortcutKey::N5 => write!(f, "5"),
 | 
			
		||||
      ShortcutKey::N6 => write!(f, "6"),
 | 
			
		||||
      ShortcutKey::N7 => write!(f, "7"),
 | 
			
		||||
      ShortcutKey::N8 => write!(f, "8"),
 | 
			
		||||
      ShortcutKey::N9 => write!(f, "9"),
 | 
			
		||||
      ShortcutKey::Numpad0 => write!(f, "NUMPAD0"),
 | 
			
		||||
      ShortcutKey::Numpad1 => write!(f, "NUMPAD1"),
 | 
			
		||||
      ShortcutKey::Numpad2 => write!(f, "NUMPAD2"),
 | 
			
		||||
      ShortcutKey::Numpad3 => write!(f, "NUMPAD3"),
 | 
			
		||||
      ShortcutKey::Numpad4 => write!(f, "NUMPAD4"),
 | 
			
		||||
      ShortcutKey::Numpad5 => write!(f, "NUMPAD5"),
 | 
			
		||||
      ShortcutKey::Numpad6 => write!(f, "NUMPAD6"),
 | 
			
		||||
      ShortcutKey::Numpad7 => write!(f, "NUMPAD7"),
 | 
			
		||||
      ShortcutKey::Numpad8 => write!(f, "NUMPAD8"),
 | 
			
		||||
      ShortcutKey::Numpad9 => write!(f, "NUMPAD9"),
 | 
			
		||||
      ShortcutKey::Raw(code) => write!(f, "RAW({})", code),
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl ShortcutKey {
 | 
			
		||||
  pub fn parse(key: &str) -> Option<ShortcutKey> {
 | 
			
		||||
    let parsed = match key {
 | 
			
		||||
      "ALT" | "OPTION" => Some(ShortcutKey::Alt),
 | 
			
		||||
      "CTRL" => Some(ShortcutKey::Control),
 | 
			
		||||
      "META" | "CMD" => Some(ShortcutKey::Meta),
 | 
			
		||||
      "SHIFT" => Some(ShortcutKey::Shift),
 | 
			
		||||
      "ENTER" => Some(ShortcutKey::Enter),
 | 
			
		||||
      "TAB" => Some(ShortcutKey::Tab),
 | 
			
		||||
      "SPACE" => Some(ShortcutKey::Space),
 | 
			
		||||
      "INSERT" => Some(ShortcutKey::Insert),
 | 
			
		||||
      "DOWN" => Some(ShortcutKey::ArrowDown),
 | 
			
		||||
      "LEFT" => Some(ShortcutKey::ArrowLeft),
 | 
			
		||||
      "RIGHT" => Some(ShortcutKey::ArrowRight),
 | 
			
		||||
      "UP" => Some(ShortcutKey::ArrowUp),
 | 
			
		||||
      "END" => Some(ShortcutKey::End),
 | 
			
		||||
      "HOME" => Some(ShortcutKey::Home),
 | 
			
		||||
      "PAGEDOWN" => Some(ShortcutKey::PageDown),
 | 
			
		||||
      "PAGEUP" => Some(ShortcutKey::PageUp),
 | 
			
		||||
      "F1" => Some(ShortcutKey::F1),
 | 
			
		||||
      "F2" => Some(ShortcutKey::F2),
 | 
			
		||||
      "F3" => Some(ShortcutKey::F3),
 | 
			
		||||
      "F4" => Some(ShortcutKey::F4),
 | 
			
		||||
      "F5" => Some(ShortcutKey::F5),
 | 
			
		||||
      "F6" => Some(ShortcutKey::F6),
 | 
			
		||||
      "F7" => Some(ShortcutKey::F7),
 | 
			
		||||
      "F8" => Some(ShortcutKey::F8),
 | 
			
		||||
      "F9" => Some(ShortcutKey::F9),
 | 
			
		||||
      "F10" => Some(ShortcutKey::F10),
 | 
			
		||||
      "F11" => Some(ShortcutKey::F11),
 | 
			
		||||
      "F12" => Some(ShortcutKey::F12),
 | 
			
		||||
      "F13" => Some(ShortcutKey::F13),
 | 
			
		||||
      "F14" => Some(ShortcutKey::F14),
 | 
			
		||||
      "F15" => Some(ShortcutKey::F15),
 | 
			
		||||
      "F16" => Some(ShortcutKey::F16),
 | 
			
		||||
      "F17" => Some(ShortcutKey::F17),
 | 
			
		||||
      "F18" => Some(ShortcutKey::F18),
 | 
			
		||||
      "F19" => Some(ShortcutKey::F19),
 | 
			
		||||
      "F20" => Some(ShortcutKey::F20),
 | 
			
		||||
      "A" => Some(ShortcutKey::A),
 | 
			
		||||
      "B" => Some(ShortcutKey::B),
 | 
			
		||||
      "C" => Some(ShortcutKey::C),
 | 
			
		||||
      "D" => Some(ShortcutKey::D),
 | 
			
		||||
      "E" => Some(ShortcutKey::E),
 | 
			
		||||
      "F" => Some(ShortcutKey::F),
 | 
			
		||||
      "G" => Some(ShortcutKey::G),
 | 
			
		||||
      "H" => Some(ShortcutKey::H),
 | 
			
		||||
      "I" => Some(ShortcutKey::I),
 | 
			
		||||
      "J" => Some(ShortcutKey::J),
 | 
			
		||||
      "K" => Some(ShortcutKey::K),
 | 
			
		||||
      "L" => Some(ShortcutKey::L),
 | 
			
		||||
      "M" => Some(ShortcutKey::M),
 | 
			
		||||
      "N" => Some(ShortcutKey::N),
 | 
			
		||||
      "O" => Some(ShortcutKey::O),
 | 
			
		||||
      "P" => Some(ShortcutKey::P),
 | 
			
		||||
      "Q" => Some(ShortcutKey::Q),
 | 
			
		||||
      "R" => Some(ShortcutKey::R),
 | 
			
		||||
      "S" => Some(ShortcutKey::S),
 | 
			
		||||
      "T" => Some(ShortcutKey::T),
 | 
			
		||||
      "U" => Some(ShortcutKey::U),
 | 
			
		||||
      "V" => Some(ShortcutKey::V),
 | 
			
		||||
      "W" => Some(ShortcutKey::W),
 | 
			
		||||
      "X" => Some(ShortcutKey::X),
 | 
			
		||||
      "Y" => Some(ShortcutKey::Y),
 | 
			
		||||
      "Z" => Some(ShortcutKey::Z),
 | 
			
		||||
      "0" => Some(ShortcutKey::N0),
 | 
			
		||||
      "1" => Some(ShortcutKey::N1),
 | 
			
		||||
      "2" => Some(ShortcutKey::N2),
 | 
			
		||||
      "3" => Some(ShortcutKey::N3),
 | 
			
		||||
      "4" => Some(ShortcutKey::N4),
 | 
			
		||||
      "5" => Some(ShortcutKey::N5),
 | 
			
		||||
      "6" => Some(ShortcutKey::N6),
 | 
			
		||||
      "7" => Some(ShortcutKey::N7),
 | 
			
		||||
      "8" => Some(ShortcutKey::N8),
 | 
			
		||||
      "9" => Some(ShortcutKey::N9),
 | 
			
		||||
      "NUMPAD0" => Some(ShortcutKey::Numpad0),
 | 
			
		||||
      "NUMPAD1" => Some(ShortcutKey::Numpad1),
 | 
			
		||||
      "NUMPAD2" => Some(ShortcutKey::Numpad2),
 | 
			
		||||
      "NUMPAD3" => Some(ShortcutKey::Numpad3),
 | 
			
		||||
      "NUMPAD4" => Some(ShortcutKey::Numpad4),
 | 
			
		||||
      "NUMPAD5" => Some(ShortcutKey::Numpad5),
 | 
			
		||||
      "NUMPAD6" => Some(ShortcutKey::Numpad6),
 | 
			
		||||
      "NUMPAD7" => Some(ShortcutKey::Numpad7),
 | 
			
		||||
      "NUMPAD8" => Some(ShortcutKey::Numpad8),
 | 
			
		||||
      "NUMPAD9" => Some(ShortcutKey::Numpad9),
 | 
			
		||||
      _ => None,
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    if parsed.is_none() {
 | 
			
		||||
      // Attempt to parse raw ShortcutKeys
 | 
			
		||||
      if RAW_PARSER.is_match(key) {
 | 
			
		||||
        if let Some(caps) = RAW_PARSER.captures(key) {
 | 
			
		||||
          let code_str = caps.get(1).map_or("", |m| m.as_str());
 | 
			
		||||
          let code = code_str.parse::<u32>();
 | 
			
		||||
          if let Ok(code) = code {
 | 
			
		||||
            return Some(ShortcutKey::Raw(code));
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    parsed
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // macOS keycodes
 | 
			
		||||
 | 
			
		||||
  #[cfg(target_os = "macos")]
 | 
			
		||||
  pub fn to_code(&self) -> Option<u32> {
 | 
			
		||||
    match self {
 | 
			
		||||
      ShortcutKey::Alt => Some(0x3A),
 | 
			
		||||
      ShortcutKey::Control => Some(0x3B),
 | 
			
		||||
      ShortcutKey::Meta => Some(0x37),
 | 
			
		||||
      ShortcutKey::Shift => Some(0x38),
 | 
			
		||||
      ShortcutKey::Enter => Some(0x24),
 | 
			
		||||
      ShortcutKey::Tab => Some(0x30),
 | 
			
		||||
      ShortcutKey::Space => Some(0x31),
 | 
			
		||||
      ShortcutKey::ArrowDown => Some(0x7D),
 | 
			
		||||
      ShortcutKey::ArrowLeft => Some(0x7B),
 | 
			
		||||
      ShortcutKey::ArrowRight => Some(0x7C),
 | 
			
		||||
      ShortcutKey::ArrowUp => Some(0x7E),
 | 
			
		||||
      ShortcutKey::End => Some(0x77),
 | 
			
		||||
      ShortcutKey::Home => Some(0x73),
 | 
			
		||||
      ShortcutKey::PageDown => Some(0x79),
 | 
			
		||||
      ShortcutKey::PageUp => Some(0x74),
 | 
			
		||||
      ShortcutKey::Insert => None,
 | 
			
		||||
      ShortcutKey::F1 => Some(0x7A),
 | 
			
		||||
      ShortcutKey::F2 => Some(0x78),
 | 
			
		||||
      ShortcutKey::F3 => Some(0x63),
 | 
			
		||||
      ShortcutKey::F4 => Some(0x76),
 | 
			
		||||
      ShortcutKey::F5 => Some(0x60),
 | 
			
		||||
      ShortcutKey::F6 => Some(0x61),
 | 
			
		||||
      ShortcutKey::F7 => Some(0x62),
 | 
			
		||||
      ShortcutKey::F8 => Some(0x64),
 | 
			
		||||
      ShortcutKey::F9 => Some(0x65),
 | 
			
		||||
      ShortcutKey::F10 => Some(0x6D),
 | 
			
		||||
      ShortcutKey::F11 => Some(0x67),
 | 
			
		||||
      ShortcutKey::F12 => Some(0x6F),
 | 
			
		||||
      ShortcutKey::F13 => Some(0x69),
 | 
			
		||||
      ShortcutKey::F14 => Some(0x6B),
 | 
			
		||||
      ShortcutKey::F15 => Some(0x71),
 | 
			
		||||
      ShortcutKey::F16 => Some(0x6A),
 | 
			
		||||
      ShortcutKey::F17 => Some(0x40),
 | 
			
		||||
      ShortcutKey::F18 => Some(0x4F),
 | 
			
		||||
      ShortcutKey::F19 => Some(0x50),
 | 
			
		||||
      ShortcutKey::F20 => Some(0x5A),
 | 
			
		||||
      ShortcutKey::A => Some(0x00),
 | 
			
		||||
      ShortcutKey::B => Some(0x0B),
 | 
			
		||||
      ShortcutKey::C => Some(0x08),
 | 
			
		||||
      ShortcutKey::D => Some(0x02),
 | 
			
		||||
      ShortcutKey::E => Some(0x0E),
 | 
			
		||||
      ShortcutKey::F => Some(0x03),
 | 
			
		||||
      ShortcutKey::G => Some(0x05),
 | 
			
		||||
      ShortcutKey::H => Some(0x04),
 | 
			
		||||
      ShortcutKey::I => Some(0x22),
 | 
			
		||||
      ShortcutKey::J => Some(0x26),
 | 
			
		||||
      ShortcutKey::K => Some(0x28),
 | 
			
		||||
      ShortcutKey::L => Some(0x25),
 | 
			
		||||
      ShortcutKey::M => Some(0x2E),
 | 
			
		||||
      ShortcutKey::N => Some(0x2D),
 | 
			
		||||
      ShortcutKey::O => Some(0x1F),
 | 
			
		||||
      ShortcutKey::P => Some(0x23),
 | 
			
		||||
      ShortcutKey::Q => Some(0x0C),
 | 
			
		||||
      ShortcutKey::R => Some(0x0F),
 | 
			
		||||
      ShortcutKey::S => Some(0x01),
 | 
			
		||||
      ShortcutKey::T => Some(0x11),
 | 
			
		||||
      ShortcutKey::U => Some(0x20),
 | 
			
		||||
      ShortcutKey::V => Some(0x09),
 | 
			
		||||
      ShortcutKey::W => Some(0x0D),
 | 
			
		||||
      ShortcutKey::X => Some(0x07),
 | 
			
		||||
      ShortcutKey::Y => Some(0x10),
 | 
			
		||||
      ShortcutKey::Z => Some(0x06),
 | 
			
		||||
      ShortcutKey::N0 => Some(0x1D),
 | 
			
		||||
      ShortcutKey::N1 => Some(0x12),
 | 
			
		||||
      ShortcutKey::N2 => Some(0x13),
 | 
			
		||||
      ShortcutKey::N3 => Some(0x14),
 | 
			
		||||
      ShortcutKey::N4 => Some(0x15),
 | 
			
		||||
      ShortcutKey::N5 => Some(0x17),
 | 
			
		||||
      ShortcutKey::N6 => Some(0x16),
 | 
			
		||||
      ShortcutKey::N7 => Some(0x1A),
 | 
			
		||||
      ShortcutKey::N8 => Some(0x1C),
 | 
			
		||||
      ShortcutKey::N9 => Some(0x19),
 | 
			
		||||
      ShortcutKey::Numpad0 => Some(0x52),
 | 
			
		||||
      ShortcutKey::Numpad1 => Some(0x53),
 | 
			
		||||
      ShortcutKey::Numpad2 => Some(0x54),
 | 
			
		||||
      ShortcutKey::Numpad3 => Some(0x55),
 | 
			
		||||
      ShortcutKey::Numpad4 => Some(0x56),
 | 
			
		||||
      ShortcutKey::Numpad5 => Some(0x57),
 | 
			
		||||
      ShortcutKey::Numpad6 => Some(0x58),
 | 
			
		||||
      ShortcutKey::Numpad7 => Some(0x59),
 | 
			
		||||
      ShortcutKey::Numpad8 => Some(0x5B),
 | 
			
		||||
      ShortcutKey::Numpad9 => Some(0x5C),
 | 
			
		||||
      ShortcutKey::Raw(code) => Some(*code),
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Windows key codes
 | 
			
		||||
 | 
			
		||||
  #[cfg(target_os = "windows")]
 | 
			
		||||
  pub fn to_code(&self) -> Option<u32> {
 | 
			
		||||
    let vkey = match self {
 | 
			
		||||
      Key::Alt => 0x12,
 | 
			
		||||
      Key::CapsLock => 0x14,
 | 
			
		||||
      Key::Control => 0x11,
 | 
			
		||||
      Key::Meta => 0x5B,
 | 
			
		||||
      Key::NumLock => 0x90,
 | 
			
		||||
      Key::Shift => 0xA0,
 | 
			
		||||
      Key::Enter => 0x0D,
 | 
			
		||||
      Key::Tab => 0x09,
 | 
			
		||||
      Key::Space => 0x20,
 | 
			
		||||
      Key::ArrowDown => 0x28,
 | 
			
		||||
      Key::ArrowLeft => 0x25,
 | 
			
		||||
      Key::ArrowRight => 0x27,
 | 
			
		||||
      Key::ArrowUp => 0x26,
 | 
			
		||||
      Key::End => 0x23,
 | 
			
		||||
      Key::Home => 0x24,
 | 
			
		||||
      Key::PageDown => 0x22,
 | 
			
		||||
      Key::PageUp => 0x21,
 | 
			
		||||
      Key::Escape => 0x1B,
 | 
			
		||||
      Key::Backspace => 0x08,
 | 
			
		||||
      Key::Insert => 0x2D,
 | 
			
		||||
      Key::Delete => 0x2E,
 | 
			
		||||
      Key::F1 => 0x70,
 | 
			
		||||
      Key::F2 => 0x71,
 | 
			
		||||
      Key::F3 => 0x72,
 | 
			
		||||
      Key::F4 => 0x73,
 | 
			
		||||
      Key::F5 => 0x74,
 | 
			
		||||
      Key::F6 => 0x75,
 | 
			
		||||
      Key::F7 => 0x76,
 | 
			
		||||
      Key::F8 => 0x77,
 | 
			
		||||
      Key::F9 => 0x78,
 | 
			
		||||
      Key::F10 => 0x79,
 | 
			
		||||
      Key::F11 => 0x7A,
 | 
			
		||||
      Key::F12 => 0x7B,
 | 
			
		||||
      Key::F13 => 0x7C,
 | 
			
		||||
      Key::F14 => 0x7D,
 | 
			
		||||
      Key::F15 => 0x7E,
 | 
			
		||||
      Key::F16 => 0x7F,
 | 
			
		||||
      Key::F17 => 0x80,
 | 
			
		||||
      Key::F18 => 0x81,
 | 
			
		||||
      Key::F19 => 0x82,
 | 
			
		||||
      Key::F20 => 0x83,
 | 
			
		||||
      Key::A => 0x41,
 | 
			
		||||
      Key::B => 0x42,
 | 
			
		||||
      Key::C => 0x43,
 | 
			
		||||
      Key::D => 0x44,
 | 
			
		||||
      Key::E => 0x45,
 | 
			
		||||
      Key::F => 0x46,
 | 
			
		||||
      Key::G => 0x47,
 | 
			
		||||
      Key::H => 0x48,
 | 
			
		||||
      Key::I => 0x49,
 | 
			
		||||
      Key::J => 0x4A,
 | 
			
		||||
      Key::K => 0x4B,
 | 
			
		||||
      Key::L => 0x4C,
 | 
			
		||||
      Key::M => 0x4D,
 | 
			
		||||
      Key::N => 0x4E,
 | 
			
		||||
      Key::O => 0x4F,
 | 
			
		||||
      Key::P => 0x50,
 | 
			
		||||
      Key::Q => 0x51,
 | 
			
		||||
      Key::R => 0x52,
 | 
			
		||||
      Key::S => 0x53,
 | 
			
		||||
      Key::T => 0x54,
 | 
			
		||||
      Key::U => 0x55,
 | 
			
		||||
      Key::V => 0x56,
 | 
			
		||||
      Key::W => 0x57,
 | 
			
		||||
      Key::X => 0x58,
 | 
			
		||||
      Key::Y => 0x59,
 | 
			
		||||
      Key::Z => 0x5A,
 | 
			
		||||
      Key::N0 => 0x30,
 | 
			
		||||
      Key::N1 => 0x31,
 | 
			
		||||
      Key::N2 => 0x32,
 | 
			
		||||
      Key::N3 => 0x33,
 | 
			
		||||
      Key::N4 => 0x34,
 | 
			
		||||
      Key::N5 => 0x35,
 | 
			
		||||
      Key::N6 => 0x36,
 | 
			
		||||
      Key::N7 => 0x37,
 | 
			
		||||
      Key::N8 => 0x38,
 | 
			
		||||
      Key::N9 => 0x39,
 | 
			
		||||
      Key::Numpad0 => 0x60,
 | 
			
		||||
      Key::Numpad1 => 0x61,
 | 
			
		||||
      Key::Numpad2 => 0x62,
 | 
			
		||||
      Key::Numpad3 => 0x63,
 | 
			
		||||
      Key::Numpad4 => 0x64,
 | 
			
		||||
      Key::Numpad5 => 0x65,
 | 
			
		||||
      Key::Numpad6 => 0x66,
 | 
			
		||||
      Key::Numpad7 => 0x67,
 | 
			
		||||
      Key::Numpad8 => 0x68,
 | 
			
		||||
      Key::Numpad9 => 0x69,
 | 
			
		||||
      Key::Raw(code) => *code,
 | 
			
		||||
    };
 | 
			
		||||
    Some(vkey)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  #[cfg(target_os = "linux")]
 | 
			
		||||
  pub fn to_code(&self) -> Option<u32> {
 | 
			
		||||
    None // Not supported on Linux
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[cfg(test)]
 | 
			
		||||
mod tests {
 | 
			
		||||
  use super::*;
 | 
			
		||||
 | 
			
		||||
  #[test]
 | 
			
		||||
  fn parse_works_correctly() {
 | 
			
		||||
    assert!(matches!(
 | 
			
		||||
      ShortcutKey::parse("ALT").unwrap(),
 | 
			
		||||
      ShortcutKey::Alt
 | 
			
		||||
    ));
 | 
			
		||||
    assert!(matches!(
 | 
			
		||||
      ShortcutKey::parse("META").unwrap(),
 | 
			
		||||
      ShortcutKey::Meta
 | 
			
		||||
    ));
 | 
			
		||||
    assert!(matches!(
 | 
			
		||||
      ShortcutKey::parse("CMD").unwrap(),
 | 
			
		||||
      ShortcutKey::Meta
 | 
			
		||||
    ));
 | 
			
		||||
    assert!(matches!(
 | 
			
		||||
      ShortcutKey::parse("RAW(1234)").unwrap(),
 | 
			
		||||
      ShortcutKey::Raw(1234)
 | 
			
		||||
    ));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  #[test]
 | 
			
		||||
  fn parse_invalid_keys() {
 | 
			
		||||
    assert!(ShortcutKey::parse("INVALID").is_none());
 | 
			
		||||
    assert!(ShortcutKey::parse("RAW(a)").is_none());
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										127
									
								
								espanso-detect/src/hotkey/mod.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										127
									
								
								espanso-detect/src/hotkey/mod.rs
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,127 @@
 | 
			
		|||
/*
 | 
			
		||||
 * 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/>.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
pub mod keys;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
use anyhow::Result;
 | 
			
		||||
use keys::ShortcutKey;
 | 
			
		||||
use thiserror::Error;
 | 
			
		||||
 | 
			
		||||
static MODIFIERS: &[ShortcutKey; 4] = &[ShortcutKey::Control, ShortcutKey::Alt, ShortcutKey::Shift, ShortcutKey::Meta];
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, PartialEq, Clone)]
 | 
			
		||||
pub struct HotKey {
 | 
			
		||||
  pub id: i32,
 | 
			
		||||
  pub key: ShortcutKey,
 | 
			
		||||
  pub modifiers: Vec<ShortcutKey>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// TODO: test all methods
 | 
			
		||||
impl HotKey {
 | 
			
		||||
  pub fn new(id: i32, shortcut: &str) -> Result<Self> {
 | 
			
		||||
    let tokens: Vec<String> = shortcut
 | 
			
		||||
      .split('+')
 | 
			
		||||
      .map(|token| token.trim().to_uppercase())
 | 
			
		||||
      .collect();
 | 
			
		||||
 | 
			
		||||
    let mut modifiers = Vec::new();
 | 
			
		||||
    let mut main_key = None;
 | 
			
		||||
    for token in tokens {
 | 
			
		||||
      let key = ShortcutKey::parse(&token);
 | 
			
		||||
      match key {
 | 
			
		||||
        Some(key) => {
 | 
			
		||||
          if MODIFIERS.contains(&key) {
 | 
			
		||||
            modifiers.push(key)
 | 
			
		||||
          } else {
 | 
			
		||||
            main_key = Some(key)
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
        None => return Err(HotKeyError::InvalidKey(token).into()),
 | 
			
		||||
      };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if modifiers.is_empty() || main_key.is_none() {
 | 
			
		||||
      return Err(HotKeyError::InvalidShortcut(shortcut.to_string()).into());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    Ok(Self {
 | 
			
		||||
      id,
 | 
			
		||||
      modifiers,
 | 
			
		||||
      key: main_key.unwrap(),
 | 
			
		||||
    })
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  pub(crate) fn has_ctrl(&self) -> bool {
 | 
			
		||||
    self.modifiers.contains(&ShortcutKey::Control)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  pub(crate) fn has_meta(&self) -> bool {
 | 
			
		||||
    self.modifiers.contains(&ShortcutKey::Meta)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  pub(crate) fn has_alt(&self) -> bool {
 | 
			
		||||
    self.modifiers.contains(&ShortcutKey::Alt)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  pub(crate) fn has_shift(&self) -> bool {
 | 
			
		||||
    self.modifiers.contains(&ShortcutKey::Shift)
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Error, Debug)]
 | 
			
		||||
pub enum HotKeyError {
 | 
			
		||||
  #[error("invalid hotkey shortcut, `{0}` is not a valid key")]
 | 
			
		||||
  InvalidKey(String),
 | 
			
		||||
 | 
			
		||||
  #[error("invalid hotkey shortcut `{0}`")]
 | 
			
		||||
  InvalidShortcut(String),
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[cfg(test)]
 | 
			
		||||
mod tests {
 | 
			
		||||
  use super::*;
 | 
			
		||||
 | 
			
		||||
  #[test]
 | 
			
		||||
  fn parse_correctly() {
 | 
			
		||||
    assert_eq!(HotKey::new(1, "CTRL+V").unwrap(), HotKey {
 | 
			
		||||
      id: 1,
 | 
			
		||||
      key: ShortcutKey::V,
 | 
			
		||||
      modifiers: vec![ShortcutKey::Control],
 | 
			
		||||
    });
 | 
			
		||||
    assert_eq!(HotKey::new(2, "SHIFT + Ctrl + v").unwrap(), HotKey {
 | 
			
		||||
      id: 2,
 | 
			
		||||
      key: ShortcutKey::V,
 | 
			
		||||
      modifiers: vec![ShortcutKey::Shift, ShortcutKey::Control],
 | 
			
		||||
    });
 | 
			
		||||
    assert!(HotKey::new(3, "invalid").is_err());
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  #[test]
 | 
			
		||||
  fn modifiers_detected_correcty() {
 | 
			
		||||
    assert!(HotKey::new(1, "CTRL+V").unwrap().has_ctrl());
 | 
			
		||||
    assert!(HotKey::new(1, "ALT + V").unwrap().has_alt());
 | 
			
		||||
    assert!(HotKey::new(1, "CMD + V").unwrap().has_meta());
 | 
			
		||||
    assert!(HotKey::new(1, "SHIFT+ V").unwrap().has_shift());
 | 
			
		||||
 | 
			
		||||
    assert!(!HotKey::new(1, "SHIFT+ V").unwrap().has_ctrl());
 | 
			
		||||
    assert!(!HotKey::new(1, "SHIFT+ V").unwrap().has_alt());
 | 
			
		||||
    assert!(!HotKey::new(1, "SHIFT+ V").unwrap().has_meta());
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -18,9 +18,11 @@
 | 
			
		|||
 */
 | 
			
		||||
 | 
			
		||||
use anyhow::Result;
 | 
			
		||||
use hotkey::HotKey;
 | 
			
		||||
use log::info;
 | 
			
		||||
 | 
			
		||||
pub mod event;
 | 
			
		||||
pub mod hotkey;
 | 
			
		||||
 | 
			
		||||
#[cfg(target_os = "windows")]
 | 
			
		||||
pub mod win32;
 | 
			
		||||
| 
						 | 
				
			
			@ -49,11 +51,15 @@ pub trait Source {
 | 
			
		|||
#[allow(dead_code)]
 | 
			
		||||
pub struct SourceCreationOptions {
 | 
			
		||||
  // Only relevant in X11 Linux systems, use the EVDEV backend instead of X11.
 | 
			
		||||
  use_evdev: bool,
 | 
			
		||||
  pub use_evdev: bool,
 | 
			
		||||
 | 
			
		||||
  // Can be used to overwrite the keymap configuration
 | 
			
		||||
  // used by espanso to inject key presses.
 | 
			
		||||
  evdev_keyboard_rmlvo: Option<KeyboardConfig>,
 | 
			
		||||
  pub evdev_keyboard_rmlvo: Option<KeyboardConfig>,
 | 
			
		||||
 | 
			
		||||
  // List of global hotkeys the detection module has to register
 | 
			
		||||
  // NOTE: Hotkeys are ignored on Linux
 | 
			
		||||
  pub hotkeys: Vec<HotKey>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// This struct identifies the keyboard layout that
 | 
			
		||||
| 
						 | 
				
			
			@ -73,6 +79,7 @@ impl Default for SourceCreationOptions {
 | 
			
		|||
    Self {
 | 
			
		||||
      use_evdev: false,
 | 
			
		||||
      evdev_keyboard_rmlvo: None,
 | 
			
		||||
      hotkeys: Vec::new(),
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -84,9 +91,9 @@ pub fn get_source(_options: SourceCreationOptions) -> Result<Box<dyn Source>> {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
#[cfg(target_os = "macos")]
 | 
			
		||||
pub fn get_source(_options: SourceCreationOptions) -> Result<Box<dyn Source>> {
 | 
			
		||||
pub fn get_source(options: SourceCreationOptions) -> Result<Box<dyn Source>> {
 | 
			
		||||
  info!("using CocoaSource");
 | 
			
		||||
  Ok(Box::new(mac::CocoaSource::new()))
 | 
			
		||||
  Ok(Box::new(mac::CocoaSource::new(&options.hotkeys)))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[cfg(target_os = "linux")]
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -17,13 +17,10 @@
 | 
			
		|||
 * along with espanso.  If not, see <https://www.gnu.org/licenses/>.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
use std::{
 | 
			
		||||
  ffi::CStr,
 | 
			
		||||
  sync::{
 | 
			
		||||
use std::{convert::TryInto, ffi::CStr, sync::{
 | 
			
		||||
    mpsc::{channel, Receiver, Sender},
 | 
			
		||||
    Arc, Mutex,
 | 
			
		||||
  },
 | 
			
		||||
};
 | 
			
		||||
  }};
 | 
			
		||||
 | 
			
		||||
use lazycell::LazyCell;
 | 
			
		||||
use log::{error, trace, warn};
 | 
			
		||||
| 
						 | 
				
			
			@ -31,13 +28,14 @@ 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::{HotKeyEvent, InputEvent, Key, KeyboardEvent, Variant};
 | 
			
		||||
use crate::event::{Key::*, MouseButton, MouseEvent};
 | 
			
		||||
use crate::{event::Status::*, Source, SourceCallback};
 | 
			
		||||
use crate::{event::Variant::*, hotkey::HotKey};
 | 
			
		||||
 | 
			
		||||
const INPUT_EVENT_TYPE_KEYBOARD: i32 = 1;
 | 
			
		||||
const INPUT_EVENT_TYPE_MOUSE: i32 = 2;
 | 
			
		||||
const INPUT_EVENT_TYPE_HOTKEY: i32 = 3;
 | 
			
		||||
 | 
			
		||||
const INPUT_STATUS_PRESSED: i32 = 1;
 | 
			
		||||
const INPUT_STATUS_RELEASED: i32 = 2;
 | 
			
		||||
| 
						 | 
				
			
			@ -58,10 +56,26 @@ pub struct RawInputEvent {
 | 
			
		|||
  pub status: i32,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[repr(C)]
 | 
			
		||||
pub struct RawHotKey {
 | 
			
		||||
  pub id: i32,
 | 
			
		||||
  pub code: u16,
 | 
			
		||||
  pub flags: u32,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[repr(C)]
 | 
			
		||||
pub struct RawInitializationOptions {
 | 
			
		||||
  pub hotkeys: *const RawHotKey,
 | 
			
		||||
  pub hotkeys_count: i32,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[allow(improper_ctypes)]
 | 
			
		||||
#[link(name = "espansodetect", kind = "static")]
 | 
			
		||||
extern "C" {
 | 
			
		||||
  pub fn detect_initialize(callback: extern "C" fn(event: RawInputEvent));
 | 
			
		||||
  pub fn detect_initialize(
 | 
			
		||||
    callback: extern "C" fn(event: RawInputEvent),
 | 
			
		||||
    options: RawInitializationOptions,
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
lazy_static! {
 | 
			
		||||
| 
						 | 
				
			
			@ -88,13 +102,15 @@ extern "C" fn native_callback(raw_event: RawInputEvent) {
 | 
			
		|||
 | 
			
		||||
pub struct CocoaSource {
 | 
			
		||||
  receiver: LazyCell<Receiver<InputEvent>>,
 | 
			
		||||
  hotkeys: Vec<HotKey>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[allow(clippy::new_without_default)]
 | 
			
		||||
impl CocoaSource {
 | 
			
		||||
  pub fn new() -> CocoaSource {
 | 
			
		||||
  pub fn new(hotkeys: &[HotKey]) -> CocoaSource {
 | 
			
		||||
    Self {
 | 
			
		||||
      receiver: LazyCell::new(),
 | 
			
		||||
      hotkeys: hotkeys.to_vec(),
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -111,7 +127,24 @@ impl Source for CocoaSource {
 | 
			
		|||
      *lock = Some(sender);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    unsafe { detect_initialize(native_callback) };
 | 
			
		||||
    // Generate the options
 | 
			
		||||
    let hotkeys: Vec<RawHotKey> = self
 | 
			
		||||
      .hotkeys
 | 
			
		||||
      .iter()
 | 
			
		||||
      .filter_map(|hk| {
 | 
			
		||||
        let raw = convert_hotkey_to_raw(&hk);
 | 
			
		||||
        if raw.is_none() {
 | 
			
		||||
          error!("unable to register hotkey: {:?}", hk);
 | 
			
		||||
        }
 | 
			
		||||
        raw
 | 
			
		||||
      })
 | 
			
		||||
      .collect();
 | 
			
		||||
    let options = RawInitializationOptions {
 | 
			
		||||
      hotkeys: hotkeys.as_ptr(),
 | 
			
		||||
      hotkeys_count: hotkeys.len() as i32,
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    unsafe { detect_initialize(native_callback, options) };
 | 
			
		||||
 | 
			
		||||
    if self.receiver.fill(receiver).is_err() {
 | 
			
		||||
      error!("Unable to set CocoaSource receiver");
 | 
			
		||||
| 
						 | 
				
			
			@ -156,6 +189,35 @@ impl Drop for CocoaSource {
 | 
			
		|||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn convert_hotkey_to_raw(hk: &HotKey) -> Option<RawHotKey> {
 | 
			
		||||
  let key_code = hk.key.to_code()?;
 | 
			
		||||
  let code: Result<u16, _> = key_code.try_into();
 | 
			
		||||
  if let Ok(code) = code {
 | 
			
		||||
    let mut flags = 0;
 | 
			
		||||
    if hk.has_ctrl() {
 | 
			
		||||
      flags |= 1 << 12;
 | 
			
		||||
    }
 | 
			
		||||
    if hk.has_alt() {
 | 
			
		||||
      flags |= 1 << 11;
 | 
			
		||||
    }
 | 
			
		||||
    if hk.has_meta() {
 | 
			
		||||
      flags |= 1 << 8;
 | 
			
		||||
    }
 | 
			
		||||
    if hk.has_shift() {
 | 
			
		||||
      flags |= 1 << 9;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    Some(RawHotKey {
 | 
			
		||||
      id: hk.id,
 | 
			
		||||
      code,
 | 
			
		||||
      flags,
 | 
			
		||||
    })
 | 
			
		||||
  } else {
 | 
			
		||||
    error!("unable to generate raw hotkey, the key_code is overflowing");
 | 
			
		||||
    None
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Error, Debug)]
 | 
			
		||||
pub enum CocoaSourceError {
 | 
			
		||||
  #[error("unknown error")]
 | 
			
		||||
| 
						 | 
				
			
			@ -213,6 +275,13 @@ impl From<RawInputEvent> for Option<InputEvent> {
 | 
			
		|||
          return Some(InputEvent::Mouse(MouseEvent { button, status }));
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      // HOTKEYS
 | 
			
		||||
      INPUT_EVENT_TYPE_HOTKEY => {
 | 
			
		||||
        let id = raw.key_code;
 | 
			
		||||
        return Some(InputEvent::HotKey(HotKeyEvent {
 | 
			
		||||
          hotkey_id: id,
 | 
			
		||||
        }))
 | 
			
		||||
      }
 | 
			
		||||
      _ => {}
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -24,6 +24,7 @@
 | 
			
		|||
 | 
			
		||||
#define INPUT_EVENT_TYPE_KEYBOARD 1
 | 
			
		||||
#define INPUT_EVENT_TYPE_MOUSE    2
 | 
			
		||||
#define INPUT_EVENT_TYPE_HOTKEY   3
 | 
			
		||||
 | 
			
		||||
#define INPUT_STATUS_PRESSED      1
 | 
			
		||||
#define INPUT_STATUS_RELEASED     2
 | 
			
		||||
| 
						 | 
				
			
			@ -45,7 +46,8 @@ typedef struct {
 | 
			
		|||
  int32_t buffer_len;
 | 
			
		||||
  
 | 
			
		||||
  // Virtual key code of the pressed key in case of keyboard events
 | 
			
		||||
  // Mouse button code otherwise.
 | 
			
		||||
  // Mouse button code if mouse_event.
 | 
			
		||||
  // Hotkey ID in case of hotkeys
 | 
			
		||||
  int32_t key_code;
 | 
			
		||||
  
 | 
			
		||||
  // Pressed or Released status
 | 
			
		||||
| 
						 | 
				
			
			@ -54,8 +56,18 @@ typedef struct {
 | 
			
		|||
 | 
			
		||||
typedef void (*EventCallback)(InputEvent data);
 | 
			
		||||
 | 
			
		||||
typedef struct {
 | 
			
		||||
  int32_t hk_id;
 | 
			
		||||
  uint16_t key_code;
 | 
			
		||||
  uint32_t flags;
 | 
			
		||||
} HotKey;
 | 
			
		||||
 | 
			
		||||
typedef struct {
 | 
			
		||||
  HotKey *hotkeys;
 | 
			
		||||
  int32_t hotkeys_count;
 | 
			
		||||
} InitializeOptions;
 | 
			
		||||
 | 
			
		||||
// Initialize the event global monitor
 | 
			
		||||
extern "C" void * detect_initialize(EventCallback callback);
 | 
			
		||||
extern "C" void * detect_initialize(EventCallback callback, InitializeOptions options);
 | 
			
		||||
 | 
			
		||||
#endif //ESPANSO_DETECT_H
 | 
			
		||||
| 
						 | 
				
			
			@ -28,8 +28,35 @@ const unsigned long long FLAGS = NSEventMaskKeyDown | NSEventMaskKeyUp | NSEvent
 | 
			
		|||
                                 NSEventMaskLeftMouseUp | NSEventMaskRightMouseDown | NSEventMaskRightMouseUp | 
 | 
			
		||||
                                 NSEventMaskOtherMouseDown | NSEventMaskOtherMouseUp;
 | 
			
		||||
 | 
			
		||||
void * detect_initialize(EventCallback callback) {
 | 
			
		||||
OSStatus hotkey_event_handler(EventHandlerCallRef _next, EventRef evt, void *userData);
 | 
			
		||||
 | 
			
		||||
void * detect_initialize(EventCallback callback, InitializeOptions options) {
 | 
			
		||||
  HotKey * hotkeys_clone = (HotKey*) malloc(sizeof(HotKey) * options.hotkeys_count);
 | 
			
		||||
  memcpy(hotkeys_clone, options.hotkeys, sizeof(HotKey) * options.hotkeys_count);
 | 
			
		||||
 | 
			
		||||
  dispatch_async(dispatch_get_main_queue(), ^(void) {
 | 
			
		||||
    // Setup hotkeys
 | 
			
		||||
    if (options.hotkeys_count > 0) {
 | 
			
		||||
      EventHotKeyRef hotkey_ref;
 | 
			
		||||
      EventHotKeyID hotkey_id;
 | 
			
		||||
      hotkey_id.signature='htk1';
 | 
			
		||||
 | 
			
		||||
      EventTypeSpec eventType;
 | 
			
		||||
      eventType.eventClass = kEventClassKeyboard;
 | 
			
		||||
      eventType.eventKind = kEventHotKeyPressed;    
 | 
			
		||||
      
 | 
			
		||||
      InstallApplicationEventHandler(&hotkey_event_handler, 1, &eventType, (void*)callback, NULL);
 | 
			
		||||
 | 
			
		||||
      for (int i = 0; i<options.hotkeys_count; i++) {
 | 
			
		||||
        hotkey_id.id=hotkeys_clone[i].hk_id;
 | 
			
		||||
        RegisterEventHotKey(hotkeys_clone[i].key_code, hotkeys_clone[i].flags, hotkey_id, GetApplicationEventTarget(), 0, &hotkey_ref);  
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    free(hotkeys_clone);
 | 
			
		||||
 | 
			
		||||
    // Setup key detection
 | 
			
		||||
 | 
			
		||||
    [NSEvent addGlobalMonitorForEventsMatchingMask:FLAGS handler:^(NSEvent *event){
 | 
			
		||||
        InputEvent inputEvent = {};
 | 
			
		||||
        if (event.type == NSEventTypeKeyDown || event.type == NSEventTypeKeyUp ) {
 | 
			
		||||
| 
						 | 
				
			
			@ -75,4 +102,19 @@ void * detect_initialize(EventCallback callback) {
 | 
			
		|||
        }
 | 
			
		||||
    }];
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
OSStatus hotkey_event_handler(EventHandlerCallRef _next, EventRef evt, void *userData)
 | 
			
		||||
{
 | 
			
		||||
    EventHotKeyID hotkey_id;
 | 
			
		||||
    GetEventParameter(evt, kEventParamDirectObject, typeEventHotKeyID, NULL, sizeof(hotkey_id), NULL, &hotkey_id);
 | 
			
		||||
    
 | 
			
		||||
    EventCallback callback = (EventCallback) userData;
 | 
			
		||||
 | 
			
		||||
    InputEvent inputEvent = {};
 | 
			
		||||
    inputEvent.event_type = INPUT_EVENT_TYPE_HOTKEY;
 | 
			
		||||
    inputEvent.key_code = hotkey_id.id;
 | 
			
		||||
    callback(inputEvent);
 | 
			
		||||
 | 
			
		||||
    return noErr;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -234,10 +234,10 @@ impl Display for Key {
 | 
			
		|||
impl Key {
 | 
			
		||||
  pub fn parse(key: &str) -> Option<Key> {
 | 
			
		||||
    let parsed = match key {
 | 
			
		||||
      "ALT" => Some(Key::Alt),
 | 
			
		||||
      "ALT" | "OPTION" => Some(Key::Alt),
 | 
			
		||||
      "CAPSLOCK" => Some(Key::CapsLock),
 | 
			
		||||
      "CTRL" => Some(Key::Control),
 | 
			
		||||
      "META" => Some(Key::Meta),
 | 
			
		||||
      "META" | "CMD" => Some(Key::Meta),
 | 
			
		||||
      "NUMLOCK" => Some(Key::NumLock),
 | 
			
		||||
      "SHIFT" => Some(Key::Shift),
 | 
			
		||||
      "ENTER" => Some(Key::Enter),
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,9 +1,6 @@
 | 
			
		|||
use std::time::Duration;
 | 
			
		||||
 | 
			
		||||
use espanso_detect::{
 | 
			
		||||
  event::{InputEvent, Status},
 | 
			
		||||
  get_source,
 | 
			
		||||
};
 | 
			
		||||
use espanso_detect::{SourceCreationOptions, event::{InputEvent, Status}, get_source, hotkey::HotKey};
 | 
			
		||||
use espanso_inject::{get_injector, keys, Injector};
 | 
			
		||||
use espanso_ui::{event::UIEvent::*, icons::TrayIcon, menu::*};
 | 
			
		||||
use simplelog::{CombinedLogger, Config, LevelFilter, TermLogger, TerminalMode};
 | 
			
		||||
| 
						 | 
				
			
			@ -63,7 +60,13 @@ fn main() {
 | 
			
		|||
 | 
			
		||||
  let handle = std::thread::spawn(move || {
 | 
			
		||||
    let injector = get_injector(Default::default()).unwrap();
 | 
			
		||||
    let mut source = get_source(Default::default()).unwrap();
 | 
			
		||||
    let mut source = get_source(SourceCreationOptions {
 | 
			
		||||
      hotkeys: vec![
 | 
			
		||||
        HotKey::new(1, "OPTION+SPACE").unwrap(),
 | 
			
		||||
        HotKey::new(2, "CMD+OPTION+3").unwrap(),
 | 
			
		||||
      ],
 | 
			
		||||
      ..Default::default()
 | 
			
		||||
    }).unwrap();
 | 
			
		||||
    source.initialize().unwrap();
 | 
			
		||||
    source
 | 
			
		||||
      .eventloop(Box::new(move |event: InputEvent| {
 | 
			
		||||
| 
						 | 
				
			
			@ -81,6 +84,8 @@ fn main() {
 | 
			
		|||
              //injector.send_key_combination(&[keys::Key::Control, keys::Key::V], Default::default()).unwrap();
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
          InputEvent::HotKey(_) => {
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      }))
 | 
			
		||||
      .unwrap();
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue
	
	Block a user