First ConfigSet steps

This commit is contained in:
Federico Terzi 2019-09-07 13:35:45 +02:00
parent 5276dc262d
commit 64d67eba99
11 changed files with 302 additions and 53 deletions

View File

@ -4,6 +4,8 @@
#include <stdio.h>
#include <stdlib.h>
#include <array>
#include <string.h>
#include <X11/Xlibint.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
@ -13,6 +15,7 @@
#include <X11/extensions/record.h>
#include <X11/extensions/XTest.h>
#include <X11/XKBlib.h>
#include <X11/Xatom.h>
extern "C" { // Needed to avoid C++ compiler name mangling
#include <xdo.h>
}
@ -210,4 +213,105 @@ void trigger_paste() {
void trigger_terminal_paste() {
xdo_send_keysequence_window(xdo_context, CURRENTWINDOW, "Control_L+Shift+v", 8000);
}
// SYSTEM MODULE
// Function taken from the wmlib tool source code
char *get_property(Display *disp, Window win,
Atom xa_prop_type, char *prop_name, unsigned long *size)
{
unsigned long ret_nitems, ret_bytes_after, tmp_size;
Atom xa_prop_name, xa_ret_type;
unsigned char *ret_prop;
int ret_format;
char *ret;
int size_in_byte;
xa_prop_name = XInternAtom(disp, prop_name, False);
if (XGetWindowProperty(disp, win, xa_prop_name, 0, 4096 / 4, False,
xa_prop_type, &xa_ret_type, &ret_format, &ret_nitems,
&ret_bytes_after, &ret_prop) != Success)
return NULL;
if (xa_ret_type != xa_prop_type)
{
XFree(ret_prop);
return NULL;
}
switch(ret_format) {
case 8: size_in_byte = sizeof(char); break;
case 16: size_in_byte = sizeof(short); break;
case 32: size_in_byte = sizeof(long); break;
}
tmp_size = size_in_byte * ret_nitems;
ret = (char*) malloc(tmp_size + 1);
memcpy(ret, ret_prop, tmp_size);
ret[tmp_size] = '\0';
if (size) *size = tmp_size;
XFree(ret_prop);
return ret;
}
// Function taken from Window Management Library for Ruby
char *xwm_get_win_title(Display *disp, Window win)
{
char *wname = (char*)get_property(disp,win, XA_STRING, "WM_NAME", NULL);
char *nwname = (char*)get_property(disp,win, XInternAtom(disp,
"UTF8_STRING", False), "_NET_WM_NAME", NULL);
return nwname ? nwname : (wname ? wname : NULL);
}
int32_t get_active_window_name(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);
char * title = xwm_get_win_title(disp, win);
snprintf(buffer, size, "%s", title);
XFree(title);
XCloseDisplay(disp);
return 1;
}
int32_t get_active_window_class(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);
XClassHint hint;
if (XGetClassHint(disp, win, &hint)) {
snprintf(buffer, size, "%s", hint.res_class);
XFree(hint.res_name);
XFree(hint.res_class);
}
XCloseDisplay(disp);
return 1;
}

View File

@ -52,4 +52,17 @@ extern "C" void trigger_paste();
*/
extern "C" void trigger_terminal_paste();
// SYSTEM MODULE
/*
* Return the active windows's WM_NAME
*/
extern "C" int32_t get_active_window_name(char * buffer, int32_t size);
/*
* Return the active windows's WM_CLASS
*/
extern "C" int32_t get_active_window_class(char * buffer, int32_t size);
#endif //ESPANSO_BRIDGE_H

21
src/bridge/linux.rs Normal file
View File

@ -0,0 +1,21 @@
use std::os::raw::{c_void, c_char};
#[allow(improper_ctypes)]
#[link(name="linuxbridge", kind="static")]
extern {
// 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;
// Keyboard
pub fn register_keypress_callback(s: *const c_void,
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();
pub fn trigger_terminal_paste();
}

8
src/bridge/mod.rs Normal file
View File

@ -0,0 +1,8 @@
#[cfg(target_os = "windows")]
pub(crate) mod windows;
#[cfg(target_os = "linux")]
pub(crate) mod linux;
#[cfg(target_os = "macos")]
pub(crate) mod macos;

View File

@ -67,6 +67,7 @@ impl Configs {
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct ConfigSet {
default: Configs,
specific: Vec<Configs>,
@ -78,17 +79,21 @@ impl ConfigSet {
panic!("Invalid config directory");
}
let default_file = espanso_dir.join(DEFAULT_CONFIG_FILE_NAME);
let default = Configs::load_config(default_file);
let default_file = dir_path.join(DEFAULT_CONFIG_FILE_NAME);
let default = Configs::load_config(default_file.as_path());
let mut specific = Vec::new();
for entry in fs::read_dir(dir_path)? {
let entry = entry?;
let path = entry.path();
for entry in fs::read_dir(dir_path)
.expect("Cannot read espanso config directory!") {
let config = Configs::load_config(path.as_path());
specific.push(config);
let entry = entry;
if let Ok(entry) = entry {
let path = entry.path();
let config = Configs::load_config(path.as_path());
specific.push(config);
}
}
ConfigSet {
@ -103,7 +108,7 @@ impl ConfigSet {
let espanso_dir = home_dir.join(".espanso");
// Create the espanso dir if id doesn't exist
let res = create_dir_all(espanso_dir);
let res = create_dir_all(espanso_dir.as_path());
if let Ok(_) = res {
let default_file = espanso_dir.join(DEFAULT_CONFIG_FILE_NAME);
@ -120,4 +125,24 @@ impl ConfigSet {
panic!("Could not generate default position for config file");
}
pub fn toggle_key(&self) -> &KeyModifier {
&self.default.toggle_key
}
pub fn toggle_interval(&self) -> u32 {
self.default.toggle_interval
}
pub fn backspace_limit(&self) -> i32 {
self.default.backspace_limit
}
pub fn backend(&self) -> &BackendType {
&BackendType::Inject // TODO make dynamic based on system current active app
}
pub fn matches(&self) -> &Vec<Match> {
&self.default.matches
}
}

View File

@ -1,6 +1,6 @@
use crate::matcher::{Match, MatchReceiver};
use crate::keyboard::KeyboardSender;
use crate::config::Configs;
use crate::config::ConfigSet;
use crate::config::BackendType;
use crate::clipboard::ClipboardManager;
use std::sync::Arc;
@ -8,12 +8,12 @@ use std::sync::Arc;
pub struct Engine<S, C> where S: KeyboardSender, C: ClipboardManager {
sender: S,
clipboard_manager: Arc<C>,
configs: Configs,
config_set: ConfigSet,
}
impl <S, C> Engine<S, C> where S: KeyboardSender, C: ClipboardManager{
pub fn new(sender: S, clipboard_manager: Arc<C>, configs: Configs) -> Engine<S, C> where S: KeyboardSender, C: ClipboardManager {
Engine{sender, clipboard_manager, configs }
pub fn new(sender: S, clipboard_manager: Arc<C>, config_set: ConfigSet) -> Engine<S, C> where S: KeyboardSender, C: ClipboardManager {
Engine{sender, clipboard_manager, config_set }
}
}
@ -21,7 +21,7 @@ impl <S, C> MatchReceiver for Engine<S, C> where S: KeyboardSender, C: Clipboard
fn on_match(&self, m: &Match) {
self.sender.delete_string(m.trigger.len() as i32);
match self.configs.backend {
match self.config_set.backend() {
BackendType::Inject => {
// Send the expected string. On linux, newlines are managed automatically
// while on windows and macos, we need to emulate a Enter key press.

View File

@ -1,10 +1,12 @@
use std::thread;
use std::{thread};
use std::sync::mpsc;
use std::os::raw::c_char;
use std::os::raw::{c_char, 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>
@ -13,7 +15,8 @@ pub struct LinuxKeyboardInterceptor {
impl super::KeyboardInterceptor for LinuxKeyboardInterceptor {
fn initialize(&self) {
unsafe {
register_keypress_callback(self,keypress_callback);
let self_ptr = self as *const LinuxKeyboardInterceptor as *const c_void;
register_keypress_callback( self_ptr,keypress_callback);
initialize(); // TODO: check initialization return codes
}
}
@ -60,9 +63,11 @@ impl super::KeyboardSender for LinuxKeyboardSender {
// Native bridge code
extern fn keypress_callback(_self: *mut LinuxKeyboardInterceptor, raw_buffer: *const u8, len: i32,
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);
@ -87,19 +92,4 @@ extern fn keypress_callback(_self: *mut LinuxKeyboardInterceptor, raw_buffer: *c
}
}
}
}
#[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, i32, i32));
fn initialize();
fn eventloop();
fn cleanup();
fn send_string(string: *const c_char);
fn delete_string(count: i32);
fn trigger_paste();
fn trigger_terminal_paste();
}

View File

@ -3,18 +3,21 @@ use crate::keyboard::KeyboardInterceptor;
use crate::matcher::Matcher;
use crate::matcher::scrolling::ScrollingMatcher;
use crate::engine::Engine;
use crate::config::Configs;
use crate::config::{Configs, ConfigSet};
use crate::ui::UIManager;
use crate::clipboard::ClipboardManager;
use crate::system::SystemManager;
use std::thread;
use clap::{App, Arg};
use std::path::Path;
mod keyboard;
mod matcher;
mod ui;
mod bridge;
mod engine;
mod config;
mod ui;
mod system;
mod matcher;
mod keyboard;
mod clipboard;
const VERSION: &'static str = env!("CARGO_PKG_VERSION");
@ -28,7 +31,7 @@ fn main() {
.short("c")
.long("config")
.value_name("FILE")
.help("Sets a custom config file. If not specified, reads the default $HOME/.espanso file, creating it if not present.")
.help("Sets a custom config directory. If not specified, reads the default $HOME/.espanso/default.yaml file, creating it if not present.")
.takes_value(true))
.arg(Arg::with_name("dump")
.long("dump")
@ -39,23 +42,27 @@ fn main() {
.help("Sets the level of verbosity"))
.get_matches();
let configs = match matches.value_of("config") {
None => {Configs::load_default()},
Some(path) => {Configs::load(Path::new(path))},
let config_set = match matches.value_of("config") {
None => {ConfigSet::load_default()},
Some(path) => {ConfigSet::load(Path::new(path))},
};
if matches.is_present("dump") {
println!("{:#?}", configs);
println!("{:#?}", config_set);
return;
}
espanso_main(configs);
espanso_main(config_set);
}
fn espanso_main(configs: Configs) {
fn espanso_main(config_set: ConfigSet) {
let ui_manager = ui::get_uimanager();
ui_manager.notify("Hello guys");
let system_manager = system::get_manager();
println!("{}", system_manager.get_current_window_title().unwrap());
println!("{}", system_manager.get_current_window_class().unwrap());
let clipboard_manager = clipboard::get_manager();
let clipboard_manager_arc = Arc::new(clipboard_manager);
@ -65,10 +72,10 @@ fn espanso_main(configs: Configs) {
let engine = Engine::new(sender,
Arc::clone(&clipboard_manager_arc),
configs.clone());
config_set.clone());
thread::spawn(move || {
let matcher = ScrollingMatcher::new(configs.clone(), engine);
let matcher = ScrollingMatcher::new(config_set.clone(), engine);
matcher.watch(rxc);
});

View File

@ -1,13 +1,13 @@
use crate::matcher::{Match, MatchReceiver};
use std::cell::RefCell;
use crate::keyboard::KeyModifier;
use crate::config::Configs;
use crate::config::ConfigSet;
use crate::keyboard::KeyModifier::BACKSPACE;
use std::time::SystemTime;
use std::collections::VecDeque;
pub struct ScrollingMatcher<'a, R> where R: MatchReceiver{
configs: Configs,
config_set: ConfigSet,
receiver: R,
current_set_queue: RefCell<VecDeque<Vec<MatchEntry<'a>>>>,
toggle_press_time: RefCell<SystemTime>,
@ -28,7 +28,7 @@ impl <'a, R> super::Matcher<'a> for ScrollingMatcher<'a, R> where R: MatchReceiv
let mut current_set_queue = self.current_set_queue.borrow_mut();
let new_matches: Vec<MatchEntry> = self.configs.matches.iter()
let new_matches: Vec<MatchEntry> = self.config_set.matches().iter()
.filter(|&x| x.trigger.chars().nth(0).unwrap() == c)
.map(|x | MatchEntry{start: 1, _match: &x})
.collect();
@ -60,7 +60,7 @@ impl <'a, R> super::Matcher<'a> for ScrollingMatcher<'a, R> where R: MatchReceiv
current_set_queue.push_back(combined_matches);
if current_set_queue.len() as i32 > (self.configs.backspace_limit + 1) {
if current_set_queue.len() as i32 > (self.config_set.backspace_limit() + 1) {
current_set_queue.pop_front();
}
@ -73,10 +73,10 @@ impl <'a, R> super::Matcher<'a> for ScrollingMatcher<'a, R> where R: MatchReceiv
}
fn handle_modifier(&'a self, m: KeyModifier) {
if m == self.configs.toggle_key {
if m == *self.config_set.toggle_key() {
let mut toggle_press_time = self.toggle_press_time.borrow_mut();
if let Ok(elapsed) = toggle_press_time.elapsed() {
if elapsed.as_millis() < self.configs.toggle_interval as u128 {
if elapsed.as_millis() < self.config_set.toggle_interval() as u128 {
let mut is_enabled = self.is_enabled.borrow_mut();
*is_enabled = !(*is_enabled);
@ -99,12 +99,12 @@ impl <'a, R> super::Matcher<'a> for ScrollingMatcher<'a, R> where R: MatchReceiv
}
}
impl <'a, R> ScrollingMatcher<'a, R> where R: MatchReceiver {
pub fn new(configs: Configs, receiver: R) -> ScrollingMatcher<'a, R> {
pub fn new(config_set: ConfigSet, receiver: R) -> ScrollingMatcher<'a, R> {
let current_set_queue = RefCell::new(VecDeque::new());
let toggle_press_time = RefCell::new(SystemTime::now());
ScrollingMatcher{
configs,
config_set,
receiver,
current_set_queue,
toggle_press_time,

58
src/system/linux.rs Normal file
View File

@ -0,0 +1,58 @@
use std::os::raw::c_char;
use crate::bridge::linux::{get_active_window_name, get_active_window_class};
use std::ffi::CStr;
pub struct LinuxSystemManager {
}
impl super::SystemManager for LinuxSystemManager {
fn initialize(&self) {
}
fn get_current_window_title(&self) -> Option<String> {
unsafe {
let mut buffer : [c_char; 100] = [0; 100];
let res = get_active_window_name(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
}
fn get_current_window_class(&self) -> Option<String> {
unsafe {
let mut buffer : [c_char; 100] = [0; 100];
let res = get_active_window_class(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
}
fn get_current_window_executable(&self) -> Option<String> {
unimplemented!()
}
}
impl LinuxSystemManager {
}

23
src/system/mod.rs Normal file
View File

@ -0,0 +1,23 @@
#[cfg(target_os = "windows")]
mod windows;
#[cfg(target_os = "linux")]
mod linux;
#[cfg(target_os = "macos")]
mod macos;
pub trait SystemManager {
fn initialize(&self);
fn get_current_window_title(&self) -> Option<String>;
fn get_current_window_class(&self) -> Option<String>;
fn get_current_window_executable(&self) -> Option<String>;
}
// LINUX IMPLEMENTATION
#[cfg(target_os = "linux")]
pub fn get_manager() -> impl SystemManager {
let manager = linux::LinuxSystemManager{};
manager.initialize();
manager
}