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*);
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:
@ -315,3 +316,35 @@ int32_t get_active_window_class(char * buffer, int32_t size) {
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>
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

View File

@ -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();

View File

@ -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<String> {
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{}
}
}

View File

@ -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

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
#[cfg(target_os = "linux")]
pub fn new(send_channel: Sender<Event>) -> Box<dyn Context> { // TODO
let manager = linux::LinuxUIManager{};
manager.initialize();
manager
pub fn new(send_channel: Sender<Event>) -> Box<dyn Context> {
linux::LinuxContext::new(send_channel)
}
// WINDOWS IMPLEMENTATION

View File

@ -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<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 {
}
@ -58,36 +29,3 @@ impl super::KeyboardManager for LinuxKeyboardManager {
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 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<String> {
@ -45,12 +43,26 @@ impl super::SystemManager for LinuxSystemManager {
}
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());
}
}
}
None
}
}
unsafe impl Send for 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>;
}
// 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

View File

@ -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<MenuItem>) {
unimplemented!()
}
}
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
#[cfg(target_os = "linux")]
pub fn get_uimanager() -> impl UIManager {
let manager = linux::LinuxUIManager{};
manager.initialize();
manager
linux::LinuxUIManager::new()
}
// WINDOWS IMPLEMENTATION