diff --git a/Cargo.lock b/Cargo.lock index d4c363c..204ea24 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,5 +1,14 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. +[[package]] +name = "aho-corasick" +version = "0.7.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7404febffaa47dac81aa44dba71523c9d069b1bdc50a77db41195149e17f68e5" +dependencies = [ + "memchr", +] + [[package]] name = "anyhow" version = "1.0.38" @@ -133,6 +142,7 @@ name = "espanso" version = "1.0.0" dependencies = [ "espanso-detect", + "espanso-inject", "espanso-ui", "maplit", "simplelog", @@ -154,6 +164,23 @@ dependencies = [ "widestring", ] +[[package]] +name = "espanso-inject" +version = "0.1.0" +dependencies = [ + "anyhow", + "cc", + "enum-as-inner", + "lazy_static", + "lazycell", + "libc", + "log", + "regex", + "scopeguard", + "thiserror", + "widestring", +] + [[package]] name = "espanso-ui" version = "0.1.0" @@ -258,6 +285,12 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d" +[[package]] +name = "memchr" +version = "2.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525" + [[package]] name = "notify-rust" version = "4.2.2" @@ -317,6 +350,12 @@ 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" @@ -364,6 +403,24 @@ dependencies = [ "rust-argon2", ] +[[package]] +name = "regex" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9251239e129e16308e70d853559389de218ac275b515068abc96829d05b948a" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", + "thread_local", +] + +[[package]] +name = "regex-syntax" +version = "0.6.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5eb417147ba9860a96cfe72a0b93bf88fee1744b5636ec99ab20c1aa9376581" + [[package]] name = "rust-argon2" version = "0.8.3" @@ -506,6 +563,15 @@ 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" diff --git a/Cargo.toml b/Cargo.toml index 8c71a4f..7eeca9f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,4 +4,5 @@ members = [ "espanso", "espanso-detect", "espanso-ui", + "espanso-inject", ] \ No newline at end of file diff --git a/espanso-inject/Cargo.toml b/espanso-inject/Cargo.toml new file mode 100644 index 0000000..8d09fce --- /dev/null +++ b/espanso-inject/Cargo.toml @@ -0,0 +1,27 @@ +[package] +name = "espanso-inject" +version = "0.1.0" +authors = ["Federico Terzi "] +edition = "2018" +build="build.rs" + +[dependencies] +log = "0.4.14" +lazycell = "1.3.0" +anyhow = "1.0.38" +thiserror = "1.0.23" +lazy_static = "1.4.0" +regex = "1.4.3" + +[target.'cfg(windows)'.dependencies] +widestring = "0.4.3" + +[target.'cfg(target_os="linux")'.dependencies] +libc = "0.2.85" +scopeguard = "1.1.0" + +[build-dependencies] +cc = "1.0.66" + +[dev-dependencies] +enum-as-inner = "0.3.3" \ No newline at end of file diff --git a/espanso-inject/build.rs b/espanso-inject/build.rs new file mode 100644 index 0000000..48f69b5 --- /dev/null +++ b/espanso-inject/build.rs @@ -0,0 +1,76 @@ +/* + * 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 . + */ + +#[cfg(target_os = "windows")] +fn cc_config() { + println!("cargo:rerun-if-changed=src/win32/native.cpp"); + println!("cargo:rerun-if-changed=src/win32/native.h"); + cc::Build::new() + .cpp(true) + .include("src/win32/native.h") + .file("src/win32/native.cpp") + .compile("espansoinject"); + + println!("cargo:rustc-link-lib=static=espansoinject"); + println!("cargo:rustc-link-lib=dylib=user32"); + #[cfg(target_env = "gnu")] + println!("cargo:rustc-link-lib=dylib=stdc++"); +} + +#[cfg(target_os = "linux")] +fn cc_config() { + println!("cargo:rerun-if-changed=src/x11/native.cpp"); + println!("cargo:rerun-if-changed=src/x11/native.h"); + println!("cargo:rerun-if-changed=src/evdev/native.cpp"); + println!("cargo:rerun-if-changed=src/evdev/native.h"); + cc::Build::new() + .cpp(true) + .include("src/x11/native.h") + .file("src/x11/native.cpp") + .compile("espansoinject"); + cc::Build::new() + .cpp(true) + .include("src/evdev/native.h") + .file("src/evdev/native.cpp") + .compile("espansoinjectevdev"); + println!("cargo:rustc-link-search=native=/usr/lib/x86_64-linux-gnu/"); + println!("cargo:rustc-link-lib=static=espansoinject"); + println!("cargo:rustc-link-lib=static=espansoinjectevdev"); + println!("cargo:rustc-link-lib=dylib=X11"); + println!("cargo:rustc-link-lib=dylib=Xtst"); + println!("cargo:rustc-link-lib=dylib=xkbcommon"); +} + +#[cfg(target_os = "macos")] +fn cc_config() { + 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("espansoinject"); + println!("cargo:rustc-link-lib=dylib=c++"); + println!("cargo:rustc-link-lib=static=espansoinject"); + println!("cargo:rustc-link-lib=framework=Cocoa"); +} + +fn main() { + cc_config(); +} diff --git a/espanso-inject/src/keys.rs b/espanso-inject/src/keys.rs new file mode 100644 index 0000000..78b5d94 --- /dev/null +++ b/espanso-inject/src/keys.rs @@ -0,0 +1,340 @@ +use std::fmt::Display; + +use regex::Regex; + +lazy_static! { + static ref RAW_PARSER: Regex = Regex::new(r"^RAW\((\d+)\)$").unwrap(); +} + +#[derive(Debug, Clone)] +pub enum Key { + // Modifiers + Alt, + CapsLock, + Control, + Meta, + NumLock, + Shift, + + // Whitespace + Enter, + Tab, + Space, + + // Navigation + ArrowDown, + ArrowLeft, + ArrowRight, + ArrowUp, + End, + Home, + PageDown, + PageUp, + + // UI + Escape, + + // Editing keys + Backspace, + Insert, + Delete, + + // Function keys + 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(i32), +} + +impl Display for Key { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match *self { + Key::Alt => write!(f, "ALT"), + Key::CapsLock => write!(f, "CAPSLOCK"), + Key::Control => write!(f, "CTRL"), + Key::Meta => write!(f, "META"), + Key::NumLock => write!(f, "NUMLOCK"), + Key::Shift => write!(f, "SHIFT"), + Key::Enter => write!(f, "ENTER"), + Key::Tab => write!(f, "TAB"), + Key::Space => write!(f, "SPACE"), + Key::ArrowDown => write!(f, "DOWN"), + Key::ArrowLeft => write!(f, "LEFT"), + Key::ArrowRight => write!(f, "RIGHT"), + Key::ArrowUp => write!(f, "UP"), + Key::End => write!(f, "END"), + Key::Home => write!(f, "HOME"), + Key::PageDown => write!(f, "PAGEDOWN"), + Key::PageUp => write!(f, "PAGEUP"), + Key::Escape => write!(f, "ESC"), + Key::Backspace => write!(f, "BACKSPACE"), + Key::Insert => write!(f, "INSERT"), + Key::Delete => write!(f, "DELETE"), + Key::F1 => write!(f, "F1"), + Key::F2 => write!(f, "F2"), + Key::F3 => write!(f, "F3"), + Key::F4 => write!(f, "F4"), + Key::F5 => write!(f, "F5"), + Key::F6 => write!(f, "F6"), + Key::F7 => write!(f, "F7"), + Key::F8 => write!(f, "F8"), + Key::F9 => write!(f, "F9"), + Key::F10 => write!(f, "F10"), + Key::F11 => write!(f, "F11"), + Key::F12 => write!(f, "F12"), + Key::F13 => write!(f, "F13"), + Key::F14 => write!(f, "F14"), + Key::F15 => write!(f, "F15"), + Key::F16 => write!(f, "F16"), + Key::F17 => write!(f, "F17"), + Key::F18 => write!(f, "F18"), + Key::F19 => write!(f, "F19"), + Key::F20 => write!(f, "F20"), + Key::A => write!(f, "A"), + Key::B => write!(f, "B"), + Key::C => write!(f, "C"), + Key::D => write!(f, "D"), + Key::E => write!(f, "E"), + Key::F => write!(f, "F"), + Key::G => write!(f, "G"), + Key::H => write!(f, "H"), + Key::I => write!(f, "I"), + Key::J => write!(f, "J"), + Key::K => write!(f, "K"), + Key::L => write!(f, "L"), + Key::M => write!(f, "M"), + Key::N => write!(f, "N"), + Key::O => write!(f, "O"), + Key::P => write!(f, "P"), + Key::Q => write!(f, "Q"), + Key::R => write!(f, "R"), + Key::S => write!(f, "S"), + Key::T => write!(f, "T"), + Key::U => write!(f, "U"), + Key::V => write!(f, "V"), + Key::W => write!(f, "W"), + Key::X => write!(f, "X"), + Key::Y => write!(f, "Y"), + Key::Z => write!(f, "Z"), + Key::N0 => write!(f, "0"), + Key::N1 => write!(f, "1"), + Key::N2 => write!(f, "2"), + Key::N3 => write!(f, "3"), + Key::N4 => write!(f, "4"), + Key::N5 => write!(f, "5"), + Key::N6 => write!(f, "6"), + Key::N7 => write!(f, "7"), + Key::N8 => write!(f, "8"), + Key::N9 => write!(f, "9"), + Key::Numpad0 => write!(f, "NUMPAD0"), + Key::Numpad1 => write!(f, "NUMPAD1"), + Key::Numpad2 => write!(f, "NUMPAD2"), + Key::Numpad3 => write!(f, "NUMPAD3"), + Key::Numpad4 => write!(f, "NUMPAD4"), + Key::Numpad5 => write!(f, "NUMPAD5"), + Key::Numpad6 => write!(f, "NUMPAD6"), + Key::Numpad7 => write!(f, "NUMPAD7"), + Key::Numpad8 => write!(f, "NUMPAD8"), + Key::Numpad9 => write!(f, "NUMPAD9"), + Key::Raw(code) => write!(f, "RAW({})", code), + } + } +} + +impl Key { + pub fn parse(key: &str) -> Option { + let parsed = match key { + "ALT" => Some(Key::Alt), + "CAPSLOCK" => Some(Key::CapsLock), + "CTRL" => Some(Key::Control), + "META" => Some(Key::Meta), + "NUMLOCK" => Some(Key::NumLock), + "SHIFT" => Some(Key::Shift), + "ENTER" => Some(Key::Enter), + "TAB" => Some(Key::Tab), + "SPACE" => Some(Key::Space), + "DOWN" => Some(Key::ArrowDown), + "LEFT" => Some(Key::ArrowLeft), + "RIGHT" => Some(Key::ArrowRight), + "UP" => Some(Key::ArrowUp), + "END" => Some(Key::End), + "HOME" => Some(Key::Home), + "PAGEDOWN" => Some(Key::PageDown), + "PAGEUP" => Some(Key::PageUp), + "ESC" => Some(Key::Escape), + "BACKSPACE" => Some(Key::Backspace), + "INSERT" => Some(Key::Insert), + "DELETE" => Some(Key::Delete), + "F1" => Some(Key::F1), + "F2" => Some(Key::F2), + "F3" => Some(Key::F3), + "F4" => Some(Key::F4), + "F5" => Some(Key::F5), + "F6" => Some(Key::F6), + "F7" => Some(Key::F7), + "F8" => Some(Key::F8), + "F9" => Some(Key::F9), + "F10" => Some(Key::F10), + "F11" => Some(Key::F11), + "F12" => Some(Key::F12), + "F13" => Some(Key::F13), + "F14" => Some(Key::F14), + "F15" => Some(Key::F15), + "F16" => Some(Key::F16), + "F17" => Some(Key::F17), + "F18" => Some(Key::F18), + "F19" => Some(Key::F19), + "F20" => Some(Key::F20), + "A" => Some(Key::A), + "B" => Some(Key::B), + "C" => Some(Key::C), + "D" => Some(Key::D), + "E" => Some(Key::E), + "F" => Some(Key::F), + "G" => Some(Key::G), + "H" => Some(Key::H), + "I" => Some(Key::I), + "J" => Some(Key::J), + "K" => Some(Key::K), + "L" => Some(Key::L), + "M" => Some(Key::M), + "N" => Some(Key::N), + "O" => Some(Key::O), + "P" => Some(Key::P), + "Q" => Some(Key::Q), + "R" => Some(Key::R), + "S" => Some(Key::S), + "T" => Some(Key::T), + "U" => Some(Key::U), + "V" => Some(Key::V), + "W" => Some(Key::W), + "X" => Some(Key::X), + "Y" => Some(Key::Y), + "Z" => Some(Key::Z), + "0" => Some(Key::N0), + "1" => Some(Key::N1), + "2" => Some(Key::N2), + "3" => Some(Key::N3), + "4" => Some(Key::N4), + "5" => Some(Key::N5), + "6" => Some(Key::N6), + "7" => Some(Key::N7), + "8" => Some(Key::N8), + "9" => Some(Key::N9), + "NUMPAD0" => Some(Key::Numpad0), + "NUMPAD1" => Some(Key::Numpad1), + "NUMPAD2" => Some(Key::Numpad2), + "NUMPAD3" => Some(Key::Numpad3), + "NUMPAD4" => Some(Key::Numpad4), + "NUMPAD5" => Some(Key::Numpad5), + "NUMPAD6" => Some(Key::Numpad6), + "NUMPAD7" => Some(Key::Numpad7), + "NUMPAD8" => Some(Key::Numpad8), + "NUMPAD9" => Some(Key::Numpad9), + _ => None, + }; + + if parsed.is_none() { + // Attempt to parse raw keys + 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::(); + if let Ok(code) = code { + return Some(Key::Raw(code)); + } + } + } + } + + parsed + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn parse_works_correctly() { + assert!(matches!(Key::parse("ALT").unwrap(), Key::Alt)); + assert!(matches!(Key::parse("RAW(1234)").unwrap(), Key::Raw(1234))); + } + + #[test] + fn parse_invalid_keys() { + assert!(Key::parse("INVALID").is_none()); + assert!(Key::parse("RAW(a)").is_none()); + } +} \ No newline at end of file diff --git a/espanso-inject/src/lib.rs b/espanso-inject/src/lib.rs new file mode 100644 index 0000000..f9ad7d9 --- /dev/null +++ b/espanso-inject/src/lib.rs @@ -0,0 +1,72 @@ +/* + * This file is part of espanso. + * + * Copyright (C) 2019-2021 Federico Terzi + * + * espanso is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * espanso is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with espanso. If not, see . + */ + +use anyhow::Result; + +pub mod keys; + +#[cfg(target_os = "windows")] +mod win32; + +#[cfg(target_os = "linux")] +mod x11; + +#[cfg(target_os = "linux")] +mod evdev; + +#[cfg(target_os = "macos")] +mod mac; + +#[macro_use] +extern crate lazy_static; + +pub trait Injector { + fn send_string(&self, string: &str) -> Result<()>; + fn send_keys(&self, keys: &[keys::Key], delay: i32) -> Result<()>; + fn send_key_combination(&self, keys: &[keys::Key], delay: i32) -> Result<()>; +} + +pub struct InjectorOptions { + // Only relevant in Linux systems + use_evdev: bool, +} + +impl Default for InjectorOptions { + fn default() -> Self { + Self { + use_evdev: false, + } + } +} + +#[cfg(target_os = "windows")] +pub fn get_injector(_options: InjectorOptions) -> impl Injector { + win32::Win32Injector::new() +} + +#[cfg(target_os = "macos")] +pub fn get_injector(_options: InjectorOptions) -> impl Injector { + // TODO +} + +#[cfg(target_os = "linux")] +pub fn get_injector(options: InjectorOptions) -> impl Injector { + // TODO: differenciate based on the options +} + diff --git a/espanso-inject/src/win32/mod.rs b/espanso-inject/src/win32/mod.rs new file mode 100644 index 0000000..6e04565 --- /dev/null +++ b/espanso-inject/src/win32/mod.rs @@ -0,0 +1,118 @@ +/* + * 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 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 u16); + pub fn inject_separate_vkeys(vkey_array: *const i32, vkey_count: i32); + pub fn inject_vkeys_combination(vkey_array: *const i32, vkey_count: i32); + pub fn inject_separate_vkeys_with_delay(vkey_array: *const i32, vkey_count: i32, delay: i32); + pub fn inject_vkeys_combination_with_delay(vkey_array: *const i32, vkey_count: i32, delay: i32); +} + +pub struct Win32Injector {} + +#[allow(clippy::new_without_default)] +impl Win32Injector { + 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(Win32InjectorError::MappingFailure(key.clone()).into()); + } + } + Ok(virtual_keys) + } +} + +impl Injector for Win32Injector { + fn send_string(&self, string: &str) -> Result<()> { + let wide_string = widestring::WideCString::from_str(string)?; + unsafe { + inject_string(wide_string.as_ptr()); + } + Ok(()) + } + + fn send_keys(&self, keys: &[keys::Key], delay: i32) -> Result<()> { + let virtual_keys = Self::convert_to_vk_array(keys)?; + + if delay == 0 { + unsafe { + inject_separate_vkeys(virtual_keys.as_ptr(), virtual_keys.len() as i32); + } + } else { + unsafe { + inject_separate_vkeys_with_delay(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)?; + + if delay == 0 { + unsafe { + inject_vkeys_combination(virtual_keys.as_ptr(), virtual_keys.len() as i32); + } + } else { + unsafe { + inject_vkeys_combination_with_delay(virtual_keys.as_ptr(), virtual_keys.len() as i32, delay); + } + } + + Ok(()) + } +} + +#[derive(Error, Debug)] +pub enum Win32InjectorError { + #[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!(Win32Injector::convert_to_vk_array(&[keys::Key::Alt, keys::Key::V]).unwrap(), vec![0x12, 0x56]); + } +} \ No newline at end of file diff --git a/espanso-inject/src/win32/native.cpp b/espanso-inject/src/win32/native.cpp new file mode 100644 index 0000000..acadfe5 --- /dev/null +++ b/espanso-inject/src/win32/native.cpp @@ -0,0 +1,173 @@ +/* + * 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 +#include +#include +#include +#include +#include + +#define UNICODE + +#ifdef __MINGW32__ +#ifndef WINVER +#define WINVER 0x0606 +#endif +#define STRSAFE_NO_DEPRECATE +#endif + +#include +#include +#include +#include + +void inject_string(wchar_t *string) +{ + std::wstring msg(string); + std::cout << msg.length() << std::endl; + + std::vector vec; + for (auto ch : msg) + { + INPUT input = {0}; + input.type = INPUT_KEYBOARD; + input.ki.dwFlags = KEYEVENTF_UNICODE; + input.ki.wScan = ch; + vec.push_back(input); + + input.ki.dwFlags |= KEYEVENTF_KEYUP; + vec.push_back(input); + } + + SendInput(vec.size(), vec.data(), sizeof(INPUT)); +} + +void inject_separate_vkeys(int32_t *vkey_array, int32_t vkey_count) +{ + std::vector vec; + + for (int i = 0; i < vkey_count; i++) + { + INPUT input = {0}; + + input.type = INPUT_KEYBOARD; + input.ki.wScan = 0; + input.ki.time = 0; + input.ki.dwExtraInfo = 0; + input.ki.wVk = vkey_array[i]; + input.ki.dwFlags = 0; // 0 for key press + vec.push_back(input); + + input.ki.dwFlags = KEYEVENTF_KEYUP; + vec.push_back(input); + } + + SendInput(vec.size(), vec.data(), sizeof(INPUT)); +} + +void inject_vkeys_combination(int32_t *vkey_array, int32_t vkey_count) +{ + std::vector vec; + + // First send the presses + for (int i = 0; i < vkey_count; i++) + { + INPUT input = {0}; + input.type = INPUT_KEYBOARD; + input.ki.wScan = 0; + input.ki.time = 0; + input.ki.dwExtraInfo = 0; + input.ki.wVk = vkey_array[i]; + input.ki.dwFlags = 0; + vec.push_back(input); + } + + // Then the releases + for (int i = (vkey_count - 1); i >= 0; i--) + { + INPUT input = {0}; + input.type = INPUT_KEYBOARD; + input.ki.wScan = 0; + input.ki.time = 0; + input.ki.dwExtraInfo = 0; + input.ki.wVk = vkey_array[i]; + input.ki.dwFlags = KEYEVENTF_KEYUP; + vec.push_back(input); + } + + SendInput(vec.size(), vec.data(), sizeof(INPUT)); +} + +void inject_separate_vkeys_with_delay(int32_t *vkey_array, int32_t vkey_count, int32_t delay) +{ + for (int i = 0; i < vkey_count; i++) + { + INPUT input = {0}; + + input.type = INPUT_KEYBOARD; + input.ki.wScan = 0; + input.ki.time = 0; + input.ki.dwExtraInfo = 0; + input.ki.wVk = vkey_array[i]; + input.ki.dwFlags = 0; // 0 for key press + SendInput(1, &input, sizeof(INPUT)); + + Sleep(delay); + + input.ki.dwFlags = KEYEVENTF_KEYUP; // KEYEVENTF_KEYUP for key release + SendInput(1, &input, sizeof(INPUT)); + + Sleep(delay); + } +} + +void inject_vkeys_combination_with_delay(int32_t *vkey_array, int32_t vkey_count, int32_t delay) +{ + // First send the presses + for (int i = 0; i < vkey_count; i++) + { + INPUT input = {0}; + input.type = INPUT_KEYBOARD; + input.ki.wScan = 0; + input.ki.time = 0; + input.ki.dwExtraInfo = 0; + input.ki.wVk = vkey_array[i]; + input.ki.dwFlags = 0; + + SendInput(1, &input, sizeof(INPUT)); + Sleep(delay); + } + + // Then the releases + for (int i = (vkey_count - 1); i >= 0; i--) + { + INPUT input = {0}; + input.type = INPUT_KEYBOARD; + input.ki.wScan = 0; + input.ki.time = 0; + input.ki.dwExtraInfo = 0; + input.ki.wVk = vkey_array[i]; + input.ki.dwFlags = KEYEVENTF_KEYUP; + + SendInput(1, &input, sizeof(INPUT)); + Sleep(delay); + } +} \ No newline at end of file diff --git a/espanso-inject/src/win32/native.h b/espanso-inject/src/win32/native.h new file mode 100644 index 0000000..7e66fb3 --- /dev/null +++ b/espanso-inject/src/win32/native.h @@ -0,0 +1,40 @@ +/* + * 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(wchar_t * string); + +// Send a sequence of vkey presses and releases +extern "C" void inject_separate_vkeys(int32_t *vkey_array, int32_t vkey_count); + +// 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); + +// These two variants introduce a delay between each event, which is sometimes needed when +// dealing with slow applications +extern "C" void inject_separate_vkeys_with_delay(int32_t *vkey_array, int32_t vkey_count, int32_t delay); +extern "C" void inject_vkeys_combination_with_delay(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/win32/raw_keys.rs b/espanso-inject/src/win32/raw_keys.rs new file mode 100644 index 0000000..81d8576 --- /dev/null +++ b/espanso-inject/src/win32/raw_keys.rs @@ -0,0 +1,95 @@ +use crate::keys::Key; + +pub fn convert_key_to_vkey(key: &Key) -> Option { + let vkey = match key { + 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) +} diff --git a/espanso/Cargo.toml b/espanso/Cargo.toml index dd6668a..13fd83e 100644 --- a/espanso/Cargo.toml +++ b/espanso/Cargo.toml @@ -11,5 +11,6 @@ edition = "2018" [dependencies] espanso-detect = { path = "../espanso-detect" } espanso-ui = { path = "../espanso-ui" } +espanso-inject = { path = "../espanso-inject" } maplit = "1.0.2" simplelog = "0.9.0" \ No newline at end of file diff --git a/espanso/src/main.rs b/espanso/src/main.rs index 0c60ef2..374364a 100644 --- a/espanso/src/main.rs +++ b/espanso/src/main.rs @@ -1,5 +1,6 @@ use espanso_detect::event::{InputEvent, Status}; -use espanso_ui::{event::UIEvent::*, icons::TrayIcon, mac::*, menu::*}; +use espanso_inject::{Injector, get_injector}; +use espanso_ui::{event::UIEvent::*, icons::TrayIcon, menu::*}; use simplelog::{CombinedLogger, Config, LevelFilter, TermLogger, TerminalMode}; fn main() { @@ -35,25 +36,26 @@ 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(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()); println!("ev {:?}", event); match event { InputEvent::Mouse(_) => {} @@ -61,6 +63,7 @@ fn main() { 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!"); + injector.send_string("hey guys"); } } }