From d0270eb99baf044f5682bfcd6dbe1d3968a2c482 Mon Sep 17 00:00:00 2001 From: Federico Terzi Date: Fri, 13 Sep 2019 22:57:53 +0200 Subject: [PATCH] Refactor Linux events and add executable filter detection --- native/liblinuxbridge/bridge.cpp | 47 ++++++++++++++++--- native/liblinuxbridge/bridge.h | 12 +++-- src/bridge/linux.rs | 12 ++--- src/clipboard/linux.rs | 12 +++-- src/clipboard/mod.rs | 6 +-- src/context/linux.rs | 77 ++++++++++++++++++++++++++++++++ src/context/mod.rs | 6 +-- src/keyboard/linux.rs | 62 ------------------------- src/system/linux.rs | 28 ++++++++---- src/system/mod.rs | 6 +-- src/ui/linux.rs | 17 ++++--- src/ui/mod.rs | 4 +- 12 files changed, 173 insertions(+), 116 deletions(-) create mode 100644 src/context/linux.rs diff --git a/native/liblinuxbridge/bridge.cpp b/native/liblinuxbridge/bridge.cpp index e0064be..4dfdb55 100644 --- a/native/liblinuxbridge/bridge.cpp +++ b/native/liblinuxbridge/bridge.cpp @@ -59,17 +59,18 @@ xdo_t * xdo_context; void event_callback (XPointer, XRecordInterceptData*); KeypressCallback keypress_callback; -void * interceptor_instance; +void * context_instance; -void register_keypress_callback(void * self, KeypressCallback callback) { +void register_keypress_callback(KeypressCallback callback) { keypress_callback = callback; - interceptor_instance = self; } -int32_t initialize() { +int32_t initialize(void * _context_instance) { setlocale(LC_ALL, ""); + context_instance = _context_instance; + /* Open the connections to the X server. RE recommends to open 2 connections to the X server: @@ -185,9 +186,9 @@ void event_callback(XPointer p, XRecordInterceptData *hook) case KeyRelease: //printf ("%d %d %s\n", key_code, res, buffer.data()); if (res > 0 && key_code != 22) { // Printable character, but not backspace - keypress_callback(interceptor_instance, buffer.data(), buffer.size(), 0, key_code); + keypress_callback(context_instance, buffer.data(), buffer.size(), 0, key_code); }else{ // Modifier key - keypress_callback(interceptor_instance, NULL, 0, 1, key_code); + keypress_callback(context_instance, NULL, 0, 1, key_code); } break; default: @@ -314,4 +315,36 @@ int32_t get_active_window_class(char * buffer, int32_t size) { XCloseDisplay(disp); return 1; -} \ No newline at end of file +} + +int32_t get_active_window_executable(char *buffer, int32_t size) { + Display *disp = XOpenDisplay(NULL); + + if (!disp) { + return -1; + } + + // Get the active window + Window win; + int revert_to_return; + XGetInputFocus(disp, &win, &revert_to_return); + + // Get the window process PID + char *pid_raw = (char*)get_property(disp,win, XA_CARDINAL, "_NET_WM_PID", NULL); + if (pid_raw == NULL) { + return -2; + } + + int pid = pid_raw[0] | pid_raw[1] << 8 | pid_raw[2] << 16 | pid_raw[3] << 24; + + // Get the executable path from it + char proc_path[250]; + snprintf(proc_path, 250, "/proc/%d/exe", pid); + + readlink(proc_path, buffer, size); + + XFree(pid_raw); + XCloseDisplay(disp); + + return 1; +} diff --git a/native/liblinuxbridge/bridge.h b/native/liblinuxbridge/bridge.h index 92d8276..28be01c 100644 --- a/native/liblinuxbridge/bridge.h +++ b/native/liblinuxbridge/bridge.h @@ -3,10 +3,12 @@ #include +extern void * context_instance; + /* * Initialize the X11 context and parameters */ -extern "C" int32_t initialize(); +extern "C" int32_t initialize(void * context_instance); /* * Start the event loop indefinitely. Blocking call. @@ -25,12 +27,11 @@ extern "C" void cleanup(); typedef void (*KeypressCallback)(void * self, const char *buffer, int32_t len, int32_t is_modifier, int32_t key_code); extern KeypressCallback keypress_callback; -extern void * interceptor_instance; /* * Register the callback that will be called when a keypress was made */ -extern "C" void register_keypress_callback(void *self, KeypressCallback callback); +extern "C" void register_keypress_callback(KeypressCallback callback); /* * Type the given string by simulating Key Presses @@ -65,4 +66,9 @@ extern "C" int32_t get_active_window_name(char * buffer, int32_t size); */ extern "C" int32_t get_active_window_class(char * buffer, int32_t size); +/* + * Return the active windows's executable path + */ +extern "C" int32_t get_active_window_executable(char * buffer, int32_t size); + #endif //ESPANSO_BRIDGE_H diff --git a/src/bridge/linux.rs b/src/bridge/linux.rs index aa44951..eedb760 100644 --- a/src/bridge/linux.rs +++ b/src/bridge/linux.rs @@ -3,17 +3,19 @@ use std::os::raw::{c_void, c_char}; #[allow(improper_ctypes)] #[link(name="linuxbridge", kind="static")] extern { + pub fn initialize(s: *const c_void); + pub fn eventloop(); + pub fn cleanup(); + // System pub fn get_active_window_name(buffer: *mut c_char, size: i32) -> i32; pub fn get_active_window_class(buffer: *mut c_char, size: i32) -> i32; + pub fn get_active_window_executable(buffer: *mut c_char, size: i32) -> i32; // Keyboard - pub fn register_keypress_callback(s: *const c_void, - cb: extern fn(_self: *mut c_void, *const u8, + pub fn register_keypress_callback(cb: extern fn(_self: *mut c_void, *const u8, i32, i32, i32)); - pub fn initialize(); - pub fn eventloop(); - pub fn cleanup(); + pub fn send_string(string: *const c_char); pub fn delete_string(count: i32); pub fn trigger_paste(); diff --git a/src/clipboard/linux.rs b/src/clipboard/linux.rs index 424eb90..c2c0b58 100644 --- a/src/clipboard/linux.rs +++ b/src/clipboard/linux.rs @@ -1,15 +1,9 @@ use std::process::{Command, Stdio}; use std::io::{Write}; -pub struct LinuxClipboardManager { - -} +pub struct LinuxClipboardManager {} impl super::ClipboardManager for LinuxClipboardManager { - fn initialize(&self) { - // TODO: check if xclip is present and log an error otherwise. - } - fn get_clipboard(&self) -> Option { let res = Command::new("xclip") .args(&["-o", "-sel", "clip"]) @@ -46,5 +40,9 @@ impl super::ClipboardManager for LinuxClipboardManager { } impl LinuxClipboardManager { + pub fn new() -> LinuxClipboardManager { + // TODO: check if xclip is present and log an error otherwise. + LinuxClipboardManager{} + } } \ No newline at end of file diff --git a/src/clipboard/mod.rs b/src/clipboard/mod.rs index f837765..76b71a4 100644 --- a/src/clipboard/mod.rs +++ b/src/clipboard/mod.rs @@ -12,14 +12,10 @@ pub trait ClipboardManager { fn set_clipboard(&self, payload: &str); } -// TODO: change windows and linux implementations to avoid initialize() call and use constructor instead - // LINUX IMPLEMENTATION #[cfg(target_os = "linux")] pub fn get_manager() -> impl ClipboardManager { - let manager = linux::LinuxClipboardManager{}; - manager.initialize(); - manager + linux::LinuxClipboardManager::new() } // WINDOWS IMPLEMENTATION diff --git a/src/context/linux.rs b/src/context/linux.rs new file mode 100644 index 0000000..d7810c0 --- /dev/null +++ b/src/context/linux.rs @@ -0,0 +1,77 @@ +use std::sync::mpsc::Sender; +use std::os::raw::c_void; +use crate::event::*; +use crate::event::KeyModifier::*; +use crate::bridge::linux::*; + +#[repr(C)] +pub struct LinuxContext { + pub send_channel: Sender +} + +impl LinuxContext { + pub fn new(send_channel: Sender) -> Box { + let context = Box::new(LinuxContext { + send_channel, + }); + + unsafe { + let context_ptr = &*context as *const LinuxContext as *const c_void; + + register_keypress_callback(keypress_callback); + + initialize(context_ptr); // TODO: check initialization return codes + } + + context + } +} + +impl super::Context for LinuxContext { + fn eventloop(&self) { + unsafe { + eventloop(); + } + } +} + +impl Drop for LinuxContext { + fn drop(&mut self) { + unsafe { cleanup(); } + } +} + +// Native bridge code + +extern fn keypress_callback(_self: *mut c_void, raw_buffer: *const u8, len: i32, + is_modifier: i32, key_code: i32) { + unsafe { + let _self = _self as *mut LinuxContext; + + if is_modifier == 0 { // Char event + // Convert the received buffer to a character + let buffer = std::slice::from_raw_parts(raw_buffer, len as usize); + let r = String::from_utf8_lossy(buffer).chars().nth(0); + + // Send the char through the channel + if let Some(c) = r { + let event = Event::Key(KeyEvent::Char(c)); + (*_self).send_channel.send(event).unwrap(); + } + }else{ // Modifier event + let modifier: Option = match key_code { + 133 => Some(META), + 50 => Some(SHIFT), + 64 => Some(ALT), + 37 => Some(CTRL), + 22 => Some(BACKSPACE), + _ => None, + }; + + if let Some(modifier) = modifier { + let event = Event::Key(KeyEvent::Modifier(modifier)); + (*_self).send_channel.send(event).unwrap(); + } + } + } +} \ No newline at end of file diff --git a/src/context/mod.rs b/src/context/mod.rs index ec5ce45..7018f22 100644 --- a/src/context/mod.rs +++ b/src/context/mod.rs @@ -22,10 +22,8 @@ pub fn new(send_channel: Sender) -> Box { // LINUX IMPLEMENTATION #[cfg(target_os = "linux")] -pub fn new(send_channel: Sender) -> Box { // TODO - let manager = linux::LinuxUIManager{}; - manager.initialize(); - manager +pub fn new(send_channel: Sender) -> Box { + linux::LinuxContext::new(send_channel) } // WINDOWS IMPLEMENTATION diff --git a/src/keyboard/linux.rs b/src/keyboard/linux.rs index 06ca82b..6793162 100644 --- a/src/keyboard/linux.rs +++ b/src/keyboard/linux.rs @@ -1,37 +1,8 @@ use std::sync::mpsc; use std::os::raw::{c_void}; use std::ffi::CString; -use crate::keyboard::{KeyEvent, KeyModifier}; -use crate::keyboard::KeyModifier::*; use crate::bridge::linux::*; -#[repr(C)] -pub struct LinuxKeyboardInterceptor { - pub sender: mpsc::Sender -} - -impl super::KeyboardInterceptor for LinuxKeyboardInterceptor { - fn initialize(&self) { - unsafe { - let self_ptr = self as *const LinuxKeyboardInterceptor as *const c_void; - register_keypress_callback( self_ptr,keypress_callback); - initialize(); // TODO: check initialization return codes - } - } - - fn start(&self) { - unsafe { - eventloop(); - } - } -} - -impl Drop for LinuxKeyboardInterceptor { - fn drop(&mut self) { - unsafe { cleanup(); } - } -} - pub struct LinuxKeyboardManager { } @@ -57,37 +28,4 @@ impl super::KeyboardManager for LinuxKeyboardManager { fn delete_string(&self, count: i32) { unsafe {delete_string(count)} } -} - -// Native bridge code - -extern fn keypress_callback(_self: *mut c_void, raw_buffer: *const u8, len: i32, - is_modifier: i32, key_code: i32) { - unsafe { - let _self = _self as *mut LinuxKeyboardInterceptor; - - if is_modifier == 0 { // Char event - // Convert the received buffer to a character - let buffer = std::slice::from_raw_parts(raw_buffer, len as usize); - let r = String::from_utf8_lossy(buffer).chars().nth(0); - - // Send the char through the channel - if let Some(c) = r { - (*_self).sender.send(KeyEvent::Char(c)).unwrap(); - } - }else{ // Modifier event - let modifier: Option = match key_code { - 133 => Some(META), - 50 => Some(SHIFT), - 64 => Some(ALT), - 37 => Some(CTRL), - 22 => Some(BACKSPACE), - _ => None, - }; - - if let Some(modifier) = modifier { - (*_self).sender.send(KeyEvent::Modifier(modifier)).unwrap(); - } - } - } } \ No newline at end of file diff --git a/src/system/linux.rs b/src/system/linux.rs index f39fa47..befef17 100644 --- a/src/system/linux.rs +++ b/src/system/linux.rs @@ -1,11 +1,9 @@ use std::os::raw::c_char; -use crate::bridge::linux::{get_active_window_name, get_active_window_class}; +use crate::bridge::linux::{get_active_window_name, get_active_window_class, get_active_window_executable}; use std::ffi::CStr; -pub struct LinuxSystemManager { - -} +pub struct LinuxSystemManager {} impl super::SystemManager for LinuxSystemManager { fn get_current_window_title(&self) -> Option { @@ -45,12 +43,26 @@ impl super::SystemManager for LinuxSystemManager { } fn get_current_window_executable(&self) -> Option { - unimplemented!() + unsafe { + let mut buffer : [c_char; 100] = [0; 100]; + let res = get_active_window_executable(buffer.as_mut_ptr(), buffer.len() as i32); + + if res > 0 { + let c_string = CStr::from_ptr(buffer.as_ptr()); + + let string = c_string.to_str(); + if let Ok(string) = string { + return Some((*string).to_owned()); + } + } + } + + None } } -unsafe impl Send for LinuxSystemManager {} - impl LinuxSystemManager { - + pub fn new() -> LinuxSystemManager { + LinuxSystemManager{} + } } \ No newline at end of file diff --git a/src/system/mod.rs b/src/system/mod.rs index e74cbf9..96df3ab 100644 --- a/src/system/mod.rs +++ b/src/system/mod.rs @@ -13,14 +13,10 @@ pub trait SystemManager { fn get_current_window_executable(&self) -> Option; } -// TODO: change windows and linux implementations to avoid initialize() call and use constructor instead - // LINUX IMPLEMENTATION #[cfg(target_os = "linux")] pub fn get_manager() -> impl SystemManager { - let manager = linux::LinuxSystemManager{}; - manager.initialize(); - manager + linux::LinuxSystemManager::new() } // WINDOWS IMPLEMENTATION diff --git a/src/ui/linux.rs b/src/ui/linux.rs index bdbbb15..ca88267 100644 --- a/src/ui/linux.rs +++ b/src/ui/linux.rs @@ -1,14 +1,9 @@ use std::process::Command; +use super::MenuItem; -pub struct LinuxUIManager { - -} +pub struct LinuxUIManager {} impl super::UIManager for LinuxUIManager { - fn initialize(&self) { - // TODO: check if notify-send is present and log an error otherwise. - } - fn notify(&self, message: &str) { let res = Command::new("notify-send") .args(&["-t", "2000", "espanso", message]) @@ -18,8 +13,16 @@ impl super::UIManager for LinuxUIManager { // TODO: print error log } } + + fn show_menu(&self, menu: Vec) { + unimplemented!() + } } impl LinuxUIManager { + pub fn new() -> LinuxUIManager { + // TODO: check if notify-send is present and log an error otherwise. + LinuxUIManager{} + } } \ No newline at end of file diff --git a/src/ui/mod.rs b/src/ui/mod.rs index 958a7b3..8984250 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -32,9 +32,7 @@ pub fn get_uimanager() -> impl UIManager { // LINUX IMPLEMENTATION #[cfg(target_os = "linux")] pub fn get_uimanager() -> impl UIManager { - let manager = linux::LinuxUIManager{}; - manager.initialize(); - manager + linux::LinuxUIManager::new() } // WINDOWS IMPLEMENTATION