Refactor Linux events and add executable filter detection

This commit is contained in:
Federico Terzi 2019-09-13 22:57:53 +02:00
parent 3e976784b8
commit d0270eb99b
12 changed files with 173 additions and 116 deletions

View File

@ -59,17 +59,18 @@ xdo_t * xdo_context;
void event_callback (XPointer, XRecordInterceptData*); void event_callback (XPointer, XRecordInterceptData*);
KeypressCallback keypress_callback; 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; keypress_callback = callback;
interceptor_instance = self;
} }
int32_t initialize() { int32_t initialize(void * _context_instance) {
setlocale(LC_ALL, ""); setlocale(LC_ALL, "");
context_instance = _context_instance;
/* /*
Open the connections to the X server. Open the connections to the X server.
RE recommends to open 2 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: case KeyRelease:
//printf ("%d %d %s\n", key_code, res, buffer.data()); //printf ("%d %d %s\n", key_code, res, buffer.data());
if (res > 0 && key_code != 22) { // Printable character, but not backspace 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 }else{ // Modifier key
keypress_callback(interceptor_instance, NULL, 0, 1, key_code); keypress_callback(context_instance, NULL, 0, 1, key_code);
} }
break; break;
default: default:
@ -315,3 +316,35 @@ int32_t get_active_window_class(char * buffer, int32_t size) {
return 1; return 1;
} }
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;
}

View File

@ -3,10 +3,12 @@
#include <stdint.h> #include <stdint.h>
extern void * context_instance;
/* /*
* Initialize the X11 context and parameters * 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. * 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); typedef void (*KeypressCallback)(void * self, const char *buffer, int32_t len, int32_t is_modifier, int32_t key_code);
extern KeypressCallback keypress_callback; extern KeypressCallback keypress_callback;
extern void * interceptor_instance;
/* /*
* Register the callback that will be called when a keypress was made * 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 * 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); 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 #endif //ESPANSO_BRIDGE_H

View File

@ -3,17 +3,19 @@ use std::os::raw::{c_void, c_char};
#[allow(improper_ctypes)] #[allow(improper_ctypes)]
#[link(name="linuxbridge", kind="static")] #[link(name="linuxbridge", kind="static")]
extern { extern {
pub fn initialize(s: *const c_void);
pub fn eventloop();
pub fn cleanup();
// System // System
pub fn get_active_window_name(buffer: *mut c_char, size: i32) -> i32; 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_class(buffer: *mut c_char, size: i32) -> i32;
pub fn get_active_window_executable(buffer: *mut c_char, size: i32) -> i32;
// Keyboard // Keyboard
pub fn register_keypress_callback(s: *const c_void, pub fn register_keypress_callback(cb: extern fn(_self: *mut c_void, *const u8,
cb: extern fn(_self: *mut c_void, *const u8,
i32, i32, i32)); i32, i32, i32));
pub fn initialize();
pub fn eventloop();
pub fn cleanup();
pub fn send_string(string: *const c_char); pub fn send_string(string: *const c_char);
pub fn delete_string(count: i32); pub fn delete_string(count: i32);
pub fn trigger_paste(); pub fn trigger_paste();

View File

@ -1,15 +1,9 @@
use std::process::{Command, Stdio}; use std::process::{Command, Stdio};
use std::io::{Write}; use std::io::{Write};
pub struct LinuxClipboardManager { pub struct LinuxClipboardManager {}
}
impl super::ClipboardManager for 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<String> { fn get_clipboard(&self) -> Option<String> {
let res = Command::new("xclip") let res = Command::new("xclip")
.args(&["-o", "-sel", "clip"]) .args(&["-o", "-sel", "clip"])
@ -46,5 +40,9 @@ impl super::ClipboardManager for LinuxClipboardManager {
} }
impl LinuxClipboardManager { impl LinuxClipboardManager {
pub fn new() -> LinuxClipboardManager {
// TODO: check if xclip is present and log an error otherwise.
LinuxClipboardManager{}
}
} }

View File

@ -12,14 +12,10 @@ pub trait ClipboardManager {
fn set_clipboard(&self, payload: &str); fn set_clipboard(&self, payload: &str);
} }
// TODO: change windows and linux implementations to avoid initialize() call and use constructor instead
// LINUX IMPLEMENTATION // LINUX IMPLEMENTATION
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
pub fn get_manager() -> impl ClipboardManager { pub fn get_manager() -> impl ClipboardManager {
let manager = linux::LinuxClipboardManager{}; linux::LinuxClipboardManager::new()
manager.initialize();
manager
} }
// WINDOWS IMPLEMENTATION // WINDOWS IMPLEMENTATION

77
src/context/linux.rs Normal file
View File

@ -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<Event>
}
impl LinuxContext {
pub fn new(send_channel: Sender<Event>) -> Box<LinuxContext> {
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<KeyModifier> = 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();
}
}
}
}

View File

@ -22,10 +22,8 @@ pub fn new(send_channel: Sender<Event>) -> Box<dyn Context> {
// LINUX IMPLEMENTATION // LINUX IMPLEMENTATION
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
pub fn new(send_channel: Sender<Event>) -> Box<dyn Context> { // TODO pub fn new(send_channel: Sender<Event>) -> Box<dyn Context> {
let manager = linux::LinuxUIManager{}; linux::LinuxContext::new(send_channel)
manager.initialize();
manager
} }
// WINDOWS IMPLEMENTATION // WINDOWS IMPLEMENTATION

View File

@ -1,37 +1,8 @@
use std::sync::mpsc; use std::sync::mpsc;
use std::os::raw::{c_void}; use std::os::raw::{c_void};
use std::ffi::CString; use std::ffi::CString;
use crate::keyboard::{KeyEvent, KeyModifier};
use crate::keyboard::KeyModifier::*;
use crate::bridge::linux::*; use crate::bridge::linux::*;
#[repr(C)]
pub struct LinuxKeyboardInterceptor {
pub sender: mpsc::Sender<KeyEvent>
}
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 { pub struct LinuxKeyboardManager {
} }
@ -58,36 +29,3 @@ impl super::KeyboardManager for LinuxKeyboardManager {
unsafe {delete_string(count)} 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<KeyModifier> = 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();
}
}
}
}

View File

@ -1,11 +1,9 @@
use std::os::raw::c_char; 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; use std::ffi::CStr;
pub struct LinuxSystemManager { pub struct LinuxSystemManager {}
}
impl super::SystemManager for LinuxSystemManager { impl super::SystemManager for LinuxSystemManager {
fn get_current_window_title(&self) -> Option<String> { fn get_current_window_title(&self) -> Option<String> {
@ -45,12 +43,26 @@ impl super::SystemManager for LinuxSystemManager {
} }
fn get_current_window_executable(&self) -> Option<String> { fn get_current_window_executable(&self) -> Option<String> {
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());
}
} }
} }
unsafe impl Send for LinuxSystemManager {} None
}
}
impl LinuxSystemManager { impl LinuxSystemManager {
pub fn new() -> LinuxSystemManager {
LinuxSystemManager{}
}
} }

View File

@ -13,14 +13,10 @@ pub trait SystemManager {
fn get_current_window_executable(&self) -> Option<String>; fn get_current_window_executable(&self) -> Option<String>;
} }
// TODO: change windows and linux implementations to avoid initialize() call and use constructor instead
// LINUX IMPLEMENTATION // LINUX IMPLEMENTATION
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
pub fn get_manager() -> impl SystemManager { pub fn get_manager() -> impl SystemManager {
let manager = linux::LinuxSystemManager{}; linux::LinuxSystemManager::new()
manager.initialize();
manager
} }
// WINDOWS IMPLEMENTATION // WINDOWS IMPLEMENTATION

View File

@ -1,14 +1,9 @@
use std::process::Command; use std::process::Command;
use super::MenuItem;
pub struct LinuxUIManager { pub struct LinuxUIManager {}
}
impl super::UIManager for 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) { fn notify(&self, message: &str) {
let res = Command::new("notify-send") let res = Command::new("notify-send")
.args(&["-t", "2000", "espanso", message]) .args(&["-t", "2000", "espanso", message])
@ -18,8 +13,16 @@ impl super::UIManager for LinuxUIManager {
// TODO: print error log // TODO: print error log
} }
} }
fn show_menu(&self, menu: Vec<MenuItem>) {
unimplemented!()
}
} }
impl LinuxUIManager { impl LinuxUIManager {
pub fn new() -> LinuxUIManager {
// TODO: check if notify-send is present and log an error otherwise.
LinuxUIManager{}
}
} }

View File

@ -32,9 +32,7 @@ pub fn get_uimanager() -> impl UIManager {
// LINUX IMPLEMENTATION // LINUX IMPLEMENTATION
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
pub fn get_uimanager() -> impl UIManager { pub fn get_uimanager() -> impl UIManager {
let manager = linux::LinuxUIManager{}; linux::LinuxUIManager::new()
manager.initialize();
manager
} }
// WINDOWS IMPLEMENTATION // WINDOWS IMPLEMENTATION