First implementation of espanso-inject on macOS
This commit is contained in:
parent
e3e1ad720f
commit
e0bf94013d
|
@ -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")]
|
||||
|
|
109
espanso-inject/src/mac/mod.rs
Normal file
109
espanso-inject/src/mac/mod.rs
Normal file
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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<Vec<i32>> {
|
||||
let mut virtual_keys: Vec<i32> = 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]
|
||||
);
|
||||
}
|
||||
}
|
35
espanso-inject/src/mac/native.h
Normal file
35
espanso-inject/src/mac/native.h
Normal file
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef ESPANSO_INJECT_H
|
||||
#define ESPANSO_INJECT_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
// 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
|
142
espanso-inject/src/mac/native.mm
Normal file
142
espanso-inject/src/mac/native.mm
Normal file
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "native.h"
|
||||
#include <string.h>
|
||||
#import <Foundation/Foundation.h>
|
||||
#include <vector>
|
||||
|
||||
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 <UniChar> 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<vkey_count; i++) {
|
||||
CGEventRef keydown;
|
||||
keydown = CGEventCreateKeyboardEvent(NULL, vkey_array[i], true);
|
||||
CGEventPost(kCGHIDEventTap, keydown);
|
||||
CFRelease(keydown);
|
||||
|
||||
usleep(udelay);
|
||||
|
||||
CGEventRef keyup;
|
||||
keyup = CGEventCreateKeyboardEvent(NULL, vkey_array[i], false);
|
||||
CGEventPost(kCGHIDEventTap, keyup);
|
||||
CFRelease(keyup);
|
||||
|
||||
usleep(udelay);
|
||||
}
|
||||
|
||||
free(vkey_array);
|
||||
});
|
||||
}
|
||||
|
||||
void inject_vkeys_combination(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) {
|
||||
// First send the presses
|
||||
for (int i = 0; i < vkey_count; i++)
|
||||
{
|
||||
CGEventRef keydown;
|
||||
keydown = CGEventCreateKeyboardEvent(NULL, vkey_array[i], true);
|
||||
CGEventPost(kCGHIDEventTap, keydown);
|
||||
CFRelease(keydown);
|
||||
|
||||
usleep(udelay);
|
||||
}
|
||||
|
||||
// Then the releases
|
||||
for (int i = (vkey_count - 1); i >= 0; i--)
|
||||
{
|
||||
CGEventRef keyup;
|
||||
keyup = CGEventCreateKeyboardEvent(NULL, vkey_array[i], false);
|
||||
CGEventPost(kCGHIDEventTap, keyup);
|
||||
CFRelease(keyup);
|
||||
|
||||
usleep(udelay);
|
||||
}
|
||||
|
||||
free(vkey_array);
|
||||
});
|
||||
}
|
94
espanso-inject/src/mac/raw_keys.rs
Normal file
94
espanso-inject/src/mac/raw_keys.rs
Normal file
|
@ -0,0 +1,94 @@
|
|||
use crate::keys::Key;
|
||||
|
||||
pub fn convert_key_to_vkey(key: &Key) -> Option<i32> {
|
||||
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),
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user