diff --git a/espanso-inject/src/lib.rs b/espanso-inject/src/lib.rs
index f9ad7d9..4e44980 100644
--- a/espanso-inject/src/lib.rs
+++ b/espanso-inject/src/lib.rs
@@ -42,6 +42,8 @@ pub trait Injector {
fn send_key_combination(&self, keys: &[keys::Key], delay: i32) -> Result<()>;
}
+
+#[allow(dead_code)]
pub struct InjectorOptions {
// Only relevant in Linux systems
use_evdev: bool,
@@ -62,7 +64,7 @@ pub fn get_injector(_options: InjectorOptions) -> impl Injector {
#[cfg(target_os = "macos")]
pub fn get_injector(_options: InjectorOptions) -> impl Injector {
- // TODO
+ mac::MacInjector::new()
}
#[cfg(target_os = "linux")]
diff --git a/espanso-inject/src/mac/mod.rs b/espanso-inject/src/mac/mod.rs
new file mode 100644
index 0000000..551e981
--- /dev/null
+++ b/espanso-inject/src/mac/mod.rs
@@ -0,0 +1,109 @@
+/*
+ * 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 .
+ */
+
+mod raw_keys;
+
+use std::{ffi::CString, os::raw::c_char};
+
+use log::error;
+use raw_keys::convert_key_to_vkey;
+
+use anyhow::Result;
+use thiserror::Error;
+
+use crate::{keys, Injector};
+
+#[allow(improper_ctypes)]
+#[link(name = "espansoinject", kind = "static")]
+extern "C" {
+ pub fn inject_string(string: *const c_char);
+ pub fn inject_separate_vkeys(vkey_array: *const i32, vkey_count: i32, delay: i32);
+ pub fn inject_vkeys_combination(vkey_array: *const i32, vkey_count: i32, delay: i32);
+}
+
+pub struct MacInjector {}
+
+#[allow(clippy::new_without_default)]
+impl MacInjector {
+ pub fn new() -> Self {
+ Self {}
+ }
+
+ pub fn convert_to_vk_array(keys: &[keys::Key]) -> Result> {
+ let mut virtual_keys: Vec = Vec::new();
+ for key in keys.iter() {
+ let vk = convert_key_to_vkey(key);
+ if let Some(vk) = vk {
+ virtual_keys.push(vk)
+ } else {
+ return Err(MacInjectorError::MappingFailure(key.clone()).into());
+ }
+ }
+ Ok(virtual_keys)
+ }
+}
+
+impl Injector for MacInjector {
+ fn send_string(&self, string: &str) -> Result<()> {
+ let c_string = CString::new(string)?;
+ unsafe {
+ inject_string(c_string.as_ptr());
+ }
+ Ok(())
+ }
+
+ fn send_keys(&self, keys: &[keys::Key], delay: i32) -> Result<()> {
+ let virtual_keys = Self::convert_to_vk_array(keys)?;
+
+ unsafe {
+ inject_separate_vkeys(virtual_keys.as_ptr(), virtual_keys.len() as i32, delay);
+ }
+
+ Ok(())
+ }
+
+ fn send_key_combination(&self, keys: &[keys::Key], delay: i32) -> Result<()> {
+ let virtual_keys = Self::convert_to_vk_array(keys)?;
+
+ unsafe {
+ inject_vkeys_combination(virtual_keys.as_ptr(), virtual_keys.len() as i32, delay);
+ }
+
+ Ok(())
+ }
+}
+
+#[derive(Error, Debug)]
+pub enum MacInjectorError {
+ #[error("missing vkey mapping for key `{0}`")]
+ MappingFailure(keys::Key),
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn convert_raw_to_virtual_key_array() {
+ assert_eq!(
+ MacInjector::convert_to_vk_array(&[keys::Key::Alt, keys::Key::V]).unwrap(),
+ vec![0x3A, 0x09]
+ );
+ }
+}
diff --git a/espanso-inject/src/mac/native.h b/espanso-inject/src/mac/native.h
new file mode 100644
index 0000000..6d52913
--- /dev/null
+++ b/espanso-inject/src/mac/native.h
@@ -0,0 +1,35 @@
+/*
+ * 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 .
+ */
+
+#ifndef ESPANSO_INJECT_H
+#define ESPANSO_INJECT_H
+
+#include
+
+// Inject a complete string using the KEYEVENTF_UNICODE flag
+extern "C" void inject_string(char * string);
+
+// Send a sequence of vkey presses and releases
+extern "C" void inject_separate_vkeys(int32_t *vkey_array, int32_t vkey_count, int32_t delay);
+
+// Send a combination of vkeys, first pressing all the vkeys and then releasing
+// This is needed for keyboard shortcuts, for example.
+extern "C" void inject_vkeys_combination(int32_t *vkey_array, int32_t vkey_count, int32_t delay);
+
+#endif //ESPANSO_INJECT_H
\ No newline at end of file
diff --git a/espanso-inject/src/mac/native.mm b/espanso-inject/src/mac/native.mm
new file mode 100644
index 0000000..ea05bcb
--- /dev/null
+++ b/espanso-inject/src/mac/native.mm
@@ -0,0 +1,142 @@
+/*
+ * 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 .
+ */
+
+#include "native.h"
+#include
+#import
+#include
+
+void inject_string(char *string)
+{
+ char * stringCopy = strdup(string);
+ dispatch_async(dispatch_get_main_queue(), ^(void) {
+ // Convert the c string to a UniChar array as required by the CGEventKeyboardSetUnicodeString method
+ NSString *nsString = [NSString stringWithUTF8String:stringCopy];
+ CFStringRef cfString = (__bridge CFStringRef) nsString;
+ std::vector buffer(nsString.length);
+ CFStringGetCharacters(cfString, CFRangeMake(0, nsString.length), buffer.data());
+
+ free(stringCopy);
+
+ // Send the event
+
+ // Check if the shift key is down, and if so, release it
+ // To see why: https://github.com/federico-terzi/espanso/issues/279
+ if (CGEventSourceKeyState(kCGEventSourceStateHIDSystemState, 0x38)) {
+ CGEventRef e2 = CGEventCreateKeyboardEvent(NULL, 0x38, false);
+ CGEventPost(kCGHIDEventTap, e2);
+ CFRelease(e2);
+
+ usleep(2000);
+ }
+
+ // Because of a bug ( or undocumented limit ) of the CGEventKeyboardSetUnicodeString method
+ // the string gets truncated after 20 characters, so we need to send multiple events.
+
+ int i = 0;
+ while (i < buffer.size()) {
+ int chunk_size = 20;
+ if ((i+chunk_size) > buffer.size()) {
+ chunk_size = buffer.size() - i;
+ }
+
+ UniChar * offset_buffer = buffer.data() + i;
+ CGEventRef e = CGEventCreateKeyboardEvent(NULL, 0x31, true);
+ CGEventKeyboardSetUnicodeString(e, chunk_size, offset_buffer);
+ CGEventPost(kCGHIDEventTap, e);
+ CFRelease(e);
+
+ usleep(2000);
+
+ // Some applications require an explicit release of the space key
+ // For more information: https://github.com/federico-terzi/espanso/issues/159
+ CGEventRef e2 = CGEventCreateKeyboardEvent(NULL, 0x31, false);
+ CGEventPost(kCGHIDEventTap, e2);
+ CFRelease(e2);
+
+ usleep(2000);
+
+ i += chunk_size;
+ }
+ });
+}
+
+void inject_separate_vkeys(int32_t *_vkey_array, int32_t vkey_count, int32_t delay)
+{
+ long udelay = delay * 1000;
+
+ // Create an heap allocated copy of the array, so that it doesn't get freed within the block
+ int32_t *vkey_array = (int32_t*)malloc(sizeof(int32_t)*vkey_count);
+ memcpy(vkey_array, _vkey_array, sizeof(int32_t)*vkey_count);
+
+ dispatch_async(dispatch_get_main_queue(), ^(void) {
+ for (int i = 0; i= 0; i--)
+ {
+ CGEventRef keyup;
+ keyup = CGEventCreateKeyboardEvent(NULL, vkey_array[i], false);
+ CGEventPost(kCGHIDEventTap, keyup);
+ CFRelease(keyup);
+
+ usleep(udelay);
+ }
+
+ free(vkey_array);
+ });
+}
diff --git a/espanso-inject/src/mac/raw_keys.rs b/espanso-inject/src/mac/raw_keys.rs
new file mode 100644
index 0000000..663632d
--- /dev/null
+++ b/espanso-inject/src/mac/raw_keys.rs
@@ -0,0 +1,94 @@
+use crate::keys::Key;
+
+pub fn convert_key_to_vkey(key: &Key) -> Option {
+ match key {
+ Key::Alt => Some(0x3A),
+ Key::CapsLock => Some(0x39),
+ Key::Control => Some(0x3B),
+ Key::Meta => Some(0x37),
+ Key::NumLock => None,
+ Key::Shift => Some(0x38),
+ Key::Enter => Some(0x24),
+ Key::Tab => Some(0x30),
+ Key::Space => Some(0x31),
+ Key::ArrowDown => Some(0x7D),
+ Key::ArrowLeft => Some(0x7B),
+ Key::ArrowRight => Some(0x7C),
+ Key::ArrowUp => Some(0x7E),
+ Key::End => Some(0x77),
+ Key::Home => Some(0x73),
+ Key::PageDown => Some(0x79),
+ Key::PageUp => Some(0x74),
+ Key::Escape => Some(0x35),
+ Key::Backspace => Some(0x33),
+ Key::Insert => None,
+ Key::Delete => Some(0x75),
+ Key::F1 => Some(0x7A),
+ Key::F2 => Some(0x78),
+ Key::F3 => Some(0x63),
+ Key::F4 => Some(0x76),
+ Key::F5 => Some(0x60),
+ Key::F6 => Some(0x61),
+ Key::F7 => Some(0x62),
+ Key::F8 => Some(0x64),
+ Key::F9 => Some(0x65),
+ Key::F10 => Some(0x6D),
+ Key::F11 => Some(0x67),
+ Key::F12 => Some(0x6F),
+ Key::F13 => Some(0x69),
+ Key::F14 => Some(0x6B),
+ Key::F15 => Some(0x71),
+ Key::F16 => Some(0x6A),
+ Key::F17 => Some(0x40),
+ Key::F18 => Some(0x4F),
+ Key::F19 => Some(0x50),
+ Key::F20 => Some(0x5A),
+ Key::A => Some(0x00),
+ Key::B => Some(0x0B),
+ Key::C => Some(0x08),
+ Key::D => Some(0x02),
+ Key::E => Some(0x0E),
+ Key::F => Some(0x03),
+ Key::G => Some(0x05),
+ Key::H => Some(0x04),
+ Key::I => Some(0x22),
+ Key::J => Some(0x26),
+ Key::K => Some(0x28),
+ Key::L => Some(0x25),
+ Key::M => Some(0x2E),
+ Key::N => Some(0x2D),
+ Key::O => Some(0x1F),
+ Key::P => Some(0x23),
+ Key::Q => Some(0x0C),
+ Key::R => Some(0x0F),
+ Key::S => Some(0x01),
+ Key::T => Some(0x11),
+ Key::U => Some(0x20),
+ Key::V => Some(0x09),
+ Key::W => Some(0x0D),
+ Key::X => Some(0x07),
+ Key::Y => Some(0x10),
+ Key::Z => Some(0x06),
+ Key::N0 => Some(0x1D),
+ Key::N1 => Some(0x12),
+ Key::N2 => Some(0x13),
+ Key::N3 => Some(0x14),
+ Key::N4 => Some(0x15),
+ Key::N5 => Some(0x17),
+ Key::N6 => Some(0x16),
+ Key::N7 => Some(0x1A),
+ Key::N8 => Some(0x1C),
+ Key::N9 => Some(0x19),
+ Key::Numpad0 => Some(0x52),
+ Key::Numpad1 => Some(0x53),
+ Key::Numpad2 => Some(0x54),
+ Key::Numpad3 => Some(0x55),
+ Key::Numpad4 => Some(0x56),
+ Key::Numpad5 => Some(0x57),
+ Key::Numpad6 => Some(0x58),
+ Key::Numpad7 => Some(0x59),
+ Key::Numpad8 => Some(0x5B),
+ Key::Numpad9 => Some(0x5C),
+ Key::Raw(code) => Some(*code),
+ }
+}
diff --git a/espanso/src/main.rs b/espanso/src/main.rs
index 65a4bc7..9f77312 100644
--- a/espanso/src/main.rs
+++ b/espanso/src/main.rs
@@ -1,5 +1,5 @@
use espanso_detect::event::{InputEvent, Status};
-use espanso_inject::{get_injector, Injector};
+use espanso_inject::{get_injector, Injector, keys};
use espanso_ui::{event::UIEvent::*, icons::TrayIcon, menu::*};
use simplelog::{CombinedLogger, Config, LevelFilter, TermLogger, TerminalMode};
@@ -36,23 +36,23 @@ fn main() {
),
];
- let (remote, mut eventloop) = espanso_ui::win32::create(espanso_ui::win32::Win32UIOptions {
- show_icon: true,
- icon_paths: &icon_paths,
- notification_icon_path: r"C:\Users\Freddy\Insync\Development\Espanso\Images\icongreensmall.png"
- .to_string(),
- });
- // let (remote, mut eventloop) = espanso_ui::mac::create(MacUIOptions {
+ // let (remote, mut eventloop) = espanso_ui::win32::create(espanso_ui::win32::Win32UIOptions {
// show_icon: true,
// icon_paths: &icon_paths,
+ // notification_icon_path: r"C:\Users\Freddy\Insync\Development\Espanso\Images\icongreensmall.png"
+ // .to_string(),
// });
+ let (remote, mut eventloop) = espanso_ui::mac::create(espanso_ui::mac::MacUIOptions {
+ show_icon: true,
+ icon_paths: &icon_paths,
+ });
eventloop.initialize();
let handle = std::thread::spawn(move || {
- let mut source = espanso_detect::win32::Win32Source::new();
+ //let mut source = espanso_detect::win32::Win32Source::new();
//let mut source = espanso_detect::x11::X11Source::new();
- //let mut source = espanso_detect::mac::CocoaSource::new();
+ let mut source = espanso_detect::mac::CocoaSource::new();
source.initialize();
source.eventloop(Box::new(move |event: InputEvent| {
let injector = get_injector(Default::default());
@@ -64,6 +64,7 @@ fn main() {
//remote.update_tray_icon(espanso_ui::icons::TrayIcon::Disabled);
//remote.show_notification("Espanso is running!");
injector.send_string("hey guys");
+ //injector.send_key_combination(&[keys::Key::Meta, keys::Key::V], 2);
}
}
}