2019-08-31 14:07:45 +00:00
|
|
|
use crate::matcher::{Match, MatchReceiver};
|
2019-09-13 13:03:03 +00:00
|
|
|
use crate::keyboard::KeyboardManager;
|
2019-09-07 14:13:13 +00:00
|
|
|
use crate::config::ConfigManager;
|
2019-09-07 08:43:23 +00:00
|
|
|
use crate::config::BackendType;
|
2019-09-06 22:38:13 +00:00
|
|
|
use crate::clipboard::ClipboardManager;
|
2019-09-15 11:03:21 +00:00
|
|
|
use log::{info, warn, error};
|
2019-09-12 21:24:55 +00:00
|
|
|
use crate::ui::{UIManager, MenuItem, MenuItemType};
|
2019-09-14 10:19:11 +00:00
|
|
|
use crate::event::{ActionEventReceiver, ActionType};
|
2019-09-15 11:03:21 +00:00
|
|
|
use crate::extension::Extension;
|
2019-09-12 21:24:55 +00:00
|
|
|
use std::cell::RefCell;
|
2019-09-13 13:03:03 +00:00
|
|
|
use std::process::exit;
|
2019-09-15 11:03:21 +00:00
|
|
|
use std::collections::HashMap;
|
|
|
|
use regex::{Regex, Captures};
|
2019-08-31 14:07:45 +00:00
|
|
|
|
2019-09-13 13:03:03 +00:00
|
|
|
pub struct Engine<'a, S: KeyboardManager, C: ClipboardManager, M: ConfigManager<'a>,
|
2019-09-08 11:37:58 +00:00
|
|
|
U: UIManager> {
|
2019-09-13 13:03:03 +00:00
|
|
|
keyboard_manager: &'a S,
|
2019-09-07 14:13:13 +00:00
|
|
|
clipboard_manager: &'a C,
|
|
|
|
config_manager: &'a M,
|
2019-09-08 11:37:58 +00:00
|
|
|
ui_manager: &'a U,
|
2019-09-15 11:03:21 +00:00
|
|
|
|
|
|
|
extension_map: HashMap<String, Box<dyn Extension>>,
|
|
|
|
|
2019-09-12 21:24:55 +00:00
|
|
|
enabled: RefCell<bool>,
|
2019-08-31 14:07:45 +00:00
|
|
|
}
|
|
|
|
|
2019-09-13 13:03:03 +00:00
|
|
|
impl <'a, S: KeyboardManager, C: ClipboardManager, M: ConfigManager<'a>, U: UIManager>
|
2019-09-08 11:37:58 +00:00
|
|
|
Engine<'a, S, C, M, U> {
|
2019-09-15 11:03:21 +00:00
|
|
|
pub fn new(keyboard_manager: &'a S, clipboard_manager: &'a C,
|
|
|
|
config_manager: &'a M, ui_manager: &'a U,
|
|
|
|
extensions: Vec<Box<dyn Extension>>) -> Engine<'a, S, C, M, U> {
|
|
|
|
// Register all the extensions
|
|
|
|
let mut extension_map = HashMap::new();
|
|
|
|
for extension in extensions.into_iter() {
|
|
|
|
extension_map.insert(extension.name(), extension);
|
|
|
|
}
|
|
|
|
|
2019-09-12 21:24:55 +00:00
|
|
|
let enabled = RefCell::new(true);
|
2019-09-15 11:03:21 +00:00
|
|
|
|
|
|
|
Engine{keyboard_manager,
|
|
|
|
clipboard_manager,
|
|
|
|
config_manager,
|
|
|
|
ui_manager,
|
|
|
|
extension_map,
|
|
|
|
enabled
|
|
|
|
}
|
2019-09-12 21:24:55 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
fn build_menu(&self) -> Vec<MenuItem> {
|
|
|
|
let mut menu = Vec::new();
|
|
|
|
|
|
|
|
let enabled = self.enabled.borrow();
|
|
|
|
let toggle_text = if *enabled {
|
|
|
|
"Disable"
|
|
|
|
}else{
|
|
|
|
"Enable"
|
|
|
|
}.to_owned();
|
|
|
|
menu.push(MenuItem{
|
|
|
|
item_type: MenuItemType::Button,
|
|
|
|
item_name: toggle_text,
|
2019-09-12 21:53:17 +00:00
|
|
|
item_id: ActionType::Toggle as i32,
|
2019-09-12 21:24:55 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
menu.push(MenuItem{
|
|
|
|
item_type: MenuItemType::Separator,
|
|
|
|
item_name: "".to_owned(),
|
|
|
|
item_id: 999,
|
|
|
|
});
|
|
|
|
|
|
|
|
menu.push(MenuItem{
|
|
|
|
item_type: MenuItemType::Button,
|
|
|
|
item_name: "Exit".to_owned(),
|
2019-09-12 21:53:17 +00:00
|
|
|
item_id: ActionType::Exit as i32,
|
2019-09-12 21:24:55 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
menu
|
2019-08-31 14:07:45 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-09-15 11:03:21 +00:00
|
|
|
lazy_static! {
|
2019-09-15 13:46:24 +00:00
|
|
|
static ref VAR_REGEX: Regex = Regex::new("\\{\\{\\s*(?P<name>\\w+)\\s*\\}\\}").unwrap();
|
2019-09-15 11:03:21 +00:00
|
|
|
}
|
|
|
|
|
2019-09-13 13:03:03 +00:00
|
|
|
impl <'a, S: KeyboardManager, C: ClipboardManager, M: ConfigManager<'a>, U: UIManager>
|
2019-09-08 11:37:58 +00:00
|
|
|
MatchReceiver for Engine<'a, S, C, M, U>{
|
|
|
|
|
2019-08-31 15:00:23 +00:00
|
|
|
fn on_match(&self, m: &Match) {
|
2019-09-09 15:13:58 +00:00
|
|
|
let config = self.config_manager.active_config();
|
|
|
|
|
|
|
|
if config.disabled {
|
|
|
|
return;
|
|
|
|
}
|
2019-09-07 15:59:34 +00:00
|
|
|
|
2019-09-13 13:03:03 +00:00
|
|
|
self.keyboard_manager.delete_string(m.trigger.len() as i32);
|
2019-09-06 08:21:33 +00:00
|
|
|
|
2019-09-15 11:03:21 +00:00
|
|
|
let target_string = if m._has_vars {
|
|
|
|
let mut output_map = HashMap::new();
|
|
|
|
|
|
|
|
for variable in m.vars.iter() {
|
|
|
|
let extension = self.extension_map.get(&variable.var_type);
|
|
|
|
if let Some(extension) = extension {
|
|
|
|
let ext_out = extension.calculate(&variable.params);
|
|
|
|
if let Some(output) = ext_out {
|
|
|
|
output_map.insert(variable.name.clone(), output);
|
|
|
|
}else{
|
|
|
|
output_map.insert(variable.name.clone(), "".to_owned());
|
|
|
|
warn!("Could not generate output for variable: {}", variable.name);
|
|
|
|
}
|
|
|
|
}else{
|
|
|
|
error!("No extension found for variable type: {}", variable.var_type);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Replace the variables
|
2019-09-15 13:46:24 +00:00
|
|
|
let result = VAR_REGEX.replace_all(&m.replace, |caps: &Captures| {
|
2019-09-15 11:03:21 +00:00
|
|
|
let var_name = caps.name("name").unwrap().as_str();
|
|
|
|
let output = output_map.get(var_name);
|
|
|
|
output.unwrap()
|
|
|
|
});
|
|
|
|
|
|
|
|
result.to_string()
|
|
|
|
}else{ // No variables, simple text substitution
|
|
|
|
m.replace.clone()
|
|
|
|
};
|
|
|
|
|
2019-09-07 15:59:34 +00:00
|
|
|
match config.backend {
|
2019-09-07 08:43:23 +00:00
|
|
|
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.
|
2019-09-06 08:21:33 +00:00
|
|
|
|
2019-09-07 08:43:23 +00:00
|
|
|
if cfg!(target_os = "linux") {
|
2019-09-15 11:03:21 +00:00
|
|
|
self.keyboard_manager.send_string(&target_string);
|
2019-09-07 08:43:23 +00:00
|
|
|
}else{
|
|
|
|
// To handle newlines, substitute each "\n" char with an Enter key press.
|
2019-09-15 11:03:21 +00:00
|
|
|
let splits = target_string.lines();
|
2019-09-06 20:19:28 +00:00
|
|
|
|
2019-09-07 08:43:23 +00:00
|
|
|
for (i, split) in splits.enumerate() {
|
|
|
|
if i > 0 {
|
2019-09-13 13:03:03 +00:00
|
|
|
self.keyboard_manager.send_enter();
|
2019-09-07 08:43:23 +00:00
|
|
|
}
|
2019-09-06 08:21:33 +00:00
|
|
|
|
2019-09-13 13:03:03 +00:00
|
|
|
self.keyboard_manager.send_string(split);
|
2019-09-07 08:43:23 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
BackendType::Clipboard => {
|
2019-09-15 11:03:21 +00:00
|
|
|
self.clipboard_manager.set_clipboard(&target_string);
|
2019-09-13 13:03:03 +00:00
|
|
|
self.keyboard_manager.trigger_paste();
|
2019-09-07 08:43:23 +00:00
|
|
|
},
|
2019-09-06 08:21:33 +00:00
|
|
|
}
|
2019-08-31 14:07:45 +00:00
|
|
|
}
|
2019-09-08 11:37:58 +00:00
|
|
|
|
2019-09-14 18:13:09 +00:00
|
|
|
fn on_enable_update(&self, status: bool) {
|
2019-09-08 11:37:58 +00:00
|
|
|
let message = if status {
|
|
|
|
"espanso enabled"
|
|
|
|
}else{
|
|
|
|
"espanso disabled"
|
|
|
|
};
|
|
|
|
|
|
|
|
info!("Toggled: {}", message);
|
|
|
|
|
2019-09-12 21:24:55 +00:00
|
|
|
let mut enabled_ref = self.enabled.borrow_mut();
|
|
|
|
*enabled_ref = status;
|
|
|
|
|
2019-09-08 11:37:58 +00:00
|
|
|
self.ui_manager.notify(message);
|
|
|
|
}
|
2019-09-12 20:14:41 +00:00
|
|
|
}
|
|
|
|
|
2019-09-13 13:03:03 +00:00
|
|
|
impl <'a, S: KeyboardManager, C: ClipboardManager,
|
2019-09-12 20:14:41 +00:00
|
|
|
M: ConfigManager<'a>, U: UIManager> ActionEventReceiver for Engine<'a, S, C, M, U>{
|
|
|
|
|
2019-09-14 10:19:11 +00:00
|
|
|
fn on_action_event(&self, e: ActionType) {
|
2019-09-12 21:24:55 +00:00
|
|
|
match e {
|
2019-09-14 10:19:11 +00:00
|
|
|
ActionType::IconClick => {
|
2019-09-12 21:24:55 +00:00
|
|
|
self.ui_manager.show_menu(self.build_menu());
|
|
|
|
},
|
2019-09-14 10:19:11 +00:00
|
|
|
ActionType::Exit => {
|
|
|
|
info!("Terminating espanso.");
|
|
|
|
exit(0);
|
|
|
|
},
|
|
|
|
_ => {}
|
2019-09-12 21:24:55 +00:00
|
|
|
}
|
2019-09-12 20:14:41 +00:00
|
|
|
}
|
2019-08-31 14:07:45 +00:00
|
|
|
}
|