From ff6bfa20cb2db4f311424162fd20262f53ab3d91 Mon Sep 17 00:00:00 2001 From: Federico Terzi Date: Sat, 13 Feb 2021 15:55:28 +0100 Subject: [PATCH] First draft of espanso inject x11 implementation --- espanso-inject/build.rs | 32 +-- espanso-inject/src/evdev/README.md | 46 +++ espanso-inject/src/lib.rs | 47 ++- espanso-inject/src/x11/README.md | 53 ++++ espanso-inject/src/x11/ffi.rs | 85 ++++++ espanso-inject/src/x11/mod.rs | 447 +++++++++++++++++++++++++++++ espanso-inject/src/x11/raw_keys.rs | 106 +++++++ espanso-ui/src/linux/mod.rs | 4 + espanso/src/main.rs | 75 ++--- 9 files changed, 837 insertions(+), 58 deletions(-) create mode 100644 espanso-inject/src/evdev/README.md create mode 100644 espanso-inject/src/x11/README.md create mode 100644 espanso-inject/src/x11/ffi.rs create mode 100644 espanso-inject/src/x11/mod.rs create mode 100644 espanso-inject/src/x11/raw_keys.rs diff --git a/espanso-inject/build.rs b/espanso-inject/build.rs index 48f69b5..3b39860 100644 --- a/espanso-inject/build.rs +++ b/espanso-inject/build.rs @@ -35,23 +35,23 @@ fn cc_config() { #[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: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=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"); diff --git a/espanso-inject/src/evdev/README.md b/espanso-inject/src/evdev/README.md new file mode 100644 index 0000000..73275f6 --- /dev/null +++ b/espanso-inject/src/evdev/README.md @@ -0,0 +1,46 @@ +To support EVDEV injection + +At startup, the EVDEVInjector has to populate a lookup table to find the string <-> keycode + modifiers pairs. + +* We know that the keycode goes from 1 to 256 +* We can then rely on the `xkb_state_key_get_utf8` to find the string correspondent to each keycode +* Then we cycle between every modifier combination, updating the `xkb_state` with `xkb_state_update_key` + +Ref: https://xkbcommon.org/doc/current/structxkb__keymap.html + +``` + 1 #include + 2 #include + 3 + 4 int main() { + 5 struct xkb_context *ctx = xkb_context_new(XKB_CONTEXT_NO_FLAGS); + 6 struct xkb_keymap *keymap =xkb_keymap_new_from_names(ctx, NULL, 0); + 7 struct xkb_state *state = xkb_state_new(keymap); + 8 // a = 38 + 9 + 10 xkb_state_update_key(state, 42 + 8, XKB_KEY_DOWN); + 11 + 12 xkb_layout_index_t num = xkb_keymap_num_layouts_for_key(keymap, 42 + 8); + 13 char buff[10]; + 14 xkb_state_key_get_utf8(state, 38, buff, 9); + 15 + 16 printf("hey %s %d\n", buff, num); + 17 } +``` + +The available modifiers can be found in the https://github.com/torvalds/linux/blob/master/include/uapi/linux/input-event-codes.h + +#define KEY_LEFTCTRL 29 +#define KEY_LEFTSHIFT 42 +#define KEY_RIGHTSHIFT 54 +#define KEY_LEFTALT 56 +#define KEY_LEFTMETA 125 +#define KEY_RIGHTMETA 126 +#define KEY_RIGHTCTRL 97 +#define KEY_RIGHTALT 100 + +All these codes have to be added the EVDEV_OFFSET = 8 + +From the lookup table, we can generate the input event as shown here: https://github.com/ReimuNotMoe/ydotool/blob/7972e5e3390489c1395b06ca9dc7639763c7cc98/Tools/Type/Type.cpp + +Note that we also need to inject the correct modifiers to obtain the text \ No newline at end of file diff --git a/espanso-inject/src/lib.rs b/espanso-inject/src/lib.rs index 4e44980..3aa5444 100644 --- a/espanso-inject/src/lib.rs +++ b/espanso-inject/src/lib.rs @@ -27,8 +27,8 @@ mod win32; #[cfg(target_os = "linux")] mod x11; -#[cfg(target_os = "linux")] -mod evdev; +//#[cfg(target_os = "linux")] +//mod evdev; #[cfg(target_os = "macos")] mod mac; @@ -37,19 +37,49 @@ mod mac; 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<()>; + fn send_string(&self, string: &str, options: InjectionOptions) -> Result<()>; + fn send_keys(&self, keys: &[keys::Key], options: InjectionOptions) -> Result<()>; + fn send_key_combination(&self, keys: &[keys::Key], options: InjectionOptions) -> Result<()>; +} + +#[allow(dead_code)] +pub struct InjectionOptions { + // Delay between injected events + delay: i32, + + // Use original libxdo methods instead of patched version + // using XSendEvent rather than XTestFakeKeyEvent + // NOTE: Only relevant on X11 linux systems. + disable_fast_inject: bool, +} + +impl Default for InjectionOptions { + fn default() -> Self { + let default_delay = if cfg!(target_os = "windows") { + 0 + } else if cfg!(target_os = "macos") { + 2 + } else if cfg!(target_os = "linux") { + 0 + } else { + panic!("unsupported OS"); + }; + + Self { + delay: default_delay, + disable_fast_inject: false, + } + } } #[allow(dead_code)] -pub struct InjectorOptions { +pub struct InjectorCreationOptions { // Only relevant in Linux systems use_evdev: bool, } -impl Default for InjectorOptions { +impl Default for InjectorCreationOptions { fn default() -> Self { Self { use_evdev: false, @@ -68,7 +98,8 @@ pub fn get_injector(_options: InjectorOptions) -> impl Injector { } #[cfg(target_os = "linux")] -pub fn get_injector(options: InjectorOptions) -> impl Injector { +pub fn get_injector(options: InjectorCreationOptions) -> Result { // TODO: differenciate based on the options + x11::X11Injector::new() } diff --git a/espanso-inject/src/x11/README.md b/espanso-inject/src/x11/README.md new file mode 100644 index 0000000..ac258d2 --- /dev/null +++ b/espanso-inject/src/x11/README.md @@ -0,0 +1,53 @@ +Same approach as evdev, but the lookup logic is: + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +Display *data_disp = NULL; + +int main() { + data_disp = XOpenDisplay(NULL); + + for (int code = 0; code<256; code++) { + for (int state = 0; state < 256; state++) { + XKeyEvent event; + event.display = data_disp; + event.window = XDefaultRootWindow(data_disp); + event.root = XDefaultRootWindow(data_disp); + event.subwindow = None; + event.time = 0; + event.x = 1; + event.y = 1; + event.x_root = 1; + event.y_root = 1; + event.same_screen = True; + event.keycode = code + 8; + event.state = state; + event.type = KeyPress; + + char buffer[10]; + int res = XLookupString(&event, buffer, 9, NULL, NULL); + + printf("hey %d %d %s\n", code, state, buffer); + } + + } + +} + + +This way, we get the state mask associated with a character, and we can pass it directly when injecting a character: +https://github.com/federico-terzi/espanso/blob/master/native/liblinuxbridge/fast_xdo.cpp#L37 \ No newline at end of file diff --git a/espanso-inject/src/x11/ffi.rs b/espanso-inject/src/x11/ffi.rs new file mode 100644 index 0000000..d90d333 --- /dev/null +++ b/espanso-inject/src/x11/ffi.rs @@ -0,0 +1,85 @@ +// Some of these structures/methods are taken from the X11-rs project +// https://github.com/erlepereira/x11-rs + +use std::{ + ffi::c_void, + os::raw::{c_char, c_long, c_uint, c_ulong}, +}; + +use libc::c_int; + +pub enum Display {} +pub type Window = u64; +pub type Bool = i32; +pub type Time = u64; +pub type KeySym = u64; +pub type KeyCode = u8; + +#[allow(non_upper_case_globals)] +pub const KeyPress: c_int = 2; +#[allow(non_upper_case_globals)] +pub const KeyRelease: c_int = 3; + +#[derive(Debug, Clone, Copy, PartialEq)] +#[repr(C)] +pub struct XKeyEvent { + pub type_: c_int, + pub serial: c_ulong, + pub send_event: Bool, + pub display: *mut Display, + pub window: Window, + pub root: Window, + pub subwindow: Window, + pub time: Time, + pub x: c_int, + pub y: c_int, + pub x_root: c_int, + pub y_root: c_int, + pub state: c_uint, + pub keycode: c_uint, + pub same_screen: Bool, +} + +#[derive(Debug, Clone, Copy, PartialEq)] +#[repr(C)] +pub struct XModifierKeymap { + pub max_keypermod: c_int, + pub modifiermap: *mut KeyCode, +} + +#[link(name = "X11")] +extern "C" { + pub fn XOpenDisplay(name: *const c_char) -> *mut Display; + pub fn XCloseDisplay(display: *mut Display); + pub fn XLookupString( + event: *const XKeyEvent, + buffer_return: *mut c_char, + bytes_buffer: c_int, + keysym_return: *mut KeySym, + status_in_out: *const c_void, + ) -> c_int; + pub fn XDefaultRootWindow(display: *mut Display) -> Window; + pub fn XGetInputFocus( + display: *mut Display, + window_out: *mut Window, + revert_to: *mut c_int, + ) -> c_int; + pub fn XFlush(display: *mut Display) -> c_int; + pub fn XSendEvent( + display: *mut Display, + window: Window, + propagate: c_int, + event_mask: c_long, + event_send: *mut XKeyEvent, + ) -> c_int; + pub fn XGetModifierMapping(display: *mut Display) -> *mut XModifierKeymap; + pub fn XFreeModifiermap(map: *mut XModifierKeymap) -> c_int; + pub fn XTestFakeKeyEvent( + display: *mut Display, + key_code: c_uint, + is_press: c_int, + time: c_ulong, + ) -> c_int; + pub fn XSync(display: *mut Display, discard: c_int) -> c_int; + pub fn XQueryKeymap(display: *mut Display, keys_return: *mut u8); +} diff --git a/espanso-inject/src/x11/mod.rs b/espanso-inject/src/x11/mod.rs new file mode 100644 index 0000000..af32330 --- /dev/null +++ b/espanso-inject/src/x11/mod.rs @@ -0,0 +1,447 @@ +/* + * 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 ffi; +mod raw_keys; + +use std::{ + collections::HashMap, + ffi::{CStr, CString}, + os::raw::c_char, + slice, +}; + +use ffi::{Display, KeyCode, KeyPress, KeyRelease, KeySym, Window, XCloseDisplay, XDefaultRootWindow, XFlush, XFreeModifiermap, XGetInputFocus, XGetModifierMapping, XKeyEvent, XLookupString, XQueryKeymap, XSendEvent, XSync, XTestFakeKeyEvent}; +use log::error; + +use anyhow::Result; +use raw_keys::convert_key_to_sym; +use thiserror::Error; + +use crate::{keys, InjectionOptions, Injector}; + +// Offset between evdev keycodes (where KEY_ESCAPE is 1), and the evdev XKB +// keycode set (where ESC is 9). +const EVDEV_OFFSET: u32 = 8; + +#[derive(Clone, Copy, Debug)] +struct KeyRecord { + // Keycode + code: u32, + // Modifier state which combined with the code produces the char + // This is a bit mask: + state: u32, +} + +type CharMap = HashMap; +type SymMap = HashMap; + +pub struct X11Injector { + display: *mut Display, + + char_map: CharMap, + sym_map: SymMap, +} + +#[allow(clippy::new_without_default)] +impl X11Injector { + pub fn new() -> Result { + // Necessary to properly handle non-ascii chars + let empty_string = CString::new("")?; + unsafe { + libc::setlocale(libc::LC_ALL, empty_string.as_ptr()); + } + + let display = unsafe { ffi::XOpenDisplay(std::ptr::null()) }; + if display.is_null() { + return Err(X11InjectorError::InitFailure().into()); + } + + let (char_map, sym_map) = Self::generate_maps(display); + + Ok(Self { + display, + char_map, + sym_map, + }) + } + + fn generate_maps(display: *mut Display) -> (CharMap, SymMap) { + let mut char_map = HashMap::new(); + let mut sym_map = HashMap::new(); + + let root_window = unsafe { XDefaultRootWindow(display) }; + + // Cycle through all state/code combinations to populate the reverse lookup tables + for key_code in 0..256u32 { + for modifier_state in 0..256u32 { + let code_with_offset = key_code + EVDEV_OFFSET; + let event = XKeyEvent { + display, + keycode: code_with_offset, + state: modifier_state, + + // These might not even need to be filled + window: root_window, + root: root_window, + same_screen: 1, + time: 0, + type_: KeyRelease, + x_root: 1, + y_root: 1, + x: 1, + y: 1, + subwindow: 0, + serial: 0, + send_event: 0, + }; + + let mut sym: KeySym = 0; + let mut buffer: [c_char; 10] = [0; 10]; + let result = unsafe { + XLookupString( + &event, + buffer.as_mut_ptr(), + (buffer.len() - 1) as i32, + &mut sym, + std::ptr::null(), + ) + }; + + let key_record = KeyRecord { + code: code_with_offset, + state: modifier_state, + }; + + // Keysym was found + if sym != 0 { + sym_map.entry(sym).or_insert(key_record); + } + + // Char was found + if result > 0 { + let raw_string = unsafe { CStr::from_ptr(buffer.as_ptr()) }; + let string = raw_string.to_string_lossy().to_string(); + char_map.entry(string).or_insert(key_record); + } + } + } + + (char_map, sym_map) + } + + fn convert_to_sym_array(keys: &[keys::Key]) -> Result> { + let mut virtual_keys: Vec = Vec::new(); + for key in keys.iter() { + let vk = convert_key_to_sym(key); + if let Some(vk) = vk { + virtual_keys.push(vk as u64) + } else { + return Err(X11InjectorError::MappingFailure(key.clone()).into()); + } + } + Ok(virtual_keys) + } + + fn convert_to_record_array(&self, syms: &[KeySym]) -> Result> { + syms + .iter() + .map(|sym| { + self + .sym_map + .get(&sym) + .cloned() + .ok_or_else(|| X11InjectorError::SymMappingFailure(*sym).into()) + }) + .collect() + } + + // This method was inspired by the wonderful xdotool by Jordan Sissel + // https://github.com/jordansissel/xdotool + fn get_modifier_codes(&self) -> Vec> { + let modifiers_ptr = unsafe { XGetModifierMapping(self.display) }; + let modifiers = unsafe { *modifiers_ptr }; + + let mut modifiers_codes = Vec::new(); + + for mod_index in 0..=7 { + let mut modifier_codes = Vec::new(); + for mod_key in 0..modifiers.max_keypermod { + let modifier_map = unsafe { + slice::from_raw_parts( + modifiers.modifiermap, + (8 * modifiers.max_keypermod) as usize, + ) + + }; + let keycode = modifier_map[(mod_index * modifiers.max_keypermod + mod_key) as usize]; + if keycode != 0 { + modifier_codes.push(keycode); + } + } + modifiers_codes.push(modifier_codes); + } + + unsafe { XFreeModifiermap(modifiers_ptr) }; + + modifiers_codes + } + + fn render_key_combination(&self, original_records: &[KeyRecord]) -> Vec { + let modifiers_codes = self.get_modifier_codes(); + let mut records = Vec::new(); + + let mut current_state = 0u32; + for record in original_records { + let mut current_record = *record; + + // Render the state by applying the modifiers + for (mod_index, modifier) in modifiers_codes.iter().enumerate() { + if modifier.contains(&(record.code as u8)) { + current_state |= 1 << mod_index; + } + } + + current_record.state = current_state; + records.push(current_record); + } + + records + } + + fn get_focused_window(&self) -> Window { + let mut focused_window: Window = 0; + let mut revert_to = 0; + unsafe { + XGetInputFocus(self.display, &mut focused_window, &mut revert_to); + } + focused_window + } + + fn send_key(&self, window: Window, record: &KeyRecord, pressed: bool, delay_us: u32) { + let root_window = unsafe { XDefaultRootWindow(self.display) }; + let mut event = XKeyEvent { + display: self.display, + keycode: record.code, + state: record.state, + window, + root: root_window, + same_screen: 1, + time: 0, + type_: if pressed { KeyPress } else { KeyRelease }, + x_root: 1, + y_root: 1, + x: 1, + y: 1, + subwindow: 0, + serial: 0, + send_event: 0, + }; + unsafe { + XSendEvent(self.display, window, 1, 0, &mut event); + XFlush(self.display); + } + + if delay_us != 0 { + unsafe { + libc::usleep(delay_us); + } + } + } + + fn xtest_send_modifiers(&self, modmask: u32, pressed: bool) { + let modifiers_codes = self.get_modifier_codes(); + for (mod_index, modifier_codes) in modifiers_codes.into_iter().enumerate() { + if (modmask & (1 << mod_index)) != 0 { + for keycode in modifier_codes { + let is_press = if pressed { 1 } else { 0 }; + unsafe { + XTestFakeKeyEvent(self.display, keycode as u32, is_press, 0); + XSync(self.display, 0); + } + } + } + } + } + + fn xtest_send_key(&self, record: &KeyRecord, pressed: bool, delay_us: u32) { + // If the key requires any modifier, we need to send those events + if record.state != 0 { + self.xtest_send_modifiers(record.state, pressed); + } + + let is_press = if pressed { 1 } else { 0 }; + unsafe { + XTestFakeKeyEvent(self.display, record.code, is_press, 0); + XSync(self.display, 0); + XFlush(self.display); + } + + if delay_us != 0 { + unsafe { + libc::usleep(delay_us); + } + } + } + + fn xtest_release_all_keys(&self) { + let mut keys: [u8; 32] = [0; 32]; + unsafe { + XQueryKeymap(self.display, keys.as_mut_ptr()); + } + + #[allow(clippy::needless_range_loop)] + for i in 0..32 { + // Only those that are pressed should be changed + if keys[i] != 0 { + for k in 0..8 { + if (keys[i] & (1 << k)) != 0 { + let key_code = i * 8 + k; + unsafe { + XTestFakeKeyEvent(self.display, key_code as u32, 0, 0); + } + } + } + } + } + } +} + +impl Drop for X11Injector { + fn drop(&mut self) { + unsafe { + XCloseDisplay(self.display); + } + } +} + +impl Injector for X11Injector { + fn send_string(&self, string: &str, options: InjectionOptions) -> Result<()> { + let focused_window = self.get_focused_window(); + + if options.disable_fast_inject { + self.xtest_release_all_keys(); + } + + // Compute all the key record sequence first to make sure a mapping is available + let records: Result> = string + .chars() + .map(|c| c.to_string()) + .map(|char| { + self + .char_map + .get(&char) + .cloned() + .ok_or_else(|| X11InjectorError::CharMappingFailure(char).into()) + }) + .collect(); + + let delay_us = options.delay as u32 * 1000; // Convert to micro seconds + + for record in records? { + if options.disable_fast_inject { + self.xtest_send_key(&record, true, delay_us); + self.xtest_send_key(&record, false, delay_us); + } else { + self.send_key(focused_window, &record, true, delay_us); + self.send_key(focused_window, &record, false, delay_us); + } + } + + Ok(()) + } + + fn send_keys(&self, keys: &[keys::Key], options: InjectionOptions) -> Result<()> { + let focused_window = self.get_focused_window(); + + // Compute all the key record sequence first to make sure a mapping is available + let syms = Self::convert_to_sym_array(keys)?; + let records = self.convert_to_record_array(&syms)?; + + if options.disable_fast_inject { + self.xtest_release_all_keys(); + } + + let delay_us = options.delay as u32 * 1000; // Convert to micro seconds + + for record in records { + if options.disable_fast_inject { + self.xtest_send_key(&record, true, delay_us); + self.xtest_send_key(&record, false, delay_us); + } else { + self.send_key(focused_window, &record, true, delay_us); + self.send_key(focused_window, &record, false, delay_us); + } + } + + Ok(()) + } + + fn send_key_combination(&self, keys: &[keys::Key], options: InjectionOptions) -> Result<()> { + let focused_window = self.get_focused_window(); + + // Compute all the key record sequence first to make sure a mapping is available + let syms = Self::convert_to_sym_array(keys)?; + let records = self.convert_to_record_array(&syms)?; + + // Render the correct modifier mask for the given sequence + let records = self.render_key_combination(&records); + + if options.disable_fast_inject { + self.xtest_release_all_keys(); + } + + let delay_us = options.delay as u32 * 1000; // Convert to micro seconds + + // First press the keys + for record in records.iter() { + if options.disable_fast_inject { + self.xtest_send_key(&record, true, delay_us); + } else { + self.send_key(focused_window, &record, true, delay_us); + } + } + + // Then release them + for record in records.iter().rev() { + if options.disable_fast_inject { + self.xtest_send_key(&record, false, delay_us); + } else { + self.send_key(focused_window, &record, false, delay_us); + } + } + + Ok(()) + } +} + +#[derive(Error, Debug)] +pub enum X11InjectorError { + #[error("failed to initialize x11 display")] + InitFailure(), + + #[error("missing vkey mapping for char `{0}`")] + CharMappingFailure(String), + + #[error("missing sym mapping for key `{0}`")] + MappingFailure(keys::Key), + + #[error("missing record mapping for sym `{0}`")] + SymMappingFailure(u64), +} \ No newline at end of file diff --git a/espanso-inject/src/x11/raw_keys.rs b/espanso-inject/src/x11/raw_keys.rs new file mode 100644 index 0000000..4899d13 --- /dev/null +++ b/espanso-inject/src/x11/raw_keys.rs @@ -0,0 +1,106 @@ +use crate::keys::Key; + +pub fn convert_key_to_sym(key: &Key) -> Option { + match key { + Key::Alt => Some(0xFFE9), + Key::CapsLock => Some(0xFFE5), + Key::Control => Some(0xFFE3), + Key::Meta => Some(0xFFEB), + Key::NumLock => Some(0xFF7F), + Key::Shift => Some(0xFFE1), + + // Whitespace + Key::Enter => Some(0xFF0D), + Key::Tab => Some(0xFF09), + Key::Space => Some(0x20), + + // Navigation + Key::ArrowDown => Some(0xFF54), + Key::ArrowLeft => Some(0xFF51), + Key::ArrowRight => Some(0xFF53), + Key::ArrowUp => Some(0xFF52), + Key::End => Some(0xFF57), + Key::Home => Some(0xFF50), + Key::PageDown => Some(0xFF56), + Key::PageUp => Some(0xFF55), + + // UI keys + Key::Escape => Some(0xFF1B), + + // Editing keys + Key::Backspace => Some(0xFF08), + Key::Insert => Some(0xff63), + Key::Delete => Some(0xffff), + + // Function keys + Key::F1 => Some(0xFFBE), + Key::F2 => Some(0xFFBF), + Key::F3 => Some(0xFFC0), + Key::F4 => Some(0xFFC1), + Key::F5 => Some(0xFFC2), + Key::F6 => Some(0xFFC3), + Key::F7 => Some(0xFFC4), + Key::F8 => Some(0xFFC5), + Key::F9 => Some(0xFFC6), + Key::F10 => Some(0xFFC7), + Key::F11 => Some(0xFFC8), + Key::F12 => Some(0xFFC9), + Key::F13 => Some(0xFFCA), + Key::F14 => Some(0xFFCB), + Key::F15 => Some(0xFFCC), + Key::F16 => Some(0xFFCD), + Key::F17 => Some(0xFFCE), + Key::F18 => Some(0xFFCF), + Key::F19 => Some(0xFFD0), + Key::F20 => Some(0xFFD1), + + Key::A => Some(0x0061), + Key::B => Some(0x0062), + Key::C => Some(0x0063), + Key::D => Some(0x0064), + Key::E => Some(0x0065), + Key::F => Some(0x0066), + Key::G => Some(0x0067), + Key::H => Some(0x0068), + Key::I => Some(0x0069), + Key::J => Some(0x006a), + Key::K => Some(0x006b), + Key::L => Some(0x006c), + Key::M => Some(0x006d), + Key::N => Some(0x006e), + Key::O => Some(0x006f), + Key::P => Some(0x0070), + Key::Q => Some(0x0071), + Key::R => Some(0x0072), + Key::S => Some(0x0073), + Key::T => Some(0x0074), + Key::U => Some(0x0075), + Key::V => Some(0x0076), + Key::W => Some(0x0077), + Key::X => Some(0x0078), + Key::Y => Some(0x0079), + Key::Z => Some(0x007a), + + Key::N0 => Some(0x0030), + Key::N1 => Some(0x0031), + Key::N2 => Some(0x0032), + Key::N3 => Some(0x0033), + Key::N4 => Some(0x0034), + Key::N5 => Some(0x0035), + Key::N6 => Some(0x0036), + Key::N7 => Some(0x0037), + Key::N8 => Some(0x0038), + Key::N9 => Some(0x0039), + Key::Numpad0 => Some(0xffb0), + Key::Numpad1 => Some(0xffb1), + Key::Numpad2 => Some(0xffb2), + Key::Numpad3 => Some(0xffb3), + Key::Numpad4 => Some(0xffb4), + Key::Numpad5 => Some(0xffb5), + Key::Numpad6 => Some(0xffb6), + Key::Numpad7 => Some(0xffb7), + Key::Numpad8 => Some(0xffb8), + Key::Numpad9 => Some(0xffb9), + Key::Raw(code) => Some(*code as u32), + } +} diff --git a/espanso-ui/src/linux/mod.rs b/espanso-ui/src/linux/mod.rs index 56c5a7f..0781573 100644 --- a/espanso-ui/src/linux/mod.rs +++ b/espanso-ui/src/linux/mod.rs @@ -52,6 +52,10 @@ impl LinuxEventLoop { Self { rx } } + pub fn initialize(&self) { + // NOOP on linux + } + pub fn run(&self) { // We don't run an event loop on Linux as there is no tray icon or application window needed. // Thad said, we still need a way to block this method, and thus we use a channel diff --git a/espanso/src/main.rs b/espanso/src/main.rs index 9f77312..b66edcc 100644 --- a/espanso/src/main.rs +++ b/espanso/src/main.rs @@ -1,3 +1,5 @@ +use std::time::Duration; + use espanso_detect::event::{InputEvent, Status}; use espanso_inject::{get_injector, Injector, keys}; use espanso_ui::{event::UIEvent::*, icons::TrayIcon, menu::*}; @@ -42,58 +44,63 @@ fn main() { // 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, + // let (remote, mut eventloop) = espanso_ui::mac::create(espanso_ui::mac::MacUIOptions { + // show_icon: true, + // icon_paths: &icon_paths, + // }); + let (remote, mut eventloop) = espanso_ui::linux::create(espanso_ui::linux::LinuxUIOptions { + notification_icon_path: r"C:\Users\Freddy\Insync\Development\Espanso\Images\icongreensmall.png".to_string(), }); eventloop.initialize(); let handle = std::thread::spawn(move || { + let injector = get_injector(Default::default()).unwrap(); //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::x11::X11Source::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(_) => {} InputEvent::Keyboard(evt) => { - if evt.key == espanso_detect::event::Key::Shift && evt.status == Status::Pressed { + if evt.key == espanso_detect::event::Key::Escape && evt.status == Status::Released { //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); + injector.send_string("Hey guys! @", Default::default()).expect("error"); + //std::thread::sleep(std::time::Duration::from_secs(2)); + //injector.send_key_combination(&[keys::Key::Control, keys::Key::V], Default::default()).unwrap(); } } } })); }); - eventloop.run(Box::new(move |event| { - println!("ui {:?}", event); - let menu = Menu::from(vec![ - MenuItem::Simple(SimpleMenuItem::new("open", "Open")), - MenuItem::Separator, - MenuItem::Sub(SubMenuItem::new( - "Sub", - vec![ - MenuItem::Simple(SimpleMenuItem::new("sub1", "Sub 1")), - MenuItem::Simple(SimpleMenuItem::new("sub2", "Sub 2")), - ], - )), - ]) - .unwrap(); - match event { - TrayIconClick => { - remote.show_context_menu(&menu); - } - ContextMenuClick(raw_id) => { - //remote.update_tray_icon(TrayIcon::Disabled); - remote.show_notification("Hello there!"); - println!("item {:?}", menu.get_item_id(raw_id)); - } - } - })); + eventloop.run(); + // eventloop.run(Box::new(move |event| { + // println!("ui {:?}", event); + // let menu = Menu::from(vec![ + // MenuItem::Simple(SimpleMenuItem::new("open", "Open")), + // MenuItem::Separator, + // MenuItem::Sub(SubMenuItem::new( + // "Sub", + // vec![ + // MenuItem::Simple(SimpleMenuItem::new("sub1", "Sub 1")), + // MenuItem::Simple(SimpleMenuItem::new("sub2", "Sub 2")), + // ], + // )), + // ]) + // .unwrap(); + // match event { + // TrayIconClick => { + // remote.show_context_menu(&menu); + // } + // ContextMenuClick(raw_id) => { + // //remote.update_tray_icon(TrayIcon::Disabled); + // remote.show_notification("Hello there!"); + // println!("item {:?}", menu.get_item_id(raw_id)); + // } + // } + // })); }