First ConfigSet steps
This commit is contained in:
parent
5276dc262d
commit
64d67eba99
|
@ -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;
|
||||
}
|
|
@ -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
21
src/bridge/linux.rs
Normal 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
8
src/bridge/mod.rs
Normal 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;
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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.
|
||||
|
|
|
@ -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();
|
||||
}
|
33
src/main.rs
33
src/main.rs
|
@ -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);
|
||||
});
|
||||
|
||||
|
|
|
@ -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
58
src/system/linux.rs
Normal 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
23
src/system/mod.rs
Normal 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
|
||||
}
|
Loading…
Reference in New Issue
Block a user