Add initial linux interceptor implementation
This commit is contained in:
parent
191d350650
commit
dc2868b217
2
Cargo.lock
generated
2
Cargo.lock
generated
|
@ -1,3 +1,5 @@
|
|||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.0.41"
|
||||
|
|
36
build.rs
36
build.rs
|
@ -1,11 +1,41 @@
|
|||
extern crate cmake;
|
||||
use cmake::Config;
|
||||
use std::path::PathBuf;
|
||||
|
||||
/* OS SPECIFIC CONFIGS */
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
fn get_config() -> PathBuf {
|
||||
Config::new("native/libwinbridge").build()
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
fn get_config() -> PathBuf {
|
||||
Config::new("native/liblinuxbridge").build()
|
||||
}
|
||||
|
||||
/*
|
||||
OS CUSTOM CARGO CONFIG LINES
|
||||
Note: this is where linked libraries should be specified.
|
||||
*/
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
fn print_config() {
|
||||
println!("cargo:rustc-link-lib=static=winbridge");
|
||||
println!("cargo:rustc-link-lib=static=user32");
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
fn print_config() {
|
||||
println!("cargo:rustc-link-lib=static=linuxbridge");
|
||||
println!("cargo:rustc-link-lib=dylib=X11");
|
||||
println!("cargo:rustc-link-lib=dylib=Xtst");
|
||||
}
|
||||
|
||||
fn main()
|
||||
{
|
||||
let dst = Config::new("native/libwinbridge").build();
|
||||
let dst = get_config();
|
||||
|
||||
println!("cargo:rustc-link-search=native={}", dst.display());
|
||||
println!("cargo:rustc-link-lib=static=winbridge");
|
||||
println!("cargo:rustc-link-lib=static=user32");
|
||||
print_config();
|
||||
}
|
8
native/liblinuxbridge/CMakeLists.txt
Normal file
8
native/liblinuxbridge/CMakeLists.txt
Normal file
|
@ -0,0 +1,8 @@
|
|||
cmake_minimum_required(VERSION 3.0)
|
||||
project(liblinuxbridge)
|
||||
|
||||
set (CMAKE_CXX_STANDARD 14)
|
||||
|
||||
add_library(linuxbridge STATIC bridge.cpp bridge.h)
|
||||
|
||||
install(TARGETS linuxbridge DESTINATION .)
|
185
native/liblinuxbridge/bridge.cpp
Normal file
185
native/liblinuxbridge/bridge.cpp
Normal file
|
@ -0,0 +1,185 @@
|
|||
#include "bridge.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <array>
|
||||
#include <X11/Xlibint.h>
|
||||
#include <X11/Xlib.h>
|
||||
#include <X11/Xutil.h>
|
||||
#include <X11/cursorfont.h>
|
||||
#include <X11/keysymdef.h>
|
||||
#include <X11/keysym.h>
|
||||
#include <X11/extensions/record.h>
|
||||
#include <X11/extensions/XTest.h>
|
||||
#include <X11/XKBlib.h>
|
||||
|
||||
/*
|
||||
This code uses the X11 Record Extension to receive keyboard
|
||||
events. Documentation of this library can be found here:
|
||||
https://www.x.org/releases/X11R7.6/doc/libXtst/recordlib.html
|
||||
|
||||
We will refer to this extension as RE from now on.
|
||||
*/
|
||||
|
||||
/*
|
||||
This struct is needed to receive events from the RE.
|
||||
The funny thing is: it's not defined there, it should though.
|
||||
The only place this is mentioned is the libxnee library,
|
||||
so check that out if you need a reference.
|
||||
*/
|
||||
typedef union {
|
||||
unsigned char type ;
|
||||
xEvent event ;
|
||||
xResourceReq req ;
|
||||
xGenericReply reply ;
|
||||
xError error ;
|
||||
xConnSetupPrefix setup;
|
||||
} XRecordDatum;
|
||||
|
||||
/*
|
||||
Connections to the X server, RE recommends 2 connections:
|
||||
one for recording control and one for reading the recorded data.
|
||||
*/
|
||||
Display *data_disp = NULL;
|
||||
Display *ctrl_disp = NULL;
|
||||
|
||||
XRecordRange *record_range;
|
||||
XRecordContext context;
|
||||
|
||||
// Callback invoked when a new key event occur.
|
||||
void event_callback (XPointer, XRecordInterceptData*);
|
||||
|
||||
KeypressCallback keypress_callback;
|
||||
void * interceptor_instance;
|
||||
|
||||
void register_keypress_callback(void * self, KeypressCallback callback) {
|
||||
keypress_callback = callback;
|
||||
interceptor_instance = self;
|
||||
}
|
||||
|
||||
|
||||
int32_t initialize() {
|
||||
/*
|
||||
Open the connections to the X server.
|
||||
RE recommends to open 2 connections to the X server:
|
||||
one for the recording control and one to read the protocol
|
||||
data.
|
||||
*/
|
||||
ctrl_disp = XOpenDisplay(NULL);
|
||||
data_disp = XOpenDisplay(NULL);
|
||||
|
||||
if (!ctrl_disp || !data_disp) { // Display error
|
||||
return -1;
|
||||
}
|
||||
|
||||
/*
|
||||
We must set the ctrl_disp to sync mode, or, when we the enable
|
||||
context in data_disp, there will be a fatal X error.
|
||||
*/
|
||||
XSynchronize(ctrl_disp, True);
|
||||
|
||||
int dummy;
|
||||
|
||||
// Make sure the X RE is installed in this system.
|
||||
if (!XRecordQueryVersion(ctrl_disp, &dummy, &dummy)) {
|
||||
return -2;
|
||||
}
|
||||
|
||||
// Make sure the X Keyboard Extension is installed
|
||||
if (!XkbQueryExtension(ctrl_disp, &dummy, &dummy, &dummy, &dummy, &dummy)) {
|
||||
return -3;
|
||||
}
|
||||
|
||||
// Initialize the record range, that is the kind of events we want to track.
|
||||
record_range = XRecordAllocRange ();
|
||||
if (!record_range) {
|
||||
return -4;
|
||||
}
|
||||
record_range->device_events.first = KeyPress;
|
||||
record_range->device_events.last = KeyRelease;
|
||||
|
||||
// We want to get the keys from all clients
|
||||
XRecordClientSpec client_spec;
|
||||
client_spec = XRecordAllClients;
|
||||
|
||||
// Initialize the context
|
||||
context = XRecordCreateContext(ctrl_disp, 0, &client_spec, 1, &record_range, 1);
|
||||
if (!context) {
|
||||
return -5;
|
||||
}
|
||||
}
|
||||
|
||||
int32_t eventloop() {
|
||||
if (!XRecordEnableContext (data_disp, context, event_callback, NULL)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
void cleanup() {
|
||||
XRecordDisableContext(ctrl_disp, context);
|
||||
XRecordFreeContext(ctrl_disp, context);
|
||||
XFree (record_range);
|
||||
XCloseDisplay(data_disp);
|
||||
XCloseDisplay(ctrl_disp);
|
||||
}
|
||||
|
||||
void event_callback(XPointer p, XRecordInterceptData *hook)
|
||||
{
|
||||
// Make sure the event comes from the X11 server
|
||||
if (hook->category != XRecordFromServer) {
|
||||
XRecordFreeData(hook);
|
||||
return;
|
||||
}
|
||||
|
||||
// Cast the event payload to a XRecordDatum, needed later to access the fields
|
||||
// This struct was hard to find and understand. Turn's out that all the
|
||||
// required data are included in the "event" field of this structure.
|
||||
// The funny thing is that it's not a XEvent as one might expect,
|
||||
// but a xEvent, a very different beast defined in the Xproto.h header.
|
||||
// I suggest you to look at that header if you want to understand where the
|
||||
// upcoming field where taken from.
|
||||
XRecordDatum *data = (XRecordDatum*) hook->data;
|
||||
|
||||
int event_type = data->type;
|
||||
int key_code = data->event.u.u.detail;
|
||||
|
||||
// In order to convert the key_code into the corresponding string,
|
||||
// we need to synthesize an artificial XKeyEvent, to feed later to the
|
||||
// XLookupString function.
|
||||
XKeyEvent event;
|
||||
event.display = ctrl_disp;
|
||||
event.window = data->event.u.focus.window;
|
||||
event.root = XDefaultRootWindow(ctrl_disp);
|
||||
event.subwindow = None;
|
||||
event.time = data->event.u.keyButtonPointer.time;
|
||||
event.x = 1;
|
||||
event.y = 1;
|
||||
event.x_root = 1;
|
||||
event.y_root = 1;
|
||||
event.same_screen = True;
|
||||
event.keycode = key_code;
|
||||
event.state = data->event.u.keyButtonPointer.state;
|
||||
event.type = KeyPress;
|
||||
|
||||
// Extract the corresponding chars.
|
||||
std::array<char, 10> buffer;
|
||||
int res = XLookupString(&event, buffer.data(), buffer.size(), NULL, NULL);
|
||||
|
||||
switch (event_type) {
|
||||
case KeyRelease:
|
||||
printf ("%d %d KeyPress: \t%s\t%s\n", key_code, res, XKeysymToString(XkbKeycodeToKeysym(ctrl_disp, key_code, 0, 0)), buffer.data());
|
||||
if (res > 0) { // Send only printable chars, todo: change
|
||||
keypress_callback(interceptor_instance, buffer.data(), buffer.size());
|
||||
}
|
||||
break;
|
||||
// case KeyPress:
|
||||
// printf ("%d %d KeyPress: \t%s\t%s\t%d\n", keycode, res, XKeysymToString(XkbKeycodeToKeysym(ctrl_disp, keycode, 0, 0)), buff, buff[0]);
|
||||
// break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
XRecordFreeData(hook);
|
||||
}
|
35
native/liblinuxbridge/bridge.h
Normal file
35
native/liblinuxbridge/bridge.h
Normal file
|
@ -0,0 +1,35 @@
|
|||
#ifndef ESPANSO_BRIDGE_H
|
||||
#define ESPANSO_BRIDGE_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
/*
|
||||
* Initialize the X11 context and parameters
|
||||
*/
|
||||
extern "C" int32_t initialize();
|
||||
|
||||
/*
|
||||
* Start the event loop indefinitely. Blocking call.
|
||||
*/
|
||||
extern "C" int32_t eventloop();
|
||||
|
||||
/*
|
||||
* Clean all the X11 resources allocated during the initialization.
|
||||
*/
|
||||
extern "C" void cleanup();
|
||||
|
||||
/*
|
||||
* Called when a new keypress is made, the first argument is an char array,
|
||||
* while the second is the size of the array.
|
||||
*/
|
||||
typedef void (*KeypressCallback)(void * self, char *buffer, int32_t len);
|
||||
|
||||
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);
|
||||
|
||||
#endif //ESPANSO_BRIDGE_H
|
63
src/keyboard/linux.rs
Normal file
63
src/keyboard/linux.rs
Normal file
|
@ -0,0 +1,63 @@
|
|||
use std::thread;
|
||||
use std::sync::mpsc;
|
||||
|
||||
#[repr(C)]
|
||||
pub struct LinuxKeyboardInterceptor {
|
||||
pub sender: mpsc::Sender<char>
|
||||
}
|
||||
|
||||
impl super::KeyboardInterceptor for LinuxKeyboardInterceptor {
|
||||
fn initialize(&self) {
|
||||
unsafe {
|
||||
register_keypress_callback(self,keypress_callback);
|
||||
}
|
||||
}
|
||||
|
||||
fn start(&self) {
|
||||
thread::spawn(|| {
|
||||
unsafe {
|
||||
initialize();
|
||||
eventloop();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
pub struct LinuxKeyboardSender {
|
||||
}
|
||||
|
||||
impl super::KeyboardSender for LinuxKeyboardSender {
|
||||
fn send_string(&self, s: &str) {
|
||||
println!("{}", s);
|
||||
|
||||
}
|
||||
|
||||
fn delete_string(&self, count: i32) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// Native bridge code
|
||||
|
||||
extern fn keypress_callback(_self: *mut LinuxKeyboardInterceptor, raw_buffer: *const u8, len: i32) {
|
||||
unsafe {
|
||||
// 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 {
|
||||
//println!("'{}'",c);
|
||||
(*_self).sender.send(c).unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(improper_ctypes)]
|
||||
#[link(name="linuxbridge", kind="static")]
|
||||
extern {
|
||||
fn register_keypress_callback(s: *const LinuxKeyboardInterceptor, cb: extern fn(_self: *mut LinuxKeyboardInterceptor, *const u8, i32));
|
||||
fn initialize();
|
||||
fn eventloop();
|
||||
fn cleanup();
|
||||
}
|
|
@ -1,6 +1,9 @@
|
|||
#[cfg(target_os = "windows")]
|
||||
mod windows;
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
mod linux;
|
||||
|
||||
use std::sync::mpsc;
|
||||
|
||||
pub trait KeyboardInterceptor {
|
||||
|
@ -13,6 +16,8 @@ pub trait KeyboardSender {
|
|||
fn delete_string(&self, count: i32);
|
||||
}
|
||||
|
||||
// WINDOWS IMPLEMENTATIONS
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
pub fn get_interceptor(sender: mpsc::Sender<char>) -> impl KeyboardInterceptor {
|
||||
windows::WindowsKeyboardInterceptor {sender}
|
||||
|
@ -21,4 +26,16 @@ pub fn get_interceptor(sender: mpsc::Sender<char>) -> impl KeyboardInterceptor {
|
|||
#[cfg(target_os = "windows")]
|
||||
pub fn get_sender() -> impl KeyboardSender {
|
||||
windows::WindowsKeyboardSender{}
|
||||
}
|
||||
|
||||
// LINUX IMPLEMENTATIONS
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
pub fn get_interceptor(sender: mpsc::Sender<char>) -> impl KeyboardInterceptor {
|
||||
linux::LinuxKeyboardInterceptor {sender}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
pub fn get_sender() -> impl KeyboardSender {
|
||||
linux::LinuxKeyboardSender{}
|
||||
}
|
|
@ -27,7 +27,6 @@ impl <'a> super::Matcher for ScrollingMatcher<'a> {
|
|||
self.current_set = old_matches;
|
||||
self.current_set.append(&mut new_matches);
|
||||
|
||||
let mut found = false;
|
||||
let mut foundMatch = None;
|
||||
|
||||
for entry in self.current_set.iter_mut() {
|
||||
|
|
Loading…
Reference in New Issue
Block a user