Adopt rustfmt formatting for the project. Fix #137
This commit is contained in:
parent
949f1e4acf
commit
5b537b4a57
5
build.rs
5
build.rs
|
@ -25,7 +25,7 @@ fn get_config() -> PathBuf {
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
fn print_config() {
|
fn print_config() {
|
||||||
println!("cargo:rustc-link-lib=static=winbridge");
|
println!("cargo:rustc-link-lib=static=winbridge");
|
||||||
println!("cargo:rustc-link-lib=dylib=user32");
|
println!("cargo:rustc-link-lib=dylib=user32");
|
||||||
}
|
}
|
||||||
|
@ -47,8 +47,7 @@ fn print_config() {
|
||||||
println!("cargo:rustc-link-lib=framework=IOKit");
|
println!("cargo:rustc-link-lib=framework=IOKit");
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main()
|
fn main() {
|
||||||
{
|
|
||||||
let dst = get_config();
|
let dst = get_config();
|
||||||
|
|
||||||
println!("cargo:rustc-link-search=native={}", dst.display());
|
println!("cargo:rustc-link-search=native={}", dst.display());
|
||||||
|
|
|
@ -17,11 +17,11 @@
|
||||||
* along with espanso. If not, see <https://www.gnu.org/licenses/>.
|
* along with espanso. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
use std::os::raw::{c_void, c_char};
|
use std::os::raw::{c_char, c_void};
|
||||||
|
|
||||||
#[allow(improper_ctypes)]
|
#[allow(improper_ctypes)]
|
||||||
#[link(name="linuxbridge", kind="static")]
|
#[link(name = "linuxbridge", kind = "static")]
|
||||||
extern {
|
extern "C" {
|
||||||
pub fn check_x11() -> i32;
|
pub fn check_x11() -> i32;
|
||||||
pub fn initialize(s: *const c_void) -> i32;
|
pub fn initialize(s: *const c_void) -> i32;
|
||||||
pub fn eventloop();
|
pub fn eventloop();
|
||||||
|
@ -34,8 +34,9 @@ extern {
|
||||||
pub fn is_current_window_special() -> i32;
|
pub fn is_current_window_special() -> i32;
|
||||||
|
|
||||||
// Keyboard
|
// Keyboard
|
||||||
pub fn register_keypress_callback(cb: extern fn(_self: *mut c_void, *const u8,
|
pub fn register_keypress_callback(
|
||||||
i32, i32, i32));
|
cb: extern "C" fn(_self: *mut c_void, *const u8, i32, i32, i32),
|
||||||
|
);
|
||||||
|
|
||||||
pub fn send_string(string: *const c_char);
|
pub fn send_string(string: *const c_char);
|
||||||
pub fn delete_string(count: i32);
|
pub fn delete_string(count: i32);
|
||||||
|
|
|
@ -17,7 +17,7 @@
|
||||||
* along with espanso. If not, see <https://www.gnu.org/licenses/>.
|
* along with espanso. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
use std::os::raw::{c_void, c_char};
|
use std::os::raw::{c_char, c_void};
|
||||||
|
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
pub struct MacMenuItem {
|
pub struct MacMenuItem {
|
||||||
|
@ -27,8 +27,8 @@ pub struct MacMenuItem {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(improper_ctypes)]
|
#[allow(improper_ctypes)]
|
||||||
#[link(name="macbridge", kind="static")]
|
#[link(name = "macbridge", kind = "static")]
|
||||||
extern {
|
extern "C" {
|
||||||
pub fn initialize(s: *const c_void, icon_path: *const c_char, show_icon: i32);
|
pub fn initialize(s: *const c_void, icon_path: *const c_char, show_icon: i32);
|
||||||
pub fn eventloop();
|
pub fn eventloop();
|
||||||
pub fn headless_eventloop();
|
pub fn headless_eventloop();
|
||||||
|
@ -39,8 +39,8 @@ extern {
|
||||||
pub fn open_settings_panel();
|
pub fn open_settings_panel();
|
||||||
pub fn get_active_app_bundle(buffer: *mut c_char, size: i32) -> i32;
|
pub fn get_active_app_bundle(buffer: *mut c_char, size: i32) -> i32;
|
||||||
pub fn get_active_app_identifier(buffer: *mut c_char, size: i32) -> i32;
|
pub fn get_active_app_identifier(buffer: *mut c_char, size: i32) -> i32;
|
||||||
pub fn get_secure_input_process(pid:*mut i64) -> i32;
|
pub fn get_secure_input_process(pid: *mut i64) -> i32;
|
||||||
pub fn get_path_from_pid(pid:i64, buffer: *mut c_char, size: i32) -> i32;
|
pub fn get_path_from_pid(pid: i64, buffer: *mut c_char, size: i32) -> i32;
|
||||||
|
|
||||||
// Clipboard
|
// Clipboard
|
||||||
pub fn get_clipboard(buffer: *mut c_char, size: i32) -> i32;
|
pub fn get_clipboard(buffer: *mut c_char, size: i32) -> i32;
|
||||||
|
@ -48,13 +48,14 @@ extern {
|
||||||
pub fn set_clipboard_image(path: *const c_char) -> i32;
|
pub fn set_clipboard_image(path: *const c_char) -> i32;
|
||||||
|
|
||||||
// UI
|
// UI
|
||||||
pub fn register_icon_click_callback(cb: extern fn(_self: *mut c_void));
|
pub fn register_icon_click_callback(cb: extern "C" fn(_self: *mut c_void));
|
||||||
pub fn show_context_menu(items: *const MacMenuItem, count: i32) -> i32;
|
pub fn show_context_menu(items: *const MacMenuItem, count: i32) -> i32;
|
||||||
pub fn register_context_menu_click_callback(cb: extern fn(_self: *mut c_void, id: i32));
|
pub fn register_context_menu_click_callback(cb: extern "C" fn(_self: *mut c_void, id: i32));
|
||||||
|
|
||||||
// Keyboard
|
// Keyboard
|
||||||
pub fn register_keypress_callback(cb: extern fn(_self: *mut c_void, *const u8,
|
pub fn register_keypress_callback(
|
||||||
i32, i32, i32));
|
cb: extern "C" fn(_self: *mut c_void, *const u8, i32, i32, i32),
|
||||||
|
);
|
||||||
|
|
||||||
pub fn send_string(string: *const c_char);
|
pub fn send_string(string: *const c_char);
|
||||||
pub fn send_vkey(vk: i32);
|
pub fn send_vkey(vk: i32);
|
||||||
|
|
|
@ -17,7 +17,7 @@
|
||||||
* along with espanso. If not, see <https://www.gnu.org/licenses/>.
|
* along with espanso. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
use std::os::raw::{c_void};
|
use std::os::raw::c_void;
|
||||||
|
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
pub struct WindowsMenuItem {
|
pub struct WindowsMenuItem {
|
||||||
|
@ -27,10 +27,15 @@ pub struct WindowsMenuItem {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(improper_ctypes)]
|
#[allow(improper_ctypes)]
|
||||||
#[link(name="winbridge", kind="static")]
|
#[link(name = "winbridge", kind = "static")]
|
||||||
extern {
|
extern "C" {
|
||||||
pub fn start_daemon_process() -> i32;
|
pub fn start_daemon_process() -> i32;
|
||||||
pub fn initialize(s: *const c_void, ico_path: *const u16, bmp_path: *const u16, show_icon: i32) -> i32;
|
pub fn initialize(
|
||||||
|
s: *const c_void,
|
||||||
|
ico_path: *const u16,
|
||||||
|
bmp_path: *const u16,
|
||||||
|
show_icon: i32,
|
||||||
|
) -> i32;
|
||||||
|
|
||||||
// SYSTEM
|
// SYSTEM
|
||||||
pub fn get_active_window_name(buffer: *mut u16, size: i32) -> i32;
|
pub fn get_active_window_name(buffer: *mut u16, size: i32) -> i32;
|
||||||
|
@ -40,8 +45,8 @@ extern {
|
||||||
pub fn show_notification(message: *const u16) -> i32;
|
pub fn show_notification(message: *const u16) -> i32;
|
||||||
pub fn close_notification();
|
pub fn close_notification();
|
||||||
pub fn show_context_menu(items: *const WindowsMenuItem, count: i32) -> i32;
|
pub fn show_context_menu(items: *const WindowsMenuItem, count: i32) -> i32;
|
||||||
pub fn register_icon_click_callback(cb: extern fn(_self: *mut c_void));
|
pub fn register_icon_click_callback(cb: extern "C" fn(_self: *mut c_void));
|
||||||
pub fn register_context_menu_click_callback(cb: extern fn(_self: *mut c_void, id: i32));
|
pub fn register_context_menu_click_callback(cb: extern "C" fn(_self: *mut c_void, id: i32));
|
||||||
pub fn cleanup_ui();
|
pub fn cleanup_ui();
|
||||||
|
|
||||||
// CLIPBOARD
|
// CLIPBOARD
|
||||||
|
@ -50,8 +55,9 @@ extern {
|
||||||
pub fn set_clipboard_image(path: *const u16) -> i32;
|
pub fn set_clipboard_image(path: *const u16) -> i32;
|
||||||
|
|
||||||
// KEYBOARD
|
// KEYBOARD
|
||||||
pub fn register_keypress_callback(cb: extern fn(_self: *mut c_void, *const u16,
|
pub fn register_keypress_callback(
|
||||||
i32, i32, i32, i32, i32));
|
cb: extern "C" fn(_self: *mut c_void, *const u16, i32, i32, i32, i32, i32),
|
||||||
|
);
|
||||||
|
|
||||||
pub fn eventloop();
|
pub fn eventloop();
|
||||||
pub fn send_string(string: *const u16);
|
pub fn send_string(string: *const u16);
|
||||||
|
|
12
src/check.rs
12
src/check.rs
|
@ -27,20 +27,18 @@ pub fn check_preconditions() -> bool {
|
||||||
let mut result = true;
|
let mut result = true;
|
||||||
|
|
||||||
// Make sure notify-send is installed
|
// Make sure notify-send is installed
|
||||||
let status = Command::new("notify-send")
|
let status = Command::new("notify-send").arg("-v").output();
|
||||||
.arg("-v")
|
|
||||||
.output();
|
|
||||||
if status.is_err() {
|
if status.is_err() {
|
||||||
println!("Error: 'notify-send' command is needed for espanso to work correctly, please install it.");
|
println!("Error: 'notify-send' command is needed for espanso to work correctly, please install it.");
|
||||||
result = false;
|
result = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make sure xclip is installed
|
// Make sure xclip is installed
|
||||||
let status = Command::new("xclip")
|
let status = Command::new("xclip").arg("-version").output();
|
||||||
.arg("-version")
|
|
||||||
.output();
|
|
||||||
if status.is_err() {
|
if status.is_err() {
|
||||||
println!("Error: 'xclip' command is needed for espanso to work correctly, please install it.");
|
println!(
|
||||||
|
"Error: 'xclip' command is needed for espanso to work correctly, please install it."
|
||||||
|
);
|
||||||
result = false;
|
result = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -17,18 +17,16 @@
|
||||||
* along with espanso. If not, see <https://www.gnu.org/licenses/>.
|
* along with espanso. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
use std::process::{Command, Stdio};
|
use log::error;
|
||||||
use std::io::{Write};
|
use std::io::Write;
|
||||||
use log::{error};
|
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
use std::process::{Command, Stdio};
|
||||||
|
|
||||||
pub struct LinuxClipboardManager {}
|
pub struct LinuxClipboardManager {}
|
||||||
|
|
||||||
impl super::ClipboardManager for LinuxClipboardManager {
|
impl super::ClipboardManager for LinuxClipboardManager {
|
||||||
fn get_clipboard(&self) -> Option<String> {
|
fn get_clipboard(&self) -> Option<String> {
|
||||||
let res = Command::new("xclip")
|
let res = Command::new("xclip").args(&["-o", "-sel", "clip"]).output();
|
||||||
.args(&["-o", "-sel", "clip"])
|
|
||||||
.output();
|
|
||||||
|
|
||||||
if let Ok(output) = res {
|
if let Ok(output) = res {
|
||||||
if output.status.success() {
|
if output.status.success() {
|
||||||
|
@ -71,14 +69,14 @@ impl super::ClipboardManager for LinuxClipboardManager {
|
||||||
Some(ext) => {
|
Some(ext) => {
|
||||||
let ext = ext.to_string_lossy().to_lowercase();
|
let ext = ext.to_string_lossy().to_lowercase();
|
||||||
match ext.as_ref() {
|
match ext.as_ref() {
|
||||||
"png" => {"image/png"},
|
"png" => "image/png",
|
||||||
"jpg" | "jpeg" => {"image/jpeg"},
|
"jpg" | "jpeg" => "image/jpeg",
|
||||||
"gif" => {"image/gif"},
|
"gif" => "image/gif",
|
||||||
"svg" => {"image/svg"},
|
"svg" => "image/svg",
|
||||||
_ => {"image/png"},
|
_ => "image/png",
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
None => {"image/png"},
|
None => "image/png",
|
||||||
};
|
};
|
||||||
|
|
||||||
let image_path = image_path.to_string_lossy().into_owned();
|
let image_path = image_path.to_string_lossy().into_owned();
|
||||||
|
@ -95,6 +93,6 @@ impl super::ClipboardManager for LinuxClipboardManager {
|
||||||
|
|
||||||
impl LinuxClipboardManager {
|
impl LinuxClipboardManager {
|
||||||
pub fn new() -> LinuxClipboardManager {
|
pub fn new() -> LinuxClipboardManager {
|
||||||
LinuxClipboardManager{}
|
LinuxClipboardManager {}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -17,20 +17,18 @@
|
||||||
* along with espanso. If not, see <https://www.gnu.org/licenses/>.
|
* along with espanso. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
use std::os::raw::c_char;
|
|
||||||
use crate::bridge::macos::*;
|
use crate::bridge::macos::*;
|
||||||
use std::ffi::{CStr, CString};
|
|
||||||
use std::path::Path;
|
|
||||||
use log::{error, warn};
|
use log::{error, warn};
|
||||||
|
use std::ffi::{CStr, CString};
|
||||||
|
use std::os::raw::c_char;
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
pub struct MacClipboardManager {
|
pub struct MacClipboardManager {}
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
impl super::ClipboardManager for MacClipboardManager {
|
impl super::ClipboardManager for MacClipboardManager {
|
||||||
fn get_clipboard(&self) -> Option<String> {
|
fn get_clipboard(&self) -> Option<String> {
|
||||||
unsafe {
|
unsafe {
|
||||||
let mut buffer : [c_char; 2000] = [0; 2000];
|
let mut buffer: [c_char; 2000] = [0; 2000];
|
||||||
let res = get_clipboard(buffer.as_mut_ptr(), buffer.len() as i32);
|
let res = get_clipboard(buffer.as_mut_ptr(), buffer.len() as i32);
|
||||||
|
|
||||||
if res > 0 {
|
if res > 0 {
|
||||||
|
@ -71,6 +69,6 @@ impl super::ClipboardManager for MacClipboardManager {
|
||||||
|
|
||||||
impl MacClipboardManager {
|
impl MacClipboardManager {
|
||||||
pub fn new() -> MacClipboardManager {
|
pub fn new() -> MacClipboardManager {
|
||||||
MacClipboardManager{}
|
MacClipboardManager {}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -17,24 +17,22 @@
|
||||||
* along with espanso. If not, see <https://www.gnu.org/licenses/>.
|
* along with espanso. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
use widestring::U16CString;
|
use crate::bridge::windows::{get_clipboard, set_clipboard, set_clipboard_image};
|
||||||
use crate::bridge::windows::{set_clipboard, get_clipboard, set_clipboard_image};
|
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
use widestring::U16CString;
|
||||||
|
|
||||||
pub struct WindowsClipboardManager {
|
pub struct WindowsClipboardManager {}
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
impl WindowsClipboardManager {
|
impl WindowsClipboardManager {
|
||||||
pub fn new() -> WindowsClipboardManager {
|
pub fn new() -> WindowsClipboardManager {
|
||||||
WindowsClipboardManager{}
|
WindowsClipboardManager {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl super::ClipboardManager for WindowsClipboardManager {
|
impl super::ClipboardManager for WindowsClipboardManager {
|
||||||
fn get_clipboard(&self) -> Option<String> {
|
fn get_clipboard(&self) -> Option<String> {
|
||||||
unsafe {
|
unsafe {
|
||||||
let mut buffer : [u16; 2000] = [0; 2000];
|
let mut buffer: [u16; 2000] = [0; 2000];
|
||||||
let res = get_clipboard(buffer.as_mut_ptr(), buffer.len() as i32);
|
let res = get_clipboard(buffer.as_mut_ptr(), buffer.len() as i32);
|
||||||
|
|
||||||
if res > 0 {
|
if res > 0 {
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -17,13 +17,13 @@
|
||||||
* along with espanso. If not, see <https://www.gnu.org/licenses/>.
|
* along with espanso. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
use regex::Regex;
|
use super::{ConfigSet, Configs};
|
||||||
|
use crate::matcher::Match;
|
||||||
use crate::system::SystemManager;
|
use crate::system::SystemManager;
|
||||||
|
use log::{debug, warn};
|
||||||
|
use regex::Regex;
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
use std::time::SystemTime;
|
use std::time::SystemTime;
|
||||||
use log::{debug, warn};
|
|
||||||
use super::{Configs, ConfigSet};
|
|
||||||
use crate::matcher::Match;
|
|
||||||
|
|
||||||
pub struct RuntimeConfigManager<'a, S: SystemManager> {
|
pub struct RuntimeConfigManager<'a, S: SystemManager> {
|
||||||
set: ConfigSet,
|
set: ConfigSet,
|
||||||
|
@ -37,10 +37,10 @@ pub struct RuntimeConfigManager<'a, S: SystemManager> {
|
||||||
|
|
||||||
// Cache
|
// Cache
|
||||||
last_config_update: RefCell<SystemTime>,
|
last_config_update: RefCell<SystemTime>,
|
||||||
last_config: RefCell<Option<&'a Configs>>
|
last_config: RefCell<Option<&'a Configs>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl <'a, S: SystemManager> RuntimeConfigManager<'a, S> {
|
impl<'a, S: SystemManager> RuntimeConfigManager<'a, S> {
|
||||||
pub fn new<'b>(set: ConfigSet, system_manager: S) -> RuntimeConfigManager<'b, S> {
|
pub fn new<'b>(set: ConfigSet, system_manager: S) -> RuntimeConfigManager<'b, S> {
|
||||||
// Compile all the regexps
|
// Compile all the regexps
|
||||||
let title_regexps = set.specific.iter().map(
|
let title_regexps = set.specific.iter().map(
|
||||||
|
@ -101,7 +101,7 @@ impl <'a, S: SystemManager> RuntimeConfigManager<'a, S> {
|
||||||
exec_regexps,
|
exec_regexps,
|
||||||
system_manager,
|
system_manager,
|
||||||
last_config_update,
|
last_config_update,
|
||||||
last_config
|
last_config,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -118,10 +118,12 @@ impl <'a, S: SystemManager> RuntimeConfigManager<'a, S> {
|
||||||
for (i, regex) in self.title_regexps.iter().enumerate() {
|
for (i, regex) in self.title_regexps.iter().enumerate() {
|
||||||
if let Some(regex) = regex {
|
if let Some(regex) = regex {
|
||||||
if regex.is_match(&title) {
|
if regex.is_match(&title) {
|
||||||
debug!("Matched 'filter_title' for '{}' config, using custom settings.",
|
debug!(
|
||||||
self.set.specific[i].name);
|
"Matched 'filter_title' for '{}' config, using custom settings.",
|
||||||
|
self.set.specific[i].name
|
||||||
|
);
|
||||||
|
|
||||||
return &self.set.specific[i]
|
return &self.set.specific[i];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -135,10 +137,12 @@ impl <'a, S: SystemManager> RuntimeConfigManager<'a, S> {
|
||||||
for (i, regex) in self.exec_regexps.iter().enumerate() {
|
for (i, regex) in self.exec_regexps.iter().enumerate() {
|
||||||
if let Some(regex) = regex {
|
if let Some(regex) = regex {
|
||||||
if regex.is_match(&executable) {
|
if regex.is_match(&executable) {
|
||||||
debug!("Matched 'filter_exec' for '{}' config, using custom settings.",
|
debug!(
|
||||||
self.set.specific[i].name);
|
"Matched 'filter_exec' for '{}' config, using custom settings.",
|
||||||
|
self.set.specific[i].name
|
||||||
|
);
|
||||||
|
|
||||||
return &self.set.specific[i]
|
return &self.set.specific[i];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -152,10 +156,12 @@ impl <'a, S: SystemManager> RuntimeConfigManager<'a, S> {
|
||||||
for (i, regex) in self.class_regexps.iter().enumerate() {
|
for (i, regex) in self.class_regexps.iter().enumerate() {
|
||||||
if let Some(regex) = regex {
|
if let Some(regex) = regex {
|
||||||
if regex.is_match(&class) {
|
if regex.is_match(&class) {
|
||||||
debug!("Matched 'filter_class' for '{}' config, using custom settings.",
|
debug!(
|
||||||
self.set.specific[i].name);
|
"Matched 'filter_class' for '{}' config, using custom settings.",
|
||||||
|
self.set.specific[i].name
|
||||||
|
);
|
||||||
|
|
||||||
return &self.set.specific[i]
|
return &self.set.specific[i];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -167,7 +173,7 @@ impl <'a, S: SystemManager> RuntimeConfigManager<'a, S> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl <'a, S: SystemManager> super::ConfigManager<'a> for RuntimeConfigManager<'a, S> {
|
impl<'a, S: SystemManager> super::ConfigManager<'a> for RuntimeConfigManager<'a, S> {
|
||||||
fn active_config(&'a self) -> &'a Configs {
|
fn active_config(&'a self) -> &'a Configs {
|
||||||
let mut last_config_update = self.last_config_update.borrow_mut();
|
let mut last_config_update = self.last_config_update.borrow_mut();
|
||||||
if let Ok(elapsed) = (*last_config_update).elapsed() {
|
if let Ok(elapsed) = (*last_config_update).elapsed() {
|
||||||
|
@ -204,8 +210,8 @@ impl <'a, S: SystemManager> super::ConfigManager<'a> for RuntimeConfigManager<'a
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::config::ConfigManager;
|
|
||||||
use crate::config::tests::{create_temp_espanso_directories, create_user_config_file};
|
use crate::config::tests::{create_temp_espanso_directories, create_user_config_file};
|
||||||
|
use crate::config::ConfigManager;
|
||||||
|
|
||||||
struct DummySystemManager {
|
struct DummySystemManager {
|
||||||
title: RefCell<String>,
|
title: RefCell<String>,
|
||||||
|
@ -225,10 +231,10 @@ mod tests {
|
||||||
}
|
}
|
||||||
impl DummySystemManager {
|
impl DummySystemManager {
|
||||||
pub fn new_custom(title: &str, class: &str, exec: &str) -> DummySystemManager {
|
pub fn new_custom(title: &str, class: &str, exec: &str) -> DummySystemManager {
|
||||||
DummySystemManager{
|
DummySystemManager {
|
||||||
title: RefCell::new(title.to_owned()),
|
title: RefCell::new(title.to_owned()),
|
||||||
class: RefCell::new(class.to_owned()),
|
class: RefCell::new(class.to_owned()),
|
||||||
exec: RefCell::new(exec.to_owned())
|
exec: RefCell::new(exec.to_owned()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -247,21 +253,33 @@ mod tests {
|
||||||
fn test_runtime_constructor_regex_load_correctly() {
|
fn test_runtime_constructor_regex_load_correctly() {
|
||||||
let (data_dir, package_dir) = create_temp_espanso_directories();
|
let (data_dir, package_dir) = create_temp_espanso_directories();
|
||||||
|
|
||||||
create_user_config_file(&data_dir.path(), "specific.yml", r###"
|
create_user_config_file(
|
||||||
|
&data_dir.path(),
|
||||||
|
"specific.yml",
|
||||||
|
r###"
|
||||||
name: myname1
|
name: myname1
|
||||||
filter_exec: "Title"
|
filter_exec: "Title"
|
||||||
"###);
|
"###,
|
||||||
|
);
|
||||||
|
|
||||||
create_user_config_file(&data_dir.path(), "specific2.yml", r###"
|
create_user_config_file(
|
||||||
|
&data_dir.path(),
|
||||||
|
"specific2.yml",
|
||||||
|
r###"
|
||||||
name: myname2
|
name: myname2
|
||||||
filter_title: "Yeah"
|
filter_title: "Yeah"
|
||||||
filter_class: "Car"
|
filter_class: "Car"
|
||||||
"###);
|
"###,
|
||||||
|
);
|
||||||
|
|
||||||
create_user_config_file(&data_dir.path(), "specific3.yml", r###"
|
create_user_config_file(
|
||||||
|
&data_dir.path(),
|
||||||
|
"specific3.yml",
|
||||||
|
r###"
|
||||||
name: myname3
|
name: myname3
|
||||||
filter_title: "Nice"
|
filter_title: "Nice"
|
||||||
"###);
|
"###,
|
||||||
|
);
|
||||||
|
|
||||||
let config_set = ConfigSet::load(data_dir.path(), package_dir.path());
|
let config_set = ConfigSet::load(data_dir.path(), package_dir.path());
|
||||||
assert!(config_set.is_ok());
|
assert!(config_set.is_ok());
|
||||||
|
@ -270,12 +288,24 @@ mod tests {
|
||||||
|
|
||||||
let config_manager = RuntimeConfigManager::new(config_set.unwrap(), dummy_system_manager);
|
let config_manager = RuntimeConfigManager::new(config_set.unwrap(), dummy_system_manager);
|
||||||
|
|
||||||
let sp1index = config_manager.set.specific
|
let sp1index = config_manager
|
||||||
.iter().position(|x| x.name == "myname1").unwrap();
|
.set
|
||||||
let sp2index = config_manager.set.specific
|
.specific
|
||||||
.iter().position(|x| x.name == "myname2").unwrap();
|
.iter()
|
||||||
let sp3index = config_manager.set.specific
|
.position(|x| x.name == "myname1")
|
||||||
.iter().position(|x| x.name == "myname3").unwrap();
|
.unwrap();
|
||||||
|
let sp2index = config_manager
|
||||||
|
.set
|
||||||
|
.specific
|
||||||
|
.iter()
|
||||||
|
.position(|x| x.name == "myname2")
|
||||||
|
.unwrap();
|
||||||
|
let sp3index = config_manager
|
||||||
|
.set
|
||||||
|
.specific
|
||||||
|
.iter()
|
||||||
|
.position(|x| x.name == "myname3")
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
assert_eq!(config_manager.exec_regexps.len(), 3);
|
assert_eq!(config_manager.exec_regexps.len(), 3);
|
||||||
assert_eq!(config_manager.title_regexps.len(), 3);
|
assert_eq!(config_manager.title_regexps.len(), 3);
|
||||||
|
@ -298,21 +328,33 @@ mod tests {
|
||||||
fn test_runtime_constructor_malformed_regexes_are_ignored() {
|
fn test_runtime_constructor_malformed_regexes_are_ignored() {
|
||||||
let (data_dir, package_dir) = create_temp_espanso_directories();
|
let (data_dir, package_dir) = create_temp_espanso_directories();
|
||||||
|
|
||||||
create_user_config_file(&data_dir.path(), "specific.yml", r###"
|
create_user_config_file(
|
||||||
|
&data_dir.path(),
|
||||||
|
"specific.yml",
|
||||||
|
r###"
|
||||||
name: myname1
|
name: myname1
|
||||||
filter_exec: "[`-_]"
|
filter_exec: "[`-_]"
|
||||||
"###);
|
"###,
|
||||||
|
);
|
||||||
|
|
||||||
create_user_config_file(&data_dir.path(), "specific2.yml", r###"
|
create_user_config_file(
|
||||||
|
&data_dir.path(),
|
||||||
|
"specific2.yml",
|
||||||
|
r###"
|
||||||
name: myname2
|
name: myname2
|
||||||
filter_title: "[`-_]"
|
filter_title: "[`-_]"
|
||||||
filter_class: "Car"
|
filter_class: "Car"
|
||||||
"###);
|
"###,
|
||||||
|
);
|
||||||
|
|
||||||
create_user_config_file(&data_dir.path(), "specific3.yml", r###"
|
create_user_config_file(
|
||||||
|
&data_dir.path(),
|
||||||
|
"specific3.yml",
|
||||||
|
r###"
|
||||||
name: myname3
|
name: myname3
|
||||||
filter_title: "Nice"
|
filter_title: "Nice"
|
||||||
"###);
|
"###,
|
||||||
|
);
|
||||||
|
|
||||||
let config_set = ConfigSet::load(data_dir.path(), package_dir.path());
|
let config_set = ConfigSet::load(data_dir.path(), package_dir.path());
|
||||||
assert!(config_set.is_ok());
|
assert!(config_set.is_ok());
|
||||||
|
@ -321,12 +363,24 @@ mod tests {
|
||||||
|
|
||||||
let config_manager = RuntimeConfigManager::new(config_set.unwrap(), dummy_system_manager);
|
let config_manager = RuntimeConfigManager::new(config_set.unwrap(), dummy_system_manager);
|
||||||
|
|
||||||
let sp1index = config_manager.set.specific
|
let sp1index = config_manager
|
||||||
.iter().position(|x| x.name == "myname1").unwrap();
|
.set
|
||||||
let sp2index = config_manager.set.specific
|
.specific
|
||||||
.iter().position(|x| x.name == "myname2").unwrap();
|
.iter()
|
||||||
let sp3index = config_manager.set.specific
|
.position(|x| x.name == "myname1")
|
||||||
.iter().position(|x| x.name == "myname3").unwrap();
|
.unwrap();
|
||||||
|
let sp2index = config_manager
|
||||||
|
.set
|
||||||
|
.specific
|
||||||
|
.iter()
|
||||||
|
.position(|x| x.name == "myname2")
|
||||||
|
.unwrap();
|
||||||
|
let sp3index = config_manager
|
||||||
|
.set
|
||||||
|
.specific
|
||||||
|
.iter()
|
||||||
|
.position(|x| x.name == "myname3")
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
assert_eq!(config_manager.exec_regexps.len(), 3);
|
assert_eq!(config_manager.exec_regexps.len(), 3);
|
||||||
assert_eq!(config_manager.title_regexps.len(), 3);
|
assert_eq!(config_manager.title_regexps.len(), 3);
|
||||||
|
@ -349,15 +403,20 @@ mod tests {
|
||||||
fn test_runtime_calculate_active_config_specific_title_match() {
|
fn test_runtime_calculate_active_config_specific_title_match() {
|
||||||
let (data_dir, package_dir) = create_temp_espanso_directories();
|
let (data_dir, package_dir) = create_temp_espanso_directories();
|
||||||
|
|
||||||
create_user_config_file(&data_dir.path(), "specific.yml", r###"
|
create_user_config_file(
|
||||||
|
&data_dir.path(),
|
||||||
|
"specific.yml",
|
||||||
|
r###"
|
||||||
name: chrome
|
name: chrome
|
||||||
filter_title: "Chrome"
|
filter_title: "Chrome"
|
||||||
"###);
|
"###,
|
||||||
|
);
|
||||||
|
|
||||||
let config_set = ConfigSet::load(data_dir.path(), package_dir.path());
|
let config_set = ConfigSet::load(data_dir.path(), package_dir.path());
|
||||||
assert!(config_set.is_ok());
|
assert!(config_set.is_ok());
|
||||||
|
|
||||||
let dummy_system_manager = DummySystemManager::new_custom("Google Chrome", "Chrome", "C:\\Path\\chrome.exe");
|
let dummy_system_manager =
|
||||||
|
DummySystemManager::new_custom("Google Chrome", "Chrome", "C:\\Path\\chrome.exe");
|
||||||
|
|
||||||
let config_manager = RuntimeConfigManager::new(config_set.unwrap(), dummy_system_manager);
|
let config_manager = RuntimeConfigManager::new(config_set.unwrap(), dummy_system_manager);
|
||||||
|
|
||||||
|
@ -368,15 +427,20 @@ mod tests {
|
||||||
fn test_runtime_calculate_active_config_specific_class_match() {
|
fn test_runtime_calculate_active_config_specific_class_match() {
|
||||||
let (data_dir, package_dir) = create_temp_espanso_directories();
|
let (data_dir, package_dir) = create_temp_espanso_directories();
|
||||||
|
|
||||||
create_user_config_file(&data_dir.path(), "specific.yml", r###"
|
create_user_config_file(
|
||||||
|
&data_dir.path(),
|
||||||
|
"specific.yml",
|
||||||
|
r###"
|
||||||
name: chrome
|
name: chrome
|
||||||
filter_class: "Chrome"
|
filter_class: "Chrome"
|
||||||
"###);
|
"###,
|
||||||
|
);
|
||||||
|
|
||||||
let config_set = ConfigSet::load(data_dir.path(), package_dir.path());
|
let config_set = ConfigSet::load(data_dir.path(), package_dir.path());
|
||||||
assert!(config_set.is_ok());
|
assert!(config_set.is_ok());
|
||||||
|
|
||||||
let dummy_system_manager = DummySystemManager::new_custom("Google Chrome", "Chrome", "C:\\Path\\chrome.exe");
|
let dummy_system_manager =
|
||||||
|
DummySystemManager::new_custom("Google Chrome", "Chrome", "C:\\Path\\chrome.exe");
|
||||||
|
|
||||||
let config_manager = RuntimeConfigManager::new(config_set.unwrap(), dummy_system_manager);
|
let config_manager = RuntimeConfigManager::new(config_set.unwrap(), dummy_system_manager);
|
||||||
|
|
||||||
|
@ -387,15 +451,20 @@ mod tests {
|
||||||
fn test_runtime_calculate_active_config_specific_exec_match() {
|
fn test_runtime_calculate_active_config_specific_exec_match() {
|
||||||
let (data_dir, package_dir) = create_temp_espanso_directories();
|
let (data_dir, package_dir) = create_temp_espanso_directories();
|
||||||
|
|
||||||
create_user_config_file(&data_dir.path(), "specific.yml", r###"
|
create_user_config_file(
|
||||||
|
&data_dir.path(),
|
||||||
|
"specific.yml",
|
||||||
|
r###"
|
||||||
name: chrome
|
name: chrome
|
||||||
filter_exec: "chrome.exe"
|
filter_exec: "chrome.exe"
|
||||||
"###);
|
"###,
|
||||||
|
);
|
||||||
|
|
||||||
let config_set = ConfigSet::load(data_dir.path(), package_dir.path());
|
let config_set = ConfigSet::load(data_dir.path(), package_dir.path());
|
||||||
assert!(config_set.is_ok());
|
assert!(config_set.is_ok());
|
||||||
|
|
||||||
let dummy_system_manager = DummySystemManager::new_custom("Google Chrome", "Chrome", "C:\\Path\\chrome.exe");
|
let dummy_system_manager =
|
||||||
|
DummySystemManager::new_custom("Google Chrome", "Chrome", "C:\\Path\\chrome.exe");
|
||||||
|
|
||||||
let config_manager = RuntimeConfigManager::new(config_set.unwrap(), dummy_system_manager);
|
let config_manager = RuntimeConfigManager::new(config_set.unwrap(), dummy_system_manager);
|
||||||
|
|
||||||
|
@ -406,16 +475,21 @@ mod tests {
|
||||||
fn test_runtime_calculate_active_config_specific_multi_filter_match() {
|
fn test_runtime_calculate_active_config_specific_multi_filter_match() {
|
||||||
let (data_dir, package_dir) = create_temp_espanso_directories();
|
let (data_dir, package_dir) = create_temp_espanso_directories();
|
||||||
|
|
||||||
create_user_config_file(&data_dir.path(), "specific.yml", r###"
|
create_user_config_file(
|
||||||
|
&data_dir.path(),
|
||||||
|
"specific.yml",
|
||||||
|
r###"
|
||||||
name: chrome
|
name: chrome
|
||||||
filter_class: Browser
|
filter_class: Browser
|
||||||
filter_exec: "firefox.exe"
|
filter_exec: "firefox.exe"
|
||||||
"###);
|
"###,
|
||||||
|
);
|
||||||
|
|
||||||
let config_set = ConfigSet::load(data_dir.path(), package_dir.path());
|
let config_set = ConfigSet::load(data_dir.path(), package_dir.path());
|
||||||
assert!(config_set.is_ok());
|
assert!(config_set.is_ok());
|
||||||
|
|
||||||
let dummy_system_manager = DummySystemManager::new_custom("Google Chrome", "Browser", "C:\\Path\\chrome.exe");
|
let dummy_system_manager =
|
||||||
|
DummySystemManager::new_custom("Google Chrome", "Browser", "C:\\Path\\chrome.exe");
|
||||||
|
|
||||||
let config_manager = RuntimeConfigManager::new(config_set.unwrap(), dummy_system_manager);
|
let config_manager = RuntimeConfigManager::new(config_set.unwrap(), dummy_system_manager);
|
||||||
|
|
||||||
|
@ -426,15 +500,20 @@ mod tests {
|
||||||
fn test_runtime_calculate_active_config_no_match() {
|
fn test_runtime_calculate_active_config_no_match() {
|
||||||
let (data_dir, package_dir) = create_temp_espanso_directories();
|
let (data_dir, package_dir) = create_temp_espanso_directories();
|
||||||
|
|
||||||
create_user_config_file(&data_dir.path(), "specific.yml", r###"
|
create_user_config_file(
|
||||||
|
&data_dir.path(),
|
||||||
|
"specific.yml",
|
||||||
|
r###"
|
||||||
name: firefox
|
name: firefox
|
||||||
filter_title: "Firefox"
|
filter_title: "Firefox"
|
||||||
"###);
|
"###,
|
||||||
|
);
|
||||||
|
|
||||||
let config_set = ConfigSet::load(data_dir.path(), package_dir.path());
|
let config_set = ConfigSet::load(data_dir.path(), package_dir.path());
|
||||||
assert!(config_set.is_ok());
|
assert!(config_set.is_ok());
|
||||||
|
|
||||||
let dummy_system_manager = DummySystemManager::new_custom("Google Chrome", "Chrome", "C:\\Path\\chrome.exe");
|
let dummy_system_manager =
|
||||||
|
DummySystemManager::new_custom("Google Chrome", "Chrome", "C:\\Path\\chrome.exe");
|
||||||
|
|
||||||
let config_manager = RuntimeConfigManager::new(config_set.unwrap(), dummy_system_manager);
|
let config_manager = RuntimeConfigManager::new(config_set.unwrap(), dummy_system_manager);
|
||||||
|
|
||||||
|
@ -445,22 +524,29 @@ mod tests {
|
||||||
fn test_runtime_active_config_cache() {
|
fn test_runtime_active_config_cache() {
|
||||||
let (data_dir, package_dir) = create_temp_espanso_directories();
|
let (data_dir, package_dir) = create_temp_espanso_directories();
|
||||||
|
|
||||||
create_user_config_file(&data_dir.path(), "specific.yml", r###"
|
create_user_config_file(
|
||||||
|
&data_dir.path(),
|
||||||
|
"specific.yml",
|
||||||
|
r###"
|
||||||
name: firefox
|
name: firefox
|
||||||
filter_title: "Firefox"
|
filter_title: "Firefox"
|
||||||
"###);
|
"###,
|
||||||
|
);
|
||||||
|
|
||||||
let config_set = ConfigSet::load(data_dir.path(), package_dir.path());
|
let config_set = ConfigSet::load(data_dir.path(), package_dir.path());
|
||||||
assert!(config_set.is_ok());
|
assert!(config_set.is_ok());
|
||||||
|
|
||||||
let dummy_system_manager = DummySystemManager::new_custom("Google Chrome", "Chrome", "C:\\Path\\chrome.exe");
|
let dummy_system_manager =
|
||||||
|
DummySystemManager::new_custom("Google Chrome", "Chrome", "C:\\Path\\chrome.exe");
|
||||||
|
|
||||||
let config_manager = RuntimeConfigManager::new(config_set.unwrap(), dummy_system_manager);
|
let config_manager = RuntimeConfigManager::new(config_set.unwrap(), dummy_system_manager);
|
||||||
|
|
||||||
assert_eq!(config_manager.active_config().name, "default");
|
assert_eq!(config_manager.active_config().name, "default");
|
||||||
assert_eq!(config_manager.calculate_active_config().name, "default");
|
assert_eq!(config_manager.calculate_active_config().name, "default");
|
||||||
|
|
||||||
config_manager.system_manager.change("Firefox", "Browser", "C\\Path\\firefox.exe");
|
config_manager
|
||||||
|
.system_manager
|
||||||
|
.change("Firefox", "Browser", "C\\Path\\firefox.exe");
|
||||||
|
|
||||||
// Active config should have changed, but not cached one
|
// Active config should have changed, but not cached one
|
||||||
assert_eq!(config_manager.calculate_active_config().name, "firefox");
|
assert_eq!(config_manager.calculate_active_config().name, "firefox");
|
||||||
|
|
|
@ -17,18 +17,18 @@
|
||||||
* along with espanso. If not, see <https://www.gnu.org/licenses/>.
|
* along with espanso. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
use std::sync::mpsc::Sender;
|
|
||||||
use std::os::raw::{c_void, c_char};
|
|
||||||
use crate::event::*;
|
|
||||||
use crate::event::KeyModifier::*;
|
|
||||||
use crate::bridge::linux::*;
|
use crate::bridge::linux::*;
|
||||||
use std::process::exit;
|
use crate::config::Configs;
|
||||||
|
use crate::event::KeyModifier::*;
|
||||||
|
use crate::event::*;
|
||||||
use log::{debug, error};
|
use log::{debug, error};
|
||||||
use std::ffi::CStr;
|
use std::ffi::CStr;
|
||||||
|
use std::os::raw::{c_char, c_void};
|
||||||
|
use std::process::exit;
|
||||||
use std::sync::atomic::AtomicBool;
|
use std::sync::atomic::AtomicBool;
|
||||||
use std::sync::Arc;
|
|
||||||
use std::sync::atomic::Ordering::Acquire;
|
use std::sync::atomic::Ordering::Acquire;
|
||||||
use crate::config::Configs;
|
use std::sync::mpsc::Sender;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
pub struct LinuxContext {
|
pub struct LinuxContext {
|
||||||
|
@ -37,11 +37,13 @@ pub struct LinuxContext {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LinuxContext {
|
impl LinuxContext {
|
||||||
pub fn new(_: Configs, send_channel: Sender<Event>, is_injecting: Arc<AtomicBool>) -> Box<LinuxContext> {
|
pub fn new(
|
||||||
|
_: Configs,
|
||||||
|
send_channel: Sender<Event>,
|
||||||
|
is_injecting: Arc<AtomicBool>,
|
||||||
|
) -> Box<LinuxContext> {
|
||||||
// Check if the X11 context is available
|
// Check if the X11 context is available
|
||||||
let x11_available = unsafe {
|
let x11_available = unsafe { check_x11() };
|
||||||
check_x11()
|
|
||||||
};
|
|
||||||
|
|
||||||
if x11_available < 0 {
|
if x11_available < 0 {
|
||||||
error!("Error, can't connect to X11 context");
|
error!("Error, can't connect to X11 context");
|
||||||
|
@ -50,7 +52,7 @@ impl LinuxContext {
|
||||||
|
|
||||||
let context = Box::new(LinuxContext {
|
let context = Box::new(LinuxContext {
|
||||||
send_channel,
|
send_channel,
|
||||||
is_injecting
|
is_injecting,
|
||||||
});
|
});
|
||||||
|
|
||||||
unsafe {
|
unsafe {
|
||||||
|
@ -79,14 +81,21 @@ impl super::Context for LinuxContext {
|
||||||
|
|
||||||
impl Drop for LinuxContext {
|
impl Drop for LinuxContext {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
unsafe { cleanup(); }
|
unsafe {
|
||||||
|
cleanup();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Native bridge code
|
// Native bridge code
|
||||||
|
|
||||||
extern fn keypress_callback(_self: *mut c_void, raw_buffer: *const u8, _len: i32,
|
extern "C" fn keypress_callback(
|
||||||
event_type: i32, key_code: i32) {
|
_self: *mut c_void,
|
||||||
|
raw_buffer: *const u8,
|
||||||
|
_len: i32,
|
||||||
|
event_type: i32,
|
||||||
|
key_code: i32,
|
||||||
|
) {
|
||||||
unsafe {
|
unsafe {
|
||||||
let _self = _self as *mut LinuxContext;
|
let _self = _self as *mut LinuxContext;
|
||||||
|
|
||||||
|
@ -98,7 +107,8 @@ extern fn keypress_callback(_self: *mut c_void, raw_buffer: *const u8, _len: i32
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if event_type == 0 { // Char event
|
if event_type == 0 {
|
||||||
|
// Char event
|
||||||
// Convert the received buffer to a string
|
// Convert the received buffer to a string
|
||||||
let c_str = CStr::from_ptr(raw_buffer as *const c_char);
|
let c_str = CStr::from_ptr(raw_buffer as *const c_char);
|
||||||
let char_str = c_str.to_str();
|
let char_str = c_str.to_str();
|
||||||
|
@ -108,12 +118,13 @@ extern fn keypress_callback(_self: *mut c_void, raw_buffer: *const u8, _len: i32
|
||||||
Ok(char_str) => {
|
Ok(char_str) => {
|
||||||
let event = Event::Key(KeyEvent::Char(char_str.to_owned()));
|
let event = Event::Key(KeyEvent::Char(char_str.to_owned()));
|
||||||
(*_self).send_channel.send(event).unwrap();
|
(*_self).send_channel.send(event).unwrap();
|
||||||
},
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
debug!("Unable to receive char: {}",e);
|
debug!("Unable to receive char: {}", e);
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
}else if event_type == 1 { // Modifier event
|
} else if event_type == 1 {
|
||||||
|
// Modifier event
|
||||||
|
|
||||||
let modifier: Option<KeyModifier> = match key_code {
|
let modifier: Option<KeyModifier> = match key_code {
|
||||||
133 => Some(LEFT_META),
|
133 => Some(LEFT_META),
|
||||||
|
@ -132,11 +143,13 @@ extern fn keypress_callback(_self: *mut c_void, raw_buffer: *const u8, _len: i32
|
||||||
if let Some(modifier) = modifier {
|
if let Some(modifier) = modifier {
|
||||||
let event = Event::Key(KeyEvent::Modifier(modifier));
|
let event = Event::Key(KeyEvent::Modifier(modifier));
|
||||||
(*_self).send_channel.send(event).unwrap();
|
(*_self).send_channel.send(event).unwrap();
|
||||||
}else{ // Not one of the default modifiers, send an "other" event
|
} else {
|
||||||
|
// Not one of the default modifiers, send an "other" event
|
||||||
let event = Event::Key(KeyEvent::Other);
|
let event = Event::Key(KeyEvent::Other);
|
||||||
(*_self).send_channel.send(event).unwrap();
|
(*_self).send_channel.send(event).unwrap();
|
||||||
}
|
}
|
||||||
}else{ // Other type of event
|
} else {
|
||||||
|
// Other type of event
|
||||||
let event = Event::Key(KeyEvent::Other);
|
let event = Event::Key(KeyEvent::Other);
|
||||||
(*_self).send_channel.send(event).unwrap();
|
(*_self).send_channel.send(event).unwrap();
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,23 +17,23 @@
|
||||||
* along with espanso. If not, see <https://www.gnu.org/licenses/>.
|
* along with espanso. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
use std::sync::mpsc::Sender;
|
|
||||||
use std::os::raw::{c_void, c_char};
|
|
||||||
use crate::bridge::macos::*;
|
use crate::bridge::macos::*;
|
||||||
use crate::event::{Event, KeyEvent, KeyModifier, ActionType, SystemEvent};
|
use crate::config::Configs;
|
||||||
use crate::event::KeyModifier::*;
|
use crate::event::KeyModifier::*;
|
||||||
use std::ffi::{CString, CStr};
|
use crate::event::{ActionType, Event, KeyEvent, KeyModifier, SystemEvent};
|
||||||
use std::{fs, thread};
|
use crate::system::macos::MacSystemManager;
|
||||||
use log::{info, error, debug};
|
use log::{debug, error, info};
|
||||||
|
use std::cell::RefCell;
|
||||||
|
use std::ffi::{CStr, CString};
|
||||||
|
use std::os::raw::{c_char, c_void};
|
||||||
use std::process::exit;
|
use std::process::exit;
|
||||||
use std::sync::atomic::AtomicBool;
|
use std::sync::atomic::AtomicBool;
|
||||||
use std::sync::Arc;
|
|
||||||
use std::sync::atomic::Ordering::Acquire;
|
use std::sync::atomic::Ordering::Acquire;
|
||||||
use crate::config::Configs;
|
use std::sync::mpsc::Sender;
|
||||||
use std::cell::RefCell;
|
use std::sync::Arc;
|
||||||
use crate::system::macos::MacSystemManager;
|
use std::{fs, thread};
|
||||||
|
|
||||||
const STATUS_ICON_BINARY : &[u8] = include_bytes!("../res/mac/icon.png");
|
const STATUS_ICON_BINARY: &[u8] = include_bytes!("../res/mac/icon.png");
|
||||||
|
|
||||||
pub struct MacContext {
|
pub struct MacContext {
|
||||||
pub send_channel: Sender<Event>,
|
pub send_channel: Sender<Event>,
|
||||||
|
@ -43,14 +43,20 @@ pub struct MacContext {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MacContext {
|
impl MacContext {
|
||||||
pub fn new(config: Configs, send_channel: Sender<Event>, is_injecting: Arc<AtomicBool>) -> Box<MacContext> {
|
pub fn new(
|
||||||
|
config: Configs,
|
||||||
|
send_channel: Sender<Event>,
|
||||||
|
is_injecting: Arc<AtomicBool>,
|
||||||
|
) -> Box<MacContext> {
|
||||||
// Check accessibility
|
// Check accessibility
|
||||||
unsafe {
|
unsafe {
|
||||||
let res = prompt_accessibility();
|
let res = prompt_accessibility();
|
||||||
|
|
||||||
if res == 0 {
|
if res == 0 {
|
||||||
error!("Accessibility must be enabled to make espanso work on MacOS.");
|
error!("Accessibility must be enabled to make espanso work on MacOS.");
|
||||||
error!("Please allow espanso in the Security & Privacy panel, then restart espanso.");
|
error!(
|
||||||
|
"Please allow espanso in the Security & Privacy panel, then restart espanso."
|
||||||
|
);
|
||||||
error!("For more information: https://espanso.org/install/mac/");
|
error!("For more information: https://espanso.org/install/mac/");
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
|
@ -69,9 +75,12 @@ impl MacContext {
|
||||||
|
|
||||||
if status_icon_target.exists() {
|
if status_icon_target.exists() {
|
||||||
info!("Status icon already initialized, skipping.");
|
info!("Status icon already initialized, skipping.");
|
||||||
}else {
|
} else {
|
||||||
fs::write(&status_icon_target, STATUS_ICON_BINARY).unwrap_or_else(|e| {
|
fs::write(&status_icon_target, STATUS_ICON_BINARY).unwrap_or_else(|e| {
|
||||||
error!("Error copying the Status Icon to the espanso data directory: {}", e);
|
error!(
|
||||||
|
"Error copying the Status Icon to the espanso data directory: {}",
|
||||||
|
e
|
||||||
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -82,12 +91,9 @@ impl MacContext {
|
||||||
register_icon_click_callback(icon_click_callback);
|
register_icon_click_callback(icon_click_callback);
|
||||||
register_context_menu_click_callback(context_menu_click_callback);
|
register_context_menu_click_callback(context_menu_click_callback);
|
||||||
|
|
||||||
let status_icon_path = CString::new(status_icon_target.to_str().unwrap_or_default()).unwrap_or_default();
|
let status_icon_path =
|
||||||
let show_icon = if config.show_icon {
|
CString::new(status_icon_target.to_str().unwrap_or_default()).unwrap_or_default();
|
||||||
1
|
let show_icon = if config.show_icon { 1 } else { 0 };
|
||||||
}else{
|
|
||||||
0
|
|
||||||
};
|
|
||||||
|
|
||||||
initialize(context_ptr, status_icon_path.as_ptr(), show_icon);
|
initialize(context_ptr, status_icon_path.as_ptr(), show_icon);
|
||||||
}
|
}
|
||||||
|
@ -155,8 +161,13 @@ impl super::Context for MacContext {
|
||||||
|
|
||||||
// Native bridge code
|
// Native bridge code
|
||||||
|
|
||||||
extern fn keypress_callback(_self: *mut c_void, raw_buffer: *const u8, len: i32,
|
extern "C" fn keypress_callback(
|
||||||
event_type: i32, key_code: i32) {
|
_self: *mut c_void,
|
||||||
|
raw_buffer: *const u8,
|
||||||
|
len: i32,
|
||||||
|
event_type: i32,
|
||||||
|
key_code: i32,
|
||||||
|
) {
|
||||||
unsafe {
|
unsafe {
|
||||||
let _self = _self as *mut MacContext;
|
let _self = _self as *mut MacContext;
|
||||||
|
|
||||||
|
@ -168,7 +179,8 @@ extern fn keypress_callback(_self: *mut c_void, raw_buffer: *const u8, len: i32,
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if event_type == 0 { // Char event
|
if event_type == 0 {
|
||||||
|
// Char event
|
||||||
// Convert the received buffer to a string
|
// Convert the received buffer to a string
|
||||||
let c_str = CStr::from_ptr(raw_buffer as (*const c_char));
|
let c_str = CStr::from_ptr(raw_buffer as (*const c_char));
|
||||||
let char_str = c_str.to_str();
|
let char_str = c_str.to_str();
|
||||||
|
@ -178,12 +190,13 @@ extern fn keypress_callback(_self: *mut c_void, raw_buffer: *const u8, len: i32,
|
||||||
Ok(char_str) => {
|
Ok(char_str) => {
|
||||||
let event = Event::Key(KeyEvent::Char(char_str.to_owned()));
|
let event = Event::Key(KeyEvent::Char(char_str.to_owned()));
|
||||||
(*_self).send_channel.send(event).unwrap();
|
(*_self).send_channel.send(event).unwrap();
|
||||||
},
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!("Unable to receive char: {}",e);
|
error!("Unable to receive char: {}", e);
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
}else if event_type == 1 { // Modifier event
|
} else if event_type == 1 {
|
||||||
|
// Modifier event
|
||||||
let modifier: Option<KeyModifier> = match key_code {
|
let modifier: Option<KeyModifier> = match key_code {
|
||||||
0x37 => Some(LEFT_META),
|
0x37 => Some(LEFT_META),
|
||||||
0x36 => Some(RIGHT_META),
|
0x36 => Some(RIGHT_META),
|
||||||
|
@ -201,18 +214,20 @@ extern fn keypress_callback(_self: *mut c_void, raw_buffer: *const u8, len: i32,
|
||||||
if let Some(modifier) = modifier {
|
if let Some(modifier) = modifier {
|
||||||
let event = Event::Key(KeyEvent::Modifier(modifier));
|
let event = Event::Key(KeyEvent::Modifier(modifier));
|
||||||
(*_self).send_channel.send(event).unwrap();
|
(*_self).send_channel.send(event).unwrap();
|
||||||
}else{ // Not one of the default modifiers, send an "other" event
|
} else {
|
||||||
|
// Not one of the default modifiers, send an "other" event
|
||||||
let event = Event::Key(KeyEvent::Other);
|
let event = Event::Key(KeyEvent::Other);
|
||||||
(*_self).send_channel.send(event).unwrap();
|
(*_self).send_channel.send(event).unwrap();
|
||||||
}
|
}
|
||||||
}else{ // Other type of event
|
} else {
|
||||||
|
// Other type of event
|
||||||
let event = Event::Key(KeyEvent::Other);
|
let event = Event::Key(KeyEvent::Other);
|
||||||
(*_self).send_channel.send(event).unwrap();
|
(*_self).send_channel.send(event).unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extern fn icon_click_callback(_self: *mut c_void) {
|
extern "C" fn icon_click_callback(_self: *mut c_void) {
|
||||||
unsafe {
|
unsafe {
|
||||||
let _self = _self as *mut MacContext;
|
let _self = _self as *mut MacContext;
|
||||||
|
|
||||||
|
@ -221,7 +236,7 @@ extern fn icon_click_callback(_self: *mut c_void) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extern fn context_menu_click_callback(_self: *mut c_void, id: i32) {
|
extern "C" fn context_menu_click_callback(_self: *mut c_void, id: i32) {
|
||||||
unsafe {
|
unsafe {
|
||||||
let _self = _self as *mut MacContext;
|
let _self = _self as *mut MacContext;
|
||||||
|
|
||||||
|
|
|
@ -26,13 +26,13 @@ mod linux;
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
pub(crate) mod macos;
|
pub(crate) mod macos;
|
||||||
|
|
||||||
use std::sync::mpsc::Sender;
|
|
||||||
use crate::event::Event;
|
|
||||||
use std::path::PathBuf;
|
|
||||||
use std::fs::create_dir_all;
|
|
||||||
use std::sync::{Once, Arc};
|
|
||||||
use std::sync::atomic::AtomicBool;
|
|
||||||
use crate::config::Configs;
|
use crate::config::Configs;
|
||||||
|
use crate::event::Event;
|
||||||
|
use std::fs::create_dir_all;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use std::sync::atomic::AtomicBool;
|
||||||
|
use std::sync::mpsc::Sender;
|
||||||
|
use std::sync::{Arc, Once};
|
||||||
|
|
||||||
pub trait Context {
|
pub trait Context {
|
||||||
fn eventloop(&self);
|
fn eventloop(&self);
|
||||||
|
@ -40,25 +40,37 @@ pub trait Context {
|
||||||
|
|
||||||
// MAC IMPLEMENTATION
|
// MAC IMPLEMENTATION
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
pub fn new(config: Configs, send_channel: Sender<Event>, is_injecting: Arc<AtomicBool>) -> Box<dyn Context> {
|
pub fn new(
|
||||||
|
config: Configs,
|
||||||
|
send_channel: Sender<Event>,
|
||||||
|
is_injecting: Arc<AtomicBool>,
|
||||||
|
) -> Box<dyn Context> {
|
||||||
macos::MacContext::new(config, send_channel, is_injecting)
|
macos::MacContext::new(config, send_channel, is_injecting)
|
||||||
}
|
}
|
||||||
|
|
||||||
// LINUX IMPLEMENTATION
|
// LINUX IMPLEMENTATION
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
pub fn new(config: Configs, send_channel: Sender<Event>, is_injecting: Arc<AtomicBool>) -> Box<dyn Context> {
|
pub fn new(
|
||||||
|
config: Configs,
|
||||||
|
send_channel: Sender<Event>,
|
||||||
|
is_injecting: Arc<AtomicBool>,
|
||||||
|
) -> Box<dyn Context> {
|
||||||
linux::LinuxContext::new(config, send_channel, is_injecting)
|
linux::LinuxContext::new(config, send_channel, is_injecting)
|
||||||
}
|
}
|
||||||
|
|
||||||
// WINDOWS IMPLEMENTATION
|
// WINDOWS IMPLEMENTATION
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
pub fn new(config: Configs, send_channel: Sender<Event>, is_injecting: Arc<AtomicBool>) -> Box<dyn Context> {
|
pub fn new(
|
||||||
|
config: Configs,
|
||||||
|
send_channel: Sender<Event>,
|
||||||
|
is_injecting: Arc<AtomicBool>,
|
||||||
|
) -> Box<dyn Context> {
|
||||||
windows::WindowsContext::new(config, send_channel, is_injecting)
|
windows::WindowsContext::new(config, send_channel, is_injecting)
|
||||||
}
|
}
|
||||||
|
|
||||||
// espanso directories
|
// espanso directories
|
||||||
|
|
||||||
static WARING_INIT : Once = Once::new();
|
static WARING_INIT: Once = Once::new();
|
||||||
|
|
||||||
pub fn get_data_dir() -> PathBuf {
|
pub fn get_data_dir() -> PathBuf {
|
||||||
let data_dir = dirs::data_local_dir().expect("Can't obtain data_local_dir(), terminating.");
|
let data_dir = dirs::data_local_dir().expect("Can't obtain data_local_dir(), terminating.");
|
||||||
|
@ -75,7 +87,10 @@ pub fn get_config_dir() -> PathBuf {
|
||||||
if let Some(parent) = exe_dir {
|
if let Some(parent) = exe_dir {
|
||||||
let config_dir = parent.join(".espanso");
|
let config_dir = parent.join(".espanso");
|
||||||
if config_dir.exists() {
|
if config_dir.exists() {
|
||||||
println!("PORTABLE MODE, using config folder: '{}'", config_dir.to_string_lossy());
|
println!(
|
||||||
|
"PORTABLE MODE, using config folder: '{}'",
|
||||||
|
config_dir.to_string_lossy()
|
||||||
|
);
|
||||||
return config_dir;
|
return config_dir;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -110,7 +125,7 @@ pub fn get_config_dir() -> PathBuf {
|
||||||
espanso_dir
|
espanso_dir
|
||||||
}
|
}
|
||||||
|
|
||||||
const PACKAGES_FOLDER_NAME : &str = "packages";
|
const PACKAGES_FOLDER_NAME: &str = "packages";
|
||||||
|
|
||||||
pub fn get_package_dir() -> PathBuf {
|
pub fn get_package_dir() -> PathBuf {
|
||||||
// Deprecated $HOME/.espanso/packages directory compatibility check
|
// Deprecated $HOME/.espanso/packages directory compatibility check
|
||||||
|
|
|
@ -17,21 +17,21 @@
|
||||||
* along with espanso. If not, see <https://www.gnu.org/licenses/>.
|
* along with espanso. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
use std::sync::mpsc::Sender;
|
|
||||||
use crate::bridge::windows::*;
|
use crate::bridge::windows::*;
|
||||||
use crate::event::{Event, KeyEvent, KeyModifier, ActionType};
|
|
||||||
use crate::event::KeyModifier::*;
|
|
||||||
use std::ffi::c_void;
|
|
||||||
use std::{fs};
|
|
||||||
use widestring::{U16CString, U16CStr};
|
|
||||||
use log::{info, error, debug};
|
|
||||||
use std::sync::atomic::AtomicBool;
|
|
||||||
use std::sync::Arc;
|
|
||||||
use std::sync::atomic::Ordering::Acquire;
|
|
||||||
use crate::config::Configs;
|
use crate::config::Configs;
|
||||||
|
use crate::event::KeyModifier::*;
|
||||||
|
use crate::event::{ActionType, Event, KeyEvent, KeyModifier};
|
||||||
|
use log::{debug, error, info};
|
||||||
|
use std::ffi::c_void;
|
||||||
|
use std::fs;
|
||||||
|
use std::sync::atomic::AtomicBool;
|
||||||
|
use std::sync::atomic::Ordering::Acquire;
|
||||||
|
use std::sync::mpsc::Sender;
|
||||||
|
use std::sync::Arc;
|
||||||
|
use widestring::{U16CStr, U16CString};
|
||||||
|
|
||||||
const BMP_BINARY : &[u8] = include_bytes!("../res/win/espanso.bmp");
|
const BMP_BINARY: &[u8] = include_bytes!("../res/win/espanso.bmp");
|
||||||
const ICO_BINARY : &[u8] = include_bytes!("../res/win/espanso.ico");
|
const ICO_BINARY: &[u8] = include_bytes!("../res/win/espanso.ico");
|
||||||
|
|
||||||
pub struct WindowsContext {
|
pub struct WindowsContext {
|
||||||
send_channel: Sender<Event>,
|
send_channel: Sender<Event>,
|
||||||
|
@ -39,31 +39,42 @@ pub struct WindowsContext {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WindowsContext {
|
impl WindowsContext {
|
||||||
pub fn new(config: Configs, send_channel: Sender<Event>, is_injecting: Arc<AtomicBool>) -> Box<WindowsContext> {
|
pub fn new(
|
||||||
|
config: Configs,
|
||||||
|
send_channel: Sender<Event>,
|
||||||
|
is_injecting: Arc<AtomicBool>,
|
||||||
|
) -> Box<WindowsContext> {
|
||||||
// Initialize image resources
|
// Initialize image resources
|
||||||
|
|
||||||
let espanso_dir = super::get_data_dir();
|
let espanso_dir = super::get_data_dir();
|
||||||
|
|
||||||
info!("Initializing Espanso resources in {}", espanso_dir.as_path().display());
|
info!(
|
||||||
|
"Initializing Espanso resources in {}",
|
||||||
|
espanso_dir.as_path().display()
|
||||||
|
);
|
||||||
|
|
||||||
let espanso_bmp_image = espanso_dir.join("espansoicon.bmp");
|
let espanso_bmp_image = espanso_dir.join("espansoicon.bmp");
|
||||||
if espanso_bmp_image.exists() {
|
if espanso_bmp_image.exists() {
|
||||||
info!("BMP already initialized, skipping.");
|
info!("BMP already initialized, skipping.");
|
||||||
}else {
|
} else {
|
||||||
fs::write(&espanso_bmp_image, BMP_BINARY)
|
fs::write(&espanso_bmp_image, BMP_BINARY).expect("Unable to write windows bmp file");
|
||||||
.expect("Unable to write windows bmp file");
|
|
||||||
|
|
||||||
info!("Extracted bmp icon to: {}", espanso_bmp_image.to_str().unwrap_or("error"));
|
info!(
|
||||||
|
"Extracted bmp icon to: {}",
|
||||||
|
espanso_bmp_image.to_str().unwrap_or("error")
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let espanso_ico_image = espanso_dir.join("espanso.ico");
|
let espanso_ico_image = espanso_dir.join("espanso.ico");
|
||||||
if espanso_ico_image.exists() {
|
if espanso_ico_image.exists() {
|
||||||
info!("ICO already initialized, skipping.");
|
info!("ICO already initialized, skipping.");
|
||||||
}else {
|
} else {
|
||||||
fs::write(&espanso_ico_image, ICO_BINARY)
|
fs::write(&espanso_ico_image, ICO_BINARY).expect("Unable to write windows ico file");
|
||||||
.expect("Unable to write windows ico file");
|
|
||||||
|
|
||||||
info!("Extracted 'ico' icon to: {}", espanso_ico_image.to_str().unwrap_or("error"));
|
info!(
|
||||||
|
"Extracted 'ico' icon to: {}",
|
||||||
|
espanso_ico_image.to_str().unwrap_or("error")
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let bmp_icon = espanso_bmp_image.to_str().unwrap_or_default();
|
let bmp_icon = espanso_bmp_image.to_str().unwrap_or_default();
|
||||||
|
@ -71,7 +82,7 @@ impl WindowsContext {
|
||||||
|
|
||||||
let send_channel = send_channel;
|
let send_channel = send_channel;
|
||||||
|
|
||||||
let context = Box::new(WindowsContext{
|
let context = Box::new(WindowsContext {
|
||||||
send_channel,
|
send_channel,
|
||||||
is_injecting,
|
is_injecting,
|
||||||
});
|
});
|
||||||
|
@ -87,14 +98,15 @@ impl WindowsContext {
|
||||||
let ico_file_c = U16CString::from_str(ico_icon).unwrap();
|
let ico_file_c = U16CString::from_str(ico_icon).unwrap();
|
||||||
let bmp_file_c = U16CString::from_str(bmp_icon).unwrap();
|
let bmp_file_c = U16CString::from_str(bmp_icon).unwrap();
|
||||||
|
|
||||||
let show_icon = if config.show_icon {
|
let show_icon = if config.show_icon { 1 } else { 0 };
|
||||||
1
|
|
||||||
}else{
|
|
||||||
0
|
|
||||||
};
|
|
||||||
|
|
||||||
// Initialize the windows
|
// Initialize the windows
|
||||||
let res = initialize(context_ptr, ico_file_c.as_ptr(), bmp_file_c.as_ptr(), show_icon);
|
let res = initialize(
|
||||||
|
context_ptr,
|
||||||
|
ico_file_c.as_ptr(),
|
||||||
|
bmp_file_c.as_ptr(),
|
||||||
|
show_icon,
|
||||||
|
);
|
||||||
if res != 1 {
|
if res != 1 {
|
||||||
panic!("Can't initialize Windows context")
|
panic!("Can't initialize Windows context")
|
||||||
}
|
}
|
||||||
|
@ -114,8 +126,15 @@ impl super::Context for WindowsContext {
|
||||||
|
|
||||||
// Native bridge code
|
// Native bridge code
|
||||||
|
|
||||||
extern fn keypress_callback(_self: *mut c_void, raw_buffer: *const u16, len: i32,
|
extern "C" fn keypress_callback(
|
||||||
event_type: i32, key_code: i32, variant: i32, is_key_down: i32) {
|
_self: *mut c_void,
|
||||||
|
raw_buffer: *const u16,
|
||||||
|
len: i32,
|
||||||
|
event_type: i32,
|
||||||
|
key_code: i32,
|
||||||
|
variant: i32,
|
||||||
|
is_key_down: i32,
|
||||||
|
) {
|
||||||
unsafe {
|
unsafe {
|
||||||
let _self = _self as *mut WindowsContext;
|
let _self = _self as *mut WindowsContext;
|
||||||
|
|
||||||
|
@ -127,8 +146,10 @@ extern fn keypress_callback(_self: *mut c_void, raw_buffer: *const u16, len: i32
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if event_type == 0 { // Char event
|
if event_type == 0 {
|
||||||
if is_key_down != 0 { // KEY DOWN EVENT
|
// Char event
|
||||||
|
if is_key_down != 0 {
|
||||||
|
// KEY DOWN EVENT
|
||||||
// Convert the received buffer to a string
|
// Convert the received buffer to a string
|
||||||
let buffer = std::slice::from_raw_parts(raw_buffer, len as usize);
|
let buffer = std::slice::from_raw_parts(raw_buffer, len as usize);
|
||||||
let c_string = U16CStr::from_slice_with_nul(buffer);
|
let c_string = U16CStr::from_slice_with_nul(buffer);
|
||||||
|
@ -141,17 +162,19 @@ extern fn keypress_callback(_self: *mut c_void, raw_buffer: *const u16, len: i32
|
||||||
Ok(string) => {
|
Ok(string) => {
|
||||||
let event = Event::Key(KeyEvent::Char(string));
|
let event = Event::Key(KeyEvent::Char(string));
|
||||||
(*_self).send_channel.send(event).unwrap();
|
(*_self).send_channel.send(event).unwrap();
|
||||||
},
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!("Unable to receive char: {}", e);
|
error!("Unable to receive char: {}", e);
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
error!("unable to decode widechar");
|
error!("unable to decode widechar");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}else if event_type == 1 { // Modifier event
|
} else if event_type == 1 {
|
||||||
if is_key_down == 1 { // Keyup event
|
// Modifier event
|
||||||
|
if is_key_down == 1 {
|
||||||
|
// Keyup event
|
||||||
let modifier: Option<KeyModifier> = match (key_code, variant) {
|
let modifier: Option<KeyModifier> = match (key_code, variant) {
|
||||||
(0x5B, _) => Some(LEFT_META),
|
(0x5B, _) => Some(LEFT_META),
|
||||||
(0x5C, _) => Some(RIGHT_META),
|
(0x5C, _) => Some(RIGHT_META),
|
||||||
|
@ -169,12 +192,13 @@ extern fn keypress_callback(_self: *mut c_void, raw_buffer: *const u16, len: i32
|
||||||
if let Some(modifier) = modifier {
|
if let Some(modifier) = modifier {
|
||||||
let event = Event::Key(KeyEvent::Modifier(modifier));
|
let event = Event::Key(KeyEvent::Modifier(modifier));
|
||||||
(*_self).send_channel.send(event).unwrap();
|
(*_self).send_channel.send(event).unwrap();
|
||||||
}else{ // Not one of the default modifiers, send an "other" event
|
} else {
|
||||||
|
// Not one of the default modifiers, send an "other" event
|
||||||
let event = Event::Key(KeyEvent::Other);
|
let event = Event::Key(KeyEvent::Other);
|
||||||
(*_self).send_channel.send(event).unwrap();
|
(*_self).send_channel.send(event).unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}else{
|
} else {
|
||||||
// Other type of event
|
// Other type of event
|
||||||
let event = Event::Key(KeyEvent::Other);
|
let event = Event::Key(KeyEvent::Other);
|
||||||
(*_self).send_channel.send(event).unwrap();
|
(*_self).send_channel.send(event).unwrap();
|
||||||
|
@ -182,7 +206,7 @@ extern fn keypress_callback(_self: *mut c_void, raw_buffer: *const u16, len: i32
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extern fn icon_click_callback(_self: *mut c_void) {
|
extern "C" fn icon_click_callback(_self: *mut c_void) {
|
||||||
unsafe {
|
unsafe {
|
||||||
let _self = _self as *mut WindowsContext;
|
let _self = _self as *mut WindowsContext;
|
||||||
|
|
||||||
|
@ -191,8 +215,7 @@ extern fn icon_click_callback(_self: *mut c_void) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extern "C" fn context_menu_click_callback(_self: *mut c_void, id: i32) {
|
||||||
extern fn context_menu_click_callback(_self: *mut c_void, id: i32) {
|
|
||||||
unsafe {
|
unsafe {
|
||||||
let _self = _self as *mut WindowsContext;
|
let _self = _self as *mut WindowsContext;
|
||||||
|
|
||||||
|
|
29
src/edit.rs
29
src/edit.rs
|
@ -20,11 +20,17 @@
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
fn default_editor() -> String{ "/bin/nano".to_owned() }
|
fn default_editor() -> String {
|
||||||
|
"/bin/nano".to_owned()
|
||||||
|
}
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
fn default_editor() -> String{ "/usr/bin/nano".to_owned() }
|
fn default_editor() -> String {
|
||||||
|
"/usr/bin/nano".to_owned()
|
||||||
|
}
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
fn default_editor() -> String{ "C:\\Windows\\System32\\notepad.exe".to_owned() }
|
fn default_editor() -> String {
|
||||||
|
"C:\\Windows\\System32\\notepad.exe".to_owned()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn open_editor(file_path: &Path) -> bool {
|
pub fn open_editor(file_path: &Path) -> bool {
|
||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
|
@ -34,20 +40,18 @@ pub fn open_editor(file_path: &Path) -> bool {
|
||||||
let visual_var = std::env::var_os("VISUAL");
|
let visual_var = std::env::var_os("VISUAL");
|
||||||
|
|
||||||
// Prioritize the editors specified by the environment variable, use the default one
|
// Prioritize the editors specified by the environment variable, use the default one
|
||||||
let editor : String = if let Some(editor_var) = editor_var {
|
let editor: String = if let Some(editor_var) = editor_var {
|
||||||
editor_var.to_string_lossy().to_string()
|
editor_var.to_string_lossy().to_string()
|
||||||
}else if let Some(visual_var) = visual_var {
|
} else if let Some(visual_var) = visual_var {
|
||||||
visual_var.to_string_lossy().to_string()
|
visual_var.to_string_lossy().to_string()
|
||||||
}else{
|
} else {
|
||||||
default_editor()
|
default_editor()
|
||||||
};
|
};
|
||||||
|
|
||||||
// Start the editor and wait for its termination
|
// Start the editor and wait for its termination
|
||||||
let status = if cfg!(target_os = "windows") {
|
let status = if cfg!(target_os = "windows") {
|
||||||
Command::new(&editor)
|
Command::new(&editor).arg(file_path).spawn()
|
||||||
.arg(file_path)
|
} else {
|
||||||
.spawn()
|
|
||||||
}else{
|
|
||||||
// On Unix, spawn the editor using the shell so that it can
|
// On Unix, spawn the editor using the shell so that it can
|
||||||
// accept parameters. See issue #245
|
// accept parameters. See issue #245
|
||||||
Command::new("/bin/bash")
|
Command::new("/bin/bash")
|
||||||
|
@ -56,17 +60,16 @@ pub fn open_editor(file_path: &Path) -> bool {
|
||||||
.spawn()
|
.spawn()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
if let Ok(mut child) = status {
|
if let Ok(mut child) = status {
|
||||||
// Wait for the user to edit the configuration
|
// Wait for the user to edit the configuration
|
||||||
let result = child.wait();
|
let result = child.wait();
|
||||||
|
|
||||||
if let Ok(exit_status) = result {
|
if let Ok(exit_status) = result {
|
||||||
exit_status.success()
|
exit_status.success()
|
||||||
}else{
|
} else {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
}else{
|
} else {
|
||||||
println!("Error: could not start editor at: {}", &editor);
|
println!("Error: could not start editor at: {}", &editor);
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
201
src/engine.rs
201
src/engine.rs
|
@ -17,25 +17,31 @@
|
||||||
* along with espanso. If not, see <https://www.gnu.org/licenses/>.
|
* along with espanso. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
use crate::matcher::{Match, MatchReceiver};
|
|
||||||
use crate::keyboard::KeyboardManager;
|
|
||||||
use crate::config::ConfigManager;
|
|
||||||
use crate::config::BackendType;
|
|
||||||
use crate::clipboard::ClipboardManager;
|
use crate::clipboard::ClipboardManager;
|
||||||
use log::{info, warn, debug, error};
|
use crate::config::BackendType;
|
||||||
use crate::ui::{UIManager, MenuItem, MenuItemType};
|
use crate::config::ConfigManager;
|
||||||
use crate::event::{ActionEventReceiver, ActionType, SystemEventReceiver, SystemEvent};
|
use crate::event::{ActionEventReceiver, ActionType, SystemEvent, SystemEventReceiver};
|
||||||
use crate::render::{Renderer, RenderResult};
|
use crate::keyboard::KeyboardManager;
|
||||||
|
use crate::matcher::{Match, MatchReceiver};
|
||||||
|
use crate::protocol::{send_command_or_warn, IPCCommand, Service};
|
||||||
|
use crate::render::{RenderResult, Renderer};
|
||||||
|
use crate::ui::{MenuItem, MenuItemType, UIManager};
|
||||||
|
use log::{debug, error, info, warn};
|
||||||
|
use regex::Regex;
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
use std::process::exit;
|
use std::process::exit;
|
||||||
use regex::{Regex};
|
|
||||||
use std::sync::Arc;
|
|
||||||
use std::sync::atomic::AtomicBool;
|
use std::sync::atomic::AtomicBool;
|
||||||
use std::sync::atomic::Ordering::Release;
|
use std::sync::atomic::Ordering::Release;
|
||||||
use crate::protocol::{Service, IPCCommand, send_command_or_warn};
|
use std::sync::Arc;
|
||||||
|
|
||||||
pub struct Engine<'a, S: KeyboardManager, C: ClipboardManager, M: ConfigManager<'a>,
|
pub struct Engine<
|
||||||
U: UIManager, R: Renderer> {
|
'a,
|
||||||
|
S: KeyboardManager,
|
||||||
|
C: ClipboardManager,
|
||||||
|
M: ConfigManager<'a>,
|
||||||
|
U: UIManager,
|
||||||
|
R: Renderer,
|
||||||
|
> {
|
||||||
keyboard_manager: &'a S,
|
keyboard_manager: &'a S,
|
||||||
clipboard_manager: &'a C,
|
clipboard_manager: &'a C,
|
||||||
config_manager: &'a M,
|
config_manager: &'a M,
|
||||||
|
@ -46,14 +52,27 @@ pub struct Engine<'a, S: KeyboardManager, C: ClipboardManager, M: ConfigManager<
|
||||||
enabled: RefCell<bool>,
|
enabled: RefCell<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl <'a, S: KeyboardManager, C: ClipboardManager, M: ConfigManager<'a>, U: UIManager, R: Renderer>
|
impl<
|
||||||
Engine<'a, S, C, M, U, R> {
|
'a,
|
||||||
pub fn new(keyboard_manager: &'a S, clipboard_manager: &'a C,
|
S: KeyboardManager,
|
||||||
config_manager: &'a M, ui_manager: &'a U,
|
C: ClipboardManager,
|
||||||
renderer: &'a R, is_injecting: Arc<AtomicBool>) -> Engine<'a, S, C, M, U, R> {
|
M: ConfigManager<'a>,
|
||||||
|
U: UIManager,
|
||||||
|
R: Renderer,
|
||||||
|
> Engine<'a, S, C, M, U, R>
|
||||||
|
{
|
||||||
|
pub fn new(
|
||||||
|
keyboard_manager: &'a S,
|
||||||
|
clipboard_manager: &'a C,
|
||||||
|
config_manager: &'a M,
|
||||||
|
ui_manager: &'a U,
|
||||||
|
renderer: &'a R,
|
||||||
|
is_injecting: Arc<AtomicBool>,
|
||||||
|
) -> Engine<'a, S, C, M, U, R> {
|
||||||
let enabled = RefCell::new(true);
|
let enabled = RefCell::new(true);
|
||||||
|
|
||||||
Engine{keyboard_manager,
|
Engine {
|
||||||
|
keyboard_manager,
|
||||||
clipboard_manager,
|
clipboard_manager,
|
||||||
config_manager,
|
config_manager,
|
||||||
ui_manager,
|
ui_manager,
|
||||||
|
@ -67,36 +86,32 @@ impl <'a, S: KeyboardManager, C: ClipboardManager, M: ConfigManager<'a>, U: UIMa
|
||||||
let mut menu = Vec::new();
|
let mut menu = Vec::new();
|
||||||
|
|
||||||
let enabled = self.enabled.borrow();
|
let enabled = self.enabled.borrow();
|
||||||
let toggle_text = if *enabled {
|
let toggle_text = if *enabled { "Disable" } else { "Enable" }.to_owned();
|
||||||
"Disable"
|
menu.push(MenuItem {
|
||||||
}else{
|
|
||||||
"Enable"
|
|
||||||
}.to_owned();
|
|
||||||
menu.push(MenuItem{
|
|
||||||
item_type: MenuItemType::Button,
|
item_type: MenuItemType::Button,
|
||||||
item_name: toggle_text,
|
item_name: toggle_text,
|
||||||
item_id: ActionType::Toggle as i32,
|
item_id: ActionType::Toggle as i32,
|
||||||
});
|
});
|
||||||
|
|
||||||
menu.push(MenuItem{
|
menu.push(MenuItem {
|
||||||
item_type: MenuItemType::Separator,
|
item_type: MenuItemType::Separator,
|
||||||
item_name: "".to_owned(),
|
item_name: "".to_owned(),
|
||||||
item_id: 998,
|
item_id: 998,
|
||||||
});
|
});
|
||||||
|
|
||||||
menu.push(MenuItem{
|
menu.push(MenuItem {
|
||||||
item_type: MenuItemType::Button,
|
item_type: MenuItemType::Button,
|
||||||
item_name: "Reload configs".to_owned(),
|
item_name: "Reload configs".to_owned(),
|
||||||
item_id: ActionType::RestartWorker as i32,
|
item_id: ActionType::RestartWorker as i32,
|
||||||
});
|
});
|
||||||
|
|
||||||
menu.push(MenuItem{
|
menu.push(MenuItem {
|
||||||
item_type: MenuItemType::Separator,
|
item_type: MenuItemType::Separator,
|
||||||
item_name: "".to_owned(),
|
item_name: "".to_owned(),
|
||||||
item_id: 999,
|
item_id: 999,
|
||||||
});
|
});
|
||||||
|
|
||||||
menu.push(MenuItem{
|
menu.push(MenuItem {
|
||||||
item_type: MenuItemType::Button,
|
item_type: MenuItemType::Button,
|
||||||
item_name: "Exit espanso".to_owned(),
|
item_name: "Exit espanso".to_owned(),
|
||||||
item_id: ActionType::Exit as i32,
|
item_id: ActionType::Exit as i32,
|
||||||
|
@ -110,10 +125,10 @@ impl <'a, S: KeyboardManager, C: ClipboardManager, M: ConfigManager<'a>, U: UIMa
|
||||||
// clipboard content in order to restore it later.
|
// clipboard content in order to restore it later.
|
||||||
if self.config_manager.default_config().preserve_clipboard {
|
if self.config_manager.default_config().preserve_clipboard {
|
||||||
match self.clipboard_manager.get_clipboard() {
|
match self.clipboard_manager.get_clipboard() {
|
||||||
Some(clipboard) => {Some(clipboard)},
|
Some(clipboard) => Some(clipboard),
|
||||||
None => {None},
|
None => None,
|
||||||
}
|
}
|
||||||
}else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -123,9 +138,15 @@ lazy_static! {
|
||||||
static ref VAR_REGEX: Regex = Regex::new("\\{\\{\\s*(?P<name>\\w+)\\s*\\}\\}").unwrap();
|
static ref VAR_REGEX: Regex = Regex::new("\\{\\{\\s*(?P<name>\\w+)\\s*\\}\\}").unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
impl <'a, S: KeyboardManager, C: ClipboardManager, M: ConfigManager<'a>, U: UIManager, R: Renderer>
|
impl<
|
||||||
MatchReceiver for Engine<'a, S, C, M, U, R>{
|
'a,
|
||||||
|
S: KeyboardManager,
|
||||||
|
C: ClipboardManager,
|
||||||
|
M: ConfigManager<'a>,
|
||||||
|
U: UIManager,
|
||||||
|
R: Renderer,
|
||||||
|
> MatchReceiver for Engine<'a, S, C, M, U, R>
|
||||||
|
{
|
||||||
fn on_match(&self, m: &Match, trailing_separator: Option<char>, trigger_offset: usize) {
|
fn on_match(&self, m: &Match, trailing_separator: Option<char>, trigger_offset: usize) {
|
||||||
let config = self.config_manager.active_config();
|
let config = self.config_manager.active_config();
|
||||||
|
|
||||||
|
@ -138,23 +159,26 @@ impl <'a, S: KeyboardManager, C: ClipboardManager, M: ConfigManager<'a>, U: UIMa
|
||||||
|
|
||||||
let char_count = if trailing_separator.is_none() {
|
let char_count = if trailing_separator.is_none() {
|
||||||
m.triggers[trigger_offset].chars().count() as i32
|
m.triggers[trigger_offset].chars().count() as i32
|
||||||
}else{
|
} else {
|
||||||
m.triggers[trigger_offset].chars().count() as i32 + 1 // Count also the separator
|
m.triggers[trigger_offset].chars().count() as i32 + 1 // Count also the separator
|
||||||
};
|
};
|
||||||
|
|
||||||
self.keyboard_manager.delete_string(&config, char_count);
|
self.keyboard_manager.delete_string(&config, char_count);
|
||||||
|
|
||||||
let mut previous_clipboard_content : Option<String> = None;
|
let mut previous_clipboard_content: Option<String> = None;
|
||||||
|
|
||||||
let rendered = self.renderer.render_match(m, trigger_offset, config, vec![]);
|
let rendered = self
|
||||||
|
.renderer
|
||||||
|
.render_match(m, trigger_offset, config, vec![]);
|
||||||
|
|
||||||
match rendered {
|
match rendered {
|
||||||
RenderResult::Text(mut target_string) => {
|
RenderResult::Text(mut target_string) => {
|
||||||
// If a trailing separator was counted in the match, add it back to the target string
|
// If a trailing separator was counted in the match, add it back to the target string
|
||||||
if let Some(trailing_separator) = trailing_separator {
|
if let Some(trailing_separator) = trailing_separator {
|
||||||
if trailing_separator == '\r' { // If the trailing separator is a carriage return,
|
if trailing_separator == '\r' {
|
||||||
target_string.push('\n'); // convert it to new line
|
// If the trailing separator is a carriage return,
|
||||||
}else{
|
target_string.push('\n'); // convert it to new line
|
||||||
|
} else {
|
||||||
target_string.push(trailing_separator);
|
target_string.push(trailing_separator);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -177,26 +201,28 @@ impl <'a, S: KeyboardManager, C: ClipboardManager, M: ConfigManager<'a>, U: UIMa
|
||||||
// Subtract also 3, equal to the number of chars of the placeholder "$|$"
|
// Subtract also 3, equal to the number of chars of the placeholder "$|$"
|
||||||
let moves = (total_size - char_index - 3) as i32;
|
let moves = (total_size - char_index - 3) as i32;
|
||||||
Some(moves)
|
Some(moves)
|
||||||
}else{
|
} else {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
let backend = if m.force_clipboard {
|
let backend = if m.force_clipboard {
|
||||||
&BackendType::Clipboard
|
&BackendType::Clipboard
|
||||||
}else if config.backend == BackendType::Auto {
|
} else if config.backend == BackendType::Auto {
|
||||||
if cfg!(target_os = "linux") {
|
if cfg!(target_os = "linux") {
|
||||||
let all_ascii = target_string.chars().all(|c| c.is_ascii());
|
let all_ascii = target_string.chars().all(|c| c.is_ascii());
|
||||||
if all_ascii {
|
if all_ascii {
|
||||||
debug!("All elements of the replacement are ascii, using Inject backend");
|
debug!(
|
||||||
|
"All elements of the replacement are ascii, using Inject backend"
|
||||||
|
);
|
||||||
&BackendType::Inject
|
&BackendType::Inject
|
||||||
}else{
|
} else {
|
||||||
debug!("There are non-ascii characters, using Clipboard backend");
|
debug!("There are non-ascii characters, using Clipboard backend");
|
||||||
&BackendType::Clipboard
|
&BackendType::Clipboard
|
||||||
}
|
}
|
||||||
}else{
|
} else {
|
||||||
&BackendType::Inject
|
&BackendType::Inject
|
||||||
}
|
}
|
||||||
}else{
|
} else {
|
||||||
&config.backend
|
&config.backend
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -212,15 +238,16 @@ impl <'a, S: KeyboardManager, C: ClipboardManager, M: ConfigManager<'a>, U: UIMa
|
||||||
|
|
||||||
self.keyboard_manager.send_string(&config, split);
|
self.keyboard_manager.send_string(&config, split);
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
BackendType::Clipboard => {
|
BackendType::Clipboard => {
|
||||||
// If the preserve_clipboard option is enabled, save the current
|
// If the preserve_clipboard option is enabled, save the current
|
||||||
// clipboard content to restore it later.
|
// clipboard content to restore it later.
|
||||||
previous_clipboard_content = self.return_content_if_preserve_clipboard_is_enabled();
|
previous_clipboard_content =
|
||||||
|
self.return_content_if_preserve_clipboard_is_enabled();
|
||||||
|
|
||||||
self.clipboard_manager.set_clipboard(&target_string);
|
self.clipboard_manager.set_clipboard(&target_string);
|
||||||
self.keyboard_manager.trigger_paste(&config);
|
self.keyboard_manager.trigger_paste(&config);
|
||||||
},
|
}
|
||||||
_ => {
|
_ => {
|
||||||
error!("Unsupported backend type evaluation.");
|
error!("Unsupported backend type evaluation.");
|
||||||
return;
|
return;
|
||||||
|
@ -231,7 +258,7 @@ impl <'a, S: KeyboardManager, C: ClipboardManager, M: ConfigManager<'a>, U: UIMa
|
||||||
// Simulate left arrow key presses to bring the cursor into the desired position
|
// Simulate left arrow key presses to bring the cursor into the desired position
|
||||||
self.keyboard_manager.move_cursor_left(&config, moves);
|
self.keyboard_manager.move_cursor_left(&config, moves);
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
RenderResult::Image(image_path) => {
|
RenderResult::Image(image_path) => {
|
||||||
// If the preserve_clipboard option is enabled, save the current
|
// If the preserve_clipboard option is enabled, save the current
|
||||||
// clipboard content to restore it later.
|
// clipboard content to restore it later.
|
||||||
|
@ -239,19 +266,22 @@ impl <'a, S: KeyboardManager, C: ClipboardManager, M: ConfigManager<'a>, U: UIMa
|
||||||
|
|
||||||
self.clipboard_manager.set_clipboard_image(&image_path);
|
self.clipboard_manager.set_clipboard_image(&image_path);
|
||||||
self.keyboard_manager.trigger_paste(&config);
|
self.keyboard_manager.trigger_paste(&config);
|
||||||
},
|
}
|
||||||
RenderResult::Error => {
|
RenderResult::Error => {
|
||||||
error!("Could not render match: {}", m.triggers[trigger_offset]);
|
error!("Could not render match: {}", m.triggers[trigger_offset]);
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Restore previous clipboard content
|
// Restore previous clipboard content
|
||||||
if let Some(previous_clipboard_content) = previous_clipboard_content {
|
if let Some(previous_clipboard_content) = previous_clipboard_content {
|
||||||
// Sometimes an expansion gets overwritten before pasting by the previous content
|
// Sometimes an expansion gets overwritten before pasting by the previous content
|
||||||
// A delay is needed to mitigate the problem
|
// A delay is needed to mitigate the problem
|
||||||
std::thread::sleep(std::time::Duration::from_millis(config.restore_clipboard_delay as u64));
|
std::thread::sleep(std::time::Duration::from_millis(
|
||||||
|
config.restore_clipboard_delay as u64,
|
||||||
|
));
|
||||||
|
|
||||||
self.clipboard_manager.set_clipboard(&previous_clipboard_content);
|
self.clipboard_manager
|
||||||
|
.set_clipboard(&previous_clipboard_content);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Re-allow espanso to interpret actions
|
// Re-allow espanso to interpret actions
|
||||||
|
@ -261,7 +291,7 @@ impl <'a, S: KeyboardManager, C: ClipboardManager, M: ConfigManager<'a>, U: UIMa
|
||||||
fn on_enable_update(&self, status: bool) {
|
fn on_enable_update(&self, status: bool) {
|
||||||
let message = if status {
|
let message = if status {
|
||||||
"espanso enabled"
|
"espanso enabled"
|
||||||
}else{
|
} else {
|
||||||
"espanso disabled"
|
"espanso disabled"
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -293,13 +323,13 @@ impl <'a, S: KeyboardManager, C: ClipboardManager, M: ConfigManager<'a>, U: UIMa
|
||||||
let previous_clipboard = self.clipboard_manager.get_clipboard();
|
let previous_clipboard = self.clipboard_manager.get_clipboard();
|
||||||
|
|
||||||
// Sleep for a while, giving time to effectively copy the text
|
// Sleep for a while, giving time to effectively copy the text
|
||||||
std::thread::sleep(std::time::Duration::from_millis(100)); // TODO: avoid hardcoding
|
std::thread::sleep(std::time::Duration::from_millis(100)); // TODO: avoid hardcoding
|
||||||
|
|
||||||
// Trigger a copy shortcut to transfer the content of the selection to the clipboard
|
// Trigger a copy shortcut to transfer the content of the selection to the clipboard
|
||||||
self.keyboard_manager.trigger_copy(&config);
|
self.keyboard_manager.trigger_copy(&config);
|
||||||
|
|
||||||
// Sleep for a while, giving time to effectively copy the text
|
// Sleep for a while, giving time to effectively copy the text
|
||||||
std::thread::sleep(std::time::Duration::from_millis(100)); // TODO: avoid hardcoding
|
std::thread::sleep(std::time::Duration::from_millis(100)); // TODO: avoid hardcoding
|
||||||
|
|
||||||
// Then get the text from the clipboard and render the match output
|
// Then get the text from the clipboard and render the match output
|
||||||
let clipboard = self.clipboard_manager.get_clipboard();
|
let clipboard = self.clipboard_manager.get_clipboard();
|
||||||
|
@ -308,7 +338,7 @@ impl <'a, S: KeyboardManager, C: ClipboardManager, M: ConfigManager<'a>, U: UIMa
|
||||||
// Don't expand empty clipboards, as usually they are the result of an empty passive selection
|
// Don't expand empty clipboards, as usually they are the result of an empty passive selection
|
||||||
if clipboard.trim().is_empty() {
|
if clipboard.trim().is_empty() {
|
||||||
info!("Avoiding passive expansion, as the user didn't select anything");
|
info!("Avoiding passive expansion, as the user didn't select anything");
|
||||||
}else{
|
} else {
|
||||||
if let Some(previous_content) = previous_clipboard {
|
if let Some(previous_content) = previous_clipboard {
|
||||||
// Because of issue #213, we need to make sure the user selected something.
|
// Because of issue #213, we need to make sure the user selected something.
|
||||||
if clipboard == previous_content {
|
if clipboard == previous_content {
|
||||||
|
@ -316,8 +346,7 @@ impl <'a, S: KeyboardManager, C: ClipboardManager, M: ConfigManager<'a>, U: UIMa
|
||||||
} else {
|
} else {
|
||||||
info!("Passive mode activated");
|
info!("Passive mode activated");
|
||||||
|
|
||||||
let rendered = self.renderer.render_passive(&clipboard,
|
let rendered = self.renderer.render_passive(&clipboard, &config);
|
||||||
&config);
|
|
||||||
|
|
||||||
match rendered {
|
match rendered {
|
||||||
RenderResult::Text(payload) => {
|
RenderResult::Text(payload) => {
|
||||||
|
@ -326,10 +355,8 @@ impl <'a, S: KeyboardManager, C: ClipboardManager, M: ConfigManager<'a>, U: UIMa
|
||||||
|
|
||||||
std::thread::sleep(std::time::Duration::from_millis(100)); // TODO: avoid hardcoding
|
std::thread::sleep(std::time::Duration::from_millis(100)); // TODO: avoid hardcoding
|
||||||
self.keyboard_manager.trigger_paste(&config);
|
self.keyboard_manager.trigger_paste(&config);
|
||||||
},
|
}
|
||||||
_ => {
|
_ => warn!("Cannot expand passive match"),
|
||||||
warn!("Cannot expand passive match")
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -341,34 +368,50 @@ impl <'a, S: KeyboardManager, C: ClipboardManager, M: ConfigManager<'a>, U: UIMa
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl <'a, S: KeyboardManager, C: ClipboardManager,
|
impl<
|
||||||
M: ConfigManager<'a>, U: UIManager, R: Renderer> ActionEventReceiver for Engine<'a, S, C, M, U, R>{
|
'a,
|
||||||
|
S: KeyboardManager,
|
||||||
|
C: ClipboardManager,
|
||||||
|
M: ConfigManager<'a>,
|
||||||
|
U: UIManager,
|
||||||
|
R: Renderer,
|
||||||
|
> ActionEventReceiver for Engine<'a, S, C, M, U, R>
|
||||||
|
{
|
||||||
fn on_action_event(&self, e: ActionType) {
|
fn on_action_event(&self, e: ActionType) {
|
||||||
let config = self.config_manager.default_config();
|
let config = self.config_manager.default_config();
|
||||||
match e {
|
match e {
|
||||||
ActionType::IconClick => {
|
ActionType::IconClick => {
|
||||||
self.ui_manager.show_menu(self.build_menu());
|
self.ui_manager.show_menu(self.build_menu());
|
||||||
},
|
}
|
||||||
ActionType::ExitWorker => {
|
ActionType::ExitWorker => {
|
||||||
info!("terminating worker process");
|
info!("terminating worker process");
|
||||||
self.ui_manager.cleanup();
|
self.ui_manager.cleanup();
|
||||||
exit(0);
|
exit(0);
|
||||||
},
|
}
|
||||||
ActionType::Exit => {
|
ActionType::Exit => {
|
||||||
send_command_or_warn(Service::Daemon, config.clone(), IPCCommand::exit());
|
send_command_or_warn(Service::Daemon, config.clone(), IPCCommand::exit());
|
||||||
},
|
}
|
||||||
ActionType::RestartWorker => {
|
ActionType::RestartWorker => {
|
||||||
send_command_or_warn(Service::Daemon, config.clone(), IPCCommand::restart_worker());
|
send_command_or_warn(
|
||||||
},
|
Service::Daemon,
|
||||||
|
config.clone(),
|
||||||
|
IPCCommand::restart_worker(),
|
||||||
|
);
|
||||||
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl <'a, S: KeyboardManager, C: ClipboardManager,
|
impl<
|
||||||
M: ConfigManager<'a>, U: UIManager, R: Renderer> SystemEventReceiver for Engine<'a, S, C, M, U, R>{
|
'a,
|
||||||
|
S: KeyboardManager,
|
||||||
|
C: ClipboardManager,
|
||||||
|
M: ConfigManager<'a>,
|
||||||
|
U: UIManager,
|
||||||
|
R: Renderer,
|
||||||
|
> SystemEventReceiver for Engine<'a, S, C, M, U, R>
|
||||||
|
{
|
||||||
fn on_system_event(&self, e: SystemEvent) {
|
fn on_system_event(&self, e: SystemEvent) {
|
||||||
match e {
|
match e {
|
||||||
// MacOS specific
|
// MacOS specific
|
||||||
|
@ -379,16 +422,16 @@ impl <'a, S: KeyboardManager, C: ClipboardManager,
|
||||||
if config.secure_input_notification && config.show_notifications {
|
if config.secure_input_notification && config.show_notifications {
|
||||||
self.ui_manager.notify_delay(&format!("{} has activated SecureInput. Espanso won't work until you disable it.", app_name), 5000);
|
self.ui_manager.notify_delay(&format!("{} has activated SecureInput. Espanso won't work until you disable it.", app_name), 5000);
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
SystemEvent::SecureInputDisabled => {
|
SystemEvent::SecureInputDisabled => {
|
||||||
info!("SecureInput has been disabled.");
|
info!("SecureInput has been disabled.");
|
||||||
},
|
}
|
||||||
SystemEvent::NotifyRequest(message) => {
|
SystemEvent::NotifyRequest(message) => {
|
||||||
let config = self.config_manager.default_config();
|
let config = self.config_manager.default_config();
|
||||||
if config.show_notifications {
|
if config.show_notifications {
|
||||||
self.ui_manager.notify(&message);
|
self.ui_manager.notify(&message);
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -17,7 +17,7 @@
|
||||||
* along with espanso. If not, see <https://www.gnu.org/licenses/>.
|
* along with espanso. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
use crate::event::{KeyEventReceiver, ActionEventReceiver, Event, SystemEventReceiver};
|
use crate::event::{ActionEventReceiver, Event, KeyEventReceiver, SystemEventReceiver};
|
||||||
use std::sync::mpsc::Receiver;
|
use std::sync::mpsc::Receiver;
|
||||||
|
|
||||||
pub trait EventManager {
|
pub trait EventManager {
|
||||||
|
@ -32,33 +32,40 @@ pub struct DefaultEventManager<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> DefaultEventManager<'a> {
|
impl<'a> DefaultEventManager<'a> {
|
||||||
pub fn new(receive_channel: Receiver<Event>, key_receivers: Vec<&'a dyn KeyEventReceiver>,
|
pub fn new(
|
||||||
action_receivers: Vec<&'a dyn ActionEventReceiver>,
|
receive_channel: Receiver<Event>,
|
||||||
system_receivers: Vec<&'a dyn SystemEventReceiver>) -> DefaultEventManager<'a> {
|
key_receivers: Vec<&'a dyn KeyEventReceiver>,
|
||||||
|
action_receivers: Vec<&'a dyn ActionEventReceiver>,
|
||||||
|
system_receivers: Vec<&'a dyn SystemEventReceiver>,
|
||||||
|
) -> DefaultEventManager<'a> {
|
||||||
DefaultEventManager {
|
DefaultEventManager {
|
||||||
receive_channel,
|
receive_channel,
|
||||||
key_receivers,
|
key_receivers,
|
||||||
action_receivers,
|
action_receivers,
|
||||||
system_receivers
|
system_receivers,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl <'a> EventManager for DefaultEventManager<'a> {
|
impl<'a> EventManager for DefaultEventManager<'a> {
|
||||||
fn eventloop(&self) {
|
fn eventloop(&self) {
|
||||||
loop {
|
loop {
|
||||||
match self.receive_channel.recv() {
|
match self.receive_channel.recv() {
|
||||||
Ok(event) => {
|
Ok(event) => match event {
|
||||||
match event {
|
Event::Key(key_event) => {
|
||||||
Event::Key(key_event) => {
|
self.key_receivers
|
||||||
self.key_receivers.iter().for_each(move |&receiver| receiver.on_key_event(key_event.clone()));
|
.iter()
|
||||||
},
|
.for_each(move |&receiver| receiver.on_key_event(key_event.clone()));
|
||||||
Event::Action(action_event) => {
|
}
|
||||||
self.action_receivers.iter().for_each(|&receiver| receiver.on_action_event(action_event.clone()));
|
Event::Action(action_event) => {
|
||||||
}
|
self.action_receivers
|
||||||
Event::System(system_event) => {
|
.iter()
|
||||||
self.system_receivers.iter().for_each(move |&receiver| receiver.on_system_event(system_event.clone()));
|
.for_each(|&receiver| receiver.on_action_event(action_event.clone()));
|
||||||
}
|
}
|
||||||
|
Event::System(system_event) => {
|
||||||
|
self.system_receivers.iter().for_each(move |&receiver| {
|
||||||
|
receiver.on_system_event(system_event.clone())
|
||||||
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Err(e) => panic!("Broken event channel {}", e),
|
Err(e) => panic!("Broken event channel {}", e),
|
||||||
|
|
|
@ -19,7 +19,7 @@
|
||||||
|
|
||||||
pub(crate) mod manager;
|
pub(crate) mod manager;
|
||||||
|
|
||||||
use serde::{Serialize, Deserialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
|
@ -60,7 +60,7 @@ impl From<i32> for ActionType {
|
||||||
pub enum KeyEvent {
|
pub enum KeyEvent {
|
||||||
Char(String),
|
Char(String),
|
||||||
Modifier(KeyModifier),
|
Modifier(KeyModifier),
|
||||||
Other
|
Other,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(non_camel_case_types)]
|
#[allow(non_camel_case_types)]
|
||||||
|
@ -99,44 +99,24 @@ impl KeyModifier {
|
||||||
match config {
|
match config {
|
||||||
KeyModifier::CTRL => {
|
KeyModifier::CTRL => {
|
||||||
current == &LEFT_CTRL || current == &RIGHT_CTRL || current == &CTRL
|
current == &LEFT_CTRL || current == &RIGHT_CTRL || current == &CTRL
|
||||||
},
|
}
|
||||||
KeyModifier::SHIFT => {
|
KeyModifier::SHIFT => {
|
||||||
current == &LEFT_SHIFT || current == &RIGHT_SHIFT || current == &SHIFT
|
current == &LEFT_SHIFT || current == &RIGHT_SHIFT || current == &SHIFT
|
||||||
},
|
}
|
||||||
KeyModifier::ALT => {
|
KeyModifier::ALT => current == &LEFT_ALT || current == &RIGHT_ALT || current == &ALT,
|
||||||
current == &LEFT_ALT || current == &RIGHT_ALT || current == &ALT
|
|
||||||
},
|
|
||||||
KeyModifier::META => {
|
KeyModifier::META => {
|
||||||
current == &LEFT_META || current == &RIGHT_META || current == &META
|
current == &LEFT_META || current == &RIGHT_META || current == &META
|
||||||
},
|
}
|
||||||
KeyModifier::BACKSPACE => {
|
KeyModifier::BACKSPACE => current == &BACKSPACE,
|
||||||
current == &BACKSPACE
|
KeyModifier::LEFT_CTRL => current == &LEFT_CTRL,
|
||||||
},
|
KeyModifier::RIGHT_CTRL => current == &RIGHT_CTRL,
|
||||||
KeyModifier::LEFT_CTRL => {
|
KeyModifier::LEFT_ALT => current == &LEFT_ALT,
|
||||||
current == &LEFT_CTRL
|
KeyModifier::RIGHT_ALT => current == &RIGHT_ALT,
|
||||||
},
|
KeyModifier::LEFT_META => current == &LEFT_META,
|
||||||
KeyModifier::RIGHT_CTRL => {
|
KeyModifier::RIGHT_META => current == &RIGHT_META,
|
||||||
current == &RIGHT_CTRL
|
KeyModifier::LEFT_SHIFT => current == &LEFT_SHIFT,
|
||||||
},
|
KeyModifier::RIGHT_SHIFT => current == &RIGHT_SHIFT,
|
||||||
KeyModifier::LEFT_ALT => {
|
_ => false,
|
||||||
current == &LEFT_ALT
|
|
||||||
},
|
|
||||||
KeyModifier::RIGHT_ALT => {
|
|
||||||
current == &RIGHT_ALT
|
|
||||||
},
|
|
||||||
KeyModifier::LEFT_META => {
|
|
||||||
current == &LEFT_META
|
|
||||||
},
|
|
||||||
KeyModifier::RIGHT_META => {
|
|
||||||
current == &RIGHT_META
|
|
||||||
},
|
|
||||||
KeyModifier::LEFT_SHIFT => {
|
|
||||||
current == &LEFT_SHIFT
|
|
||||||
},
|
|
||||||
KeyModifier::RIGHT_SHIFT => {
|
|
||||||
current == &RIGHT_SHIFT
|
|
||||||
},
|
|
||||||
_ => {false},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -145,11 +125,11 @@ impl KeyModifier {
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum SystemEvent {
|
pub enum SystemEvent {
|
||||||
// MacOS specific
|
// MacOS specific
|
||||||
SecureInputEnabled(String, String), // AppName, App Path
|
SecureInputEnabled(String, String), // AppName, App Path
|
||||||
SecureInputDisabled,
|
SecureInputDisabled,
|
||||||
|
|
||||||
// Notification
|
// Notification
|
||||||
NotifyRequest(String)
|
NotifyRequest(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
// Receivers
|
// Receivers
|
||||||
|
@ -170,8 +150,8 @@ pub trait SystemEventReceiver {
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
|
||||||
use super::KeyModifier::*;
|
use super::KeyModifier::*;
|
||||||
|
use super::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_shallow_equals_ctrl() {
|
fn test_shallow_equals_ctrl() {
|
||||||
|
|
|
@ -17,8 +17,8 @@
|
||||||
* along with espanso. If not, see <https://www.gnu.org/licenses/>.
|
* along with espanso. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
use serde_yaml::{Mapping};
|
|
||||||
use crate::clipboard::ClipboardManager;
|
use crate::clipboard::ClipboardManager;
|
||||||
|
use serde_yaml::Mapping;
|
||||||
|
|
||||||
pub struct ClipboardExtension {
|
pub struct ClipboardExtension {
|
||||||
clipboard_manager: Box<dyn ClipboardManager>,
|
clipboard_manager: Box<dyn ClipboardManager>,
|
||||||
|
@ -26,9 +26,7 @@ pub struct ClipboardExtension {
|
||||||
|
|
||||||
impl ClipboardExtension {
|
impl ClipboardExtension {
|
||||||
pub fn new(clipboard_manager: Box<dyn ClipboardManager>) -> ClipboardExtension {
|
pub fn new(clipboard_manager: Box<dyn ClipboardManager>) -> ClipboardExtension {
|
||||||
ClipboardExtension{
|
ClipboardExtension { clipboard_manager }
|
||||||
clipboard_manager
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -17,14 +17,14 @@
|
||||||
* along with espanso. If not, see <https://www.gnu.org/licenses/>.
|
* along with espanso. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
use serde_yaml::{Mapping, Value};
|
|
||||||
use chrono::{DateTime, Local};
|
use chrono::{DateTime, Local};
|
||||||
|
use serde_yaml::{Mapping, Value};
|
||||||
|
|
||||||
pub struct DateExtension {}
|
pub struct DateExtension {}
|
||||||
|
|
||||||
impl DateExtension {
|
impl DateExtension {
|
||||||
pub fn new() -> DateExtension {
|
pub fn new() -> DateExtension {
|
||||||
DateExtension{}
|
DateExtension {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -40,7 +40,7 @@ impl super::Extension for DateExtension {
|
||||||
|
|
||||||
let date = if let Some(format) = format {
|
let date = if let Some(format) = format {
|
||||||
now.format(format.as_str().unwrap()).to_string()
|
now.format(format.as_str().unwrap()).to_string()
|
||||||
}else{
|
} else {
|
||||||
now.to_rfc2822()
|
now.to_rfc2822()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -23,7 +23,7 @@ pub struct DummyExtension {}
|
||||||
|
|
||||||
impl DummyExtension {
|
impl DummyExtension {
|
||||||
pub fn new() -> DummyExtension {
|
pub fn new() -> DummyExtension {
|
||||||
DummyExtension{}
|
DummyExtension {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -37,7 +37,7 @@ impl super::Extension for DummyExtension {
|
||||||
|
|
||||||
if let Some(echo) = echo {
|
if let Some(echo) = echo {
|
||||||
Some(echo.as_str().unwrap_or_default().to_owned())
|
Some(echo.as_str().unwrap_or_default().to_owned())
|
||||||
}else{
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,22 +17,22 @@
|
||||||
* along with espanso. If not, see <https://www.gnu.org/licenses/>.
|
* along with espanso. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
use serde_yaml::Mapping;
|
|
||||||
use crate::clipboard::ClipboardManager;
|
use crate::clipboard::ClipboardManager;
|
||||||
|
use serde_yaml::Mapping;
|
||||||
|
|
||||||
mod date;
|
|
||||||
mod shell;
|
|
||||||
mod script;
|
|
||||||
mod random;
|
|
||||||
mod clipboard;
|
mod clipboard;
|
||||||
|
mod date;
|
||||||
pub mod dummy;
|
pub mod dummy;
|
||||||
|
mod random;
|
||||||
|
mod script;
|
||||||
|
mod shell;
|
||||||
|
|
||||||
pub trait Extension {
|
pub trait Extension {
|
||||||
fn name(&self) -> String;
|
fn name(&self) -> String;
|
||||||
fn calculate(&self, params: &Mapping, args: &Vec<String>) -> Option<String>;
|
fn calculate(&self, params: &Mapping, args: &Vec<String>) -> Option<String>;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_extensions(clipboard_manager: Box<dyn ClipboardManager>) -> Vec<Box<dyn Extension>>{
|
pub fn get_extensions(clipboard_manager: Box<dyn ClipboardManager>) -> Vec<Box<dyn Extension>> {
|
||||||
vec![
|
vec![
|
||||||
Box::new(date::DateExtension::new()),
|
Box::new(date::DateExtension::new()),
|
||||||
Box::new(shell::ShellExtension::new()),
|
Box::new(shell::ShellExtension::new()),
|
||||||
|
|
|
@ -17,15 +17,15 @@
|
||||||
* along with espanso. If not, see <https://www.gnu.org/licenses/>.
|
* along with espanso. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
use serde_yaml::{Mapping, Value};
|
use log::{error, warn};
|
||||||
use rand::seq::SliceRandom;
|
use rand::seq::SliceRandom;
|
||||||
use log::{warn, error};
|
use serde_yaml::{Mapping, Value};
|
||||||
|
|
||||||
pub struct RandomExtension {}
|
pub struct RandomExtension {}
|
||||||
|
|
||||||
impl RandomExtension {
|
impl RandomExtension {
|
||||||
pub fn new() -> RandomExtension {
|
pub fn new() -> RandomExtension {
|
||||||
RandomExtension{}
|
RandomExtension {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -38,13 +38,14 @@ impl super::Extension for RandomExtension {
|
||||||
let choices = params.get(&Value::from("choices"));
|
let choices = params.get(&Value::from("choices"));
|
||||||
if choices.is_none() {
|
if choices.is_none() {
|
||||||
warn!("No 'choices' parameter specified for random variable");
|
warn!("No 'choices' parameter specified for random variable");
|
||||||
return None
|
return None;
|
||||||
}
|
}
|
||||||
let choices = choices.unwrap().as_sequence();
|
let choices = choices.unwrap().as_sequence();
|
||||||
if let Some(choices) = choices {
|
if let Some(choices) = choices {
|
||||||
let str_choices = choices.iter().map(|arg| {
|
let str_choices = choices
|
||||||
arg.as_str().unwrap_or_default().to_string()
|
.iter()
|
||||||
}).collect::<Vec<String>>();
|
.map(|arg| arg.as_str().unwrap_or_default().to_string())
|
||||||
|
.collect::<Vec<String>>();
|
||||||
|
|
||||||
// Select a random choice between the possibilities
|
// Select a random choice between the possibilities
|
||||||
let choice = str_choices.choose(&mut rand::thread_rng());
|
let choice = str_choices.choose(&mut rand::thread_rng());
|
||||||
|
@ -54,14 +55,13 @@ impl super::Extension for RandomExtension {
|
||||||
// Render arguments
|
// Render arguments
|
||||||
let output = crate::render::utils::render_args(output, args);
|
let output = crate::render::utils::render_args(output, args);
|
||||||
|
|
||||||
return Some(output)
|
return Some(output);
|
||||||
},
|
}
|
||||||
None => {
|
None => {
|
||||||
error!("Could not select a random choice.");
|
error!("Could not select a random choice.");
|
||||||
return None
|
return None;
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
error!("choices array have an invalid format '{:?}'", choices);
|
error!("choices array have an invalid format '{:?}'", choices);
|
||||||
|
@ -77,11 +77,7 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_random_basic() {
|
fn test_random_basic() {
|
||||||
let mut params = Mapping::new();
|
let mut params = Mapping::new();
|
||||||
let choices = vec!(
|
let choices = vec!["first", "second", "third"];
|
||||||
"first",
|
|
||||||
"second",
|
|
||||||
"third",
|
|
||||||
);
|
|
||||||
params.insert(Value::from("choices"), Value::from(choices.clone()));
|
params.insert(Value::from("choices"), Value::from(choices.clone()));
|
||||||
|
|
||||||
let extension = RandomExtension::new();
|
let extension = RandomExtension::new();
|
||||||
|
@ -97,11 +93,7 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_random_with_args() {
|
fn test_random_with_args() {
|
||||||
let mut params = Mapping::new();
|
let mut params = Mapping::new();
|
||||||
let choices = vec!(
|
let choices = vec!["first $0$", "second $0$", "$0$ third"];
|
||||||
"first $0$",
|
|
||||||
"second $0$",
|
|
||||||
"$0$ third",
|
|
||||||
);
|
|
||||||
params.insert(Value::from("choices"), Value::from(choices.clone()));
|
params.insert(Value::from("choices"), Value::from(choices.clone()));
|
||||||
|
|
||||||
let extension = RandomExtension::new();
|
let extension = RandomExtension::new();
|
||||||
|
@ -111,11 +103,7 @@ mod tests {
|
||||||
|
|
||||||
let output = output.unwrap();
|
let output = output.unwrap();
|
||||||
|
|
||||||
let rendered_choices = vec!(
|
let rendered_choices = vec!["first test", "second test", "test third"];
|
||||||
"first test",
|
|
||||||
"second test",
|
|
||||||
"test third",
|
|
||||||
);
|
|
||||||
|
|
||||||
assert!(rendered_choices.iter().any(|x| x == &output));
|
assert!(rendered_choices.iter().any(|x| x == &output));
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,16 +17,16 @@
|
||||||
* along with espanso. If not, see <https://www.gnu.org/licenses/>.
|
* along with espanso. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
use log::{error, warn};
|
||||||
use serde_yaml::{Mapping, Value};
|
use serde_yaml::{Mapping, Value};
|
||||||
use std::process::Command;
|
|
||||||
use log::{warn, error};
|
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
use std::process::Command;
|
||||||
|
|
||||||
pub struct ScriptExtension {}
|
pub struct ScriptExtension {}
|
||||||
|
|
||||||
impl ScriptExtension {
|
impl ScriptExtension {
|
||||||
pub fn new() -> ScriptExtension {
|
pub fn new() -> ScriptExtension {
|
||||||
ScriptExtension{}
|
ScriptExtension {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -39,17 +39,21 @@ impl super::Extension for ScriptExtension {
|
||||||
let args = params.get(&Value::from("args"));
|
let args = params.get(&Value::from("args"));
|
||||||
if args.is_none() {
|
if args.is_none() {
|
||||||
warn!("No 'args' parameter specified for script variable");
|
warn!("No 'args' parameter specified for script variable");
|
||||||
return None
|
return None;
|
||||||
}
|
}
|
||||||
let args = args.unwrap().as_sequence();
|
let args = args.unwrap().as_sequence();
|
||||||
if let Some(args) = args {
|
if let Some(args) = args {
|
||||||
let mut str_args = args.iter().map(|arg| {
|
let mut str_args = args
|
||||||
arg.as_str().unwrap_or_default().to_string()
|
.iter()
|
||||||
}).collect::<Vec<String>>();
|
.map(|arg| arg.as_str().unwrap_or_default().to_string())
|
||||||
|
.collect::<Vec<String>>();
|
||||||
|
|
||||||
// The user has to enable argument concatenation explicitly
|
// The user has to enable argument concatenation explicitly
|
||||||
let inject_args = params.get(&Value::from("inject_args"))
|
let inject_args = params
|
||||||
.unwrap_or(&Value::from(false)).as_bool().unwrap_or(false);
|
.get(&Value::from("inject_args"))
|
||||||
|
.unwrap_or(&Value::from(false))
|
||||||
|
.as_bool()
|
||||||
|
.unwrap_or(false);
|
||||||
if inject_args {
|
if inject_args {
|
||||||
str_args.extend(user_args.clone());
|
str_args.extend(user_args.clone());
|
||||||
}
|
}
|
||||||
|
@ -71,17 +75,12 @@ impl super::Extension for ScriptExtension {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
let output = if str_args.len() > 1 {
|
let output = if str_args.len() > 1 {
|
||||||
Command::new(&str_args[0])
|
Command::new(&str_args[0]).args(&str_args[1..]).output()
|
||||||
.args(&str_args[1..])
|
} else {
|
||||||
.output()
|
Command::new(&str_args[0]).output()
|
||||||
}else{
|
|
||||||
Command::new(&str_args[0])
|
|
||||||
.output()
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
match output {
|
match output {
|
||||||
Ok(output) => {
|
Ok(output) => {
|
||||||
let output_str = String::from_utf8_lossy(output.stdout.as_slice());
|
let output_str = String::from_utf8_lossy(output.stdout.as_slice());
|
||||||
|
@ -94,12 +93,12 @@ impl super::Extension for ScriptExtension {
|
||||||
warn!("Script command reported error: \n{}", error_str);
|
warn!("Script command reported error: \n{}", error_str);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Some(output_str.into_owned())
|
return Some(output_str.into_owned());
|
||||||
},
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!("Could not execute script '{:?}', error: {}", args, e);
|
error!("Could not execute script '{:?}', error: {}", args, e);
|
||||||
return None
|
return None;
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -117,7 +116,10 @@ mod tests {
|
||||||
#[cfg(not(target_os = "windows"))]
|
#[cfg(not(target_os = "windows"))]
|
||||||
fn test_script_basic() {
|
fn test_script_basic() {
|
||||||
let mut params = Mapping::new();
|
let mut params = Mapping::new();
|
||||||
params.insert(Value::from("args"), Value::from(vec!["echo", "hello world"]));
|
params.insert(
|
||||||
|
Value::from("args"),
|
||||||
|
Value::from(vec!["echo", "hello world"]),
|
||||||
|
);
|
||||||
|
|
||||||
let extension = ScriptExtension::new();
|
let extension = ScriptExtension::new();
|
||||||
let output = extension.calculate(¶ms, &vec![]);
|
let output = extension.calculate(¶ms, &vec![]);
|
||||||
|
@ -130,7 +132,10 @@ mod tests {
|
||||||
#[cfg(not(target_os = "windows"))]
|
#[cfg(not(target_os = "windows"))]
|
||||||
fn test_script_inject_args_off() {
|
fn test_script_inject_args_off() {
|
||||||
let mut params = Mapping::new();
|
let mut params = Mapping::new();
|
||||||
params.insert(Value::from("args"), Value::from(vec!["echo", "hello world"]));
|
params.insert(
|
||||||
|
Value::from("args"),
|
||||||
|
Value::from(vec!["echo", "hello world"]),
|
||||||
|
);
|
||||||
|
|
||||||
let extension = ScriptExtension::new();
|
let extension = ScriptExtension::new();
|
||||||
let output = extension.calculate(¶ms, &vec!["jon".to_owned()]);
|
let output = extension.calculate(¶ms, &vec!["jon".to_owned()]);
|
||||||
|
@ -143,7 +148,10 @@ mod tests {
|
||||||
#[cfg(not(target_os = "windows"))]
|
#[cfg(not(target_os = "windows"))]
|
||||||
fn test_script_inject_args_on() {
|
fn test_script_inject_args_on() {
|
||||||
let mut params = Mapping::new();
|
let mut params = Mapping::new();
|
||||||
params.insert(Value::from("args"), Value::from(vec!["echo", "hello world"]));
|
params.insert(
|
||||||
|
Value::from("args"),
|
||||||
|
Value::from(vec!["echo", "hello world"]),
|
||||||
|
);
|
||||||
params.insert(Value::from("inject_args"), Value::from(true));
|
params.insert(Value::from("inject_args"), Value::from(true));
|
||||||
|
|
||||||
let extension = ScriptExtension::new();
|
let extension = ScriptExtension::new();
|
||||||
|
|
|
@ -17,15 +17,15 @@
|
||||||
* along with espanso. If not, see <https://www.gnu.org/licenses/>.
|
* along with espanso. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
use log::{error, warn};
|
||||||
|
use regex::{Captures, Regex};
|
||||||
use serde_yaml::{Mapping, Value};
|
use serde_yaml::{Mapping, Value};
|
||||||
use std::process::{Command, Output};
|
use std::process::{Command, Output};
|
||||||
use log::{warn, error};
|
|
||||||
use regex::{Regex, Captures};
|
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
static ref POS_ARG_REGEX: Regex = if cfg!(target_os = "windows") {
|
static ref POS_ARG_REGEX: Regex = if cfg!(target_os = "windows") {
|
||||||
Regex::new("%(?P<pos>\\d+)").unwrap()
|
Regex::new("%(?P<pos>\\d+)").unwrap()
|
||||||
}else{
|
} else {
|
||||||
Regex::new("\\$(?P<pos>\\d+)").unwrap()
|
Regex::new("\\$(?P<pos>\\d+)").unwrap()
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -39,33 +39,15 @@ pub enum Shell {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Shell {
|
impl Shell {
|
||||||
fn execute_cmd(&self, cmd: &str) -> std::io::Result<Output>{
|
fn execute_cmd(&self, cmd: &str) -> std::io::Result<Output> {
|
||||||
match self {
|
match self {
|
||||||
Shell::Cmd => {
|
Shell::Cmd => Command::new("cmd").args(&["/C", &cmd]).output(),
|
||||||
Command::new("cmd")
|
Shell::Powershell => Command::new("powershell")
|
||||||
.args(&["/C", &cmd])
|
.args(&["-Command", &cmd])
|
||||||
.output()
|
.output(),
|
||||||
},
|
Shell::WSL => Command::new("wsl").args(&["bash", "-c", &cmd]).output(),
|
||||||
Shell::Powershell => {
|
Shell::Bash => Command::new("bash").args(&["-c", &cmd]).output(),
|
||||||
Command::new("powershell")
|
Shell::Sh => Command::new("sh").args(&["-c", &cmd]).output(),
|
||||||
.args(&["-Command", &cmd])
|
|
||||||
.output()
|
|
||||||
},
|
|
||||||
Shell::WSL => {
|
|
||||||
Command::new("wsl")
|
|
||||||
.args(&["bash", "-c", &cmd])
|
|
||||||
.output()
|
|
||||||
},
|
|
||||||
Shell::Bash => {
|
|
||||||
Command::new("bash")
|
|
||||||
.args(&["-c", &cmd])
|
|
||||||
.output()
|
|
||||||
},
|
|
||||||
Shell::Sh => {
|
|
||||||
Command::new("sh")
|
|
||||||
.args(&["-c", &cmd])
|
|
||||||
.output()
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -76,7 +58,7 @@ impl Shell {
|
||||||
"wsl" => Some(Shell::WSL),
|
"wsl" => Some(Shell::WSL),
|
||||||
"bash" => Some(Shell::Bash),
|
"bash" => Some(Shell::Bash),
|
||||||
"sh" => Some(Shell::Sh),
|
"sh" => Some(Shell::Sh),
|
||||||
_ => None
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -85,11 +67,11 @@ impl Default for Shell {
|
||||||
fn default() -> Shell {
|
fn default() -> Shell {
|
||||||
if cfg!(target_os = "windows") {
|
if cfg!(target_os = "windows") {
|
||||||
Shell::Powershell
|
Shell::Powershell
|
||||||
}else if cfg!(target_os = "macos") {
|
} else if cfg!(target_os = "macos") {
|
||||||
Shell::Sh
|
Shell::Sh
|
||||||
}else if cfg!(target_os = "linux") {
|
} else if cfg!(target_os = "linux") {
|
||||||
Shell::Bash
|
Shell::Bash
|
||||||
}else{
|
} else {
|
||||||
panic!("invalid target os for shell")
|
panic!("invalid target os for shell")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -99,7 +81,7 @@ pub struct ShellExtension {}
|
||||||
|
|
||||||
impl ShellExtension {
|
impl ShellExtension {
|
||||||
pub fn new() -> ShellExtension {
|
pub fn new() -> ShellExtension {
|
||||||
ShellExtension{}
|
ShellExtension {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -112,20 +94,22 @@ impl super::Extension for ShellExtension {
|
||||||
let cmd = params.get(&Value::from("cmd"));
|
let cmd = params.get(&Value::from("cmd"));
|
||||||
if cmd.is_none() {
|
if cmd.is_none() {
|
||||||
warn!("No 'cmd' parameter specified for shell variable");
|
warn!("No 'cmd' parameter specified for shell variable");
|
||||||
return None
|
return None;
|
||||||
}
|
}
|
||||||
let cmd = cmd.unwrap().as_str().unwrap();
|
let cmd = cmd.unwrap().as_str().unwrap();
|
||||||
|
|
||||||
// Render positional parameters in args
|
// Render positional parameters in args
|
||||||
let cmd = POS_ARG_REGEX.replace_all(&cmd, |caps: &Captures| {
|
let cmd = POS_ARG_REGEX
|
||||||
let position_str = caps.name("pos").unwrap().as_str();
|
.replace_all(&cmd, |caps: &Captures| {
|
||||||
let position = position_str.parse::<i32>().unwrap_or(-1);
|
let position_str = caps.name("pos").unwrap().as_str();
|
||||||
if position >= 0 && position < args.len() as i32 {
|
let position = position_str.parse::<i32>().unwrap_or(-1);
|
||||||
args[position as usize].to_owned()
|
if position >= 0 && position < args.len() as i32 {
|
||||||
}else{
|
args[position as usize].to_owned()
|
||||||
"".to_owned()
|
} else {
|
||||||
}
|
"".to_owned()
|
||||||
}).to_string();
|
}
|
||||||
|
})
|
||||||
|
.to_string();
|
||||||
|
|
||||||
let shell_param = params.get(&Value::from("shell"));
|
let shell_param = params.get(&Value::from("shell"));
|
||||||
let shell = if let Some(shell_param) = shell_param {
|
let shell = if let Some(shell_param) = shell_param {
|
||||||
|
@ -138,7 +122,7 @@ impl super::Extension for ShellExtension {
|
||||||
}
|
}
|
||||||
|
|
||||||
shell.unwrap()
|
shell.unwrap()
|
||||||
}else{
|
} else {
|
||||||
Shell::default()
|
Shell::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -169,11 +153,11 @@ impl super::Extension for ShellExtension {
|
||||||
}
|
}
|
||||||
|
|
||||||
Some(output_str)
|
Some(output_str)
|
||||||
},
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!("Could not execute cmd '{}', error: {}", cmd, e);
|
error!("Could not execute cmd '{}', error: {}", cmd, e);
|
||||||
None
|
None
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -195,7 +179,7 @@ mod tests {
|
||||||
|
|
||||||
if cfg!(target_os = "windows") {
|
if cfg!(target_os = "windows") {
|
||||||
assert_eq!(output.unwrap(), "hello world\r\n");
|
assert_eq!(output.unwrap(), "hello world\r\n");
|
||||||
}else{
|
} else {
|
||||||
assert_eq!(output.unwrap(), "hello world\n");
|
assert_eq!(output.unwrap(), "hello world\n");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -216,7 +200,10 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_shell_trimmed_2() {
|
fn test_shell_trimmed_2() {
|
||||||
let mut params = Mapping::new();
|
let mut params = Mapping::new();
|
||||||
params.insert(Value::from("cmd"), Value::from("echo \" hello world \""));
|
params.insert(
|
||||||
|
Value::from("cmd"),
|
||||||
|
Value::from("echo \" hello world \""),
|
||||||
|
);
|
||||||
|
|
||||||
params.insert(Value::from("trim"), Value::from(true));
|
params.insert(Value::from("trim"), Value::from(true));
|
||||||
|
|
||||||
|
@ -239,7 +226,7 @@ mod tests {
|
||||||
assert!(output.is_some());
|
assert!(output.is_some());
|
||||||
if cfg!(target_os = "windows") {
|
if cfg!(target_os = "windows") {
|
||||||
assert_eq!(output.unwrap(), "hello world\r\n");
|
assert_eq!(output.unwrap(), "hello world\r\n");
|
||||||
}else{
|
} else {
|
||||||
assert_eq!(output.unwrap(), "hello world\n");
|
assert_eq!(output.unwrap(), "hello world\n");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,14 +17,13 @@
|
||||||
* along with espanso. If not, see <https://www.gnu.org/licenses/>.
|
* along with espanso. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
use std::ffi::CString;
|
|
||||||
use crate::bridge::linux::*;
|
|
||||||
use super::PasteShortcut;
|
use super::PasteShortcut;
|
||||||
use log::error;
|
use crate::bridge::linux::*;
|
||||||
use crate::config::Configs;
|
use crate::config::Configs;
|
||||||
|
use log::error;
|
||||||
|
use std::ffi::CString;
|
||||||
|
|
||||||
pub struct LinuxKeyboardManager {
|
pub struct LinuxKeyboardManager {}
|
||||||
}
|
|
||||||
|
|
||||||
impl super::KeyboardManager for LinuxKeyboardManager {
|
impl super::KeyboardManager for LinuxKeyboardManager {
|
||||||
fn send_string(&self, active_config: &Configs, s: &str) {
|
fn send_string(&self, active_config: &Configs, s: &str) {
|
||||||
|
@ -33,11 +32,11 @@ impl super::KeyboardManager for LinuxKeyboardManager {
|
||||||
Ok(cstr) => unsafe {
|
Ok(cstr) => unsafe {
|
||||||
if active_config.fast_inject {
|
if active_config.fast_inject {
|
||||||
fast_send_string(cstr.as_ptr(), active_config.inject_delay);
|
fast_send_string(cstr.as_ptr(), active_config.inject_delay);
|
||||||
}else{
|
} else {
|
||||||
send_string(cstr.as_ptr());
|
send_string(cstr.as_ptr());
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
Err(e) => panic!(e.to_string())
|
Err(e) => panic!(e.to_string()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -45,7 +44,7 @@ impl super::KeyboardManager for LinuxKeyboardManager {
|
||||||
unsafe {
|
unsafe {
|
||||||
if active_config.fast_inject {
|
if active_config.fast_inject {
|
||||||
fast_send_enter();
|
fast_send_enter();
|
||||||
}else{
|
} else {
|
||||||
send_enter();
|
send_enter();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -94,7 +93,7 @@ impl super::KeyboardManager for LinuxKeyboardManager {
|
||||||
unsafe {
|
unsafe {
|
||||||
if active_config.fast_inject {
|
if active_config.fast_inject {
|
||||||
fast_delete_string(count, active_config.backspace_delay);
|
fast_delete_string(count, active_config.backspace_delay);
|
||||||
}else{
|
} else {
|
||||||
delete_string(count)
|
delete_string(count)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -104,7 +103,7 @@ impl super::KeyboardManager for LinuxKeyboardManager {
|
||||||
unsafe {
|
unsafe {
|
||||||
if active_config.fast_inject {
|
if active_config.fast_inject {
|
||||||
fast_left_arrow(count);
|
fast_left_arrow(count);
|
||||||
}else{
|
} else {
|
||||||
left_arrow(count);
|
left_arrow(count);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,21 +17,22 @@
|
||||||
* along with espanso. If not, see <https://www.gnu.org/licenses/>.
|
* along with espanso. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
use std::ffi::CString;
|
|
||||||
use crate::bridge::macos::*;
|
|
||||||
use super::PasteShortcut;
|
use super::PasteShortcut;
|
||||||
use log::error;
|
use crate::bridge::macos::*;
|
||||||
use crate::config::Configs;
|
use crate::config::Configs;
|
||||||
|
use log::error;
|
||||||
|
use std::ffi::CString;
|
||||||
|
|
||||||
pub struct MacKeyboardManager {
|
pub struct MacKeyboardManager {}
|
||||||
}
|
|
||||||
|
|
||||||
impl super::KeyboardManager for MacKeyboardManager {
|
impl super::KeyboardManager for MacKeyboardManager {
|
||||||
fn send_string(&self, _: &Configs, s: &str) {
|
fn send_string(&self, _: &Configs, s: &str) {
|
||||||
let res = CString::new(s);
|
let res = CString::new(s);
|
||||||
match res {
|
match res {
|
||||||
Ok(cstr) => unsafe { send_string(cstr.as_ptr()); }
|
Ok(cstr) => unsafe {
|
||||||
Err(e) => panic!(e.to_string())
|
send_string(cstr.as_ptr());
|
||||||
|
},
|
||||||
|
Err(e) => panic!(e.to_string()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -64,7 +65,7 @@ impl super::KeyboardManager for MacKeyboardManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn delete_string(&self, _: &Configs, count: i32) {
|
fn delete_string(&self, _: &Configs, count: i32) {
|
||||||
unsafe {delete_string(count)}
|
unsafe { delete_string(count) }
|
||||||
}
|
}
|
||||||
|
|
||||||
fn move_cursor_left(&self, _: &Configs, count: i32) {
|
fn move_cursor_left(&self, _: &Configs, count: i32) {
|
||||||
|
|
|
@ -17,8 +17,8 @@
|
||||||
* along with espanso. If not, see <https://www.gnu.org/licenses/>.
|
* along with espanso. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
use serde::{Serialize, Deserialize};
|
|
||||||
use crate::config::Configs;
|
use crate::config::Configs;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
mod windows;
|
mod windows;
|
||||||
|
@ -40,15 +40,15 @@ pub trait KeyboardManager {
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
pub enum PasteShortcut {
|
pub enum PasteShortcut {
|
||||||
Default, // Default one for the current system
|
Default, // Default one for the current system
|
||||||
CtrlV, // Classic Ctrl+V shortcut
|
CtrlV, // Classic Ctrl+V shortcut
|
||||||
CtrlShiftV, // Could be used to paste without formatting in many applications
|
CtrlShiftV, // Could be used to paste without formatting in many applications
|
||||||
ShiftInsert, // Often used in Linux systems
|
ShiftInsert, // Often used in Linux systems
|
||||||
CtrlAltV, // Used in some Linux terminals (urxvt)
|
CtrlAltV, // Used in some Linux terminals (urxvt)
|
||||||
MetaV, // Corresponding to Win+V on Windows and Linux, CMD+V on macOS
|
MetaV, // Corresponding to Win+V on Windows and Linux, CMD+V on macOS
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for PasteShortcut{
|
impl Default for PasteShortcut {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
PasteShortcut::Default
|
PasteShortcut::Default
|
||||||
}
|
}
|
||||||
|
@ -57,17 +57,17 @@ impl Default for PasteShortcut{
|
||||||
// WINDOWS IMPLEMENTATION
|
// WINDOWS IMPLEMENTATION
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
pub fn get_manager() -> impl KeyboardManager {
|
pub fn get_manager() -> impl KeyboardManager {
|
||||||
windows::WindowsKeyboardManager{}
|
windows::WindowsKeyboardManager {}
|
||||||
}
|
}
|
||||||
|
|
||||||
// LINUX IMPLEMENTATION
|
// LINUX IMPLEMENTATION
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
pub fn get_manager() -> impl KeyboardManager {
|
pub fn get_manager() -> impl KeyboardManager {
|
||||||
linux::LinuxKeyboardManager{}
|
linux::LinuxKeyboardManager {}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MAC IMPLEMENTATION
|
// MAC IMPLEMENTATION
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
pub fn get_manager() -> impl KeyboardManager {
|
pub fn get_manager() -> impl KeyboardManager {
|
||||||
macos::MacKeyboardManager{}
|
macos::MacKeyboardManager {}
|
||||||
}
|
}
|
|
@ -17,27 +17,23 @@
|
||||||
* along with espanso. If not, see <https://www.gnu.org/licenses/>.
|
* along with espanso. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
use widestring::{U16CString};
|
|
||||||
use crate::bridge::windows::*;
|
|
||||||
use super::PasteShortcut;
|
use super::PasteShortcut;
|
||||||
use log::error;
|
use crate::bridge::windows::*;
|
||||||
use crate::config::Configs;
|
use crate::config::Configs;
|
||||||
|
use log::error;
|
||||||
|
use widestring::U16CString;
|
||||||
|
|
||||||
pub struct WindowsKeyboardManager {
|
pub struct WindowsKeyboardManager {}
|
||||||
}
|
|
||||||
|
|
||||||
impl super::KeyboardManager for WindowsKeyboardManager {
|
impl super::KeyboardManager for WindowsKeyboardManager {
|
||||||
fn send_string(&self, _: &Configs, s: &str) {
|
fn send_string(&self, _: &Configs, s: &str) {
|
||||||
let res = U16CString::from_str(s);
|
let res = U16CString::from_str(s);
|
||||||
match res {
|
match res {
|
||||||
Ok(s) => {
|
Ok(s) => unsafe {
|
||||||
unsafe {
|
send_string(s.as_ptr());
|
||||||
send_string(s.as_ptr());
|
},
|
||||||
}
|
Err(e) => println!("Error while sending string: {}", e.to_string()),
|
||||||
}
|
|
||||||
Err(e) => println!("Error while sending string: {}", e.to_string())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn send_enter(&self, _: &Configs) {
|
fn send_enter(&self, _: &Configs) {
|
||||||
|
@ -63,9 +59,7 @@ impl super::KeyboardManager for WindowsKeyboardManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn delete_string(&self, config: &Configs, count: i32) {
|
fn delete_string(&self, config: &Configs, count: i32) {
|
||||||
unsafe {
|
unsafe { delete_string(count, config.backspace_delay) }
|
||||||
delete_string(count, config.backspace_delay)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn move_cursor_left(&self, _: &Configs, count: i32) {
|
fn move_cursor_left(&self, _: &Configs, count: i32) {
|
||||||
|
|
530
src/main.rs
530
src/main.rs
|
@ -20,56 +20,56 @@
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate lazy_static;
|
extern crate lazy_static;
|
||||||
|
|
||||||
|
use notify::{DebouncedEvent, RecommendedWatcher, RecursiveMode, Watcher};
|
||||||
|
use regex::Regex;
|
||||||
use std::fs::{File, OpenOptions};
|
use std::fs::{File, OpenOptions};
|
||||||
use std::io::{BufRead, BufReader};
|
use std::io::{BufRead, BufReader};
|
||||||
use std::process::exit;
|
use std::process::exit;
|
||||||
use std::sync::{Arc, mpsc};
|
use std::process::{Command, Stdio};
|
||||||
use std::sync::atomic::AtomicBool;
|
use std::sync::atomic::AtomicBool;
|
||||||
use std::sync::mpsc::{Receiver, Sender, RecvError};
|
use std::sync::mpsc::channel;
|
||||||
|
use std::sync::mpsc::{Receiver, RecvError, Sender};
|
||||||
|
use std::sync::{mpsc, Arc};
|
||||||
use std::thread;
|
use std::thread;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use std::process::{Command, Stdio};
|
|
||||||
use notify::{RecommendedWatcher, Watcher, RecursiveMode, DebouncedEvent};
|
|
||||||
use std::sync::mpsc::channel;
|
|
||||||
use regex::Regex;
|
|
||||||
|
|
||||||
use clap::{App, Arg, ArgMatches, SubCommand, AppSettings};
|
use clap::{App, AppSettings, Arg, ArgMatches, SubCommand};
|
||||||
use fs2::FileExt;
|
use fs2::FileExt;
|
||||||
use log::{info, LevelFilter, warn, error};
|
use log::{error, info, warn, LevelFilter};
|
||||||
use simplelog::{CombinedLogger, SharedLogger, TerminalMode, TermLogger, WriteLogger};
|
use simplelog::{CombinedLogger, SharedLogger, TermLogger, TerminalMode, WriteLogger};
|
||||||
|
|
||||||
use crate::config::{ConfigManager, ConfigSet, Configs};
|
|
||||||
use crate::config::runtime::RuntimeConfigManager;
|
use crate::config::runtime::RuntimeConfigManager;
|
||||||
|
use crate::config::{ConfigManager, ConfigSet, Configs};
|
||||||
use crate::engine::Engine;
|
use crate::engine::Engine;
|
||||||
use crate::event::*;
|
|
||||||
use crate::event::manager::{DefaultEventManager, EventManager};
|
use crate::event::manager::{DefaultEventManager, EventManager};
|
||||||
|
use crate::event::*;
|
||||||
use crate::matcher::scrolling::ScrollingMatcher;
|
use crate::matcher::scrolling::ScrollingMatcher;
|
||||||
use crate::package::{InstallResult, PackageManager, RemoveResult, UpdateResult};
|
|
||||||
use crate::package::default::DefaultPackageManager;
|
use crate::package::default::DefaultPackageManager;
|
||||||
use crate::package::zip::ZipPackageResolver;
|
use crate::package::zip::ZipPackageResolver;
|
||||||
|
use crate::package::{InstallResult, PackageManager, RemoveResult, UpdateResult};
|
||||||
use crate::protocol::*;
|
use crate::protocol::*;
|
||||||
use crate::system::SystemManager;
|
use crate::system::SystemManager;
|
||||||
use crate::ui::UIManager;
|
use crate::ui::UIManager;
|
||||||
|
|
||||||
mod ui;
|
|
||||||
mod edit;
|
|
||||||
mod event;
|
|
||||||
mod check;
|
|
||||||
mod utils;
|
|
||||||
mod bridge;
|
mod bridge;
|
||||||
mod engine;
|
mod check;
|
||||||
mod config;
|
|
||||||
mod render;
|
|
||||||
mod system;
|
|
||||||
mod context;
|
|
||||||
mod matcher;
|
|
||||||
mod process;
|
|
||||||
mod package;
|
|
||||||
mod keyboard;
|
|
||||||
mod protocol;
|
|
||||||
mod clipboard;
|
mod clipboard;
|
||||||
|
mod config;
|
||||||
|
mod context;
|
||||||
|
mod edit;
|
||||||
|
mod engine;
|
||||||
|
mod event;
|
||||||
mod extension;
|
mod extension;
|
||||||
|
mod keyboard;
|
||||||
|
mod matcher;
|
||||||
|
mod package;
|
||||||
|
mod process;
|
||||||
|
mod protocol;
|
||||||
|
mod render;
|
||||||
mod sysdaemon;
|
mod sysdaemon;
|
||||||
|
mod system;
|
||||||
|
mod ui;
|
||||||
|
mod utils;
|
||||||
|
|
||||||
const VERSION: &str = env!("CARGO_PKG_VERSION");
|
const VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||||
const LOG_FILE: &str = "espanso.log";
|
const LOG_FILE: &str = "espanso.log";
|
||||||
|
@ -77,24 +77,25 @@ const LOG_FILE: &str = "espanso.log";
|
||||||
fn main() {
|
fn main() {
|
||||||
let install_subcommand = SubCommand::with_name("install")
|
let install_subcommand = SubCommand::with_name("install")
|
||||||
.about("Install a package. Equivalent to 'espanso package install'")
|
.about("Install a package. Equivalent to 'espanso package install'")
|
||||||
.arg(Arg::with_name("external")
|
.arg(
|
||||||
.short("e")
|
Arg::with_name("external")
|
||||||
.long("external")
|
.short("e")
|
||||||
.required(false)
|
.long("external")
|
||||||
.takes_value(false)
|
.required(false)
|
||||||
.help("Allow installing packages from non-verified repositories."))
|
.takes_value(false)
|
||||||
.arg(Arg::with_name("package_name")
|
.help("Allow installing packages from non-verified repositories."),
|
||||||
.help("Package name"))
|
)
|
||||||
.arg(Arg::with_name("repository_url")
|
.arg(Arg::with_name("package_name").help("Package name"))
|
||||||
.help("(Optional) Link to GitHub repository")
|
.arg(
|
||||||
.required(false)
|
Arg::with_name("repository_url")
|
||||||
.default_value("hub")
|
.help("(Optional) Link to GitHub repository")
|
||||||
|
.required(false)
|
||||||
|
.default_value("hub"),
|
||||||
);
|
);
|
||||||
|
|
||||||
let uninstall_subcommand = SubCommand::with_name("uninstall")
|
let uninstall_subcommand = SubCommand::with_name("uninstall")
|
||||||
.about("Remove an installed package. Equivalent to 'espanso package uninstall'")
|
.about("Remove an installed package. Equivalent to 'espanso package uninstall'")
|
||||||
.arg(Arg::with_name("package_name")
|
.arg(Arg::with_name("package_name").help("Package name"));
|
||||||
.help("Package name"));
|
|
||||||
|
|
||||||
let mut clap_instance = App::new("espanso")
|
let mut clap_instance = App::new("espanso")
|
||||||
.version(VERSION)
|
.version(VERSION)
|
||||||
|
@ -191,7 +192,6 @@ fn main() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
let log_level = matches.occurrences_of("v") as i32;
|
let log_level = matches.occurrences_of("v") as i32;
|
||||||
|
|
||||||
// Load the configuration
|
// Load the configuration
|
||||||
|
@ -299,7 +299,9 @@ fn main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Defaults help print
|
// Defaults help print
|
||||||
clap_instance.print_long_help().expect("Unable to print help");
|
clap_instance
|
||||||
|
.print_long_help()
|
||||||
|
.expect("Unable to print help");
|
||||||
println!();
|
println!();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -314,8 +316,8 @@ fn init_logger(config_set: &ConfigSet, reset: bool) {
|
||||||
let mut log_outputs: Vec<Box<dyn SharedLogger>> = Vec::new();
|
let mut log_outputs: Vec<Box<dyn SharedLogger>> = Vec::new();
|
||||||
|
|
||||||
// Initialize terminal output
|
// Initialize terminal output
|
||||||
let terminal_out = TermLogger::new(log_level,
|
let terminal_out =
|
||||||
simplelog::Config::default(), TerminalMode::Mixed);
|
TermLogger::new(log_level, simplelog::Config::default(), TerminalMode::Mixed);
|
||||||
if let Some(terminal_out) = terminal_out {
|
if let Some(terminal_out) = terminal_out {
|
||||||
log_outputs.push(terminal_out);
|
log_outputs.push(terminal_out);
|
||||||
}
|
}
|
||||||
|
@ -338,9 +340,7 @@ fn init_logger(config_set: &ConfigSet, reset: bool) {
|
||||||
let file_out = WriteLogger::new(LevelFilter::Info, simplelog::Config::default(), log_file);
|
let file_out = WriteLogger::new(LevelFilter::Info, simplelog::Config::default(), log_file);
|
||||||
log_outputs.push(file_out);
|
log_outputs.push(file_out);
|
||||||
|
|
||||||
CombinedLogger::init(
|
CombinedLogger::init(log_outputs).expect("Error opening log destination");
|
||||||
log_outputs
|
|
||||||
).expect("Error opening log destination");
|
|
||||||
|
|
||||||
// Activate logging for panics
|
// Activate logging for panics
|
||||||
log_panics::init();
|
log_panics::init();
|
||||||
|
@ -360,26 +360,42 @@ fn daemon_main(config_set: ConfigSet) {
|
||||||
init_logger(&config_set, true);
|
init_logger(&config_set, true);
|
||||||
|
|
||||||
info!("espanso version {}", VERSION);
|
info!("espanso version {}", VERSION);
|
||||||
info!("using config path: {}", context::get_config_dir().to_string_lossy());
|
info!(
|
||||||
info!("using package path: {}", context::get_package_dir().to_string_lossy());
|
"using config path: {}",
|
||||||
|
context::get_config_dir().to_string_lossy()
|
||||||
|
);
|
||||||
|
info!(
|
||||||
|
"using package path: {}",
|
||||||
|
context::get_package_dir().to_string_lossy()
|
||||||
|
);
|
||||||
|
|
||||||
let (send_channel, receive_channel) = mpsc::channel();
|
let (send_channel, receive_channel) = mpsc::channel();
|
||||||
|
|
||||||
let ipc_server = protocol::get_ipc_server(Service::Daemon, config_set.default.clone(), send_channel.clone());
|
let ipc_server = protocol::get_ipc_server(
|
||||||
|
Service::Daemon,
|
||||||
|
config_set.default.clone(),
|
||||||
|
send_channel.clone(),
|
||||||
|
);
|
||||||
ipc_server.start();
|
ipc_server.start();
|
||||||
|
|
||||||
info!("spawning worker process...");
|
info!("spawning worker process...");
|
||||||
|
|
||||||
let espanso_path = std::env::current_exe().expect("unable to obtain espanso path location");
|
let espanso_path = std::env::current_exe().expect("unable to obtain espanso path location");
|
||||||
crate::process::spawn_process(&espanso_path.to_string_lossy().to_string(), &vec!("worker".to_owned()));
|
crate::process::spawn_process(
|
||||||
|
&espanso_path.to_string_lossy().to_string(),
|
||||||
|
&vec!["worker".to_owned()],
|
||||||
|
);
|
||||||
|
|
||||||
std::thread::sleep(Duration::from_millis(200));
|
std::thread::sleep(Duration::from_millis(200));
|
||||||
|
|
||||||
if config_set.default.auto_restart {
|
if config_set.default.auto_restart {
|
||||||
let send_channel_clone = send_channel.clone();
|
let send_channel_clone = send_channel.clone();
|
||||||
thread::Builder::new().name("watcher_background".to_string()).spawn(move || {
|
thread::Builder::new()
|
||||||
watcher_background(send_channel_clone);
|
.name("watcher_background".to_string())
|
||||||
}).expect("Unable to spawn watcher background thread");
|
.spawn(move || {
|
||||||
|
watcher_background(send_channel_clone);
|
||||||
|
})
|
||||||
|
.expect("Unable to spawn watcher background thread");
|
||||||
}
|
}
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
|
@ -388,33 +404,48 @@ fn daemon_main(config_set: ConfigSet) {
|
||||||
match event {
|
match event {
|
||||||
Event::Action(ActionType::RestartWorker) => {
|
Event::Action(ActionType::RestartWorker) => {
|
||||||
// Terminate the worker process
|
// Terminate the worker process
|
||||||
send_command_or_warn(Service::Worker, config_set.default.clone(), IPCCommand::exit_worker());
|
send_command_or_warn(
|
||||||
|
Service::Worker,
|
||||||
|
config_set.default.clone(),
|
||||||
|
IPCCommand::exit_worker(),
|
||||||
|
);
|
||||||
|
|
||||||
std::thread::sleep(Duration::from_millis(500));
|
std::thread::sleep(Duration::from_millis(500));
|
||||||
|
|
||||||
// Restart the worker process
|
// Restart the worker process
|
||||||
crate::process::spawn_process(&espanso_path.to_string_lossy().to_string(), &vec!("worker".to_owned(), "--reload".to_owned()));
|
crate::process::spawn_process(
|
||||||
},
|
&espanso_path.to_string_lossy().to_string(),
|
||||||
|
&vec!["worker".to_owned(), "--reload".to_owned()],
|
||||||
|
);
|
||||||
|
}
|
||||||
Event::Action(ActionType::Exit) => {
|
Event::Action(ActionType::Exit) => {
|
||||||
send_command_or_warn(Service::Worker, config_set.default.clone(), IPCCommand::exit_worker());
|
send_command_or_warn(
|
||||||
|
Service::Worker,
|
||||||
|
config_set.default.clone(),
|
||||||
|
IPCCommand::exit_worker(),
|
||||||
|
);
|
||||||
|
|
||||||
std::thread::sleep(Duration::from_millis(200));
|
std::thread::sleep(Duration::from_millis(200));
|
||||||
|
|
||||||
info!("terminating espanso.");
|
info!("terminating espanso.");
|
||||||
std::process::exit(0);
|
std::process::exit(0);
|
||||||
},
|
}
|
||||||
_ => {
|
_ => {
|
||||||
// Forward the command to the worker
|
// Forward the command to the worker
|
||||||
let command = IPCCommand::from(event);
|
let command = IPCCommand::from(event);
|
||||||
if let Some(command) = command {
|
if let Some(command) = command {
|
||||||
send_command_or_warn(Service::Worker, config_set.default.clone(), command);
|
send_command_or_warn(
|
||||||
|
Service::Worker,
|
||||||
|
config_set.default.clone(),
|
||||||
|
command,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
warn!("error while reading event in daemon process: {}", e);
|
warn!("error while reading event in daemon process: {}", e);
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -423,13 +454,18 @@ fn watcher_background(sender: Sender<Event>) {
|
||||||
// Create a channel to receive the events.
|
// Create a channel to receive the events.
|
||||||
let (tx, rx) = channel();
|
let (tx, rx) = channel();
|
||||||
|
|
||||||
let mut watcher: RecommendedWatcher = Watcher::new(tx, Duration::from_secs(1)).expect("unable to create file watcher");
|
let mut watcher: RecommendedWatcher =
|
||||||
|
Watcher::new(tx, Duration::from_secs(1)).expect("unable to create file watcher");
|
||||||
|
|
||||||
let config_path = crate::context::get_config_dir();
|
let config_path = crate::context::get_config_dir();
|
||||||
watcher.watch(&config_path, RecursiveMode::Recursive).expect("unable to start watcher");
|
watcher
|
||||||
|
.watch(&config_path, RecursiveMode::Recursive)
|
||||||
info!("watching for changes in path: {}", config_path.to_string_lossy());
|
.expect("unable to start watcher");
|
||||||
|
|
||||||
|
info!(
|
||||||
|
"watching for changes in path: {}",
|
||||||
|
config_path.to_string_lossy()
|
||||||
|
);
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
let should_reload = match rx.recv() {
|
let should_reload = match rx.recv() {
|
||||||
|
@ -443,15 +479,16 @@ fn watcher_background(sender: Sender<Event>) {
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(path) = path {
|
if let Some(path) = path {
|
||||||
if path.extension().unwrap_or_default() == "yml" { // Only load yml files
|
if path.extension().unwrap_or_default() == "yml" {
|
||||||
|
// Only load yml files
|
||||||
true
|
true
|
||||||
}else{
|
} else {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
}else{
|
} else {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
warn!("error while watching files: {:?}", e);
|
warn!("error while watching files: {:?}", e);
|
||||||
false
|
false
|
||||||
|
@ -469,11 +506,12 @@ fn watcher_background(sender: Sender<Event>) {
|
||||||
sender.send(event).unwrap_or_else(|e| {
|
sender.send(event).unwrap_or_else(|e| {
|
||||||
warn!("unable to communicate with daemon thread: {}", e);
|
warn!("unable to communicate with daemon thread: {}", e);
|
||||||
})
|
})
|
||||||
},
|
}
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
error!("Unable to reload configuration due to an error: {}", error);
|
error!("Unable to reload configuration due to an error: {}", error);
|
||||||
let event = Event::System(SystemEvent::NotifyRequest(
|
let event = Event::System(SystemEvent::NotifyRequest(
|
||||||
"Unable to reload config due to an error, see the logs for more details.".to_owned()
|
"Unable to reload config due to an error, see the logs for more details."
|
||||||
|
.to_owned(),
|
||||||
));
|
));
|
||||||
sender.send(event).unwrap_or_else(|e| {
|
sender.send(event).unwrap_or_else(|e| {
|
||||||
warn!("unable to communicate with daemon thread: {}", e);
|
warn!("unable to communicate with daemon thread: {}", e);
|
||||||
|
@ -492,7 +530,7 @@ fn worker_main(config_set: ConfigSet, matches: &ArgMatches) {
|
||||||
|
|
||||||
let is_reloading: bool = if matches.is_present("reload") {
|
let is_reloading: bool = if matches.is_present("reload") {
|
||||||
true
|
true
|
||||||
}else{
|
} else {
|
||||||
false
|
false
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -502,21 +540,34 @@ fn worker_main(config_set: ConfigSet, matches: &ArgMatches) {
|
||||||
// we could reinterpret the characters we are injecting
|
// we could reinterpret the characters we are injecting
|
||||||
let is_injecting = Arc::new(std::sync::atomic::AtomicBool::new(false));
|
let is_injecting = Arc::new(std::sync::atomic::AtomicBool::new(false));
|
||||||
|
|
||||||
let context = context::new(config_set.default.clone(), send_channel.clone(), is_injecting.clone());
|
let context = context::new(
|
||||||
|
config_set.default.clone(),
|
||||||
|
send_channel.clone(),
|
||||||
|
is_injecting.clone(),
|
||||||
|
);
|
||||||
|
|
||||||
let config_set_copy = config_set.clone();
|
let config_set_copy = config_set.clone();
|
||||||
thread::Builder::new().name("daemon_background".to_string()).spawn(move || {
|
thread::Builder::new()
|
||||||
worker_background(receive_channel, config_set_copy, is_injecting, is_reloading);
|
.name("daemon_background".to_string())
|
||||||
}).expect("Unable to spawn daemon background thread");
|
.spawn(move || {
|
||||||
|
worker_background(receive_channel, config_set_copy, is_injecting, is_reloading);
|
||||||
|
})
|
||||||
|
.expect("Unable to spawn daemon background thread");
|
||||||
|
|
||||||
let ipc_server = protocol::get_ipc_server(Service::Worker, config_set.default, send_channel.clone());
|
let ipc_server =
|
||||||
|
protocol::get_ipc_server(Service::Worker, config_set.default, send_channel.clone());
|
||||||
ipc_server.start();
|
ipc_server.start();
|
||||||
|
|
||||||
context.eventloop();
|
context.eventloop();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Background thread worker for the daemon
|
/// Background thread worker for the daemon
|
||||||
fn worker_background(receive_channel: Receiver<Event>, config_set: ConfigSet, is_injecting: Arc<AtomicBool>, is_reloading: bool) {
|
fn worker_background(
|
||||||
|
receive_channel: Receiver<Event>,
|
||||||
|
config_set: ConfigSet,
|
||||||
|
is_injecting: Arc<AtomicBool>,
|
||||||
|
is_reloading: bool,
|
||||||
|
) {
|
||||||
let system_manager = system::get_manager();
|
let system_manager = system::get_manager();
|
||||||
let config_manager = RuntimeConfigManager::new(config_set, system_manager);
|
let config_manager = RuntimeConfigManager::new(config_set, system_manager);
|
||||||
|
|
||||||
|
@ -524,7 +575,7 @@ fn worker_background(receive_channel: Receiver<Event>, config_set: ConfigSet, is
|
||||||
if config_manager.default_config().show_notifications {
|
if config_manager.default_config().show_notifications {
|
||||||
if !is_reloading {
|
if !is_reloading {
|
||||||
ui_manager.notify("espanso is running!");
|
ui_manager.notify("espanso is running!");
|
||||||
}else{
|
} else {
|
||||||
ui_manager.notify("Reloaded config!");
|
ui_manager.notify("Reloaded config!");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -535,24 +586,25 @@ fn worker_background(receive_channel: Receiver<Event>, config_set: ConfigSet, is
|
||||||
|
|
||||||
let extensions = extension::get_extensions(Box::new(clipboard::get_manager()));
|
let extensions = extension::get_extensions(Box::new(clipboard::get_manager()));
|
||||||
|
|
||||||
let renderer = render::default::DefaultRenderer::new(extensions,
|
let renderer =
|
||||||
config_manager.default_config().clone());
|
render::default::DefaultRenderer::new(extensions, config_manager.default_config().clone());
|
||||||
|
|
||||||
let engine = Engine::new(&keyboard_manager,
|
let engine = Engine::new(
|
||||||
&clipboard_manager,
|
&keyboard_manager,
|
||||||
&config_manager,
|
&clipboard_manager,
|
||||||
&ui_manager,
|
&config_manager,
|
||||||
&renderer,
|
&ui_manager,
|
||||||
is_injecting,
|
&renderer,
|
||||||
|
is_injecting,
|
||||||
);
|
);
|
||||||
|
|
||||||
let matcher = ScrollingMatcher::new(&config_manager, &engine);
|
let matcher = ScrollingMatcher::new(&config_manager, &engine);
|
||||||
|
|
||||||
let event_manager = DefaultEventManager::new(
|
let event_manager = DefaultEventManager::new(
|
||||||
receive_channel,
|
receive_channel,
|
||||||
vec!(&matcher),
|
vec![&matcher],
|
||||||
vec!(&engine, &matcher),
|
vec![&engine, &matcher],
|
||||||
vec!(&engine),
|
vec![&engine],
|
||||||
);
|
);
|
||||||
|
|
||||||
info!("worker is running!");
|
info!("worker is running!");
|
||||||
|
@ -597,21 +649,21 @@ fn start_daemon(config_set: ConfigSet) {
|
||||||
if let Ok(status) = res {
|
if let Ok(status) = res {
|
||||||
if status.success() {
|
if status.success() {
|
||||||
println!("Daemon started correctly!")
|
println!("Daemon started correctly!")
|
||||||
}else{
|
} else {
|
||||||
eprintln!("Error starting launchd daemon with status: {}", status);
|
eprintln!("Error starting launchd daemon with status: {}", status);
|
||||||
}
|
}
|
||||||
}else{
|
} else {
|
||||||
eprintln!("Error starting launchd daemon: {}", res.unwrap_err());
|
eprintln!("Error starting launchd daemon: {}", res.unwrap_err());
|
||||||
}
|
}
|
||||||
}else{
|
} else {
|
||||||
fork_daemon(config_set);
|
fork_daemon(config_set);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
fn start_daemon(config_set: ConfigSet) {
|
fn start_daemon(config_set: ConfigSet) {
|
||||||
use std::process::{Command, Stdio};
|
|
||||||
use crate::sysdaemon::{verify, VerifyResult};
|
use crate::sysdaemon::{verify, VerifyResult};
|
||||||
|
use std::process::{Command, Stdio};
|
||||||
|
|
||||||
// Check if Systemd is available in the system
|
// Check if Systemd is available in the system
|
||||||
let status = Command::new("systemctl")
|
let status = Command::new("systemctl")
|
||||||
|
@ -622,11 +674,7 @@ fn start_daemon(config_set: ConfigSet) {
|
||||||
|
|
||||||
// If Systemd is not available in the system, espanso should default to unmanaged mode
|
// If Systemd is not available in the system, espanso should default to unmanaged mode
|
||||||
// See issue https://github.com/federico-terzi/espanso/issues/139
|
// See issue https://github.com/federico-terzi/espanso/issues/139
|
||||||
let force_unmanaged = if let Err(_) = status {
|
let force_unmanaged = if let Err(_) = status { true } else { false };
|
||||||
true
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
};
|
|
||||||
|
|
||||||
if config_set.default.use_system_agent && !force_unmanaged {
|
if config_set.default.use_system_agent && !force_unmanaged {
|
||||||
// Make sure espanso is currently registered in systemd
|
// Make sure espanso is currently registered in systemd
|
||||||
|
@ -634,12 +682,12 @@ fn start_daemon(config_set: ConfigSet) {
|
||||||
match res {
|
match res {
|
||||||
VerifyResult::EnabledAndValid => {
|
VerifyResult::EnabledAndValid => {
|
||||||
// Do nothing, everything is ok!
|
// Do nothing, everything is ok!
|
||||||
},
|
}
|
||||||
VerifyResult::EnabledButInvalidPath => {
|
VerifyResult::EnabledButInvalidPath => {
|
||||||
eprintln!("Updating espanso service file with new path...");
|
eprintln!("Updating espanso service file with new path...");
|
||||||
unregister_main(config_set.clone());
|
unregister_main(config_set.clone());
|
||||||
register_main(config_set);
|
register_main(config_set);
|
||||||
},
|
}
|
||||||
VerifyResult::NotEnabled => {
|
VerifyResult::NotEnabled => {
|
||||||
use dialoguer::Confirmation;
|
use dialoguer::Confirmation;
|
||||||
if Confirmation::new()
|
if Confirmation::new()
|
||||||
|
@ -656,7 +704,7 @@ fn start_daemon(config_set: ConfigSet) {
|
||||||
|
|
||||||
std::process::exit(4);
|
std::process::exit(4);
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start the espanso service
|
// Start the espanso service
|
||||||
|
@ -667,13 +715,13 @@ fn start_daemon(config_set: ConfigSet) {
|
||||||
if let Ok(status) = res {
|
if let Ok(status) = res {
|
||||||
if status.success() {
|
if status.success() {
|
||||||
println!("Daemon started correctly!")
|
println!("Daemon started correctly!")
|
||||||
}else{
|
} else {
|
||||||
eprintln!("Error starting systemd daemon with status: {}", status);
|
eprintln!("Error starting systemd daemon with status: {}", status);
|
||||||
}
|
}
|
||||||
}else{
|
} else {
|
||||||
eprintln!("Error starting systemd daemon: {}", res.unwrap_err());
|
eprintln!("Error starting systemd daemon: {}", res.unwrap_err());
|
||||||
}
|
}
|
||||||
}else{
|
} else {
|
||||||
if force_unmanaged {
|
if force_unmanaged {
|
||||||
eprintln!("Systemd is not available in this system, switching to unmanaged mode.");
|
eprintln!("Systemd is not available in this system, switching to unmanaged mode.");
|
||||||
}
|
}
|
||||||
|
@ -690,7 +738,8 @@ fn fork_daemon(config_set: ConfigSet) {
|
||||||
println!("Unable to fork.");
|
println!("Unable to fork.");
|
||||||
exit(4);
|
exit(4);
|
||||||
}
|
}
|
||||||
if pid > 0 { // Parent process exit
|
if pid > 0 {
|
||||||
|
// Parent process exit
|
||||||
println!("daemon started!");
|
println!("daemon started!");
|
||||||
exit(0);
|
exit(0);
|
||||||
}
|
}
|
||||||
|
@ -723,12 +772,11 @@ fn status_main() {
|
||||||
println!("espanso is not running");
|
println!("espanso is not running");
|
||||||
|
|
||||||
release_lock(lock_file);
|
release_lock(lock_file);
|
||||||
}else{
|
} else {
|
||||||
println!("espanso is running");
|
println!("espanso is running");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// Stop subcommand, used to stop the daemon.
|
/// Stop subcommand, used to stop the daemon.
|
||||||
fn stop_main(config_set: ConfigSet) {
|
fn stop_main(config_set: ConfigSet) {
|
||||||
// Try to acquire lock file
|
// Try to acquire lock file
|
||||||
|
@ -748,8 +796,12 @@ fn restart_main(config_set: ConfigSet) {
|
||||||
let lock_file = acquire_lock();
|
let lock_file = acquire_lock();
|
||||||
if lock_file.is_none() {
|
if lock_file.is_none() {
|
||||||
// Terminate the current espanso daemon
|
// Terminate the current espanso daemon
|
||||||
send_command_or_warn(Service::Daemon, config_set.default.clone(), IPCCommand::exit());
|
send_command_or_warn(
|
||||||
}else{
|
Service::Daemon,
|
||||||
|
config_set.default.clone(),
|
||||||
|
IPCCommand::exit(),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
release_lock(lock_file.unwrap());
|
release_lock(lock_file.unwrap());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -768,14 +820,20 @@ fn detect_main() {
|
||||||
println!("Listening for changes, now focus the window you want to analyze.");
|
println!("Listening for changes, now focus the window you want to analyze.");
|
||||||
println!("You can terminate with CTRL+C\n");
|
println!("You can terminate with CTRL+C\n");
|
||||||
|
|
||||||
let mut last_title : String = "".to_owned();
|
let mut last_title: String = "".to_owned();
|
||||||
let mut last_class : String = "".to_owned();
|
let mut last_class: String = "".to_owned();
|
||||||
let mut last_exec : String = "".to_owned();
|
let mut last_exec: String = "".to_owned();
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
let curr_title = system_manager.get_current_window_title().unwrap_or_default();
|
let curr_title = system_manager
|
||||||
let curr_class = system_manager.get_current_window_class().unwrap_or_default();
|
.get_current_window_title()
|
||||||
let curr_exec = system_manager.get_current_window_executable().unwrap_or_default();
|
.unwrap_or_default();
|
||||||
|
let curr_class = system_manager
|
||||||
|
.get_current_window_class()
|
||||||
|
.unwrap_or_default();
|
||||||
|
let curr_exec = system_manager
|
||||||
|
.get_current_window_executable()
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
// Check if a change occurred
|
// Check if a change occurred
|
||||||
if curr_title != last_title || curr_class != last_class || curr_exec != last_exec {
|
if curr_title != last_title || curr_class != last_class || curr_exec != last_exec {
|
||||||
|
@ -800,23 +858,31 @@ fn detect_main() {
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
fn detect_main() {
|
fn detect_main() {
|
||||||
thread::spawn(|| {
|
thread::spawn(|| {
|
||||||
use std::io::Write;
|
|
||||||
use std::io::stdout;
|
use std::io::stdout;
|
||||||
|
use std::io::Write;
|
||||||
|
|
||||||
let system_manager = system::get_manager();
|
let system_manager = system::get_manager();
|
||||||
|
|
||||||
println!("Listening for changes, now focus the window you want to analyze.");
|
println!("Listening for changes, now focus the window you want to analyze.");
|
||||||
println!("Warning: stay on the window for a few seconds, as it may take a while to register.");
|
println!(
|
||||||
|
"Warning: stay on the window for a few seconds, as it may take a while to register."
|
||||||
|
);
|
||||||
println!("You can terminate with CTRL+C\n");
|
println!("You can terminate with CTRL+C\n");
|
||||||
|
|
||||||
let mut last_title : String = "".to_owned();
|
let mut last_title: String = "".to_owned();
|
||||||
let mut last_class : String = "".to_owned();
|
let mut last_class: String = "".to_owned();
|
||||||
let mut last_exec : String = "".to_owned();
|
let mut last_exec: String = "".to_owned();
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
let curr_title = system_manager.get_current_window_title().unwrap_or_default();
|
let curr_title = system_manager
|
||||||
let curr_class = system_manager.get_current_window_class().unwrap_or_default();
|
.get_current_window_title()
|
||||||
let curr_exec = system_manager.get_current_window_executable().unwrap_or_default();
|
.unwrap_or_default();
|
||||||
|
let curr_class = system_manager
|
||||||
|
.get_current_window_class()
|
||||||
|
.unwrap_or_default();
|
||||||
|
let curr_exec = system_manager
|
||||||
|
.get_current_window_executable()
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
// Check if a change occurred
|
// Check if a change occurred
|
||||||
if curr_title != last_title || curr_class != last_class || curr_exec != last_exec {
|
if curr_title != last_title || curr_class != last_class || curr_exec != last_exec {
|
||||||
|
@ -844,22 +910,22 @@ fn detect_main() {
|
||||||
fn cmd_main(config_set: ConfigSet, matches: &ArgMatches) {
|
fn cmd_main(config_set: ConfigSet, matches: &ArgMatches) {
|
||||||
let command = if matches.subcommand_matches("exit").is_some() {
|
let command = if matches.subcommand_matches("exit").is_some() {
|
||||||
Some(IPCCommand::exit())
|
Some(IPCCommand::exit())
|
||||||
}else if matches.subcommand_matches("toggle").is_some() {
|
} else if matches.subcommand_matches("toggle").is_some() {
|
||||||
Some(IPCCommand {
|
Some(IPCCommand {
|
||||||
id: String::from("toggle"),
|
id: String::from("toggle"),
|
||||||
payload: String::from(""),
|
payload: String::from(""),
|
||||||
})
|
})
|
||||||
}else if matches.subcommand_matches("enable").is_some() {
|
} else if matches.subcommand_matches("enable").is_some() {
|
||||||
Some(IPCCommand {
|
Some(IPCCommand {
|
||||||
id: String::from("enable"),
|
id: String::from("enable"),
|
||||||
payload: String::from(""),
|
payload: String::from(""),
|
||||||
})
|
})
|
||||||
}else if matches.subcommand_matches("disable").is_some() {
|
} else if matches.subcommand_matches("disable").is_some() {
|
||||||
Some(IPCCommand {
|
Some(IPCCommand {
|
||||||
id: String::from("disable"),
|
id: String::from("disable"),
|
||||||
payload: String::from(""),
|
payload: String::from(""),
|
||||||
})
|
})
|
||||||
}else{
|
} else {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -889,7 +955,7 @@ fn log_main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
exit(0);
|
exit(0);
|
||||||
}else{
|
} else {
|
||||||
println!("Error reading log file");
|
println!("Error reading log file");
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
|
@ -911,44 +977,43 @@ fn install_main(_config_set: ConfigSet, matches: &ArgMatches) {
|
||||||
|
|
||||||
let repository = matches.value_of("repository_url").unwrap_or("hub");
|
let repository = matches.value_of("repository_url").unwrap_or("hub");
|
||||||
|
|
||||||
let package_resolver= Box::new(ZipPackageResolver::new());
|
let package_resolver = Box::new(ZipPackageResolver::new());
|
||||||
|
|
||||||
let allow_external: bool = if matches.is_present("external") {
|
let allow_external: bool = if matches.is_present("external") {
|
||||||
println!("Allowing external repositories");
|
println!("Allowing external repositories");
|
||||||
true
|
true
|
||||||
}else{
|
} else {
|
||||||
false
|
false
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut package_manager = DefaultPackageManager::new_default(Some(package_resolver));
|
let mut package_manager = DefaultPackageManager::new_default(Some(package_resolver));
|
||||||
|
|
||||||
let res = if repository == "hub" { // Installation from the Hub
|
let res = if repository == "hub" {
|
||||||
|
// Installation from the Hub
|
||||||
if package_manager.is_index_outdated() {
|
if package_manager.is_index_outdated() {
|
||||||
println!("Updating package index...");
|
println!("Updating package index...");
|
||||||
let res = package_manager.update_index(false);
|
let res = package_manager.update_index(false);
|
||||||
|
|
||||||
match res {
|
match res {
|
||||||
Ok(update_result) => {
|
Ok(update_result) => match update_result {
|
||||||
match update_result {
|
UpdateResult::NotOutdated => {
|
||||||
UpdateResult::NotOutdated => {
|
eprintln!("Index was already up to date");
|
||||||
eprintln!("Index was already up to date");
|
}
|
||||||
},
|
UpdateResult::Updated => {
|
||||||
UpdateResult::Updated => {
|
println!("Index updated!");
|
||||||
println!("Index updated!");
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
eprintln!("{}", e);
|
eprintln!("{}", e);
|
||||||
exit(2);
|
exit(2);
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
}else{
|
} else {
|
||||||
println!("Using cached package index, run 'espanso package refresh' to update it.")
|
println!("Using cached package index, run 'espanso package refresh' to update it.")
|
||||||
}
|
}
|
||||||
|
|
||||||
package_manager.install_package(package_name, allow_external)
|
package_manager.install_package(package_name, allow_external)
|
||||||
}else{
|
} else {
|
||||||
// Make sure the repo is a valid github url
|
// Make sure the repo is a valid github url
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
static ref GITHUB_REGEX: Regex = Regex::new(r#"https://github\.com/\S*/\S*"#).unwrap();
|
static ref GITHUB_REGEX: Regex = Regex::new(r#"https://github\.com/\S*/\S*"#).unwrap();
|
||||||
|
@ -962,61 +1027,61 @@ fn install_main(_config_set: ConfigSet, matches: &ArgMatches) {
|
||||||
|
|
||||||
if !allow_external {
|
if !allow_external {
|
||||||
Ok(InstallResult::BlockedExternalPackage(repository.to_owned()))
|
Ok(InstallResult::BlockedExternalPackage(repository.to_owned()))
|
||||||
}else{
|
} else {
|
||||||
package_manager.install_package_from_repo(package_name, repository)
|
package_manager.install_package_from_repo(package_name, repository)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
match res {
|
match res {
|
||||||
Ok(install_result) => {
|
Ok(install_result) => match install_result {
|
||||||
match install_result {
|
InstallResult::NotFoundInIndex => {
|
||||||
InstallResult::NotFoundInIndex => {
|
eprintln!("Package not found");
|
||||||
eprintln!("Package not found");
|
}
|
||||||
},
|
InstallResult::NotFoundInRepo => {
|
||||||
InstallResult::NotFoundInRepo => {
|
eprintln!(
|
||||||
eprintln!("Package not found in repository, are you sure the folder exist in the repo?");
|
"Package not found in repository, are you sure the folder exist in the repo?"
|
||||||
},
|
);
|
||||||
InstallResult::UnableToParsePackageInfo => {
|
}
|
||||||
eprintln!("Unable to parse Package info from README.md");
|
InstallResult::UnableToParsePackageInfo => {
|
||||||
},
|
eprintln!("Unable to parse Package info from README.md");
|
||||||
InstallResult::MissingPackageVersion => {
|
}
|
||||||
eprintln!("Missing package version");
|
InstallResult::MissingPackageVersion => {
|
||||||
},
|
eprintln!("Missing package version");
|
||||||
InstallResult::AlreadyInstalled => {
|
}
|
||||||
eprintln!("{} already installed!", package_name);
|
InstallResult::AlreadyInstalled => {
|
||||||
},
|
eprintln!("{} already installed!", package_name);
|
||||||
InstallResult::BlockedExternalPackage(repo_url) => {
|
}
|
||||||
eprintln!("Warning: the requested package is hosted on an external repository:");
|
InstallResult::BlockedExternalPackage(repo_url) => {
|
||||||
eprintln!();
|
eprintln!("Warning: the requested package is hosted on an external repository:");
|
||||||
eprintln!("{}", repo_url);
|
eprintln!();
|
||||||
eprintln!();
|
eprintln!("{}", repo_url);
|
||||||
eprintln!("and its contents may not have been verified by espanso.");
|
eprintln!();
|
||||||
eprintln!();
|
eprintln!("and its contents may not have been verified by espanso.");
|
||||||
eprintln!("For your security, espanso blocks packages that are not verified.");
|
eprintln!();
|
||||||
eprintln!("If you want to install the package anyway, you can force espanso");
|
eprintln!("For your security, espanso blocks packages that are not verified.");
|
||||||
eprintln!("to install it with the following command, but please do it only");
|
eprintln!("If you want to install the package anyway, you can force espanso");
|
||||||
eprintln!("if you trust the source or you verified the contents of the package");
|
eprintln!("to install it with the following command, but please do it only");
|
||||||
eprintln!("by checking out the repository listed above.");
|
eprintln!("if you trust the source or you verified the contents of the package");
|
||||||
eprintln!();
|
eprintln!("by checking out the repository listed above.");
|
||||||
|
eprintln!();
|
||||||
|
|
||||||
if repository == "hub" {
|
if repository == "hub" {
|
||||||
eprintln!("espanso install {} --external", package_name);
|
eprintln!("espanso install {} --external", package_name);
|
||||||
}else{
|
} else {
|
||||||
eprintln!("espanso install {} {} --external", package_name, repository);
|
eprintln!("espanso install {} {} --external", package_name, repository);
|
||||||
}
|
|
||||||
eprintln!();
|
|
||||||
}
|
}
|
||||||
InstallResult::Installed => {
|
eprintln!();
|
||||||
println!("{} successfully installed!", package_name);
|
}
|
||||||
println!();
|
InstallResult::Installed => {
|
||||||
println!("You need to restart espanso for changes to take effect, using:");
|
println!("{} successfully installed!", package_name);
|
||||||
println!(" espanso restart");
|
println!();
|
||||||
},
|
println!("You need to restart espanso for changes to take effect, using:");
|
||||||
|
println!(" espanso restart");
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
eprintln!("{}", e);
|
eprintln!("{}", e);
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1031,22 +1096,20 @@ fn remove_package_main(_config_set: ConfigSet, matches: &ArgMatches) {
|
||||||
let res = package_manager.remove_package(package_name);
|
let res = package_manager.remove_package(package_name);
|
||||||
|
|
||||||
match res {
|
match res {
|
||||||
Ok(remove_result) => {
|
Ok(remove_result) => match remove_result {
|
||||||
match remove_result {
|
RemoveResult::NotFound => {
|
||||||
RemoveResult::NotFound => {
|
eprintln!("{} package was not installed.", package_name);
|
||||||
eprintln!("{} package was not installed.", package_name);
|
}
|
||||||
},
|
RemoveResult::Removed => {
|
||||||
RemoveResult::Removed => {
|
println!("{} successfully removed!", package_name);
|
||||||
println!("{} successfully removed!", package_name);
|
println!();
|
||||||
println!();
|
println!("You need to restart espanso for changes to take effect, using:");
|
||||||
println!("You need to restart espanso for changes to take effect, using:");
|
println!(" espanso restart");
|
||||||
println!(" espanso restart");
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
eprintln!("{}", e);
|
eprintln!("{}", e);
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1056,20 +1119,18 @@ fn update_index_main(_config_set: ConfigSet) {
|
||||||
let res = package_manager.update_index(true);
|
let res = package_manager.update_index(true);
|
||||||
|
|
||||||
match res {
|
match res {
|
||||||
Ok(update_result) => {
|
Ok(update_result) => match update_result {
|
||||||
match update_result {
|
UpdateResult::NotOutdated => {
|
||||||
UpdateResult::NotOutdated => {
|
eprintln!("Index was already up to date");
|
||||||
eprintln!("Index was already up to date");
|
}
|
||||||
},
|
UpdateResult::Updated => {
|
||||||
UpdateResult::Updated => {
|
println!("Index updated!");
|
||||||
println!("Index updated!");
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
eprintln!("{}", e);
|
eprintln!("{}", e);
|
||||||
exit(2);
|
exit(2);
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1082,7 +1143,7 @@ fn list_package_main(_config_set: ConfigSet, matches: &ArgMatches) {
|
||||||
for package in list.iter() {
|
for package in list.iter() {
|
||||||
println!("{:?}", package);
|
println!("{:?}", package);
|
||||||
}
|
}
|
||||||
}else{
|
} else {
|
||||||
for package in list.iter() {
|
for package in list.iter() {
|
||||||
println!("{} - {}", package.name, package.version);
|
println!("{} - {}", package.name, package.version);
|
||||||
}
|
}
|
||||||
|
@ -1096,14 +1157,14 @@ fn path_main(_config_set: ConfigSet, matches: &ArgMatches) {
|
||||||
|
|
||||||
if matches.subcommand_matches("config").is_some() {
|
if matches.subcommand_matches("config").is_some() {
|
||||||
println!("{}", config.to_string_lossy());
|
println!("{}", config.to_string_lossy());
|
||||||
}else if matches.subcommand_matches("packages").is_some() {
|
} else if matches.subcommand_matches("packages").is_some() {
|
||||||
println!("{}", packages.to_string_lossy());
|
println!("{}", packages.to_string_lossy());
|
||||||
}else if matches.subcommand_matches("data").is_some() {
|
} else if matches.subcommand_matches("data").is_some() {
|
||||||
println!("{}", data.to_string_lossy());
|
println!("{}", data.to_string_lossy());
|
||||||
}else if matches.subcommand_matches("default").is_some() {
|
} else if matches.subcommand_matches("default").is_some() {
|
||||||
let default_file = config.join(crate::config::DEFAULT_CONFIG_FILE_NAME);
|
let default_file = config.join(crate::config::DEFAULT_CONFIG_FILE_NAME);
|
||||||
println!("{}", default_file.to_string_lossy());
|
println!("{}", default_file.to_string_lossy());
|
||||||
}else{
|
} else {
|
||||||
println!("Config: {}", config.to_string_lossy());
|
println!("Config: {}", config.to_string_lossy());
|
||||||
println!("Packages: {}", packages.to_string_lossy());
|
println!("Packages: {}", packages.to_string_lossy());
|
||||||
println!("Data: {}", data.to_string_lossy());
|
println!("Data: {}", data.to_string_lossy());
|
||||||
|
@ -1117,11 +1178,11 @@ fn edit_main(matches: &ArgMatches) {
|
||||||
let config_dir = crate::context::get_config_dir();
|
let config_dir = crate::context::get_config_dir();
|
||||||
|
|
||||||
let config_path = match config {
|
let config_path = match config {
|
||||||
"default" => {
|
"default" => config_dir.join(crate::config::DEFAULT_CONFIG_FILE_NAME),
|
||||||
config_dir.join(crate::config::DEFAULT_CONFIG_FILE_NAME)
|
name => {
|
||||||
},
|
// Otherwise, search in the user/ config folder
|
||||||
name => { // Otherwise, search in the user/ config folder
|
config_dir
|
||||||
config_dir.join(crate::config::USER_CONFIGS_FOLDER_NAME)
|
.join(crate::config::USER_CONFIGS_FOLDER_NAME)
|
||||||
.join(name.to_owned() + ".yml")
|
.join(name.to_owned() + ".yml")
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -1130,39 +1191,44 @@ fn edit_main(matches: &ArgMatches) {
|
||||||
|
|
||||||
// Based on the fact that the file already exists or not, we should detect in different
|
// Based on the fact that the file already exists or not, we should detect in different
|
||||||
// ways if a reload is needed
|
// ways if a reload is needed
|
||||||
let should_reload =if config_path.exists() {
|
let should_reload = if config_path.exists() {
|
||||||
// Get the last modified date, so that we can detect if the user actually edits the file
|
// Get the last modified date, so that we can detect if the user actually edits the file
|
||||||
// before reloading
|
// before reloading
|
||||||
let metadata = std::fs::metadata(&config_path).expect("cannot gather file metadata");
|
let metadata = std::fs::metadata(&config_path).expect("cannot gather file metadata");
|
||||||
let last_modified = metadata.modified().expect("cannot read file last modified date");
|
let last_modified = metadata
|
||||||
|
.modified()
|
||||||
|
.expect("cannot read file last modified date");
|
||||||
|
|
||||||
let result = crate::edit::open_editor(&config_path);
|
let result = crate::edit::open_editor(&config_path);
|
||||||
if result {
|
if result {
|
||||||
let new_metadata = std::fs::metadata(&config_path).expect("cannot gather file metadata");
|
let new_metadata =
|
||||||
let new_last_modified = new_metadata.modified().expect("cannot read file last modified date");
|
std::fs::metadata(&config_path).expect("cannot gather file metadata");
|
||||||
|
let new_last_modified = new_metadata
|
||||||
|
.modified()
|
||||||
|
.expect("cannot read file last modified date");
|
||||||
|
|
||||||
if last_modified != new_last_modified {
|
if last_modified != new_last_modified {
|
||||||
println!("File has been modified, reloading configuration");
|
println!("File has been modified, reloading configuration");
|
||||||
true
|
true
|
||||||
}else{
|
} else {
|
||||||
println!("File has not been modified, avoiding reload");
|
println!("File has not been modified, avoiding reload");
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
}else{
|
} else {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
}else{
|
} else {
|
||||||
let result = crate::edit::open_editor(&config_path);
|
let result = crate::edit::open_editor(&config_path);
|
||||||
if result {
|
if result {
|
||||||
// If the file has been created, we should reload the espanso config
|
// If the file has been created, we should reload the espanso config
|
||||||
if config_path.exists() {
|
if config_path.exists() {
|
||||||
println!("A new file has been created, reloading configuration");
|
println!("A new file has been created, reloading configuration");
|
||||||
true
|
true
|
||||||
}else{
|
} else {
|
||||||
println!("No file has been created, avoiding reload");
|
println!("No file has been created, avoiding reload");
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
}else{
|
} else {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -1170,11 +1236,11 @@ fn edit_main(matches: &ArgMatches) {
|
||||||
let no_restart: bool = if matches.is_present("norestart") {
|
let no_restart: bool = if matches.is_present("norestart") {
|
||||||
println!("Avoiding automatic restart");
|
println!("Avoiding automatic restart");
|
||||||
true
|
true
|
||||||
}else{
|
} else {
|
||||||
false
|
false
|
||||||
};
|
};
|
||||||
|
|
||||||
if should_reload && !no_restart{
|
if should_reload && !no_restart {
|
||||||
// Load the configuration
|
// Load the configuration
|
||||||
let config_set = ConfigSet::load_default().unwrap_or_else(|e| {
|
let config_set = ConfigSet::load_default().unwrap_or_else(|e| {
|
||||||
eprintln!("{}", e);
|
eprintln!("{}", e);
|
||||||
|
@ -1203,7 +1269,7 @@ fn acquire_custom_lock(name: &str) -> Option<File> {
|
||||||
let res = file.try_lock_exclusive();
|
let res = file.try_lock_exclusive();
|
||||||
|
|
||||||
if res.is_ok() {
|
if res.is_ok() {
|
||||||
return Some(file)
|
return Some(file);
|
||||||
}
|
}
|
||||||
|
|
||||||
None
|
None
|
||||||
|
|
|
@ -17,13 +17,13 @@
|
||||||
* along with espanso. If not, see <https://www.gnu.org/licenses/>.
|
* along with espanso. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
use serde::{Serialize, Deserialize, Deserializer};
|
|
||||||
use crate::event::{KeyEvent, KeyModifier};
|
|
||||||
use crate::event::KeyEventReceiver;
|
use crate::event::KeyEventReceiver;
|
||||||
use serde_yaml::Mapping;
|
use crate::event::{KeyEvent, KeyModifier};
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
use std::path::PathBuf;
|
use serde::{Deserialize, Deserializer, Serialize};
|
||||||
|
use serde_yaml::Mapping;
|
||||||
use std::fs;
|
use std::fs;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
pub(crate) mod scrolling;
|
pub(crate) mod scrolling;
|
||||||
|
|
||||||
|
@ -61,16 +61,17 @@ pub struct ImageContent {
|
||||||
pub path: PathBuf,
|
pub path: PathBuf,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl <'de> serde::Deserialize<'de> for Match {
|
impl<'de> serde::Deserialize<'de> for Match {
|
||||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> where
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||||
D: Deserializer<'de> {
|
where
|
||||||
|
D: Deserializer<'de>,
|
||||||
|
{
|
||||||
let auto_match = AutoMatch::deserialize(deserializer)?;
|
let auto_match = AutoMatch::deserialize(deserializer)?;
|
||||||
Ok(Match::from(&auto_match))
|
Ok(Match::from(&auto_match))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> From<&'a AutoMatch> for Match{
|
impl<'a> From<&'a AutoMatch> for Match {
|
||||||
fn from(other: &'a AutoMatch) -> Self {
|
fn from(other: &'a AutoMatch) -> Self {
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
static ref VAR_REGEX: Regex = Regex::new("\\{\\{\\s*(\\w+)\\s*\\}\\}").unwrap();
|
static ref VAR_REGEX: Regex = Regex::new("\\{\\{\\s*(\\w+)\\s*\\}\\}").unwrap();
|
||||||
|
@ -78,9 +79,9 @@ impl<'a> From<&'a AutoMatch> for Match{
|
||||||
|
|
||||||
let mut triggers = if !other.triggers.is_empty() {
|
let mut triggers = if !other.triggers.is_empty() {
|
||||||
other.triggers.clone()
|
other.triggers.clone()
|
||||||
}else if !other.trigger.is_empty() {
|
} else if !other.trigger.is_empty() {
|
||||||
vec!(other.trigger.clone())
|
vec![other.trigger.clone()]
|
||||||
}else{
|
} else {
|
||||||
panic!("Match does not have any trigger defined: {:?}", other)
|
panic!("Match does not have any trigger defined: {:?}", other)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -89,44 +90,48 @@ impl<'a> From<&'a AutoMatch> for Match{
|
||||||
// "hello", "Hello", "HELLO"
|
// "hello", "Hello", "HELLO"
|
||||||
if other.propagate_case {
|
if other.propagate_case {
|
||||||
// List with first letter capitalized
|
// List with first letter capitalized
|
||||||
let first_capitalized : Vec<String> = triggers.iter().map(|trigger| {
|
let first_capitalized: Vec<String> = triggers
|
||||||
let capitalized = trigger.clone();
|
.iter()
|
||||||
let mut v: Vec<char> = capitalized.chars().collect();
|
.map(|trigger| {
|
||||||
|
let capitalized = trigger.clone();
|
||||||
|
let mut v: Vec<char> = capitalized.chars().collect();
|
||||||
|
|
||||||
// Capitalize the first alphabetic letter
|
// Capitalize the first alphabetic letter
|
||||||
// See issue #244
|
// See issue #244
|
||||||
let first_alphabetic = v.iter().position(|c| {
|
let first_alphabetic = v.iter().position(|c| c.is_alphabetic()).unwrap_or(0);
|
||||||
c.is_alphabetic()
|
|
||||||
}).unwrap_or(0);
|
|
||||||
|
|
||||||
v[first_alphabetic] = v[first_alphabetic].to_uppercase().nth(0).unwrap();
|
v[first_alphabetic] = v[first_alphabetic].to_uppercase().nth(0).unwrap();
|
||||||
v.into_iter().collect()
|
v.into_iter().collect()
|
||||||
}).collect();
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
let all_capitalized : Vec<String> = triggers.iter().map(|trigger| {
|
let all_capitalized: Vec<String> = triggers
|
||||||
trigger.to_uppercase()
|
.iter()
|
||||||
}).collect();
|
.map(|trigger| trigger.to_uppercase())
|
||||||
|
.collect();
|
||||||
|
|
||||||
triggers.extend(first_capitalized);
|
triggers.extend(first_capitalized);
|
||||||
triggers.extend(all_capitalized);
|
triggers.extend(all_capitalized);
|
||||||
}
|
}
|
||||||
|
|
||||||
let trigger_sequences = triggers.iter().map(|trigger| {
|
let trigger_sequences = triggers
|
||||||
// Calculate the trigger sequence
|
.iter()
|
||||||
let mut trigger_sequence = Vec::new();
|
.map(|trigger| {
|
||||||
let trigger_chars : Vec<char> = trigger.chars().collect();
|
// Calculate the trigger sequence
|
||||||
trigger_sequence.extend(trigger_chars.into_iter().map(|c| {
|
let mut trigger_sequence = Vec::new();
|
||||||
TriggerEntry::Char(c)
|
let trigger_chars: Vec<char> = trigger.chars().collect();
|
||||||
}));
|
trigger_sequence.extend(trigger_chars.into_iter().map(|c| TriggerEntry::Char(c)));
|
||||||
if other.word { // If it's a word match, end with a word separator
|
if other.word {
|
||||||
trigger_sequence.push(TriggerEntry::WordSeparator);
|
// If it's a word match, end with a word separator
|
||||||
}
|
trigger_sequence.push(TriggerEntry::WordSeparator);
|
||||||
|
}
|
||||||
|
|
||||||
trigger_sequence
|
trigger_sequence
|
||||||
}).collect();
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let content = if let Some(replace) = &other.replace {
|
||||||
let content = if let Some(replace) = &other.replace { // Text match
|
// Text match
|
||||||
let new_replace = replace.clone();
|
let new_replace = replace.clone();
|
||||||
|
|
||||||
// Check if the match contains variables
|
// Check if the match contains variables
|
||||||
|
@ -139,11 +144,12 @@ impl<'a> From<&'a AutoMatch> for Match{
|
||||||
};
|
};
|
||||||
|
|
||||||
MatchContentType::Text(content)
|
MatchContentType::Text(content)
|
||||||
}else if let Some(image_path) = &other.image_path { // Image match
|
} else if let Some(image_path) = &other.image_path {
|
||||||
|
// Image match
|
||||||
// On Windows, we have to replace the forward / with the backslash \ in the path
|
// On Windows, we have to replace the forward / with the backslash \ in the path
|
||||||
let new_path = if cfg!(target_os = "windows") {
|
let new_path = if cfg!(target_os = "windows") {
|
||||||
image_path.replace("/", "\\")
|
image_path.replace("/", "\\")
|
||||||
}else{
|
} else {
|
||||||
image_path.to_owned()
|
image_path.to_owned()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -153,20 +159,20 @@ impl<'a> From<&'a AutoMatch> for Match{
|
||||||
let config_path = fs::canonicalize(&config_dir);
|
let config_path = fs::canonicalize(&config_dir);
|
||||||
let config_path = if let Ok(config_path) = config_path {
|
let config_path = if let Ok(config_path) = config_path {
|
||||||
config_path.to_string_lossy().into_owned()
|
config_path.to_string_lossy().into_owned()
|
||||||
}else{
|
} else {
|
||||||
"".to_owned()
|
"".to_owned()
|
||||||
};
|
};
|
||||||
new_path.replace("$CONFIG", &config_path)
|
new_path.replace("$CONFIG", &config_path)
|
||||||
}else{
|
} else {
|
||||||
new_path.to_owned()
|
new_path.to_owned()
|
||||||
};
|
};
|
||||||
|
|
||||||
let content = ImageContent {
|
let content = ImageContent {
|
||||||
path: PathBuf::from(new_path)
|
path: PathBuf::from(new_path),
|
||||||
};
|
};
|
||||||
|
|
||||||
MatchContentType::Image(content)
|
MatchContentType::Image(content)
|
||||||
}else {
|
} else {
|
||||||
eprintln!("ERROR: no action specified for match {}, please specify either 'replace' or 'image_path'", other.trigger);
|
eprintln!("ERROR: no action specified for match {}, please specify either 'replace' or 'image_path'", other.trigger);
|
||||||
std::process::exit(2);
|
std::process::exit(2);
|
||||||
};
|
};
|
||||||
|
@ -214,15 +220,33 @@ struct AutoMatch {
|
||||||
pub force_clipboard: bool,
|
pub force_clipboard: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn default_trigger() -> String {"".to_owned()}
|
fn default_trigger() -> String {
|
||||||
fn default_triggers() -> Vec<String> {Vec::new()}
|
"".to_owned()
|
||||||
fn default_vars() -> Vec<MatchVariable> {Vec::new()}
|
}
|
||||||
fn default_word() -> bool {false}
|
fn default_triggers() -> Vec<String> {
|
||||||
fn default_passive_only() -> bool {false}
|
Vec::new()
|
||||||
fn default_replace() -> Option<String> {None}
|
}
|
||||||
fn default_image_path() -> Option<String> {None}
|
fn default_vars() -> Vec<MatchVariable> {
|
||||||
fn default_propagate_case() -> bool {false}
|
Vec::new()
|
||||||
fn default_force_clipboard() -> bool {false}
|
}
|
||||||
|
fn default_word() -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
fn default_passive_only() -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
fn default_replace() -> Option<String> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
fn default_image_path() -> Option<String> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
fn default_propagate_case() -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
fn default_force_clipboard() -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
pub struct MatchVariable {
|
pub struct MatchVariable {
|
||||||
|
@ -235,12 +259,14 @@ pub struct MatchVariable {
|
||||||
pub params: Mapping,
|
pub params: Mapping,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn default_params() -> Mapping {Mapping::new()}
|
fn default_params() -> Mapping {
|
||||||
|
Mapping::new()
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
|
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
|
||||||
pub enum TriggerEntry {
|
pub enum TriggerEntry {
|
||||||
Char(char),
|
Char(char),
|
||||||
WordSeparator
|
WordSeparator,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait MatchReceiver {
|
pub trait MatchReceiver {
|
||||||
|
@ -249,29 +275,28 @@ pub trait MatchReceiver {
|
||||||
fn on_passive(&self);
|
fn on_passive(&self);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait Matcher : KeyEventReceiver {
|
pub trait Matcher: KeyEventReceiver {
|
||||||
fn handle_char(&self, c: &str);
|
fn handle_char(&self, c: &str);
|
||||||
fn handle_modifier(&self, m: KeyModifier);
|
fn handle_modifier(&self, m: KeyModifier);
|
||||||
fn handle_other(&self);
|
fn handle_other(&self);
|
||||||
}
|
}
|
||||||
|
|
||||||
impl <M: Matcher> KeyEventReceiver for M {
|
impl<M: Matcher> KeyEventReceiver for M {
|
||||||
fn on_key_event(&self, e: KeyEvent) {
|
fn on_key_event(&self, e: KeyEvent) {
|
||||||
match e {
|
match e {
|
||||||
KeyEvent::Char(c) => {
|
KeyEvent::Char(c) => {
|
||||||
self.handle_char(&c);
|
self.handle_char(&c);
|
||||||
},
|
}
|
||||||
KeyEvent::Modifier(m) => {
|
KeyEvent::Modifier(m) => {
|
||||||
self.handle_modifier(m);
|
self.handle_modifier(m);
|
||||||
},
|
}
|
||||||
KeyEvent::Other => {
|
KeyEvent::Other => {
|
||||||
self.handle_other();
|
self.handle_other();
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// TESTS
|
// TESTS
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
@ -285,15 +310,15 @@ mod tests {
|
||||||
replace: "There are no variables"
|
replace: "There are no variables"
|
||||||
"###;
|
"###;
|
||||||
|
|
||||||
let _match : Match = serde_yaml::from_str(match_str).unwrap();
|
let _match: Match = serde_yaml::from_str(match_str).unwrap();
|
||||||
|
|
||||||
match _match.content {
|
match _match.content {
|
||||||
MatchContentType::Text(content) => {
|
MatchContentType::Text(content) => {
|
||||||
assert_eq!(content._has_vars, false);
|
assert_eq!(content._has_vars, false);
|
||||||
},
|
}
|
||||||
_ => {
|
_ => {
|
||||||
assert!(false);
|
assert!(false);
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -304,15 +329,15 @@ mod tests {
|
||||||
replace: "There are {{one}} and {{two}} variables"
|
replace: "There are {{one}} and {{two}} variables"
|
||||||
"###;
|
"###;
|
||||||
|
|
||||||
let _match : Match = serde_yaml::from_str(match_str).unwrap();
|
let _match: Match = serde_yaml::from_str(match_str).unwrap();
|
||||||
|
|
||||||
match _match.content {
|
match _match.content {
|
||||||
MatchContentType::Text(content) => {
|
MatchContentType::Text(content) => {
|
||||||
assert_eq!(content._has_vars, true);
|
assert_eq!(content._has_vars, true);
|
||||||
},
|
}
|
||||||
_ => {
|
_ => {
|
||||||
assert!(false);
|
assert!(false);
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -323,15 +348,15 @@ mod tests {
|
||||||
replace: "There is {{ one }} variable"
|
replace: "There is {{ one }} variable"
|
||||||
"###;
|
"###;
|
||||||
|
|
||||||
let _match : Match = serde_yaml::from_str(match_str).unwrap();
|
let _match: Match = serde_yaml::from_str(match_str).unwrap();
|
||||||
|
|
||||||
match _match.content {
|
match _match.content {
|
||||||
MatchContentType::Text(content) => {
|
MatchContentType::Text(content) => {
|
||||||
assert_eq!(content._has_vars, true);
|
assert_eq!(content._has_vars, true);
|
||||||
},
|
}
|
||||||
_ => {
|
_ => {
|
||||||
assert!(false);
|
assert!(false);
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -342,7 +367,7 @@ mod tests {
|
||||||
replace: "This is a test"
|
replace: "This is a test"
|
||||||
"###;
|
"###;
|
||||||
|
|
||||||
let _match : Match = serde_yaml::from_str(match_str).unwrap();
|
let _match: Match = serde_yaml::from_str(match_str).unwrap();
|
||||||
|
|
||||||
assert_eq!(_match._trigger_sequences[0][0], TriggerEntry::Char('t'));
|
assert_eq!(_match._trigger_sequences[0][0], TriggerEntry::Char('t'));
|
||||||
assert_eq!(_match._trigger_sequences[0][1], TriggerEntry::Char('e'));
|
assert_eq!(_match._trigger_sequences[0][1], TriggerEntry::Char('e'));
|
||||||
|
@ -358,7 +383,7 @@ mod tests {
|
||||||
word: true
|
word: true
|
||||||
"###;
|
"###;
|
||||||
|
|
||||||
let _match : Match = serde_yaml::from_str(match_str).unwrap();
|
let _match: Match = serde_yaml::from_str(match_str).unwrap();
|
||||||
|
|
||||||
assert_eq!(_match._trigger_sequences[0][0], TriggerEntry::Char('t'));
|
assert_eq!(_match._trigger_sequences[0][0], TriggerEntry::Char('t'));
|
||||||
assert_eq!(_match._trigger_sequences[0][1], TriggerEntry::Char('e'));
|
assert_eq!(_match._trigger_sequences[0][1], TriggerEntry::Char('e'));
|
||||||
|
@ -374,15 +399,15 @@ mod tests {
|
||||||
image_path: "/path/to/file"
|
image_path: "/path/to/file"
|
||||||
"###;
|
"###;
|
||||||
|
|
||||||
let _match : Match = serde_yaml::from_str(match_str).unwrap();
|
let _match: Match = serde_yaml::from_str(match_str).unwrap();
|
||||||
|
|
||||||
match _match.content {
|
match _match.content {
|
||||||
MatchContentType::Image(content) => {
|
MatchContentType::Image(content) => {
|
||||||
assert_eq!(content.path, PathBuf::from("/path/to/file"));
|
assert_eq!(content.path, PathBuf::from("/path/to/file"));
|
||||||
},
|
}
|
||||||
_ => {
|
_ => {
|
||||||
assert!(false);
|
assert!(false);
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -393,7 +418,7 @@ mod tests {
|
||||||
replace: "This is a test"
|
replace: "This is a test"
|
||||||
"###;
|
"###;
|
||||||
|
|
||||||
let _match : Match = serde_yaml::from_str(match_str).unwrap();
|
let _match: Match = serde_yaml::from_str(match_str).unwrap();
|
||||||
|
|
||||||
assert_eq!(_match.triggers, vec![":test"])
|
assert_eq!(_match.triggers, vec![":test"])
|
||||||
}
|
}
|
||||||
|
@ -407,7 +432,7 @@ mod tests {
|
||||||
replace: "This is a test"
|
replace: "This is a test"
|
||||||
"###;
|
"###;
|
||||||
|
|
||||||
let _match : Match = serde_yaml::from_str(match_str).unwrap();
|
let _match: Match = serde_yaml::from_str(match_str).unwrap();
|
||||||
|
|
||||||
assert_eq!(_match.triggers, vec![":test1", ":test2"])
|
assert_eq!(_match.triggers, vec![":test1", ":test2"])
|
||||||
}
|
}
|
||||||
|
@ -419,7 +444,7 @@ mod tests {
|
||||||
replace: "This is a test"
|
replace: "This is a test"
|
||||||
"###;
|
"###;
|
||||||
|
|
||||||
let _match : Match = serde_yaml::from_str(match_str).unwrap();
|
let _match: Match = serde_yaml::from_str(match_str).unwrap();
|
||||||
|
|
||||||
assert_eq!(_match.triggers, vec![":test1", ":test2"])
|
assert_eq!(_match.triggers, vec![":test1", ":test2"])
|
||||||
}
|
}
|
||||||
|
@ -432,7 +457,7 @@ mod tests {
|
||||||
propagate_case: true
|
propagate_case: true
|
||||||
"###;
|
"###;
|
||||||
|
|
||||||
let _match : Match = serde_yaml::from_str(match_str).unwrap();
|
let _match: Match = serde_yaml::from_str(match_str).unwrap();
|
||||||
|
|
||||||
assert_eq!(_match.triggers, vec!["hello", "Hello", "HELLO"])
|
assert_eq!(_match.triggers, vec!["hello", "Hello", "HELLO"])
|
||||||
}
|
}
|
||||||
|
@ -445,9 +470,12 @@ mod tests {
|
||||||
propagate_case: true
|
propagate_case: true
|
||||||
"###;
|
"###;
|
||||||
|
|
||||||
let _match : Match = serde_yaml::from_str(match_str).unwrap();
|
let _match: Match = serde_yaml::from_str(match_str).unwrap();
|
||||||
|
|
||||||
assert_eq!(_match.triggers, vec!["hello", "hi", "Hello", "Hi", "HELLO", "HI"])
|
assert_eq!(
|
||||||
|
_match.triggers,
|
||||||
|
vec!["hello", "hi", "Hello", "Hi", "HELLO", "HI"]
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -459,7 +487,7 @@ mod tests {
|
||||||
propagate_case: true
|
propagate_case: true
|
||||||
"###;
|
"###;
|
||||||
|
|
||||||
let _match : Match = serde_yaml::from_str(match_str).unwrap();
|
let _match: Match = serde_yaml::from_str(match_str).unwrap();
|
||||||
|
|
||||||
assert_eq!(_match._trigger_sequences[0][0], TriggerEntry::Char('t'));
|
assert_eq!(_match._trigger_sequences[0][0], TriggerEntry::Char('t'));
|
||||||
assert_eq!(_match._trigger_sequences[0][1], TriggerEntry::Char('e'));
|
assert_eq!(_match._trigger_sequences[0][1], TriggerEntry::Char('e'));
|
||||||
|
@ -487,7 +515,7 @@ mod tests {
|
||||||
replace: ""
|
replace: ""
|
||||||
"###;
|
"###;
|
||||||
|
|
||||||
let _match : Match = serde_yaml::from_str(match_str).unwrap();
|
let _match: Match = serde_yaml::from_str(match_str).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -498,7 +526,7 @@ mod tests {
|
||||||
propagate_case: true
|
propagate_case: true
|
||||||
"###;
|
"###;
|
||||||
|
|
||||||
let _match : Match = serde_yaml::from_str(match_str).unwrap();
|
let _match: Match = serde_yaml::from_str(match_str).unwrap();
|
||||||
|
|
||||||
assert_eq!(_match.triggers, vec![":hello", ":Hello", ":HELLO"])
|
assert_eq!(_match.triggers, vec![":hello", ":Hello", ":HELLO"])
|
||||||
}
|
}
|
||||||
|
@ -511,7 +539,7 @@ mod tests {
|
||||||
propagate_case: true
|
propagate_case: true
|
||||||
"###;
|
"###;
|
||||||
|
|
||||||
let _match : Match = serde_yaml::from_str(match_str).unwrap();
|
let _match: Match = serde_yaml::from_str(match_str).unwrap();
|
||||||
|
|
||||||
assert_eq!(_match.triggers, vec![":..", ":..", ":.."])
|
assert_eq!(_match.triggers, vec![":..", ":..", ":.."])
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,13 +17,13 @@
|
||||||
* along with espanso. If not, see <https://www.gnu.org/licenses/>.
|
* along with espanso. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
use crate::matcher::{Match, MatchReceiver, TriggerEntry};
|
|
||||||
use std::cell::{RefCell};
|
|
||||||
use crate::event::{KeyModifier, ActionEventReceiver, ActionType};
|
|
||||||
use crate::config::ConfigManager;
|
use crate::config::ConfigManager;
|
||||||
use crate::event::KeyModifier::{BACKSPACE, LEFT_SHIFT, RIGHT_SHIFT, CAPS_LOCK};
|
use crate::event::KeyModifier::{BACKSPACE, CAPS_LOCK, LEFT_SHIFT, RIGHT_SHIFT};
|
||||||
use std::time::SystemTime;
|
use crate::event::{ActionEventReceiver, ActionType, KeyModifier};
|
||||||
|
use crate::matcher::{Match, MatchReceiver, TriggerEntry};
|
||||||
|
use std::cell::RefCell;
|
||||||
use std::collections::VecDeque;
|
use std::collections::VecDeque;
|
||||||
|
use std::time::SystemTime;
|
||||||
|
|
||||||
pub struct ScrollingMatcher<'a, R: MatchReceiver, M: ConfigManager<'a>> {
|
pub struct ScrollingMatcher<'a, R: MatchReceiver, M: ConfigManager<'a>> {
|
||||||
config_manager: &'a M,
|
config_manager: &'a M,
|
||||||
|
@ -40,16 +40,16 @@ struct MatchEntry<'a> {
|
||||||
start: usize,
|
start: usize,
|
||||||
count: usize,
|
count: usize,
|
||||||
trigger_offset: usize, // The index of the trigger in the Match that matched
|
trigger_offset: usize, // The index of the trigger in the Match that matched
|
||||||
_match: &'a Match
|
_match: &'a Match,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl <'a, R: MatchReceiver, M: ConfigManager<'a>> ScrollingMatcher<'a, R, M> {
|
impl<'a, R: MatchReceiver, M: ConfigManager<'a>> ScrollingMatcher<'a, R, M> {
|
||||||
pub fn new(config_manager: &'a M, receiver: &'a R) -> ScrollingMatcher<'a, R, M> {
|
pub fn new(config_manager: &'a M, receiver: &'a R) -> ScrollingMatcher<'a, R, M> {
|
||||||
let current_set_queue = RefCell::new(VecDeque::new());
|
let current_set_queue = RefCell::new(VecDeque::new());
|
||||||
let toggle_press_time = RefCell::new(SystemTime::now());
|
let toggle_press_time = RefCell::new(SystemTime::now());
|
||||||
let passive_press_time = RefCell::new(SystemTime::now());
|
let passive_press_time = RefCell::new(SystemTime::now());
|
||||||
|
|
||||||
ScrollingMatcher{
|
ScrollingMatcher {
|
||||||
config_manager,
|
config_manager,
|
||||||
receiver,
|
receiver,
|
||||||
current_set_queue,
|
current_set_queue,
|
||||||
|
@ -74,19 +74,21 @@ impl <'a, R: MatchReceiver, M: ConfigManager<'a>> ScrollingMatcher<'a, R, M> {
|
||||||
self.receiver.on_enable_update(*is_enabled);
|
self.receiver.on_enable_update(*is_enabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_matching(mtc: &Match, current_char: &str, start: usize, trigger_offset: usize, is_current_word_separator: bool) -> bool {
|
fn is_matching(
|
||||||
|
mtc: &Match,
|
||||||
|
current_char: &str,
|
||||||
|
start: usize,
|
||||||
|
trigger_offset: usize,
|
||||||
|
is_current_word_separator: bool,
|
||||||
|
) -> bool {
|
||||||
match mtc._trigger_sequences[trigger_offset][start] {
|
match mtc._trigger_sequences[trigger_offset][start] {
|
||||||
TriggerEntry::Char(c) => {
|
TriggerEntry::Char(c) => current_char.starts_with(c),
|
||||||
current_char.starts_with(c)
|
TriggerEntry::WordSeparator => is_current_word_separator,
|
||||||
},
|
|
||||||
TriggerEntry::WordSeparator => {
|
|
||||||
is_current_word_separator
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl <'a, R: MatchReceiver, M: ConfigManager<'a>> super::Matcher for ScrollingMatcher<'a, R, M> {
|
impl<'a, R: MatchReceiver, M: ConfigManager<'a>> super::Matcher for ScrollingMatcher<'a, R, M> {
|
||||||
fn handle_char(&self, c: &str) {
|
fn handle_char(&self, c: &str) {
|
||||||
// if not enabled, avoid any processing
|
// if not enabled, avoid any processing
|
||||||
if !*(self.is_enabled.borrow()) {
|
if !*(self.is_enabled.borrow()) {
|
||||||
|
@ -98,9 +100,9 @@ impl <'a, R: MatchReceiver, M: ConfigManager<'a>> super::Matcher for ScrollingMa
|
||||||
let active_config = self.config_manager.active_config();
|
let active_config = self.config_manager.active_config();
|
||||||
|
|
||||||
// Check if the current char is a word separator
|
// Check if the current char is a word separator
|
||||||
let mut is_current_word_separator = active_config.word_separators.contains(
|
let mut is_current_word_separator = active_config
|
||||||
&c.chars().nth(0).unwrap_or_default()
|
.word_separators
|
||||||
);
|
.contains(&c.chars().nth(0).unwrap_or_default());
|
||||||
|
|
||||||
// Workaround needed on macos to consider espanso replacement key presses as separators.
|
// Workaround needed on macos to consider espanso replacement key presses as separators.
|
||||||
if cfg!(target_os = "macos") {
|
if cfg!(target_os = "macos") {
|
||||||
|
@ -118,22 +120,23 @@ impl <'a, R: MatchReceiver, M: ConfigManager<'a>> super::Matcher for ScrollingMa
|
||||||
for m in active_config.matches.iter() {
|
for m in active_config.matches.iter() {
|
||||||
// only active-enabled matches are considered
|
// only active-enabled matches are considered
|
||||||
if m.passive_only {
|
if m.passive_only {
|
||||||
continue
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
for trigger_offset in 0..m._trigger_sequences.len() {
|
for trigger_offset in 0..m._trigger_sequences.len() {
|
||||||
let mut result = Self::is_matching(m, c, 0, trigger_offset, is_current_word_separator);
|
let mut result =
|
||||||
|
Self::is_matching(m, c, 0, trigger_offset, is_current_word_separator);
|
||||||
|
|
||||||
if m.word {
|
if m.word {
|
||||||
result = result && *was_previous_word_separator
|
result = result && *was_previous_word_separator
|
||||||
}
|
}
|
||||||
|
|
||||||
if result {
|
if result {
|
||||||
new_matches.push(MatchEntry{
|
new_matches.push(MatchEntry {
|
||||||
start: 1,
|
start: 1,
|
||||||
count: m._trigger_sequences[trigger_offset].len(),
|
count: m._trigger_sequences[trigger_offset].len(),
|
||||||
trigger_offset,
|
trigger_offset,
|
||||||
_match: &m
|
_match: &m,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -142,22 +145,29 @@ impl <'a, R: MatchReceiver, M: ConfigManager<'a>> super::Matcher for ScrollingMa
|
||||||
|
|
||||||
let combined_matches: Vec<MatchEntry> = match current_set_queue.back_mut() {
|
let combined_matches: Vec<MatchEntry> = match current_set_queue.back_mut() {
|
||||||
Some(last_matches) => {
|
Some(last_matches) => {
|
||||||
let mut updated: Vec<MatchEntry> = last_matches.iter()
|
let mut updated: Vec<MatchEntry> = last_matches
|
||||||
|
.iter()
|
||||||
.filter(|&x| {
|
.filter(|&x| {
|
||||||
Self::is_matching(x._match, c, x.start, x.trigger_offset, is_current_word_separator)
|
Self::is_matching(
|
||||||
|
x._match,
|
||||||
|
c,
|
||||||
|
x.start,
|
||||||
|
x.trigger_offset,
|
||||||
|
is_current_word_separator,
|
||||||
|
)
|
||||||
})
|
})
|
||||||
.map(|x | MatchEntry{
|
.map(|x| MatchEntry {
|
||||||
start: x.start+1,
|
start: x.start + 1,
|
||||||
count: x.count,
|
count: x.count,
|
||||||
trigger_offset: x.trigger_offset,
|
trigger_offset: x.trigger_offset,
|
||||||
_match: &x._match
|
_match: &x._match,
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
updated.extend(new_matches);
|
updated.extend(new_matches);
|
||||||
updated
|
updated
|
||||||
},
|
}
|
||||||
None => {new_matches},
|
None => new_matches,
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut found_entry = None;
|
let mut found_entry = None;
|
||||||
|
@ -171,7 +181,9 @@ impl <'a, R: MatchReceiver, M: ConfigManager<'a>> super::Matcher for ScrollingMa
|
||||||
|
|
||||||
current_set_queue.push_back(combined_matches);
|
current_set_queue.push_back(combined_matches);
|
||||||
|
|
||||||
if current_set_queue.len() as i32 > (self.config_manager.default_config().backspace_limit + 1) {
|
if current_set_queue.len() as i32
|
||||||
|
> (self.config_manager.default_config().backspace_limit + 1)
|
||||||
|
{
|
||||||
current_set_queue.pop_front();
|
current_set_queue.pop_front();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -189,20 +201,21 @@ impl <'a, R: MatchReceiver, M: ConfigManager<'a>> super::Matcher for ScrollingMa
|
||||||
None
|
None
|
||||||
} else if !is_current_word_separator {
|
} else if !is_current_word_separator {
|
||||||
None
|
None
|
||||||
}else{
|
} else {
|
||||||
let as_char = c.chars().nth(0);
|
let as_char = c.chars().nth(0);
|
||||||
match as_char {
|
match as_char {
|
||||||
Some(c) => {
|
Some(c) => {
|
||||||
Some(c) // Current char is the trailing separator
|
Some(c) // Current char is the trailing separator
|
||||||
},
|
}
|
||||||
None => {None},
|
None => None,
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Force espanso to consider the last char as a separator
|
// Force espanso to consider the last char as a separator
|
||||||
*was_previous_word_separator = true;
|
*was_previous_word_separator = true;
|
||||||
|
|
||||||
self.receiver.on_match(mtc, trailing_separator, entry.trigger_offset);
|
self.receiver
|
||||||
|
.on_match(mtc, trailing_separator, entry.trigger_offset);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -212,22 +225,28 @@ impl <'a, R: MatchReceiver, M: ConfigManager<'a>> super::Matcher for ScrollingMa
|
||||||
// TODO: at the moment, activating the passive key triggers the toggle key
|
// TODO: at the moment, activating the passive key triggers the toggle key
|
||||||
// study a mechanism to avoid this problem
|
// study a mechanism to avoid this problem
|
||||||
|
|
||||||
if KeyModifier::shallow_equals(&m, &config.toggle_key) {
|
if KeyModifier::shallow_equals(&m, &config.toggle_key) {
|
||||||
check_interval(&self.toggle_press_time,
|
check_interval(
|
||||||
u128::from(config.toggle_interval), || {
|
&self.toggle_press_time,
|
||||||
self.toggle();
|
u128::from(config.toggle_interval),
|
||||||
|
|| {
|
||||||
|
self.toggle();
|
||||||
|
|
||||||
let is_enabled = self.is_enabled.borrow();
|
let is_enabled = self.is_enabled.borrow();
|
||||||
|
|
||||||
if !*is_enabled {
|
if !*is_enabled {
|
||||||
self.current_set_queue.borrow_mut().clear();
|
self.current_set_queue.borrow_mut().clear();
|
||||||
}
|
}
|
||||||
});
|
},
|
||||||
}else if KeyModifier::shallow_equals(&m, &config.passive_key) {
|
);
|
||||||
check_interval(&self.passive_press_time,
|
} else if KeyModifier::shallow_equals(&m, &config.passive_key) {
|
||||||
u128::from(config.toggle_interval), || {
|
check_interval(
|
||||||
self.receiver.on_passive();
|
&self.passive_press_time,
|
||||||
});
|
u128::from(config.toggle_interval),
|
||||||
|
|| {
|
||||||
|
self.receiver.on_passive();
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Backspace handling, basically "rewinding history"
|
// Backspace handling, basically "rewinding history"
|
||||||
|
@ -238,7 +257,8 @@ impl <'a, R: MatchReceiver, M: ConfigManager<'a>> super::Matcher for ScrollingMa
|
||||||
|
|
||||||
// Consider modifiers as separators to improve word matches reliability
|
// Consider modifiers as separators to improve word matches reliability
|
||||||
if m != LEFT_SHIFT && m != RIGHT_SHIFT && m != CAPS_LOCK {
|
if m != LEFT_SHIFT && m != RIGHT_SHIFT && m != CAPS_LOCK {
|
||||||
let mut was_previous_char_word_separator = self.was_previous_char_word_separator.borrow_mut();
|
let mut was_previous_char_word_separator =
|
||||||
|
self.was_previous_char_word_separator.borrow_mut();
|
||||||
*was_previous_char_word_separator = true;
|
*was_previous_char_word_separator = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -246,29 +266,35 @@ impl <'a, R: MatchReceiver, M: ConfigManager<'a>> super::Matcher for ScrollingMa
|
||||||
fn handle_other(&self) {
|
fn handle_other(&self) {
|
||||||
// When receiving "other" type of events, we mark them as valid separators.
|
// When receiving "other" type of events, we mark them as valid separators.
|
||||||
// This dramatically improves the reliability of word matches
|
// This dramatically improves the reliability of word matches
|
||||||
let mut was_previous_char_word_separator = self.was_previous_char_word_separator.borrow_mut();
|
let mut was_previous_char_word_separator =
|
||||||
|
self.was_previous_char_word_separator.borrow_mut();
|
||||||
*was_previous_char_word_separator = true;
|
*was_previous_char_word_separator = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl <'a, R: MatchReceiver, M: ConfigManager<'a>> ActionEventReceiver for ScrollingMatcher<'a, R, M> {
|
impl<'a, R: MatchReceiver, M: ConfigManager<'a>> ActionEventReceiver
|
||||||
|
for ScrollingMatcher<'a, R, M>
|
||||||
|
{
|
||||||
fn on_action_event(&self, e: ActionType) {
|
fn on_action_event(&self, e: ActionType) {
|
||||||
match e {
|
match e {
|
||||||
ActionType::Toggle => {
|
ActionType::Toggle => {
|
||||||
self.toggle();
|
self.toggle();
|
||||||
},
|
}
|
||||||
ActionType::Enable => {
|
ActionType::Enable => {
|
||||||
self.set_enabled(true);
|
self.set_enabled(true);
|
||||||
},
|
}
|
||||||
ActionType::Disable => {
|
ActionType::Disable => {
|
||||||
self.set_enabled(false);
|
self.set_enabled(false);
|
||||||
},
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn check_interval<F>(state_var: &RefCell<SystemTime>, interval: u128, elapsed_callback: F) where F:Fn() {
|
fn check_interval<F>(state_var: &RefCell<SystemTime>, interval: u128, elapsed_callback: F)
|
||||||
|
where
|
||||||
|
F: Fn(),
|
||||||
|
{
|
||||||
let mut press_time = state_var.borrow_mut();
|
let mut press_time = state_var.borrow_mut();
|
||||||
if let Ok(elapsed) = press_time.elapsed() {
|
if let Ok(elapsed) = press_time.elapsed() {
|
||||||
if elapsed.as_millis() < interval {
|
if elapsed.as_millis() < interval {
|
||||||
|
|
|
@ -17,20 +17,22 @@
|
||||||
* along with espanso. If not, see <https://www.gnu.org/licenses/>.
|
* along with espanso. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
use std::path::{PathBuf, Path};
|
use crate::package::InstallResult::{AlreadyInstalled, BlockedExternalPackage, NotFoundInIndex};
|
||||||
use crate::package::{PackageIndex, UpdateResult, Package, InstallResult, RemoveResult, PackageResolver};
|
|
||||||
use std::error::Error;
|
|
||||||
use std::fs::{File, create_dir};
|
|
||||||
use std::io::{BufReader, BufRead};
|
|
||||||
use std::time::{SystemTime, UNIX_EPOCH};
|
|
||||||
use crate::package::UpdateResult::{NotOutdated, Updated};
|
|
||||||
use crate::package::InstallResult::{NotFoundInIndex, AlreadyInstalled, BlockedExternalPackage};
|
|
||||||
use std::fs;
|
|
||||||
use regex::Regex;
|
|
||||||
use crate::package::RemoveResult::Removed;
|
use crate::package::RemoveResult::Removed;
|
||||||
|
use crate::package::UpdateResult::{NotOutdated, Updated};
|
||||||
|
use crate::package::{
|
||||||
|
InstallResult, Package, PackageIndex, PackageResolver, RemoveResult, UpdateResult,
|
||||||
|
};
|
||||||
|
use regex::Regex;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
use std::error::Error;
|
||||||
|
use std::fs;
|
||||||
|
use std::fs::{create_dir, File};
|
||||||
|
use std::io::{BufRead, BufReader};
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
use std::time::{SystemTime, UNIX_EPOCH};
|
||||||
|
|
||||||
const DEFAULT_PACKAGE_INDEX_FILE : &str = "package_index.json";
|
const DEFAULT_PACKAGE_INDEX_FILE: &str = "package_index.json";
|
||||||
|
|
||||||
pub struct DefaultPackageManager {
|
pub struct DefaultPackageManager {
|
||||||
package_dir: PathBuf,
|
package_dir: PathBuf,
|
||||||
|
@ -42,18 +44,24 @@ pub struct DefaultPackageManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DefaultPackageManager {
|
impl DefaultPackageManager {
|
||||||
pub fn new(package_dir: PathBuf, data_dir: PathBuf, package_resolver: Option<Box<dyn PackageResolver>>) -> DefaultPackageManager {
|
pub fn new(
|
||||||
|
package_dir: PathBuf,
|
||||||
|
data_dir: PathBuf,
|
||||||
|
package_resolver: Option<Box<dyn PackageResolver>>,
|
||||||
|
) -> DefaultPackageManager {
|
||||||
let local_index = Self::load_local_index(&data_dir);
|
let local_index = Self::load_local_index(&data_dir);
|
||||||
|
|
||||||
DefaultPackageManager{
|
DefaultPackageManager {
|
||||||
package_dir,
|
package_dir,
|
||||||
data_dir,
|
data_dir,
|
||||||
package_resolver,
|
package_resolver,
|
||||||
local_index
|
local_index,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new_default(package_resolver: Option<Box<dyn PackageResolver>>) -> DefaultPackageManager {
|
pub fn new_default(
|
||||||
|
package_resolver: Option<Box<dyn PackageResolver>>,
|
||||||
|
) -> DefaultPackageManager {
|
||||||
DefaultPackageManager::new(
|
DefaultPackageManager::new(
|
||||||
crate::context::get_package_dir(),
|
crate::context::get_package_dir(),
|
||||||
crate::context::get_data_dir(),
|
crate::context::get_data_dir(),
|
||||||
|
@ -72,7 +80,7 @@ impl DefaultPackageManager {
|
||||||
let local_index = serde_json::from_reader(reader);
|
let local_index = serde_json::from_reader(reader);
|
||||||
|
|
||||||
if let Ok(local_index) = local_index {
|
if let Ok(local_index) = local_index {
|
||||||
return local_index
|
return local_index;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -81,19 +89,21 @@ impl DefaultPackageManager {
|
||||||
|
|
||||||
fn request_index() -> Result<super::PackageIndex, Box<dyn Error>> {
|
fn request_index() -> Result<super::PackageIndex, Box<dyn Error>> {
|
||||||
let client = reqwest::Client::new();
|
let client = reqwest::Client::new();
|
||||||
let request = client.get("https://hub.espanso.org/json/")
|
let request = client
|
||||||
|
.get("https://hub.espanso.org/json/")
|
||||||
.header("User-Agent", format!("espanso/{}", crate::VERSION));
|
.header("User-Agent", format!("espanso/{}", crate::VERSION));
|
||||||
|
|
||||||
let mut res = request.send()?;
|
let mut res = request.send()?;
|
||||||
let body = res.text()?;
|
let body = res.text()?;
|
||||||
let index : PackageIndex = serde_json::from_str(&body)?;
|
let index: PackageIndex = serde_json::from_str(&body)?;
|
||||||
|
|
||||||
Ok(index)
|
Ok(index)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_package_from_readme(readme_path: &Path) -> Option<Package> {
|
fn parse_package_from_readme(readme_path: &Path) -> Option<Package> {
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
static ref FIELD_REGEX: Regex = Regex::new(r###"^\s*(.*?)\s*:\s*"?(.*?)"?$"###).unwrap();
|
static ref FIELD_REGEX: Regex =
|
||||||
|
Regex::new(r###"^\s*(.*?)\s*:\s*"?(.*?)"?$"###).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read readme line by line
|
// Read readme line by line
|
||||||
|
@ -101,7 +111,7 @@ impl DefaultPackageManager {
|
||||||
if let Ok(file) = file {
|
if let Ok(file) = file {
|
||||||
let reader = BufReader::new(file);
|
let reader = BufReader::new(file);
|
||||||
|
|
||||||
let mut fields :HashMap<String, String> = HashMap::new();
|
let mut fields: HashMap<String, String> = HashMap::new();
|
||||||
|
|
||||||
let mut started = false;
|
let mut started = false;
|
||||||
|
|
||||||
|
@ -109,35 +119,38 @@ impl DefaultPackageManager {
|
||||||
let line = line.unwrap();
|
let line = line.unwrap();
|
||||||
if line.contains("---") {
|
if line.contains("---") {
|
||||||
if started {
|
if started {
|
||||||
break
|
break;
|
||||||
}else{
|
} else {
|
||||||
started = true;
|
started = true;
|
||||||
}
|
}
|
||||||
}else if started {
|
} else if started {
|
||||||
let caps = FIELD_REGEX.captures(&line);
|
let caps = FIELD_REGEX.captures(&line);
|
||||||
if let Some(caps) = caps {
|
if let Some(caps) = caps {
|
||||||
let property = caps.get(1);
|
let property = caps.get(1);
|
||||||
let value = caps.get(2);
|
let value = caps.get(2);
|
||||||
if property.is_some() && value.is_some() {
|
if property.is_some() && value.is_some() {
|
||||||
fields.insert(property.unwrap().as_str().to_owned(),
|
fields.insert(
|
||||||
value.unwrap().as_str().to_owned());
|
property.unwrap().as_str().to_owned(),
|
||||||
|
value.unwrap().as_str().to_owned(),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !fields.contains_key("package_name") ||
|
if !fields.contains_key("package_name")
|
||||||
!fields.contains_key("package_title") ||
|
|| !fields.contains_key("package_title")
|
||||||
!fields.contains_key("package_version") ||
|
|| !fields.contains_key("package_version")
|
||||||
!fields.contains_key("package_repo") ||
|
|| !fields.contains_key("package_repo")
|
||||||
!fields.contains_key("package_desc") ||
|
|| !fields.contains_key("package_desc")
|
||||||
!fields.contains_key("package_author") {
|
|| !fields.contains_key("package_author")
|
||||||
return None
|
{
|
||||||
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
let original_repo = if fields.contains_key("package_original_repo") {
|
let original_repo = if fields.contains_key("package_original_repo") {
|
||||||
fields.get("package_original_repo").unwrap().clone()
|
fields.get("package_original_repo").unwrap().clone()
|
||||||
}else{
|
} else {
|
||||||
fields.get("package_repo").unwrap().clone()
|
fields.get("package_repo").unwrap().clone()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -147,7 +160,7 @@ impl DefaultPackageManager {
|
||||||
"false" => false,
|
"false" => false,
|
||||||
_ => false,
|
_ => false,
|
||||||
}
|
}
|
||||||
}else{
|
} else {
|
||||||
false
|
false
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -159,18 +172,18 @@ impl DefaultPackageManager {
|
||||||
desc: fields.get("package_desc").unwrap().clone(),
|
desc: fields.get("package_desc").unwrap().clone(),
|
||||||
author: fields.get("package_author").unwrap().clone(),
|
author: fields.get("package_author").unwrap().clone(),
|
||||||
is_core,
|
is_core,
|
||||||
original_repo
|
original_repo,
|
||||||
};
|
};
|
||||||
|
|
||||||
Some(package)
|
Some(package)
|
||||||
}else{
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn local_index_timestamp(&self) -> u64 {
|
fn local_index_timestamp(&self) -> u64 {
|
||||||
if let Some(local_index) = &self.local_index {
|
if let Some(local_index) = &self.local_index {
|
||||||
return local_index.last_update
|
return local_index.last_update;
|
||||||
}
|
}
|
||||||
|
|
||||||
0
|
0
|
||||||
|
@ -198,7 +211,8 @@ impl DefaultPackageManager {
|
||||||
|
|
||||||
fn cache_local_index(&self) {
|
fn cache_local_index(&self) {
|
||||||
if let Some(local_index) = &self.local_index {
|
if let Some(local_index) = &self.local_index {
|
||||||
let serialized = serde_json::to_string(local_index).expect("Unable to serialize local index");
|
let serialized =
|
||||||
|
serde_json::to_string(local_index).expect("Unable to serialize local index");
|
||||||
let local_index_file = self.data_dir.join(DEFAULT_PACKAGE_INDEX_FILE);
|
let local_index_file = self.data_dir.join(DEFAULT_PACKAGE_INDEX_FILE);
|
||||||
std::fs::write(local_index_file, serialized).expect("Unable to cache local index");
|
std::fs::write(local_index_file, serialized).expect("Unable to cache local index");
|
||||||
}
|
}
|
||||||
|
@ -207,13 +221,15 @@ impl DefaultPackageManager {
|
||||||
|
|
||||||
impl super::PackageManager for DefaultPackageManager {
|
impl super::PackageManager for DefaultPackageManager {
|
||||||
fn is_index_outdated(&self) -> bool {
|
fn is_index_outdated(&self) -> bool {
|
||||||
let current_time = SystemTime::now().duration_since(UNIX_EPOCH).expect("Time went backwards");
|
let current_time = SystemTime::now()
|
||||||
|
.duration_since(UNIX_EPOCH)
|
||||||
|
.expect("Time went backwards");
|
||||||
let current_timestamp = current_time.as_secs();
|
let current_timestamp = current_time.as_secs();
|
||||||
|
|
||||||
let local_index_timestamp = self.local_index_timestamp();
|
let local_index_timestamp = self.local_index_timestamp();
|
||||||
|
|
||||||
// Local index is outdated if older than a day
|
// Local index is outdated if older than a day
|
||||||
local_index_timestamp + 60*60*24 < current_timestamp
|
local_index_timestamp + 60 * 60 * 24 < current_timestamp
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_index(&mut self, force: bool) -> Result<UpdateResult, Box<dyn Error>> {
|
fn update_index(&mut self, force: bool) -> Result<UpdateResult, Box<dyn Error>> {
|
||||||
|
@ -225,48 +241,60 @@ impl super::PackageManager for DefaultPackageManager {
|
||||||
self.cache_local_index();
|
self.cache_local_index();
|
||||||
|
|
||||||
Ok(Updated)
|
Ok(Updated)
|
||||||
}else{
|
} else {
|
||||||
Ok(NotOutdated)
|
Ok(NotOutdated)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_package(&self, name: &str) -> Option<Package> {
|
fn get_package(&self, name: &str) -> Option<Package> {
|
||||||
if let Some(local_index) = &self.local_index {
|
if let Some(local_index) = &self.local_index {
|
||||||
let result = local_index.packages.iter().find(|package| {
|
let result = local_index
|
||||||
package.name == name
|
.packages
|
||||||
});
|
.iter()
|
||||||
|
.find(|package| package.name == name);
|
||||||
if let Some(package) = result {
|
if let Some(package) = result {
|
||||||
return Some(package.clone())
|
return Some(package.clone());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
fn install_package(&self, name: &str, allow_external: bool) -> Result<InstallResult, Box<dyn Error>> {
|
fn install_package(
|
||||||
|
&self,
|
||||||
|
name: &str,
|
||||||
|
allow_external: bool,
|
||||||
|
) -> Result<InstallResult, Box<dyn Error>> {
|
||||||
let package = self.get_package(name);
|
let package = self.get_package(name);
|
||||||
match package {
|
match package {
|
||||||
Some(package) => {
|
Some(package) => {
|
||||||
if package.is_core || allow_external {
|
if package.is_core || allow_external {
|
||||||
self.install_package_from_repo(name, &package.repo)
|
self.install_package_from_repo(name, &package.repo)
|
||||||
}else{
|
} else {
|
||||||
Ok(BlockedExternalPackage(package.original_repo))
|
Ok(BlockedExternalPackage(package.original_repo))
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
None => {
|
None => Ok(NotFoundInIndex),
|
||||||
Ok(NotFoundInIndex)
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn install_package_from_repo(&self, name: &str, repo_url: &str) -> Result<InstallResult, Box<dyn Error>> {
|
fn install_package_from_repo(
|
||||||
|
&self,
|
||||||
|
name: &str,
|
||||||
|
repo_url: &str,
|
||||||
|
) -> Result<InstallResult, Box<dyn Error>> {
|
||||||
// Check if package is already installed
|
// Check if package is already installed
|
||||||
let packages = self.list_local_packages_names();
|
let packages = self.list_local_packages_names();
|
||||||
if packages.iter().any(|p| p == name) { // Package already installed
|
if packages.iter().any(|p| p == name) {
|
||||||
|
// Package already installed
|
||||||
return Ok(AlreadyInstalled);
|
return Ok(AlreadyInstalled);
|
||||||
}
|
}
|
||||||
|
|
||||||
let temp_dir = self.package_resolver.as_ref().unwrap().clone_repo_to_temp(repo_url)?;
|
let temp_dir = self
|
||||||
|
.package_resolver
|
||||||
|
.as_ref()
|
||||||
|
.unwrap()
|
||||||
|
.clone_repo_to_temp(repo_url)?;
|
||||||
|
|
||||||
let temp_package_dir = temp_dir.path().join(name);
|
let temp_package_dir = temp_dir.path().join(name);
|
||||||
if !temp_package_dir.exists() {
|
if !temp_package_dir.exists() {
|
||||||
|
@ -329,15 +357,16 @@ impl super::PackageManager for DefaultPackageManager {
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use tempfile::{TempDir, NamedTempFile};
|
use crate::package::zip::ZipPackageResolver;
|
||||||
use std::path::Path;
|
use crate::package::InstallResult::*;
|
||||||
use crate::package::PackageManager;
|
use crate::package::PackageManager;
|
||||||
use std::fs::{create_dir, create_dir_all};
|
use std::fs::{create_dir, create_dir_all};
|
||||||
use crate::package::InstallResult::*;
|
use std::path::Path;
|
||||||
use crate::package::zip::ZipPackageResolver;
|
use tempfile::{NamedTempFile, TempDir};
|
||||||
|
|
||||||
const OUTDATED_INDEX_CONTENT : &str = include_str!("../res/test/outdated_index.json");
|
const OUTDATED_INDEX_CONTENT: &str = include_str!("../res/test/outdated_index.json");
|
||||||
const INDEX_CONTENT_WITHOUT_UPDATE: &str = include_str!("../res/test/index_without_update.json");
|
const INDEX_CONTENT_WITHOUT_UPDATE: &str =
|
||||||
|
include_str!("../res/test/index_without_update.json");
|
||||||
const GET_PACKAGE_INDEX: &str = include_str!("../res/test/get_package_index.json");
|
const GET_PACKAGE_INDEX: &str = include_str!("../res/test/get_package_index.json");
|
||||||
const INSTALL_PACKAGE_INDEX: &str = include_str!("../res/test/install_package_index.json");
|
const INSTALL_PACKAGE_INDEX: &str = include_str!("../res/test/install_package_index.json");
|
||||||
|
|
||||||
|
@ -347,7 +376,10 @@ mod tests {
|
||||||
package_manager: DefaultPackageManager,
|
package_manager: DefaultPackageManager,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_temp_package_manager<F>(setup: F) -> TempPackageManager where F: Fn(&Path, &Path) -> (){
|
fn create_temp_package_manager<F>(setup: F) -> TempPackageManager
|
||||||
|
where
|
||||||
|
F: Fn(&Path, &Path) -> (),
|
||||||
|
{
|
||||||
let package_dir = TempDir::new().expect("unable to create temp directory");
|
let package_dir = TempDir::new().expect("unable to create temp directory");
|
||||||
let data_dir = TempDir::new().expect("unable to create temp directory");
|
let data_dir = TempDir::new().expect("unable to create temp directory");
|
||||||
|
|
||||||
|
@ -362,7 +394,7 @@ mod tests {
|
||||||
TempPackageManager {
|
TempPackageManager {
|
||||||
package_dir,
|
package_dir,
|
||||||
data_dir,
|
data_dir,
|
||||||
package_manager
|
package_manager,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -389,26 +421,38 @@ mod tests {
|
||||||
fn test_up_to_date_index_should_not_be_updated() {
|
fn test_up_to_date_index_should_not_be_updated() {
|
||||||
let mut temp = create_temp_package_manager(|_, data_dir| {
|
let mut temp = create_temp_package_manager(|_, data_dir| {
|
||||||
let index_file = data_dir.join(DEFAULT_PACKAGE_INDEX_FILE);
|
let index_file = data_dir.join(DEFAULT_PACKAGE_INDEX_FILE);
|
||||||
let current_time = SystemTime::now().duration_since(UNIX_EPOCH).expect("Time went backwards");
|
let current_time = SystemTime::now()
|
||||||
|
.duration_since(UNIX_EPOCH)
|
||||||
|
.expect("Time went backwards");
|
||||||
let current_timestamp = current_time.as_secs();
|
let current_timestamp = current_time.as_secs();
|
||||||
let new_contents = INDEX_CONTENT_WITHOUT_UPDATE.replace("XXXX", &format!("{}", current_timestamp));
|
let new_contents =
|
||||||
|
INDEX_CONTENT_WITHOUT_UPDATE.replace("XXXX", &format!("{}", current_timestamp));
|
||||||
std::fs::write(index_file, new_contents).unwrap();
|
std::fs::write(index_file, new_contents).unwrap();
|
||||||
});
|
});
|
||||||
|
|
||||||
assert_eq!(temp.package_manager.update_index(false).unwrap(), UpdateResult::NotOutdated);
|
assert_eq!(
|
||||||
|
temp.package_manager.update_index(false).unwrap(),
|
||||||
|
UpdateResult::NotOutdated
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_up_to_date_index_with_force_should_be_updated() {
|
fn test_up_to_date_index_with_force_should_be_updated() {
|
||||||
let mut temp = create_temp_package_manager(|_, data_dir| {
|
let mut temp = create_temp_package_manager(|_, data_dir| {
|
||||||
let index_file = data_dir.join(DEFAULT_PACKAGE_INDEX_FILE);
|
let index_file = data_dir.join(DEFAULT_PACKAGE_INDEX_FILE);
|
||||||
let current_time = SystemTime::now().duration_since(UNIX_EPOCH).expect("Time went backwards");
|
let current_time = SystemTime::now()
|
||||||
|
.duration_since(UNIX_EPOCH)
|
||||||
|
.expect("Time went backwards");
|
||||||
let current_timestamp = current_time.as_secs();
|
let current_timestamp = current_time.as_secs();
|
||||||
let new_contents = INDEX_CONTENT_WITHOUT_UPDATE.replace("XXXX", &format!("{}", current_timestamp));
|
let new_contents =
|
||||||
|
INDEX_CONTENT_WITHOUT_UPDATE.replace("XXXX", &format!("{}", current_timestamp));
|
||||||
std::fs::write(index_file, new_contents).unwrap();
|
std::fs::write(index_file, new_contents).unwrap();
|
||||||
});
|
});
|
||||||
|
|
||||||
assert_eq!(temp.package_manager.update_index(true).unwrap(), UpdateResult::Updated);
|
assert_eq!(
|
||||||
|
temp.package_manager.update_index(true).unwrap(),
|
||||||
|
UpdateResult::Updated
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -418,15 +462,25 @@ mod tests {
|
||||||
std::fs::write(index_file, OUTDATED_INDEX_CONTENT).unwrap();
|
std::fs::write(index_file, OUTDATED_INDEX_CONTENT).unwrap();
|
||||||
});
|
});
|
||||||
|
|
||||||
assert_eq!(temp.package_manager.update_index(false).unwrap(), UpdateResult::Updated);
|
assert_eq!(
|
||||||
|
temp.package_manager.update_index(false).unwrap(),
|
||||||
|
UpdateResult::Updated
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_update_index_should_create_file() {
|
fn test_update_index_should_create_file() {
|
||||||
let mut temp = create_temp_package_manager(|_, _| {});
|
let mut temp = create_temp_package_manager(|_, _| {});
|
||||||
|
|
||||||
assert_eq!(temp.package_manager.update_index(false).unwrap(), UpdateResult::Updated);
|
assert_eq!(
|
||||||
assert!(temp.data_dir.path().join(DEFAULT_PACKAGE_INDEX_FILE).exists())
|
temp.package_manager.update_index(false).unwrap(),
|
||||||
|
UpdateResult::Updated
|
||||||
|
);
|
||||||
|
assert!(temp
|
||||||
|
.data_dir
|
||||||
|
.path()
|
||||||
|
.join(DEFAULT_PACKAGE_INDEX_FILE)
|
||||||
|
.exists())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -436,7 +490,13 @@ mod tests {
|
||||||
std::fs::write(index_file, GET_PACKAGE_INDEX).unwrap();
|
std::fs::write(index_file, GET_PACKAGE_INDEX).unwrap();
|
||||||
});
|
});
|
||||||
|
|
||||||
assert_eq!(temp.package_manager.get_package("italian-accents").unwrap().title, "Italian Accents");
|
assert_eq!(
|
||||||
|
temp.package_manager
|
||||||
|
.get_package("italian-accents")
|
||||||
|
.unwrap()
|
||||||
|
.title,
|
||||||
|
"Italian Accents"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -470,7 +530,12 @@ mod tests {
|
||||||
std::fs::write(index_file, INSTALL_PACKAGE_INDEX).unwrap();
|
std::fs::write(index_file, INSTALL_PACKAGE_INDEX).unwrap();
|
||||||
});
|
});
|
||||||
|
|
||||||
assert_eq!(temp.package_manager.install_package("doesnotexist", false).unwrap(), NotFoundInIndex);
|
assert_eq!(
|
||||||
|
temp.package_manager
|
||||||
|
.install_package("doesnotexist", false)
|
||||||
|
.unwrap(),
|
||||||
|
NotFoundInIndex
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -481,7 +546,12 @@ mod tests {
|
||||||
std::fs::write(index_file, INSTALL_PACKAGE_INDEX).unwrap();
|
std::fs::write(index_file, INSTALL_PACKAGE_INDEX).unwrap();
|
||||||
});
|
});
|
||||||
|
|
||||||
assert_eq!(temp.package_manager.install_package("italian-accents", false).unwrap(), AlreadyInstalled);
|
assert_eq!(
|
||||||
|
temp.package_manager
|
||||||
|
.install_package("italian-accents", false)
|
||||||
|
.unwrap(),
|
||||||
|
AlreadyInstalled
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -491,10 +561,23 @@ mod tests {
|
||||||
std::fs::write(index_file, INSTALL_PACKAGE_INDEX).unwrap();
|
std::fs::write(index_file, INSTALL_PACKAGE_INDEX).unwrap();
|
||||||
});
|
});
|
||||||
|
|
||||||
assert_eq!(temp.package_manager.install_package("dummy-package", false).unwrap(), Installed);
|
assert_eq!(
|
||||||
|
temp.package_manager
|
||||||
|
.install_package("dummy-package", false)
|
||||||
|
.unwrap(),
|
||||||
|
Installed
|
||||||
|
);
|
||||||
assert!(temp.package_dir.path().join("dummy-package").exists());
|
assert!(temp.package_dir.path().join("dummy-package").exists());
|
||||||
assert!(temp.package_dir.path().join("dummy-package/README.md").exists());
|
assert!(temp
|
||||||
assert!(temp.package_dir.path().join("dummy-package/package.yml").exists());
|
.package_dir
|
||||||
|
.path()
|
||||||
|
.join("dummy-package/README.md")
|
||||||
|
.exists());
|
||||||
|
assert!(temp
|
||||||
|
.package_dir
|
||||||
|
.path()
|
||||||
|
.join("dummy-package/package.yml")
|
||||||
|
.exists());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -504,7 +587,12 @@ mod tests {
|
||||||
std::fs::write(index_file, INSTALL_PACKAGE_INDEX).unwrap();
|
std::fs::write(index_file, INSTALL_PACKAGE_INDEX).unwrap();
|
||||||
});
|
});
|
||||||
|
|
||||||
assert_eq!(temp.package_manager.install_package("not-existing", false).unwrap(), NotFoundInRepo);
|
assert_eq!(
|
||||||
|
temp.package_manager
|
||||||
|
.install_package("not-existing", false)
|
||||||
|
.unwrap(),
|
||||||
|
NotFoundInRepo
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -514,7 +602,12 @@ mod tests {
|
||||||
std::fs::write(index_file, INSTALL_PACKAGE_INDEX).unwrap();
|
std::fs::write(index_file, INSTALL_PACKAGE_INDEX).unwrap();
|
||||||
});
|
});
|
||||||
|
|
||||||
assert_eq!(temp.package_manager.install_package("dummy-package2", false).unwrap(), MissingPackageVersion);
|
assert_eq!(
|
||||||
|
temp.package_manager
|
||||||
|
.install_package("dummy-package2", false)
|
||||||
|
.unwrap(),
|
||||||
|
MissingPackageVersion
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -524,7 +617,12 @@ mod tests {
|
||||||
std::fs::write(index_file, INSTALL_PACKAGE_INDEX).unwrap();
|
std::fs::write(index_file, INSTALL_PACKAGE_INDEX).unwrap();
|
||||||
});
|
});
|
||||||
|
|
||||||
assert_eq!(temp.package_manager.install_package("dummy-package3", false).unwrap(), UnableToParsePackageInfo);
|
assert_eq!(
|
||||||
|
temp.package_manager
|
||||||
|
.install_package("dummy-package3", false)
|
||||||
|
.unwrap(),
|
||||||
|
UnableToParsePackageInfo
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -534,7 +632,12 @@ mod tests {
|
||||||
std::fs::write(index_file, INSTALL_PACKAGE_INDEX).unwrap();
|
std::fs::write(index_file, INSTALL_PACKAGE_INDEX).unwrap();
|
||||||
});
|
});
|
||||||
|
|
||||||
assert_eq!(temp.package_manager.install_package("dummy-package4", false).unwrap(), UnableToParsePackageInfo);
|
assert_eq!(
|
||||||
|
temp.package_manager
|
||||||
|
.install_package("dummy-package4", false)
|
||||||
|
.unwrap(),
|
||||||
|
UnableToParsePackageInfo
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -544,10 +647,23 @@ mod tests {
|
||||||
std::fs::write(index_file, INSTALL_PACKAGE_INDEX).unwrap();
|
std::fs::write(index_file, INSTALL_PACKAGE_INDEX).unwrap();
|
||||||
});
|
});
|
||||||
|
|
||||||
assert_eq!(temp.package_manager.install_package("dummy-package", false).unwrap(), Installed);
|
assert_eq!(
|
||||||
|
temp.package_manager
|
||||||
|
.install_package("dummy-package", false)
|
||||||
|
.unwrap(),
|
||||||
|
Installed
|
||||||
|
);
|
||||||
assert!(temp.package_dir.path().join("dummy-package").exists());
|
assert!(temp.package_dir.path().join("dummy-package").exists());
|
||||||
assert!(temp.package_dir.path().join("dummy-package/README.md").exists());
|
assert!(temp
|
||||||
assert!(temp.package_dir.path().join("dummy-package/package.yml").exists());
|
.package_dir
|
||||||
|
.path()
|
||||||
|
.join("dummy-package/README.md")
|
||||||
|
.exists());
|
||||||
|
assert!(temp
|
||||||
|
.package_dir
|
||||||
|
.path()
|
||||||
|
.join("dummy-package/package.yml")
|
||||||
|
.exists());
|
||||||
|
|
||||||
let list = temp.package_manager.list_local_packages();
|
let list = temp.package_manager.list_local_packages();
|
||||||
assert_eq!(list.len(), 1);
|
assert_eq!(list.len(), 1);
|
||||||
|
@ -564,25 +680,51 @@ mod tests {
|
||||||
});
|
});
|
||||||
|
|
||||||
assert!(temp.package_dir.path().join("dummy-package").exists());
|
assert!(temp.package_dir.path().join("dummy-package").exists());
|
||||||
assert!(temp.package_dir.path().join("dummy-package/README.md").exists());
|
assert!(temp
|
||||||
assert!(temp.package_dir.path().join("dummy-package/package.yml").exists());
|
.package_dir
|
||||||
assert_eq!(temp.package_manager.remove_package("dummy-package").unwrap(), RemoveResult::Removed);
|
.path()
|
||||||
|
.join("dummy-package/README.md")
|
||||||
|
.exists());
|
||||||
|
assert!(temp
|
||||||
|
.package_dir
|
||||||
|
.path()
|
||||||
|
.join("dummy-package/package.yml")
|
||||||
|
.exists());
|
||||||
|
assert_eq!(
|
||||||
|
temp.package_manager
|
||||||
|
.remove_package("dummy-package")
|
||||||
|
.unwrap(),
|
||||||
|
RemoveResult::Removed
|
||||||
|
);
|
||||||
assert!(!temp.package_dir.path().join("dummy-package").exists());
|
assert!(!temp.package_dir.path().join("dummy-package").exists());
|
||||||
assert!(!temp.package_dir.path().join("dummy-package/README.md").exists());
|
assert!(!temp
|
||||||
assert!(!temp.package_dir.path().join("dummy-package/package.yml").exists());
|
.package_dir
|
||||||
|
.path()
|
||||||
|
.join("dummy-package/README.md")
|
||||||
|
.exists());
|
||||||
|
assert!(!temp
|
||||||
|
.package_dir
|
||||||
|
.path()
|
||||||
|
.join("dummy-package/package.yml")
|
||||||
|
.exists());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_remove_package_not_found() {
|
fn test_remove_package_not_found() {
|
||||||
let temp = create_temp_package_manager(|_, _| {});
|
let temp = create_temp_package_manager(|_, _| {});
|
||||||
|
|
||||||
assert_eq!(temp.package_manager.remove_package("not-existing").unwrap(), RemoveResult::NotFound);
|
assert_eq!(
|
||||||
|
temp.package_manager.remove_package("not-existing").unwrap(),
|
||||||
|
RemoveResult::NotFound
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_parse_package_from_readme() {
|
fn test_parse_package_from_readme() {
|
||||||
let file = NamedTempFile::new().unwrap();
|
let file = NamedTempFile::new().unwrap();
|
||||||
fs::write(file.path(), r###"
|
fs::write(
|
||||||
|
file.path(),
|
||||||
|
r###"
|
||||||
---
|
---
|
||||||
package_name: "italian-accents"
|
package_name: "italian-accents"
|
||||||
package_title: "Italian Accents"
|
package_title: "Italian Accents"
|
||||||
|
@ -592,7 +734,9 @@ mod tests {
|
||||||
package_repo: "https://github.com/federico-terzi/espanso-hub-core"
|
package_repo: "https://github.com/federico-terzi/espanso-hub-core"
|
||||||
is_core: true
|
is_core: true
|
||||||
---
|
---
|
||||||
"###).unwrap();
|
"###,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
let package = DefaultPackageManager::parse_package_from_readme(file.path()).unwrap();
|
let package = DefaultPackageManager::parse_package_from_readme(file.path()).unwrap();
|
||||||
|
|
||||||
|
@ -613,7 +757,9 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_parse_package_from_readme_with_bad_metadata() {
|
fn test_parse_package_from_readme_with_bad_metadata() {
|
||||||
let file = NamedTempFile::new().unwrap();
|
let file = NamedTempFile::new().unwrap();
|
||||||
fs::write(file.path(), r###"
|
fs::write(
|
||||||
|
file.path(),
|
||||||
|
r###"
|
||||||
---
|
---
|
||||||
package_name: italian-accents
|
package_name: italian-accents
|
||||||
package_title: "Italian Accents"
|
package_title: "Italian Accents"
|
||||||
|
@ -624,7 +770,9 @@ mod tests {
|
||||||
is_core: true
|
is_core: true
|
||||||
---
|
---
|
||||||
Readme text
|
Readme text
|
||||||
"###).unwrap();
|
"###,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
let package = DefaultPackageManager::parse_package_from_readme(file.path()).unwrap();
|
let package = DefaultPackageManager::parse_package_from_readme(file.path()).unwrap();
|
||||||
|
|
||||||
|
|
|
@ -17,10 +17,10 @@
|
||||||
* along with espanso. If not, see <https://www.gnu.org/licenses/>.
|
* along with espanso. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
pub(crate) mod zip;
|
|
||||||
pub(crate) mod default;
|
pub(crate) mod default;
|
||||||
|
pub(crate) mod zip;
|
||||||
|
|
||||||
use serde::{Serialize, Deserialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
use tempfile::TempDir;
|
use tempfile::TempDir;
|
||||||
|
|
||||||
|
@ -30,8 +30,16 @@ pub trait PackageManager {
|
||||||
|
|
||||||
fn get_package(&self, name: &str) -> Option<Package>;
|
fn get_package(&self, name: &str) -> Option<Package>;
|
||||||
|
|
||||||
fn install_package(&self, name: &str, allow_external: bool) -> Result<InstallResult, Box<dyn Error>>;
|
fn install_package(
|
||||||
fn install_package_from_repo(&self, name: &str, repo_url: &str) -> Result<InstallResult, Box<dyn Error>>;
|
&self,
|
||||||
|
name: &str,
|
||||||
|
allow_external: bool,
|
||||||
|
) -> Result<InstallResult, Box<dyn Error>>;
|
||||||
|
fn install_package_from_repo(
|
||||||
|
&self,
|
||||||
|
name: &str,
|
||||||
|
repo_url: &str,
|
||||||
|
) -> Result<InstallResult, Box<dyn Error>>;
|
||||||
|
|
||||||
fn remove_package(&self, name: &str) -> Result<RemoveResult, Box<dyn Error>>;
|
fn remove_package(&self, name: &str) -> Result<RemoveResult, Box<dyn Error>>;
|
||||||
|
|
||||||
|
@ -57,18 +65,21 @@ pub struct Package {
|
||||||
pub original_repo: String,
|
pub original_repo: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn default_is_core() -> bool {false}
|
fn default_is_core() -> bool {
|
||||||
fn default_original_repo() -> String {"".to_owned()}
|
false
|
||||||
|
}
|
||||||
|
fn default_original_repo() -> String {
|
||||||
|
"".to_owned()
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||||
pub struct PackageIndex {
|
pub struct PackageIndex {
|
||||||
#[serde(rename = "lastUpdate")]
|
#[serde(rename = "lastUpdate")]
|
||||||
pub last_update: u64,
|
pub last_update: u64,
|
||||||
|
|
||||||
pub packages: Vec<Package>
|
pub packages: Vec<Package>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
pub enum UpdateResult {
|
pub enum UpdateResult {
|
||||||
NotOutdated,
|
NotOutdated,
|
||||||
|
@ -83,11 +94,11 @@ pub enum InstallResult {
|
||||||
MissingPackageVersion,
|
MissingPackageVersion,
|
||||||
AlreadyInstalled,
|
AlreadyInstalled,
|
||||||
Installed,
|
Installed,
|
||||||
BlockedExternalPackage(String)
|
BlockedExternalPackage(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
pub enum RemoveResult {
|
pub enum RemoveResult {
|
||||||
NotFound,
|
NotFound,
|
||||||
Removed
|
Removed,
|
||||||
}
|
}
|
|
@ -1,14 +1,14 @@
|
||||||
use tempfile::TempDir;
|
|
||||||
use std::error::Error;
|
|
||||||
use std::io::{Cursor, copy};
|
|
||||||
use std::{fs, io};
|
|
||||||
use log::debug;
|
use log::debug;
|
||||||
|
use std::error::Error;
|
||||||
|
use std::io::{copy, Cursor};
|
||||||
|
use std::{fs, io};
|
||||||
|
use tempfile::TempDir;
|
||||||
|
|
||||||
pub struct ZipPackageResolver;
|
pub struct ZipPackageResolver;
|
||||||
|
|
||||||
impl ZipPackageResolver {
|
impl ZipPackageResolver {
|
||||||
pub fn new() -> ZipPackageResolver {
|
pub fn new() -> ZipPackageResolver {
|
||||||
return ZipPackageResolver{};
|
return ZipPackageResolver {};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -54,10 +54,19 @@ impl super::PackageResolver for ZipPackageResolver {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (&*file.name()).ends_with('/') {
|
if (&*file.name()).ends_with('/') {
|
||||||
debug!("File {} extracted to \"{}\"", i, outpath.as_path().display());
|
debug!(
|
||||||
|
"File {} extracted to \"{}\"",
|
||||||
|
i,
|
||||||
|
outpath.as_path().display()
|
||||||
|
);
|
||||||
fs::create_dir_all(&outpath).unwrap();
|
fs::create_dir_all(&outpath).unwrap();
|
||||||
} else {
|
} else {
|
||||||
debug!("File {} extracted to \"{}\" ({} bytes)", i, outpath.as_path().display(), file.size());
|
debug!(
|
||||||
|
"File {} extracted to \"{}\" ({} bytes)",
|
||||||
|
i,
|
||||||
|
outpath.as_path().display(),
|
||||||
|
file.size()
|
||||||
|
);
|
||||||
if let Some(p) = outpath.parent() {
|
if let Some(p) = outpath.parent() {
|
||||||
if !p.exists() {
|
if !p.exists() {
|
||||||
fs::create_dir_all(&p).unwrap();
|
fs::create_dir_all(&p).unwrap();
|
||||||
|
@ -74,13 +83,15 @@ impl super::PackageResolver for ZipPackageResolver {
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
|
||||||
use super::super::PackageResolver;
|
use super::super::PackageResolver;
|
||||||
|
use super::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_clone_temp_repository() {
|
fn test_clone_temp_repository() {
|
||||||
let resolver = ZipPackageResolver::new();
|
let resolver = ZipPackageResolver::new();
|
||||||
let cloned_dir = resolver.clone_repo_to_temp("https://github.com/federico-terzi/espanso-hub-core").unwrap();
|
let cloned_dir = resolver
|
||||||
|
.clone_repo_to_temp("https://github.com/federico-terzi/espanso-hub-core")
|
||||||
|
.unwrap();
|
||||||
assert!(cloned_dir.path().join("LICENSE").exists());
|
assert!(cloned_dir.path().join("LICENSE").exists());
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -22,9 +22,7 @@ use widestring::WideCString;
|
||||||
|
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
pub fn spawn_process(cmd: &str, args: &Vec<String>) {
|
pub fn spawn_process(cmd: &str, args: &Vec<String>) {
|
||||||
let quoted_args: Vec<String> = args.iter().map(|arg| {
|
let quoted_args: Vec<String> = args.iter().map(|arg| format!("\"{}\"", arg)).collect();
|
||||||
format!("\"{}\"", arg)
|
|
||||||
}).collect();
|
|
||||||
let quoted_args = quoted_args.join(" ");
|
let quoted_args = quoted_args.join(" ");
|
||||||
let final_cmd = format!("\"{}\" {}", cmd, quoted_args);
|
let final_cmd = format!("\"{}\" {}", cmd, quoted_args);
|
||||||
unsafe {
|
unsafe {
|
||||||
|
@ -34,7 +32,7 @@ pub fn spawn_process(cmd: &str, args: &Vec<String>) {
|
||||||
if res < 0 {
|
if res < 0 {
|
||||||
warn!("unable to start process: {}", final_cmd);
|
warn!("unable to start process: {}", final_cmd);
|
||||||
}
|
}
|
||||||
}else{
|
} else {
|
||||||
warn!("unable to convert process string into wide format")
|
warn!("unable to convert process string into wide format")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,14 +17,14 @@
|
||||||
* along with espanso. If not, see <https://www.gnu.org/licenses/>.
|
* along with espanso. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
use std::sync::mpsc::Sender;
|
|
||||||
use crate::event::{Event, SystemEvent};
|
|
||||||
use crate::event::ActionType;
|
|
||||||
use std::io::{BufReader, Read, Write};
|
|
||||||
use std::error::Error;
|
|
||||||
use log::error;
|
|
||||||
use crate::config::Configs;
|
use crate::config::Configs;
|
||||||
|
use crate::event::ActionType;
|
||||||
|
use crate::event::{Event, SystemEvent};
|
||||||
|
use log::error;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::error::Error;
|
||||||
|
use std::io::{BufReader, Read, Write};
|
||||||
|
use std::sync::mpsc::Sender;
|
||||||
|
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
mod windows;
|
mod windows;
|
||||||
|
@ -47,7 +47,6 @@ pub fn send_command_or_warn(service: Service, configs: Configs, command: IPCComm
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
pub struct IPCCommand {
|
pub struct IPCCommand {
|
||||||
pub id: String,
|
pub id: String,
|
||||||
|
@ -59,41 +58,50 @@ pub struct IPCCommand {
|
||||||
impl IPCCommand {
|
impl IPCCommand {
|
||||||
fn to_event(&self) -> Option<Event> {
|
fn to_event(&self) -> Option<Event> {
|
||||||
match self.id.as_ref() {
|
match self.id.as_ref() {
|
||||||
"exit" => {
|
"exit" => Some(Event::Action(ActionType::Exit)),
|
||||||
Some(Event::Action(ActionType::Exit))
|
"wexit" => Some(Event::Action(ActionType::ExitWorker)),
|
||||||
},
|
"toggle" => Some(Event::Action(ActionType::Toggle)),
|
||||||
"wexit" => {
|
"enable" => Some(Event::Action(ActionType::Enable)),
|
||||||
Some(Event::Action(ActionType::ExitWorker))
|
"disable" => Some(Event::Action(ActionType::Disable)),
|
||||||
},
|
"restartworker" => Some(Event::Action(ActionType::RestartWorker)),
|
||||||
"toggle" => {
|
"notify" => Some(Event::System(SystemEvent::NotifyRequest(
|
||||||
Some(Event::Action(ActionType::Toggle))
|
self.payload.clone(),
|
||||||
},
|
))),
|
||||||
"enable" => {
|
_ => None,
|
||||||
Some(Event::Action(ActionType::Enable))
|
|
||||||
},
|
|
||||||
"disable" => {
|
|
||||||
Some(Event::Action(ActionType::Disable))
|
|
||||||
},
|
|
||||||
"restartworker" => {
|
|
||||||
Some(Event::Action(ActionType::RestartWorker))
|
|
||||||
},
|
|
||||||
"notify" => {
|
|
||||||
Some(Event::System(SystemEvent::NotifyRequest(self.payload.clone())))
|
|
||||||
},
|
|
||||||
_ => None
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn from(event: Event) -> Option<IPCCommand> {
|
pub fn from(event: Event) -> Option<IPCCommand> {
|
||||||
match event {
|
match event {
|
||||||
Event::Action(ActionType::Exit) => Some(IPCCommand{id: "exit".to_owned(), payload: "".to_owned()}),
|
Event::Action(ActionType::Exit) => Some(IPCCommand {
|
||||||
Event::Action(ActionType::ExitWorker) => Some(IPCCommand{id: "wexit".to_owned(), payload: "".to_owned()}),
|
id: "exit".to_owned(),
|
||||||
Event::Action(ActionType::Toggle) => Some(IPCCommand{id: "toggle".to_owned(), payload: "".to_owned()}),
|
payload: "".to_owned(),
|
||||||
Event::Action(ActionType::Enable) => Some(IPCCommand{id: "enable".to_owned(), payload: "".to_owned()}),
|
}),
|
||||||
Event::Action(ActionType::Disable) => Some(IPCCommand{id: "disable".to_owned(), payload: "".to_owned()}),
|
Event::Action(ActionType::ExitWorker) => Some(IPCCommand {
|
||||||
Event::Action(ActionType::RestartWorker) => Some(IPCCommand{id: "restartworker".to_owned(), payload: "".to_owned()}),
|
id: "wexit".to_owned(),
|
||||||
Event::System(SystemEvent::NotifyRequest(message)) => Some(IPCCommand{id: "notify".to_owned(), payload: message}),
|
payload: "".to_owned(),
|
||||||
_ => None
|
}),
|
||||||
|
Event::Action(ActionType::Toggle) => Some(IPCCommand {
|
||||||
|
id: "toggle".to_owned(),
|
||||||
|
payload: "".to_owned(),
|
||||||
|
}),
|
||||||
|
Event::Action(ActionType::Enable) => Some(IPCCommand {
|
||||||
|
id: "enable".to_owned(),
|
||||||
|
payload: "".to_owned(),
|
||||||
|
}),
|
||||||
|
Event::Action(ActionType::Disable) => Some(IPCCommand {
|
||||||
|
id: "disable".to_owned(),
|
||||||
|
payload: "".to_owned(),
|
||||||
|
}),
|
||||||
|
Event::Action(ActionType::RestartWorker) => Some(IPCCommand {
|
||||||
|
id: "restartworker".to_owned(),
|
||||||
|
payload: "".to_owned(),
|
||||||
|
}),
|
||||||
|
Event::System(SystemEvent::NotifyRequest(message)) => Some(IPCCommand {
|
||||||
|
id: "notify".to_owned(),
|
||||||
|
payload: message,
|
||||||
|
}),
|
||||||
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -122,22 +130,23 @@ impl IPCCommand {
|
||||||
fn process_event<R: Read, E: Error>(event_channel: &Sender<Event>, stream: Result<R, E>) {
|
fn process_event<R: Read, E: Error>(event_channel: &Sender<Event>, stream: Result<R, E>) {
|
||||||
match stream {
|
match stream {
|
||||||
Ok(stream) => {
|
Ok(stream) => {
|
||||||
let mut json_str= String::new();
|
let mut json_str = String::new();
|
||||||
let mut buf_reader = BufReader::new(stream);
|
let mut buf_reader = BufReader::new(stream);
|
||||||
let res = buf_reader.read_to_string(&mut json_str);
|
let res = buf_reader.read_to_string(&mut json_str);
|
||||||
|
|
||||||
if res.is_ok() {
|
if res.is_ok() {
|
||||||
let command : Result<IPCCommand, serde_json::Error> = serde_json::from_str(&json_str);
|
let command: Result<IPCCommand, serde_json::Error> =
|
||||||
|
serde_json::from_str(&json_str);
|
||||||
match command {
|
match command {
|
||||||
Ok(command) => {
|
Ok(command) => {
|
||||||
let event = command.to_event();
|
let event = command.to_event();
|
||||||
if let Some(event) = event {
|
if let Some(event) = event {
|
||||||
event_channel.send(event).expect("Broken event channel");
|
event_channel.send(event).expect("Broken event channel");
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!("Error deserializing JSON command: {}", e);
|
error!("Error deserializing JSON command: {}", e);
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -147,7 +156,10 @@ fn process_event<R: Read, E: Error>(event_channel: &Sender<Event>, stream: Resul
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn send_command<W: Write, E: Error>(command: IPCCommand, stream: Result<W, E>) -> Result<(), String>{
|
fn send_command<W: Write, E: Error>(
|
||||||
|
command: IPCCommand,
|
||||||
|
stream: Result<W, E>,
|
||||||
|
) -> Result<(), String> {
|
||||||
match stream {
|
match stream {
|
||||||
Ok(mut stream) => {
|
Ok(mut stream) => {
|
||||||
let json_str = serde_json::to_string(&command);
|
let json_str = serde_json::to_string(&command);
|
||||||
|
@ -155,12 +167,10 @@ fn send_command<W: Write, E: Error>(command: IPCCommand, stream: Result<W, E>) -
|
||||||
stream.write_all(json_str.as_bytes()).unwrap_or_else(|e| {
|
stream.write_all(json_str.as_bytes()).unwrap_or_else(|e| {
|
||||||
println!("Can't write to IPC socket: {}", e);
|
println!("Can't write to IPC socket: {}", e);
|
||||||
});
|
});
|
||||||
return Ok(())
|
return Ok(());
|
||||||
}
|
}
|
||||||
},
|
|
||||||
Err(e) => {
|
|
||||||
return Err(format!("Can't connect to daemon: {}", e))
|
|
||||||
}
|
}
|
||||||
|
Err(e) => return Err(format!("Can't connect to daemon: {}", e)),
|
||||||
}
|
}
|
||||||
|
|
||||||
Err("Can't send command".to_owned())
|
Err("Can't send command".to_owned())
|
||||||
|
@ -173,7 +183,11 @@ pub enum Service {
|
||||||
|
|
||||||
// UNIX IMPLEMENTATION
|
// UNIX IMPLEMENTATION
|
||||||
#[cfg(not(target_os = "windows"))]
|
#[cfg(not(target_os = "windows"))]
|
||||||
pub fn get_ipc_server(service: Service, _: Configs, event_channel: Sender<Event>) -> impl IPCServer {
|
pub fn get_ipc_server(
|
||||||
|
service: Service,
|
||||||
|
_: Configs,
|
||||||
|
event_channel: Sender<Event>,
|
||||||
|
) -> impl IPCServer {
|
||||||
unix::UnixIPCServer::new(service, event_channel)
|
unix::UnixIPCServer::new(service, event_channel)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -184,7 +198,11 @@ pub fn get_ipc_client(service: Service, _: Configs) -> impl IPCClient {
|
||||||
|
|
||||||
// WINDOWS IMPLEMENTATION
|
// WINDOWS IMPLEMENTATION
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
pub fn get_ipc_server(service: Service, config: Configs, event_channel: Sender<Event>) -> impl IPCServer {
|
pub fn get_ipc_server(
|
||||||
|
service: Service,
|
||||||
|
config: Configs,
|
||||||
|
event_channel: Sender<Event>,
|
||||||
|
) -> impl IPCServer {
|
||||||
windows::WindowsIPCServer::new(service, config, event_channel)
|
windows::WindowsIPCServer::new(service, config, event_channel)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -17,18 +17,18 @@
|
||||||
* along with espanso. If not, see <https://www.gnu.org/licenses/>.
|
* along with espanso. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
use std::os::unix::net::{UnixStream,UnixListener};
|
|
||||||
use log::{info, warn};
|
|
||||||
use std::sync::mpsc::Sender;
|
|
||||||
use super::IPCCommand;
|
use super::IPCCommand;
|
||||||
|
use log::{info, warn};
|
||||||
|
use std::os::unix::net::{UnixListener, UnixStream};
|
||||||
|
use std::sync::mpsc::Sender;
|
||||||
|
|
||||||
|
use super::Service;
|
||||||
use crate::context;
|
use crate::context;
|
||||||
use crate::event::*;
|
use crate::event::*;
|
||||||
use crate::protocol::{process_event, send_command};
|
use crate::protocol::{process_event, send_command};
|
||||||
use super::Service;
|
|
||||||
|
|
||||||
const DAEMON_UNIX_SOCKET_NAME : &str = "espanso.sock";
|
const DAEMON_UNIX_SOCKET_NAME: &str = "espanso.sock";
|
||||||
const WORKER_UNIX_SOCKET_NAME : &str = "worker.sock";
|
const WORKER_UNIX_SOCKET_NAME: &str = "worker.sock";
|
||||||
|
|
||||||
pub struct UnixIPCServer {
|
pub struct UnixIPCServer {
|
||||||
service: Service,
|
service: Service,
|
||||||
|
@ -39,15 +39,15 @@ impl UnixIPCServer {
|
||||||
pub fn new(service: Service, event_channel: Sender<Event>) -> UnixIPCServer {
|
pub fn new(service: Service, event_channel: Sender<Event>) -> UnixIPCServer {
|
||||||
UnixIPCServer {
|
UnixIPCServer {
|
||||||
service,
|
service,
|
||||||
event_channel
|
event_channel,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_unix_name(service: &Service) -> String{
|
fn get_unix_name(service: &Service) -> String {
|
||||||
match service {
|
match service {
|
||||||
Service::Daemon => {DAEMON_UNIX_SOCKET_NAME.to_owned()},
|
Service::Daemon => DAEMON_UNIX_SOCKET_NAME.to_owned(),
|
||||||
Service::Worker => {WORKER_UNIX_SOCKET_NAME.to_owned()},
|
Service::Worker => WORKER_UNIX_SOCKET_NAME.to_owned(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -55,21 +55,28 @@ impl super::IPCServer for UnixIPCServer {
|
||||||
fn start(&self) {
|
fn start(&self) {
|
||||||
let event_channel = self.event_channel.clone();
|
let event_channel = self.event_channel.clone();
|
||||||
let socket_name = get_unix_name(&self.service);
|
let socket_name = get_unix_name(&self.service);
|
||||||
std::thread::Builder::new().name("ipc_server".to_string()).spawn(move || {
|
std::thread::Builder::new()
|
||||||
let espanso_dir = context::get_data_dir();
|
.name("ipc_server".to_string())
|
||||||
let unix_socket = espanso_dir.join(socket_name);
|
.spawn(move || {
|
||||||
|
let espanso_dir = context::get_data_dir();
|
||||||
|
let unix_socket = espanso_dir.join(socket_name);
|
||||||
|
|
||||||
std::fs::remove_file(unix_socket.clone()).unwrap_or_else(|e| {
|
std::fs::remove_file(unix_socket.clone()).unwrap_or_else(|e| {
|
||||||
warn!("Unable to delete Unix socket: {}", e);
|
warn!("Unable to delete Unix socket: {}", e);
|
||||||
});
|
});
|
||||||
let listener = UnixListener::bind(unix_socket.clone()).expect("Can't bind to Unix Socket");
|
let listener =
|
||||||
|
UnixListener::bind(unix_socket.clone()).expect("Can't bind to Unix Socket");
|
||||||
|
|
||||||
info!("Binded to IPC unix socket: {}", unix_socket.as_path().display());
|
info!(
|
||||||
|
"Binded to IPC unix socket: {}",
|
||||||
|
unix_socket.as_path().display()
|
||||||
|
);
|
||||||
|
|
||||||
for stream in listener.incoming() {
|
for stream in listener.incoming() {
|
||||||
process_event(&event_channel, stream);
|
process_event(&event_channel, stream);
|
||||||
}
|
}
|
||||||
}).expect("Unable to spawn IPC server thread");
|
})
|
||||||
|
.expect("Unable to spawn IPC server thread");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -79,7 +86,7 @@ pub struct UnixIPCClient {
|
||||||
|
|
||||||
impl UnixIPCClient {
|
impl UnixIPCClient {
|
||||||
pub fn new(service: Service) -> UnixIPCClient {
|
pub fn new(service: Service) -> UnixIPCClient {
|
||||||
UnixIPCClient{service}
|
UnixIPCClient { service }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -17,14 +17,14 @@
|
||||||
* along with espanso. If not, see <https://www.gnu.org/licenses/>.
|
* along with espanso. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
use log::{info};
|
|
||||||
use std::sync::mpsc::Sender;
|
|
||||||
use std::net::{TcpListener, TcpStream};
|
|
||||||
use super::IPCCommand;
|
use super::IPCCommand;
|
||||||
|
use log::info;
|
||||||
|
use std::net::{TcpListener, TcpStream};
|
||||||
|
use std::sync::mpsc::Sender;
|
||||||
|
|
||||||
|
use crate::config::Configs;
|
||||||
use crate::event::*;
|
use crate::event::*;
|
||||||
use crate::protocol::{process_event, send_command, Service};
|
use crate::protocol::{process_event, send_command, Service};
|
||||||
use crate::config::{Configs};
|
|
||||||
|
|
||||||
pub struct WindowsIPCServer {
|
pub struct WindowsIPCServer {
|
||||||
service: Service,
|
service: Service,
|
||||||
|
@ -34,15 +34,23 @@ pub struct WindowsIPCServer {
|
||||||
|
|
||||||
fn to_port(config: &Configs, service: &Service) -> u16 {
|
fn to_port(config: &Configs, service: &Service) -> u16 {
|
||||||
let port = match service {
|
let port = match service {
|
||||||
Service::Daemon => {config.ipc_server_port},
|
Service::Daemon => config.ipc_server_port,
|
||||||
Service::Worker => {config.worker_ipc_server_port},
|
Service::Worker => config.worker_ipc_server_port,
|
||||||
};
|
};
|
||||||
port as u16
|
port as u16
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WindowsIPCServer {
|
impl WindowsIPCServer {
|
||||||
pub fn new(service: Service, config: Configs, event_channel: Sender<Event>) -> WindowsIPCServer {
|
pub fn new(
|
||||||
WindowsIPCServer {service, config, event_channel}
|
service: Service,
|
||||||
|
config: Configs,
|
||||||
|
event_channel: Sender<Event>,
|
||||||
|
) -> WindowsIPCServer {
|
||||||
|
WindowsIPCServer {
|
||||||
|
service,
|
||||||
|
config,
|
||||||
|
event_channel,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -50,17 +58,22 @@ impl super::IPCServer for WindowsIPCServer {
|
||||||
fn start(&self) {
|
fn start(&self) {
|
||||||
let event_channel = self.event_channel.clone();
|
let event_channel = self.event_channel.clone();
|
||||||
let server_port = to_port(&self.config, &self.service);
|
let server_port = to_port(&self.config, &self.service);
|
||||||
std::thread::Builder::new().name("ipc_server".to_string()).spawn(move || {
|
std::thread::Builder::new()
|
||||||
let listener = TcpListener::bind(
|
.name("ipc_server".to_string())
|
||||||
format!("127.0.0.1:{}", server_port)
|
.spawn(move || {
|
||||||
).expect("Error binding to IPC server port");
|
let listener = TcpListener::bind(format!("127.0.0.1:{}", server_port))
|
||||||
|
.expect("Error binding to IPC server port");
|
||||||
|
|
||||||
info!("Binded to IPC tcp socket: {}", listener.local_addr().unwrap().to_string());
|
info!(
|
||||||
|
"Binded to IPC tcp socket: {}",
|
||||||
|
listener.local_addr().unwrap().to_string()
|
||||||
|
);
|
||||||
|
|
||||||
for stream in listener.incoming() {
|
for stream in listener.incoming() {
|
||||||
process_event(&event_channel, stream);
|
process_event(&event_channel, stream);
|
||||||
}
|
}
|
||||||
}).expect("Unable to spawn IPC server thread");
|
})
|
||||||
|
.expect("Unable to spawn IPC server thread");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -71,16 +84,14 @@ pub struct WindowsIPCClient {
|
||||||
|
|
||||||
impl WindowsIPCClient {
|
impl WindowsIPCClient {
|
||||||
pub fn new(service: Service, config: Configs) -> WindowsIPCClient {
|
pub fn new(service: Service, config: Configs) -> WindowsIPCClient {
|
||||||
WindowsIPCClient{service, config}
|
WindowsIPCClient { service, config }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl super::IPCClient for WindowsIPCClient {
|
impl super::IPCClient for WindowsIPCClient {
|
||||||
fn send_command(&self, command: IPCCommand) -> Result<(), String> {
|
fn send_command(&self, command: IPCCommand) -> Result<(), String> {
|
||||||
let port = to_port(&self.config, &self.service);
|
let port = to_port(&self.config, &self.service);
|
||||||
let stream = TcpStream::connect(
|
let stream = TcpStream::connect(("127.0.0.1", port));
|
||||||
("127.0.0.1", port)
|
|
||||||
);
|
|
||||||
|
|
||||||
send_command(command, stream)
|
send_command(command, stream)
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,18 +17,18 @@
|
||||||
* along with espanso. If not, see <https://www.gnu.org/licenses/>.
|
* along with espanso. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
use serde_yaml::{Value};
|
|
||||||
use std::collections::{HashMap, HashSet};
|
|
||||||
use regex::{Regex, Captures};
|
|
||||||
use log::{warn, error};
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::matcher::{Match, MatchContentType};
|
|
||||||
use crate::config::Configs;
|
use crate::config::Configs;
|
||||||
use crate::extension::Extension;
|
use crate::extension::Extension;
|
||||||
|
use crate::matcher::{Match, MatchContentType};
|
||||||
|
use log::{error, warn};
|
||||||
|
use regex::{Captures, Regex};
|
||||||
|
use serde_yaml::Value;
|
||||||
|
use std::collections::{HashMap, HashSet};
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
static ref VAR_REGEX: Regex = Regex::new("\\{\\{\\s*(?P<name>\\w+)\\s*\\}\\}").unwrap();
|
static ref VAR_REGEX: Regex = Regex::new("\\{\\{\\s*(?P<name>\\w+)\\s*\\}\\}").unwrap();
|
||||||
static ref UNKNOWN_VARIABLE : String = "".to_string();
|
static ref UNKNOWN_VARIABLE: String = "".to_string();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct DefaultRenderer {
|
pub struct DefaultRenderer {
|
||||||
|
@ -47,12 +47,11 @@ impl DefaultRenderer {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Compile the regexes
|
// Compile the regexes
|
||||||
let passive_match_regex = Regex::new(&config.passive_match_regex)
|
let passive_match_regex = Regex::new(&config.passive_match_regex).unwrap_or_else(|e| {
|
||||||
.unwrap_or_else(|e| {
|
panic!("Invalid passive match regex: {:?}", e);
|
||||||
panic!("Invalid passive match regex: {:?}", e);
|
});
|
||||||
});
|
|
||||||
|
|
||||||
DefaultRenderer{
|
DefaultRenderer {
|
||||||
extension_map,
|
extension_map,
|
||||||
passive_match_regex,
|
passive_match_regex,
|
||||||
}
|
}
|
||||||
|
@ -69,7 +68,6 @@ impl DefaultRenderer {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
result
|
result
|
||||||
|
@ -77,7 +75,13 @@ impl DefaultRenderer {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl super::Renderer for DefaultRenderer {
|
impl super::Renderer for DefaultRenderer {
|
||||||
fn render_match(&self, m: &Match, trigger_offset: usize, config: &Configs, args: Vec<String>) -> RenderResult {
|
fn render_match(
|
||||||
|
&self,
|
||||||
|
m: &Match,
|
||||||
|
trigger_offset: usize,
|
||||||
|
config: &Configs,
|
||||||
|
args: Vec<String>,
|
||||||
|
) -> RenderResult {
|
||||||
// Manage the different types of matches
|
// Manage the different types of matches
|
||||||
match &m.content {
|
match &m.content {
|
||||||
// Text Match
|
// Text Match
|
||||||
|
@ -88,7 +92,7 @@ impl super::Renderer for DefaultRenderer {
|
||||||
for caps in VAR_REGEX.captures_iter(&content.replace) {
|
for caps in VAR_REGEX.captures_iter(&content.replace) {
|
||||||
let var_name = caps.name("name").unwrap().as_str();
|
let var_name = caps.name("name").unwrap().as_str();
|
||||||
target_vars.insert(var_name.to_owned());
|
target_vars.insert(var_name.to_owned());
|
||||||
};
|
}
|
||||||
|
|
||||||
let target_string = if target_vars.len() > 0 {
|
let target_string = if target_vars.len() > 0 {
|
||||||
let mut output_map = HashMap::new();
|
let mut output_map = HashMap::new();
|
||||||
|
@ -106,24 +110,32 @@ impl super::Renderer for DefaultRenderer {
|
||||||
// Extract the match trigger from the variable params
|
// Extract the match trigger from the variable params
|
||||||
let trigger = variable.params.get(&Value::from("trigger"));
|
let trigger = variable.params.get(&Value::from("trigger"));
|
||||||
if trigger.is_none() {
|
if trigger.is_none() {
|
||||||
warn!("Missing param 'trigger' in match variable: {}", variable.name);
|
warn!(
|
||||||
|
"Missing param 'trigger' in match variable: {}",
|
||||||
|
variable.name
|
||||||
|
);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
let trigger = trigger.unwrap();
|
let trigger = trigger.unwrap();
|
||||||
|
|
||||||
// Find the given match from the active configs
|
// Find the given match from the active configs
|
||||||
let inner_match = DefaultRenderer::find_match(config, trigger.as_str().unwrap_or(""));
|
let inner_match =
|
||||||
|
DefaultRenderer::find_match(config, trigger.as_str().unwrap_or(""));
|
||||||
|
|
||||||
if inner_match.is_none() {
|
if inner_match.is_none() {
|
||||||
warn!("Could not find inner match with trigger: '{}'", trigger.as_str().unwrap_or("undefined"));
|
warn!(
|
||||||
continue
|
"Could not find inner match with trigger: '{}'",
|
||||||
|
trigger.as_str().unwrap_or("undefined")
|
||||||
|
);
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
let (inner_match, trigger_offset) = inner_match.unwrap();
|
let (inner_match, trigger_offset) = inner_match.unwrap();
|
||||||
|
|
||||||
// Render the inner match
|
// Render the inner match
|
||||||
// TODO: inner arguments
|
// TODO: inner arguments
|
||||||
let result = self.render_match(&inner_match, trigger_offset, config, vec![]);
|
let result =
|
||||||
|
self.render_match(&inner_match, trigger_offset, config, vec![]);
|
||||||
|
|
||||||
// Inner matches are only supported for text-expansions, warn the user otherwise
|
// Inner matches are only supported for text-expansions, warn the user otherwise
|
||||||
match result {
|
match result {
|
||||||
|
@ -134,18 +146,25 @@ impl super::Renderer for DefaultRenderer {
|
||||||
warn!("Inner matches must be of TEXT type. Mixing images is not supported yet.")
|
warn!("Inner matches must be of TEXT type. Mixing images is not supported yet.")
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}else{ // Normal extension variables
|
} else {
|
||||||
|
// Normal extension variables
|
||||||
let extension = self.extension_map.get(&variable.var_type);
|
let extension = self.extension_map.get(&variable.var_type);
|
||||||
if let Some(extension) = extension {
|
if let Some(extension) = extension {
|
||||||
let ext_out = extension.calculate(&variable.params, &args);
|
let ext_out = extension.calculate(&variable.params, &args);
|
||||||
if let Some(output) = ext_out {
|
if let Some(output) = ext_out {
|
||||||
output_map.insert(variable.name.clone(), output);
|
output_map.insert(variable.name.clone(), output);
|
||||||
}else{
|
} else {
|
||||||
output_map.insert(variable.name.clone(), "".to_owned());
|
output_map.insert(variable.name.clone(), "".to_owned());
|
||||||
warn!("Could not generate output for variable: {}", variable.name);
|
warn!(
|
||||||
|
"Could not generate output for variable: {}",
|
||||||
|
variable.name
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}else{
|
} else {
|
||||||
error!("No extension found for variable type: {}", variable.var_type);
|
error!(
|
||||||
|
"No extension found for variable type: {}",
|
||||||
|
variable.var_type
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -158,14 +177,14 @@ impl super::Renderer for DefaultRenderer {
|
||||||
});
|
});
|
||||||
|
|
||||||
result.to_string()
|
result.to_string()
|
||||||
}else{ // No variables, simple text substitution
|
} else {
|
||||||
|
// No variables, simple text substitution
|
||||||
content.replace.clone()
|
content.replace.clone()
|
||||||
};
|
};
|
||||||
|
|
||||||
// Unescape any brackets (needed to be able to insert double brackets in replacement
|
// Unescape any brackets (needed to be able to insert double brackets in replacement
|
||||||
// text, without triggering the variable system). See issue #187
|
// text, without triggering the variable system). See issue #187
|
||||||
let target_string = target_string.replace("\\{", "{")
|
let target_string = target_string.replace("\\{", "{").replace("\\}", "}");
|
||||||
.replace("\\}", "}");
|
|
||||||
|
|
||||||
// Render any argument that may be present
|
// Render any argument that may be present
|
||||||
let target_string = utils::render_args(&target_string, &args);
|
let target_string = utils::render_args(&target_string, &args);
|
||||||
|
@ -177,27 +196,26 @@ impl super::Renderer for DefaultRenderer {
|
||||||
// The check should be carried out from the position of the first
|
// The check should be carried out from the position of the first
|
||||||
// alphabetic letter
|
// alphabetic letter
|
||||||
// See issue #244
|
// See issue #244
|
||||||
let first_alphabetic = trigger.chars().position(|c| {
|
let first_alphabetic =
|
||||||
c.is_alphabetic()
|
trigger.chars().position(|c| c.is_alphabetic()).unwrap_or(0);
|
||||||
}).unwrap_or(0);
|
|
||||||
|
|
||||||
let first_char = trigger.chars().nth(first_alphabetic);
|
let first_char = trigger.chars().nth(first_alphabetic);
|
||||||
let second_char = trigger.chars().nth(first_alphabetic + 1);
|
let second_char = trigger.chars().nth(first_alphabetic + 1);
|
||||||
let mode: i32 = if let Some(first_char) = first_char {
|
let mode: i32 = if let Some(first_char) = first_char {
|
||||||
if first_char.is_uppercase() {
|
if first_char.is_uppercase() {
|
||||||
if let Some(second_char) = second_char {
|
if let Some(second_char) = second_char {
|
||||||
if second_char.is_uppercase() {
|
if second_char.is_uppercase() {
|
||||||
2 // Full CAPITALIZATION
|
2 // Full CAPITALIZATION
|
||||||
}else{
|
} else {
|
||||||
1 // Only first letter capitalized: Capitalization
|
1 // Only first letter capitalized: Capitalization
|
||||||
}
|
}
|
||||||
}else{
|
} else {
|
||||||
2 // Single char, defaults to full CAPITALIZATION
|
2 // Single char, defaults to full CAPITALIZATION
|
||||||
}
|
}
|
||||||
}else{
|
} else {
|
||||||
0 // Lowercase, no action
|
0 // Lowercase, no action
|
||||||
}
|
}
|
||||||
}else{
|
} else {
|
||||||
0
|
0
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -207,78 +225,79 @@ impl super::Renderer for DefaultRenderer {
|
||||||
let mut v: Vec<char> = target_string.chars().collect();
|
let mut v: Vec<char> = target_string.chars().collect();
|
||||||
v[0] = v[0].to_uppercase().nth(0).unwrap();
|
v[0] = v[0].to_uppercase().nth(0).unwrap();
|
||||||
v.into_iter().collect()
|
v.into_iter().collect()
|
||||||
},
|
}
|
||||||
2 => { // Full capitalization
|
2 => {
|
||||||
|
// Full capitalization
|
||||||
target_string.to_uppercase()
|
target_string.to_uppercase()
|
||||||
},
|
}
|
||||||
_ => { // Noop
|
_ => {
|
||||||
|
// Noop
|
||||||
target_string
|
target_string
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}else{
|
} else {
|
||||||
target_string
|
target_string
|
||||||
};
|
};
|
||||||
|
|
||||||
RenderResult::Text(target_string)
|
RenderResult::Text(target_string)
|
||||||
},
|
}
|
||||||
|
|
||||||
// Image Match
|
// Image Match
|
||||||
MatchContentType::Image(content) => {
|
MatchContentType::Image(content) => {
|
||||||
// Make sure the image exist beforehand
|
// Make sure the image exist beforehand
|
||||||
if content.path.exists() {
|
if content.path.exists() {
|
||||||
RenderResult::Image(content.path.clone())
|
RenderResult::Image(content.path.clone())
|
||||||
}else{
|
} else {
|
||||||
error!("Image not found in path: {:?}", content.path);
|
error!("Image not found in path: {:?}", content.path);
|
||||||
RenderResult::Error
|
RenderResult::Error
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_passive(&self, text: &str, config: &Configs) -> RenderResult {
|
fn render_passive(&self, text: &str, config: &Configs) -> RenderResult {
|
||||||
// Render the matches
|
// Render the matches
|
||||||
let result = self.passive_match_regex.replace_all(&text, |caps: &Captures| {
|
let result = self
|
||||||
let match_name = if let Some(name) = caps.name("name") {
|
.passive_match_regex
|
||||||
name.as_str()
|
.replace_all(&text, |caps: &Captures| {
|
||||||
}else{
|
let match_name = if let Some(name) = caps.name("name") {
|
||||||
""
|
name.as_str()
|
||||||
};
|
} else {
|
||||||
|
""
|
||||||
|
};
|
||||||
|
|
||||||
|
// Get the original matching string, useful to return the match untouched
|
||||||
|
let original_match = caps.get(0).unwrap().as_str();
|
||||||
|
|
||||||
// Get the original matching string, useful to return the match untouched
|
// Find the corresponding match
|
||||||
let original_match = caps.get(0).unwrap().as_str();
|
let m = DefaultRenderer::find_match(config, match_name);
|
||||||
|
|
||||||
// Find the corresponding match
|
// If no match is found, leave the match without modifications
|
||||||
let m = DefaultRenderer::find_match(config, match_name);
|
if m.is_none() {
|
||||||
|
return original_match.to_owned();
|
||||||
// If no match is found, leave the match without modifications
|
|
||||||
if m.is_none() {
|
|
||||||
return original_match.to_owned();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Compute the args by separating them
|
|
||||||
let match_args = if let Some(args) = caps.name("args") {
|
|
||||||
args.as_str()
|
|
||||||
}else{
|
|
||||||
""
|
|
||||||
};
|
|
||||||
let args : Vec<String> = utils::split_args(match_args,
|
|
||||||
config.passive_arg_delimiter,
|
|
||||||
config.passive_arg_escape);
|
|
||||||
|
|
||||||
let (m, trigger_offset) = m.unwrap();
|
|
||||||
// Render the actual match
|
|
||||||
let result = self.render_match(&m, trigger_offset, &config, args);
|
|
||||||
|
|
||||||
match result {
|
|
||||||
RenderResult::Text(out) => {
|
|
||||||
out
|
|
||||||
},
|
|
||||||
_ => {
|
|
||||||
original_match.to_owned()
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
});
|
// Compute the args by separating them
|
||||||
|
let match_args = if let Some(args) = caps.name("args") {
|
||||||
|
args.as_str()
|
||||||
|
} else {
|
||||||
|
""
|
||||||
|
};
|
||||||
|
let args: Vec<String> = utils::split_args(
|
||||||
|
match_args,
|
||||||
|
config.passive_arg_delimiter,
|
||||||
|
config.passive_arg_escape,
|
||||||
|
);
|
||||||
|
|
||||||
|
let (m, trigger_offset) = m.unwrap();
|
||||||
|
// Render the actual match
|
||||||
|
let result = self.render_match(&m, trigger_offset, &config, args);
|
||||||
|
|
||||||
|
match result {
|
||||||
|
RenderResult::Text(out) => out,
|
||||||
|
_ => original_match.to_owned(),
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
RenderResult::Text(result.into_owned())
|
RenderResult::Text(result.into_owned())
|
||||||
}
|
}
|
||||||
|
@ -291,11 +310,14 @@ mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
fn get_renderer(config: Configs) -> DefaultRenderer {
|
fn get_renderer(config: Configs) -> DefaultRenderer {
|
||||||
DefaultRenderer::new(vec![Box::new(crate::extension::dummy::DummyExtension::new())], config)
|
DefaultRenderer::new(
|
||||||
|
vec![Box::new(crate::extension::dummy::DummyExtension::new())],
|
||||||
|
config,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_config_for(s: &str) -> Configs {
|
fn get_config_for(s: &str) -> Configs {
|
||||||
let config : Configs = serde_yaml::from_str(s).unwrap();
|
let config: Configs = serde_yaml::from_str(s).unwrap();
|
||||||
config
|
config
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -303,10 +325,8 @@ mod tests {
|
||||||
match rendered {
|
match rendered {
|
||||||
RenderResult::Text(rendered) => {
|
RenderResult::Text(rendered) => {
|
||||||
assert_eq!(rendered, target);
|
assert_eq!(rendered, target);
|
||||||
},
|
|
||||||
_ => {
|
|
||||||
assert!(false)
|
|
||||||
}
|
}
|
||||||
|
_ => assert!(false),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -316,11 +336,13 @@ mod tests {
|
||||||
this text contains no matches
|
this text contains no matches
|
||||||
"###;
|
"###;
|
||||||
|
|
||||||
let config = get_config_for(r###"
|
let config = get_config_for(
|
||||||
|
r###"
|
||||||
matches:
|
matches:
|
||||||
- trigger: test
|
- trigger: test
|
||||||
replace: result
|
replace: result
|
||||||
"###);
|
"###,
|
||||||
|
);
|
||||||
|
|
||||||
let renderer = get_renderer(config.clone());
|
let renderer = get_renderer(config.clone());
|
||||||
|
|
||||||
|
@ -333,11 +355,13 @@ mod tests {
|
||||||
fn test_render_passive_simple_match_no_args() {
|
fn test_render_passive_simple_match_no_args() {
|
||||||
let text = "this is a :test";
|
let text = "this is a :test";
|
||||||
|
|
||||||
let config = get_config_for(r###"
|
let config = get_config_for(
|
||||||
|
r###"
|
||||||
matches:
|
matches:
|
||||||
- trigger: ':test'
|
- trigger: ':test'
|
||||||
replace: result
|
replace: result
|
||||||
"###);
|
"###,
|
||||||
|
);
|
||||||
|
|
||||||
let renderer = get_renderer(config.clone());
|
let renderer = get_renderer(config.clone());
|
||||||
|
|
||||||
|
@ -350,11 +374,13 @@ mod tests {
|
||||||
fn test_render_passive_multiple_match_no_args() {
|
fn test_render_passive_multiple_match_no_args() {
|
||||||
let text = "this is a :test and then another :test";
|
let text = "this is a :test and then another :test";
|
||||||
|
|
||||||
let config = get_config_for(r###"
|
let config = get_config_for(
|
||||||
|
r###"
|
||||||
matches:
|
matches:
|
||||||
- trigger: ':test'
|
- trigger: ':test'
|
||||||
replace: result
|
replace: result
|
||||||
"###);
|
"###,
|
||||||
|
);
|
||||||
|
|
||||||
let renderer = get_renderer(config.clone());
|
let renderer = get_renderer(config.clone());
|
||||||
|
|
||||||
|
@ -369,15 +395,17 @@ mod tests {
|
||||||
:test
|
:test
|
||||||
"###;
|
"###;
|
||||||
|
|
||||||
let result= r###"this is a
|
let result = r###"this is a
|
||||||
result
|
result
|
||||||
"###;
|
"###;
|
||||||
|
|
||||||
let config = get_config_for(r###"
|
let config = get_config_for(
|
||||||
|
r###"
|
||||||
matches:
|
matches:
|
||||||
- trigger: ':test'
|
- trigger: ':test'
|
||||||
replace: result
|
replace: result
|
||||||
"###);
|
"###,
|
||||||
|
);
|
||||||
|
|
||||||
let renderer = get_renderer(config.clone());
|
let renderer = get_renderer(config.clone());
|
||||||
|
|
||||||
|
@ -390,7 +418,8 @@ mod tests {
|
||||||
fn test_render_passive_nested_matches_no_args() {
|
fn test_render_passive_nested_matches_no_args() {
|
||||||
let text = ":greet";
|
let text = ":greet";
|
||||||
|
|
||||||
let config = get_config_for(r###"
|
let config = get_config_for(
|
||||||
|
r###"
|
||||||
matches:
|
matches:
|
||||||
- trigger: ':greet'
|
- trigger: ':greet'
|
||||||
replace: "hi {{name}}"
|
replace: "hi {{name}}"
|
||||||
|
@ -402,7 +431,8 @@ mod tests {
|
||||||
|
|
||||||
- trigger: ':name'
|
- trigger: ':name'
|
||||||
replace: john
|
replace: john
|
||||||
"###);
|
"###,
|
||||||
|
);
|
||||||
|
|
||||||
let renderer = get_renderer(config.clone());
|
let renderer = get_renderer(config.clone());
|
||||||
|
|
||||||
|
@ -415,11 +445,13 @@ mod tests {
|
||||||
fn test_render_passive_simple_match_with_args() {
|
fn test_render_passive_simple_match_with_args() {
|
||||||
let text = ":greet/Jon/";
|
let text = ":greet/Jon/";
|
||||||
|
|
||||||
let config = get_config_for(r###"
|
let config = get_config_for(
|
||||||
|
r###"
|
||||||
matches:
|
matches:
|
||||||
- trigger: ':greet'
|
- trigger: ':greet'
|
||||||
replace: "Hi $0$"
|
replace: "Hi $0$"
|
||||||
"###);
|
"###,
|
||||||
|
);
|
||||||
|
|
||||||
let renderer = get_renderer(config.clone());
|
let renderer = get_renderer(config.clone());
|
||||||
|
|
||||||
|
@ -432,11 +464,13 @@ mod tests {
|
||||||
fn test_render_passive_simple_match_with_multiple_args() {
|
fn test_render_passive_simple_match_with_multiple_args() {
|
||||||
let text = ":greet/Jon/Snow/";
|
let text = ":greet/Jon/Snow/";
|
||||||
|
|
||||||
let config = get_config_for(r###"
|
let config = get_config_for(
|
||||||
|
r###"
|
||||||
matches:
|
matches:
|
||||||
- trigger: ':greet'
|
- trigger: ':greet'
|
||||||
replace: "Hi $0$, there is $1$ outside"
|
replace: "Hi $0$, there is $1$ outside"
|
||||||
"###);
|
"###,
|
||||||
|
);
|
||||||
|
|
||||||
let renderer = get_renderer(config.clone());
|
let renderer = get_renderer(config.clone());
|
||||||
|
|
||||||
|
@ -449,11 +483,13 @@ mod tests {
|
||||||
fn test_render_passive_simple_match_with_escaped_args() {
|
fn test_render_passive_simple_match_with_escaped_args() {
|
||||||
let text = ":greet/Jon/10\\/12/";
|
let text = ":greet/Jon/10\\/12/";
|
||||||
|
|
||||||
let config = get_config_for(r###"
|
let config = get_config_for(
|
||||||
|
r###"
|
||||||
matches:
|
matches:
|
||||||
- trigger: ':greet'
|
- trigger: ':greet'
|
||||||
replace: "Hi $0$, today is $1$"
|
replace: "Hi $0$, today is $1$"
|
||||||
"###);
|
"###,
|
||||||
|
);
|
||||||
|
|
||||||
let renderer = get_renderer(config.clone());
|
let renderer = get_renderer(config.clone());
|
||||||
|
|
||||||
|
@ -466,11 +502,13 @@ mod tests {
|
||||||
fn test_render_passive_simple_match_with_args_not_closed() {
|
fn test_render_passive_simple_match_with_args_not_closed() {
|
||||||
let text = ":greet/Jon/Snow";
|
let text = ":greet/Jon/Snow";
|
||||||
|
|
||||||
let config = get_config_for(r###"
|
let config = get_config_for(
|
||||||
|
r###"
|
||||||
matches:
|
matches:
|
||||||
- trigger: ':greet'
|
- trigger: ':greet'
|
||||||
replace: "Hi $0$"
|
replace: "Hi $0$"
|
||||||
"###);
|
"###,
|
||||||
|
);
|
||||||
|
|
||||||
let renderer = get_renderer(config.clone());
|
let renderer = get_renderer(config.clone());
|
||||||
|
|
||||||
|
@ -483,7 +521,8 @@ mod tests {
|
||||||
fn test_render_passive_local_var() {
|
fn test_render_passive_local_var() {
|
||||||
let text = "this is :test";
|
let text = "this is :test";
|
||||||
|
|
||||||
let config = get_config_for(r###"
|
let config = get_config_for(
|
||||||
|
r###"
|
||||||
matches:
|
matches:
|
||||||
- trigger: ':test'
|
- trigger: ':test'
|
||||||
replace: "my {{output}}"
|
replace: "my {{output}}"
|
||||||
|
@ -492,7 +531,8 @@ mod tests {
|
||||||
type: dummy
|
type: dummy
|
||||||
params:
|
params:
|
||||||
echo: "result"
|
echo: "result"
|
||||||
"###);
|
"###,
|
||||||
|
);
|
||||||
|
|
||||||
let renderer = get_renderer(config.clone());
|
let renderer = get_renderer(config.clone());
|
||||||
|
|
||||||
|
@ -505,7 +545,8 @@ mod tests {
|
||||||
fn test_render_passive_global_var() {
|
fn test_render_passive_global_var() {
|
||||||
let text = "this is :test";
|
let text = "this is :test";
|
||||||
|
|
||||||
let config = get_config_for(r###"
|
let config = get_config_for(
|
||||||
|
r###"
|
||||||
global_vars:
|
global_vars:
|
||||||
- name: output
|
- name: output
|
||||||
type: dummy
|
type: dummy
|
||||||
|
@ -515,7 +556,8 @@ mod tests {
|
||||||
- trigger: ':test'
|
- trigger: ':test'
|
||||||
replace: "my {{output}}"
|
replace: "my {{output}}"
|
||||||
|
|
||||||
"###);
|
"###,
|
||||||
|
);
|
||||||
|
|
||||||
let renderer = get_renderer(config.clone());
|
let renderer = get_renderer(config.clone());
|
||||||
|
|
||||||
|
@ -528,7 +570,8 @@ mod tests {
|
||||||
fn test_render_passive_global_var_is_overridden_by_local() {
|
fn test_render_passive_global_var_is_overridden_by_local() {
|
||||||
let text = "this is :test";
|
let text = "this is :test";
|
||||||
|
|
||||||
let config = get_config_for(r###"
|
let config = get_config_for(
|
||||||
|
r###"
|
||||||
global_vars:
|
global_vars:
|
||||||
- name: output
|
- name: output
|
||||||
type: dummy
|
type: dummy
|
||||||
|
@ -543,7 +586,8 @@ mod tests {
|
||||||
params:
|
params:
|
||||||
echo: "local"
|
echo: "local"
|
||||||
|
|
||||||
"###);
|
"###,
|
||||||
|
);
|
||||||
|
|
||||||
let renderer = get_renderer(config.clone());
|
let renderer = get_renderer(config.clone());
|
||||||
|
|
||||||
|
@ -556,11 +600,13 @@ mod tests {
|
||||||
fn test_render_match_with_unknown_variable_does_not_crash() {
|
fn test_render_match_with_unknown_variable_does_not_crash() {
|
||||||
let text = "this is :test";
|
let text = "this is :test";
|
||||||
|
|
||||||
let config = get_config_for(r###"
|
let config = get_config_for(
|
||||||
|
r###"
|
||||||
matches:
|
matches:
|
||||||
- trigger: ':test'
|
- trigger: ':test'
|
||||||
replace: "my {{unknown}}"
|
replace: "my {{unknown}}"
|
||||||
"###);
|
"###,
|
||||||
|
);
|
||||||
|
|
||||||
let renderer = get_renderer(config.clone());
|
let renderer = get_renderer(config.clone());
|
||||||
|
|
||||||
|
@ -573,11 +619,13 @@ mod tests {
|
||||||
fn test_render_escaped_double_brackets_should_not_consider_them_variable() {
|
fn test_render_escaped_double_brackets_should_not_consider_them_variable() {
|
||||||
let text = "this is :test";
|
let text = "this is :test";
|
||||||
|
|
||||||
let config = get_config_for(r###"
|
let config = get_config_for(
|
||||||
|
r###"
|
||||||
matches:
|
matches:
|
||||||
- trigger: ':test'
|
- trigger: ':test'
|
||||||
replace: "my \\{\\{unknown\\}\\}"
|
replace: "my \\{\\{unknown\\}\\}"
|
||||||
"###);
|
"###,
|
||||||
|
);
|
||||||
|
|
||||||
let renderer = get_renderer(config.clone());
|
let renderer = get_renderer(config.clone());
|
||||||
|
|
||||||
|
@ -590,11 +638,13 @@ mod tests {
|
||||||
fn test_render_passive_simple_match_multi_trigger_no_args() {
|
fn test_render_passive_simple_match_multi_trigger_no_args() {
|
||||||
let text = "this is a :yolo and :test";
|
let text = "this is a :yolo and :test";
|
||||||
|
|
||||||
let config = get_config_for(r###"
|
let config = get_config_for(
|
||||||
|
r###"
|
||||||
matches:
|
matches:
|
||||||
- triggers: [':test', ':yolo']
|
- triggers: [':test', ':yolo']
|
||||||
replace: result
|
replace: result
|
||||||
"###);
|
"###,
|
||||||
|
);
|
||||||
|
|
||||||
let renderer = get_renderer(config.clone());
|
let renderer = get_renderer(config.clone());
|
||||||
|
|
||||||
|
@ -607,11 +657,13 @@ mod tests {
|
||||||
fn test_render_passive_simple_match_multi_trigger_with_args() {
|
fn test_render_passive_simple_match_multi_trigger_with_args() {
|
||||||
let text = ":yolo/Jon/";
|
let text = ":yolo/Jon/";
|
||||||
|
|
||||||
let config = get_config_for(r###"
|
let config = get_config_for(
|
||||||
|
r###"
|
||||||
matches:
|
matches:
|
||||||
- triggers: [':greet', ':yolo']
|
- triggers: [':greet', ':yolo']
|
||||||
replace: "Hi $0$"
|
replace: "Hi $0$"
|
||||||
"###);
|
"###,
|
||||||
|
);
|
||||||
|
|
||||||
let renderer = get_renderer(config.clone());
|
let renderer = get_renderer(config.clone());
|
||||||
|
|
||||||
|
@ -622,18 +674,20 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_render_match_case_propagation_no_case() {
|
fn test_render_match_case_propagation_no_case() {
|
||||||
let config = get_config_for(r###"
|
let config = get_config_for(
|
||||||
|
r###"
|
||||||
matches:
|
matches:
|
||||||
- trigger: 'test'
|
- trigger: 'test'
|
||||||
replace: result
|
replace: result
|
||||||
propagate_case: true
|
propagate_case: true
|
||||||
"###);
|
"###,
|
||||||
|
);
|
||||||
|
|
||||||
let renderer = get_renderer(config.clone());
|
let renderer = get_renderer(config.clone());
|
||||||
|
|
||||||
let m = config.matches[0].clone();
|
let m = config.matches[0].clone();
|
||||||
|
|
||||||
let trigger_offset = m.triggers.iter().position(|x| x== "test").unwrap();
|
let trigger_offset = m.triggers.iter().position(|x| x == "test").unwrap();
|
||||||
|
|
||||||
let rendered = renderer.render_match(&m, trigger_offset, &config, vec![]);
|
let rendered = renderer.render_match(&m, trigger_offset, &config, vec![]);
|
||||||
|
|
||||||
|
@ -642,18 +696,20 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_render_match_case_propagation_first_capital() {
|
fn test_render_match_case_propagation_first_capital() {
|
||||||
let config = get_config_for(r###"
|
let config = get_config_for(
|
||||||
|
r###"
|
||||||
matches:
|
matches:
|
||||||
- trigger: 'test'
|
- trigger: 'test'
|
||||||
replace: result
|
replace: result
|
||||||
propagate_case: true
|
propagate_case: true
|
||||||
"###);
|
"###,
|
||||||
|
);
|
||||||
|
|
||||||
let renderer = get_renderer(config.clone());
|
let renderer = get_renderer(config.clone());
|
||||||
|
|
||||||
let m = config.matches[0].clone();
|
let m = config.matches[0].clone();
|
||||||
|
|
||||||
let trigger_offset = m.triggers.iter().position(|x| x== "Test").unwrap();
|
let trigger_offset = m.triggers.iter().position(|x| x == "Test").unwrap();
|
||||||
|
|
||||||
let rendered = renderer.render_match(&m, trigger_offset, &config, vec![]);
|
let rendered = renderer.render_match(&m, trigger_offset, &config, vec![]);
|
||||||
|
|
||||||
|
@ -662,18 +718,20 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_render_match_case_propagation_all_capital() {
|
fn test_render_match_case_propagation_all_capital() {
|
||||||
let config = get_config_for(r###"
|
let config = get_config_for(
|
||||||
|
r###"
|
||||||
matches:
|
matches:
|
||||||
- trigger: 'test'
|
- trigger: 'test'
|
||||||
replace: result
|
replace: result
|
||||||
propagate_case: true
|
propagate_case: true
|
||||||
"###);
|
"###,
|
||||||
|
);
|
||||||
|
|
||||||
let renderer = get_renderer(config.clone());
|
let renderer = get_renderer(config.clone());
|
||||||
|
|
||||||
let m = config.matches[0].clone();
|
let m = config.matches[0].clone();
|
||||||
|
|
||||||
let trigger_offset = m.triggers.iter().position(|x| x== "TEST").unwrap();
|
let trigger_offset = m.triggers.iter().position(|x| x == "TEST").unwrap();
|
||||||
|
|
||||||
let rendered = renderer.render_match(&m, trigger_offset, &config, vec![]);
|
let rendered = renderer.render_match(&m, trigger_offset, &config, vec![]);
|
||||||
|
|
||||||
|
|
|
@ -17,16 +17,22 @@
|
||||||
* along with espanso. If not, see <https://www.gnu.org/licenses/>.
|
* along with espanso. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
use std::path::PathBuf;
|
|
||||||
use crate::matcher::{Match};
|
|
||||||
use crate::config::Configs;
|
use crate::config::Configs;
|
||||||
|
use crate::matcher::Match;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
pub(crate) mod default;
|
pub(crate) mod default;
|
||||||
pub(crate) mod utils;
|
pub(crate) mod utils;
|
||||||
|
|
||||||
pub trait Renderer {
|
pub trait Renderer {
|
||||||
// Render a match output
|
// Render a match output
|
||||||
fn render_match(&self, m: &Match, trigger_offset: usize, config: &Configs, args: Vec<String>) -> RenderResult;
|
fn render_match(
|
||||||
|
&self,
|
||||||
|
m: &Match,
|
||||||
|
trigger_offset: usize,
|
||||||
|
config: &Configs,
|
||||||
|
args: Vec<String>,
|
||||||
|
) -> RenderResult;
|
||||||
|
|
||||||
// Render a passive expansion text
|
// Render a passive expansion text
|
||||||
fn render_passive(&self, text: &str, config: &Configs) -> RenderResult;
|
fn render_passive(&self, text: &str, config: &Configs) -> RenderResult;
|
||||||
|
@ -35,5 +41,5 @@ pub trait Renderer {
|
||||||
pub enum RenderResult {
|
pub enum RenderResult {
|
||||||
Text(String),
|
Text(String),
|
||||||
Image(PathBuf),
|
Image(PathBuf),
|
||||||
Error
|
Error,
|
||||||
}
|
}
|
|
@ -17,7 +17,7 @@
|
||||||
* along with espanso. If not, see <https://www.gnu.org/licenses/>.
|
* along with espanso. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
use regex::{Regex, Captures};
|
use regex::{Captures, Regex};
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
static ref ARG_REGEX: Regex = Regex::new("\\$(?P<pos>\\d+)\\$").unwrap();
|
static ref ARG_REGEX: Regex = Regex::new("\\$(?P<pos>\\d+)\\$").unwrap();
|
||||||
|
@ -25,12 +25,12 @@ lazy_static! {
|
||||||
|
|
||||||
pub fn render_args(text: &str, args: &Vec<String>) -> String {
|
pub fn render_args(text: &str, args: &Vec<String>) -> String {
|
||||||
let result = ARG_REGEX.replace_all(text, |caps: &Captures| {
|
let result = ARG_REGEX.replace_all(text, |caps: &Captures| {
|
||||||
let position_str = caps.name("pos").unwrap().as_str();
|
let position_str = caps.name("pos").unwrap().as_str();
|
||||||
let position = position_str.parse::<i32>().unwrap_or(-1);
|
let position = position_str.parse::<i32>().unwrap_or(-1);
|
||||||
|
|
||||||
if position >= 0 && position < args.len() as i32 {
|
if position >= 0 && position < args.len() as i32 {
|
||||||
args[position as usize].to_owned()
|
args[position as usize].to_owned()
|
||||||
}else{
|
} else {
|
||||||
"".to_owned()
|
"".to_owned()
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -43,24 +43,24 @@ pub fn split_args(text: &str, delimiter: char, escape: char) -> Vec<String> {
|
||||||
|
|
||||||
// Make sure the text is not empty
|
// Make sure the text is not empty
|
||||||
if text.is_empty() {
|
if text.is_empty() {
|
||||||
return output
|
return output;
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut last = String::from("");
|
let mut last = String::from("");
|
||||||
let mut previous : char = char::from(0);
|
let mut previous: char = char::from(0);
|
||||||
text.chars().into_iter().for_each(|c| {
|
text.chars().into_iter().for_each(|c| {
|
||||||
if c == delimiter {
|
if c == delimiter {
|
||||||
if previous != escape {
|
if previous != escape {
|
||||||
output.push(last.clone());
|
output.push(last.clone());
|
||||||
last = String::from("");
|
last = String::from("");
|
||||||
}else{
|
} else {
|
||||||
last.push(c);
|
last.push(c);
|
||||||
}
|
}
|
||||||
}else if c == escape {
|
} else if c == escape {
|
||||||
if previous == escape {
|
if previous == escape {
|
||||||
last.push(c);
|
last.push(c);
|
||||||
}
|
}
|
||||||
}else{
|
} else {
|
||||||
last.push(c);
|
last.push(c);
|
||||||
}
|
}
|
||||||
previous = c;
|
previous = c;
|
||||||
|
@ -80,25 +80,28 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_render_args_no_args() {
|
fn test_render_args_no_args() {
|
||||||
let args = vec!("hello".to_owned());
|
let args = vec!["hello".to_owned()];
|
||||||
assert_eq!(render_args("no args", &args), "no args")
|
assert_eq!(render_args("no args", &args), "no args")
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_render_args_one_arg() {
|
fn test_render_args_one_arg() {
|
||||||
let args = vec!("jon".to_owned());
|
let args = vec!["jon".to_owned()];
|
||||||
assert_eq!(render_args("hello $0$", &args), "hello jon")
|
assert_eq!(render_args("hello $0$", &args), "hello jon")
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_render_args_one_multiple_args() {
|
fn test_render_args_one_multiple_args() {
|
||||||
let args = vec!("jon".to_owned(), "snow".to_owned());
|
let args = vec!["jon".to_owned(), "snow".to_owned()];
|
||||||
assert_eq!(render_args("hello $0$, the $1$ is white", &args), "hello jon, the snow is white")
|
assert_eq!(
|
||||||
|
render_args("hello $0$, the $1$ is white", &args),
|
||||||
|
"hello jon, the snow is white"
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_render_args_out_of_range() {
|
fn test_render_args_out_of_range() {
|
||||||
let args = vec!("jon".to_owned());
|
let args = vec!["jon".to_owned()];
|
||||||
assert_eq!(render_args("hello $10$", &args), "hello ")
|
assert_eq!(render_args("hello $10$", &args), "hello ")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -124,7 +127,7 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_split_args_empty() {
|
fn test_split_args_empty() {
|
||||||
let empty_vec : Vec<String> = vec![];
|
let empty_vec: Vec<String> = vec![];
|
||||||
assert_eq!(split_args("", '/', '\\'), empty_vec)
|
assert_eq!(split_args("", '/', '\\'), empty_vec)
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -20,14 +20,14 @@
|
||||||
// This functions are used to register/unregister espanso from the system daemon manager.
|
// This functions are used to register/unregister espanso from the system daemon manager.
|
||||||
|
|
||||||
use crate::config::ConfigSet;
|
use crate::config::ConfigSet;
|
||||||
use crate::sysdaemon::VerifyResult::{EnabledAndValid, NotEnabled, EnabledButInvalidPath};
|
use crate::sysdaemon::VerifyResult::{EnabledAndValid, EnabledButInvalidPath, NotEnabled};
|
||||||
|
|
||||||
// INSTALLATION
|
// INSTALLATION
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
const MAC_PLIST_CONTENT : &str = include_str!("res/mac/com.federicoterzi.espanso.plist");
|
const MAC_PLIST_CONTENT: &str = include_str!("res/mac/com.federicoterzi.espanso.plist");
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
const MAC_PLIST_FILENAME : &str = "com.federicoterzi.espanso.plist";
|
const MAC_PLIST_FILENAME: &str = "com.federicoterzi.espanso.plist";
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
pub fn register(_config_set: ConfigSet) {
|
pub fn register(_config_set: ConfigSet) {
|
||||||
|
@ -45,13 +45,21 @@ pub fn register(_config_set: ConfigSet) {
|
||||||
|
|
||||||
let plist_file = agents_dir.join(MAC_PLIST_FILENAME);
|
let plist_file = agents_dir.join(MAC_PLIST_FILENAME);
|
||||||
if !plist_file.exists() {
|
if !plist_file.exists() {
|
||||||
println!("Creating LaunchAgents entry: {}", plist_file.to_str().unwrap_or_default());
|
println!(
|
||||||
|
"Creating LaunchAgents entry: {}",
|
||||||
|
plist_file.to_str().unwrap_or_default()
|
||||||
|
);
|
||||||
|
|
||||||
let espanso_path = std::env::current_exe().expect("Could not get espanso executable path");
|
let espanso_path = std::env::current_exe().expect("Could not get espanso executable path");
|
||||||
println!("Entry will point to: {}", espanso_path.to_str().unwrap_or_default());
|
println!(
|
||||||
|
"Entry will point to: {}",
|
||||||
|
espanso_path.to_str().unwrap_or_default()
|
||||||
|
);
|
||||||
|
|
||||||
let plist_content = String::from(MAC_PLIST_CONTENT)
|
let plist_content = String::from(MAC_PLIST_CONTENT).replace(
|
||||||
.replace("{{{espanso_path}}}", espanso_path.to_str().unwrap_or_default());
|
"{{{espanso_path}}}",
|
||||||
|
espanso_path.to_str().unwrap_or_default(),
|
||||||
|
);
|
||||||
|
|
||||||
std::fs::write(plist_file.clone(), plist_content).expect("Unable to write plist file");
|
std::fs::write(plist_file.clone(), plist_content).expect("Unable to write plist file");
|
||||||
|
|
||||||
|
@ -61,8 +69,8 @@ pub fn register(_config_set: ConfigSet) {
|
||||||
println!("Reloading entry...");
|
println!("Reloading entry...");
|
||||||
|
|
||||||
let res = Command::new("launchctl")
|
let res = Command::new("launchctl")
|
||||||
.args(&["unload", "-w", plist_file.to_str().unwrap_or_default()])
|
.args(&["unload", "-w", plist_file.to_str().unwrap_or_default()])
|
||||||
.output();
|
.output();
|
||||||
|
|
||||||
let res = Command::new("launchctl")
|
let res = Command::new("launchctl")
|
||||||
.args(&["load", "-w", plist_file.to_str().unwrap_or_default()])
|
.args(&["load", "-w", plist_file.to_str().unwrap_or_default()])
|
||||||
|
@ -72,7 +80,7 @@ pub fn register(_config_set: ConfigSet) {
|
||||||
if status.success() {
|
if status.success() {
|
||||||
println!("Entry loaded correctly!")
|
println!("Entry loaded correctly!")
|
||||||
}
|
}
|
||||||
}else{
|
} else {
|
||||||
println!("Error loading new entry");
|
println!("Error loading new entry");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -95,7 +103,7 @@ pub fn unregister(_config_set: ConfigSet) {
|
||||||
std::fs::remove_file(&plist_file).expect("Could not remove espanso entry");
|
std::fs::remove_file(&plist_file).expect("Could not remove espanso entry");
|
||||||
|
|
||||||
println!("Entry removed correctly!")
|
println!("Entry removed correctly!")
|
||||||
}else{
|
} else {
|
||||||
println!("espanso is not installed");
|
println!("espanso is not installed");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -103,14 +111,14 @@ pub fn unregister(_config_set: ConfigSet) {
|
||||||
// LINUX
|
// LINUX
|
||||||
|
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
const LINUX_SERVICE_CONTENT : &str = include_str!("res/linux/systemd.service");
|
const LINUX_SERVICE_CONTENT: &str = include_str!("res/linux/systemd.service");
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
const LINUX_SERVICE_FILENAME : &str = "espanso.service";
|
const LINUX_SERVICE_FILENAME: &str = "espanso.service";
|
||||||
|
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
pub fn register(_: ConfigSet) {
|
pub fn register(_: ConfigSet) {
|
||||||
use std::fs::create_dir_all;
|
use std::fs::create_dir_all;
|
||||||
use std::process::{Command};
|
use std::process::Command;
|
||||||
|
|
||||||
// Check if espanso service is already registered
|
// Check if espanso service is already registered
|
||||||
let res = Command::new("systemctl")
|
let res = Command::new("systemctl")
|
||||||
|
@ -126,7 +134,7 @@ pub fn register(_: ConfigSet) {
|
||||||
eprintln!(" espanso unregister");
|
eprintln!(" espanso unregister");
|
||||||
std::process::exit(5);
|
std::process::exit(5);
|
||||||
}
|
}
|
||||||
}else{
|
} else {
|
||||||
if output == "disabled" {
|
if output == "disabled" {
|
||||||
use dialoguer::Confirmation;
|
use dialoguer::Confirmation;
|
||||||
if !Confirmation::new()
|
if !Confirmation::new()
|
||||||
|
@ -154,15 +162,24 @@ pub fn register(_: ConfigSet) {
|
||||||
|
|
||||||
let service_file = user_dir.join(LINUX_SERVICE_FILENAME);
|
let service_file = user_dir.join(LINUX_SERVICE_FILENAME);
|
||||||
if !service_file.exists() {
|
if !service_file.exists() {
|
||||||
println!("Creating service entry: {}", service_file.to_str().unwrap_or_default());
|
println!(
|
||||||
|
"Creating service entry: {}",
|
||||||
|
service_file.to_str().unwrap_or_default()
|
||||||
|
);
|
||||||
|
|
||||||
let espanso_path = std::env::current_exe().expect("Could not get espanso executable path");
|
let espanso_path = std::env::current_exe().expect("Could not get espanso executable path");
|
||||||
println!("Entry will point to: {}", espanso_path.to_str().unwrap_or_default());
|
println!(
|
||||||
|
"Entry will point to: {}",
|
||||||
|
espanso_path.to_str().unwrap_or_default()
|
||||||
|
);
|
||||||
|
|
||||||
let service_content = String::from(LINUX_SERVICE_CONTENT)
|
let service_content = String::from(LINUX_SERVICE_CONTENT).replace(
|
||||||
.replace("{{{espanso_path}}}", espanso_path.to_str().unwrap_or_default());
|
"{{{espanso_path}}}",
|
||||||
|
espanso_path.to_str().unwrap_or_default(),
|
||||||
|
);
|
||||||
|
|
||||||
std::fs::write(service_file.clone(), service_content).expect("Unable to write service file");
|
std::fs::write(service_file.clone(), service_content)
|
||||||
|
.expect("Unable to write service file");
|
||||||
|
|
||||||
println!("Service file created correctly!")
|
println!("Service file created correctly!")
|
||||||
}
|
}
|
||||||
|
@ -177,7 +194,7 @@ pub fn register(_: ConfigSet) {
|
||||||
if status.success() {
|
if status.success() {
|
||||||
println!("Service registered correctly!")
|
println!("Service registered correctly!")
|
||||||
}
|
}
|
||||||
}else{
|
} else {
|
||||||
println!("Error loading espanso service");
|
println!("Error loading espanso service");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -191,7 +208,7 @@ pub enum VerifyResult {
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
pub fn verify() -> VerifyResult {
|
pub fn verify() -> VerifyResult {
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
use std::process::{Command};
|
use std::process::Command;
|
||||||
|
|
||||||
// Check if espanso service is already registered
|
// Check if espanso service is already registered
|
||||||
let res = Command::new("systemctl")
|
let res = Command::new("systemctl")
|
||||||
|
@ -201,7 +218,7 @@ pub fn verify() -> VerifyResult {
|
||||||
let output = String::from_utf8_lossy(res.stdout.as_slice());
|
let output = String::from_utf8_lossy(res.stdout.as_slice());
|
||||||
let output = output.trim();
|
let output = output.trim();
|
||||||
if !res.status.success() || output != "enabled" {
|
if !res.status.success() || output != "enabled" {
|
||||||
return NotEnabled
|
return NotEnabled;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -219,10 +236,11 @@ pub fn verify() -> VerifyResult {
|
||||||
if res.status.success() {
|
if res.status.success() {
|
||||||
let caps = EXEC_PATH_REGEX.captures(output).unwrap();
|
let caps = EXEC_PATH_REGEX.captures(output).unwrap();
|
||||||
let path = caps.get(1).map_or("", |m| m.as_str());
|
let path = caps.get(1).map_or("", |m| m.as_str());
|
||||||
let espanso_path = std::env::current_exe().expect("Could not get espanso executable path");
|
let espanso_path =
|
||||||
|
std::env::current_exe().expect("Could not get espanso executable path");
|
||||||
|
|
||||||
if espanso_path.to_string_lossy() != path {
|
if espanso_path.to_string_lossy() != path {
|
||||||
return EnabledButInvalidPath
|
return EnabledButInvalidPath;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -232,12 +250,13 @@ pub fn verify() -> VerifyResult {
|
||||||
|
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
pub fn unregister(_: ConfigSet) {
|
pub fn unregister(_: ConfigSet) {
|
||||||
use std::process::{Command};
|
use std::process::Command;
|
||||||
|
|
||||||
// Disable the service first
|
// Disable the service first
|
||||||
Command::new("systemctl")
|
Command::new("systemctl")
|
||||||
.args(&["--user", "disable", "espanso"])
|
.args(&["--user", "disable", "espanso"])
|
||||||
.status().expect("Unable to invoke systemctl");
|
.status()
|
||||||
|
.expect("Unable to invoke systemctl");
|
||||||
|
|
||||||
// Then delete the espanso.service entry
|
// Then delete the espanso.service entry
|
||||||
let config_dir = dirs::config_dir().expect("Could not get configuration directory");
|
let config_dir = dirs::config_dir().expect("Could not get configuration directory");
|
||||||
|
@ -251,13 +270,16 @@ pub fn unregister(_: ConfigSet) {
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
println!("Deleted entry at {}", service_file.to_string_lossy());
|
println!("Deleted entry at {}", service_file.to_string_lossy());
|
||||||
println!("Service unregistered successfully!");
|
println!("Service unregistered successfully!");
|
||||||
},
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
println!("Error, could not delete service entry at {} with error {}",
|
println!(
|
||||||
service_file.to_string_lossy(), e);
|
"Error, could not delete service entry at {} with error {}",
|
||||||
},
|
service_file.to_string_lossy(),
|
||||||
|
e
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}else{
|
} else {
|
||||||
eprintln!("Error, could not find espanso service file");
|
eprintln!("Error, could not find espanso service file");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,7 +19,9 @@
|
||||||
|
|
||||||
use std::os::raw::c_char;
|
use std::os::raw::c_char;
|
||||||
|
|
||||||
use crate::bridge::linux::{get_active_window_name, get_active_window_class, get_active_window_executable};
|
use crate::bridge::linux::{
|
||||||
|
get_active_window_class, get_active_window_executable, get_active_window_name,
|
||||||
|
};
|
||||||
use std::ffi::CStr;
|
use std::ffi::CStr;
|
||||||
|
|
||||||
pub struct LinuxSystemManager {}
|
pub struct LinuxSystemManager {}
|
||||||
|
@ -27,7 +29,7 @@ pub struct LinuxSystemManager {}
|
||||||
impl super::SystemManager for LinuxSystemManager {
|
impl super::SystemManager for LinuxSystemManager {
|
||||||
fn get_current_window_title(&self) -> Option<String> {
|
fn get_current_window_title(&self) -> Option<String> {
|
||||||
unsafe {
|
unsafe {
|
||||||
let mut buffer : [c_char; 100] = [0; 100];
|
let mut buffer: [c_char; 100] = [0; 100];
|
||||||
let res = get_active_window_name(buffer.as_mut_ptr(), buffer.len() as i32);
|
let res = get_active_window_name(buffer.as_mut_ptr(), buffer.len() as i32);
|
||||||
|
|
||||||
if res > 0 {
|
if res > 0 {
|
||||||
|
@ -45,7 +47,7 @@ impl super::SystemManager for LinuxSystemManager {
|
||||||
|
|
||||||
fn get_current_window_class(&self) -> Option<String> {
|
fn get_current_window_class(&self) -> Option<String> {
|
||||||
unsafe {
|
unsafe {
|
||||||
let mut buffer : [c_char; 100] = [0; 100];
|
let mut buffer: [c_char; 100] = [0; 100];
|
||||||
let res = get_active_window_class(buffer.as_mut_ptr(), buffer.len() as i32);
|
let res = get_active_window_class(buffer.as_mut_ptr(), buffer.len() as i32);
|
||||||
|
|
||||||
if res > 0 {
|
if res > 0 {
|
||||||
|
@ -63,7 +65,7 @@ impl super::SystemManager for LinuxSystemManager {
|
||||||
|
|
||||||
fn get_current_window_executable(&self) -> Option<String> {
|
fn get_current_window_executable(&self) -> Option<String> {
|
||||||
unsafe {
|
unsafe {
|
||||||
let mut buffer : [c_char; 100] = [0; 100];
|
let mut buffer: [c_char; 100] = [0; 100];
|
||||||
let res = get_active_window_executable(buffer.as_mut_ptr(), buffer.len() as i32);
|
let res = get_active_window_executable(buffer.as_mut_ptr(), buffer.len() as i32);
|
||||||
|
|
||||||
if res > 0 {
|
if res > 0 {
|
||||||
|
@ -82,6 +84,6 @@ impl super::SystemManager for LinuxSystemManager {
|
||||||
|
|
||||||
impl LinuxSystemManager {
|
impl LinuxSystemManager {
|
||||||
pub fn new() -> LinuxSystemManager {
|
pub fn new() -> LinuxSystemManager {
|
||||||
LinuxSystemManager{}
|
LinuxSystemManager {}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -19,12 +19,12 @@
|
||||||
|
|
||||||
use std::os::raw::c_char;
|
use std::os::raw::c_char;
|
||||||
|
|
||||||
|
use crate::bridge::macos::{
|
||||||
|
get_active_app_bundle, get_active_app_identifier, get_path_from_pid, get_secure_input_process,
|
||||||
|
};
|
||||||
use std::ffi::CStr;
|
use std::ffi::CStr;
|
||||||
use crate::bridge::macos::{get_active_app_bundle, get_active_app_identifier, get_secure_input_process, get_path_from_pid};
|
|
||||||
|
|
||||||
pub struct MacSystemManager {
|
pub struct MacSystemManager {}
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
impl super::SystemManager for MacSystemManager {
|
impl super::SystemManager for MacSystemManager {
|
||||||
fn get_current_window_title(&self) -> Option<String> {
|
fn get_current_window_title(&self) -> Option<String> {
|
||||||
|
@ -33,7 +33,7 @@ impl super::SystemManager for MacSystemManager {
|
||||||
|
|
||||||
fn get_current_window_class(&self) -> Option<String> {
|
fn get_current_window_class(&self) -> Option<String> {
|
||||||
unsafe {
|
unsafe {
|
||||||
let mut buffer : [c_char; 250] = [0; 250];
|
let mut buffer: [c_char; 250] = [0; 250];
|
||||||
let res = get_active_app_identifier(buffer.as_mut_ptr(), buffer.len() as i32);
|
let res = get_active_app_identifier(buffer.as_mut_ptr(), buffer.len() as i32);
|
||||||
|
|
||||||
if res > 0 {
|
if res > 0 {
|
||||||
|
@ -51,7 +51,7 @@ impl super::SystemManager for MacSystemManager {
|
||||||
|
|
||||||
fn get_current_window_executable(&self) -> Option<String> {
|
fn get_current_window_executable(&self) -> Option<String> {
|
||||||
unsafe {
|
unsafe {
|
||||||
let mut buffer : [c_char; 250] = [0; 250];
|
let mut buffer: [c_char; 250] = [0; 250];
|
||||||
let res = get_active_app_bundle(buffer.as_mut_ptr(), buffer.len() as i32);
|
let res = get_active_app_bundle(buffer.as_mut_ptr(), buffer.len() as i32);
|
||||||
|
|
||||||
if res > 0 {
|
if res > 0 {
|
||||||
|
@ -70,9 +70,7 @@ impl super::SystemManager for MacSystemManager {
|
||||||
|
|
||||||
impl MacSystemManager {
|
impl MacSystemManager {
|
||||||
pub fn new() -> MacSystemManager {
|
pub fn new() -> MacSystemManager {
|
||||||
MacSystemManager{
|
MacSystemManager {}
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Check whether an application is currently holding the Secure Input.
|
/// Check whether an application is currently holding the Secure Input.
|
||||||
|
@ -82,9 +80,9 @@ impl MacSystemManager {
|
||||||
let mut pid: i64 = -1;
|
let mut pid: i64 = -1;
|
||||||
let res = get_secure_input_process(&mut pid as *mut i64);
|
let res = get_secure_input_process(&mut pid as *mut i64);
|
||||||
|
|
||||||
if res > 0{
|
if res > 0 {
|
||||||
Some(pid)
|
Some(pid)
|
||||||
}else{
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -105,7 +103,7 @@ impl MacSystemManager {
|
||||||
if let Some(pid) = pid {
|
if let Some(pid) = pid {
|
||||||
// Size of the buffer is ruled by the PROC_PIDPATHINFO_MAXSIZE constant.
|
// Size of the buffer is ruled by the PROC_PIDPATHINFO_MAXSIZE constant.
|
||||||
// the underlying proc_pidpath REQUIRES a buffer of that dimension, otherwise it fail silently.
|
// the underlying proc_pidpath REQUIRES a buffer of that dimension, otherwise it fail silently.
|
||||||
let mut buffer : [c_char; 4096] = [0; 4096];
|
let mut buffer: [c_char; 4096] = [0; 4096];
|
||||||
let res = get_path_from_pid(pid, buffer.as_mut_ptr(), buffer.len() as i32);
|
let res = get_path_from_pid(pid, buffer.as_mut_ptr(), buffer.len() as i32);
|
||||||
|
|
||||||
if res > 0 {
|
if res > 0 {
|
||||||
|
@ -117,21 +115,21 @@ impl MacSystemManager {
|
||||||
let caps = APP_REGEX.captures(&process);
|
let caps = APP_REGEX.captures(&process);
|
||||||
let app_name = if let Some(caps) = caps {
|
let app_name = if let Some(caps) = caps {
|
||||||
caps.get(1).map_or("", |m| m.as_str()).to_owned()
|
caps.get(1).map_or("", |m| m.as_str()).to_owned()
|
||||||
}else{
|
} else {
|
||||||
process.to_owned()
|
process.to_owned()
|
||||||
};
|
};
|
||||||
|
|
||||||
Some((app_name, process))
|
Some((app_name, process))
|
||||||
}else{
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}else{
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}else{
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}else{
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,23 +17,21 @@
|
||||||
* along with espanso. If not, see <https://www.gnu.org/licenses/>.
|
* along with espanso. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
use widestring::U16CString;
|
|
||||||
use crate::bridge::windows::*;
|
use crate::bridge::windows::*;
|
||||||
|
use widestring::U16CString;
|
||||||
|
|
||||||
pub struct WindowsSystemManager {
|
pub struct WindowsSystemManager {}
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
impl WindowsSystemManager {
|
impl WindowsSystemManager {
|
||||||
pub fn new() -> WindowsSystemManager {
|
pub fn new() -> WindowsSystemManager {
|
||||||
WindowsSystemManager{}
|
WindowsSystemManager {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl super::SystemManager for WindowsSystemManager {
|
impl super::SystemManager for WindowsSystemManager {
|
||||||
fn get_current_window_title(&self) -> Option<String> {
|
fn get_current_window_title(&self) -> Option<String> {
|
||||||
unsafe {
|
unsafe {
|
||||||
let mut buffer : [u16; 100] = [0; 100];
|
let mut buffer: [u16; 100] = [0; 100];
|
||||||
let res = get_active_window_name(buffer.as_mut_ptr(), buffer.len() as i32);
|
let res = get_active_window_name(buffer.as_mut_ptr(), buffer.len() as i32);
|
||||||
|
|
||||||
if res > 0 {
|
if res > 0 {
|
||||||
|
@ -53,7 +51,7 @@ impl super::SystemManager for WindowsSystemManager {
|
||||||
|
|
||||||
fn get_current_window_executable(&self) -> Option<String> {
|
fn get_current_window_executable(&self) -> Option<String> {
|
||||||
unsafe {
|
unsafe {
|
||||||
let mut buffer : [u16; 250] = [0; 250];
|
let mut buffer: [u16; 250] = [0; 250];
|
||||||
let res = get_active_window_executable(buffer.as_mut_ptr(), buffer.len() as i32);
|
let res = get_active_window_executable(buffer.as_mut_ptr(), buffer.len() as i32);
|
||||||
|
|
||||||
if res > 0 {
|
if res > 0 {
|
||||||
|
|
|
@ -17,12 +17,12 @@
|
||||||
* along with espanso. If not, see <https://www.gnu.org/licenses/>.
|
* along with espanso. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
use std::process::Command;
|
|
||||||
use super::MenuItem;
|
use super::MenuItem;
|
||||||
use log::{error, info};
|
use log::{error, info};
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
use std::process::Command;
|
||||||
|
|
||||||
const LINUX_ICON_CONTENT : &[u8] = include_bytes!("../res/linux/icon.png");
|
const LINUX_ICON_CONTENT: &[u8] = include_bytes!("../res/linux/icon.png");
|
||||||
|
|
||||||
pub struct LinuxUIManager {
|
pub struct LinuxUIManager {
|
||||||
icon_path: PathBuf,
|
icon_path: PathBuf,
|
||||||
|
@ -35,8 +35,14 @@ impl super::UIManager for LinuxUIManager {
|
||||||
|
|
||||||
fn notify_delay(&self, message: &str, duration: i32) {
|
fn notify_delay(&self, message: &str, duration: i32) {
|
||||||
let res = Command::new("notify-send")
|
let res = Command::new("notify-send")
|
||||||
.args(&["-i", self.icon_path.to_str().unwrap_or_default(),
|
.args(&[
|
||||||
"-t", &duration.to_string(), "espanso", message])
|
"-i",
|
||||||
|
self.icon_path.to_str().unwrap_or_default(),
|
||||||
|
"-t",
|
||||||
|
&duration.to_string(),
|
||||||
|
"espanso",
|
||||||
|
message,
|
||||||
|
])
|
||||||
.output();
|
.output();
|
||||||
|
|
||||||
if let Err(e) = res {
|
if let Err(e) = res {
|
||||||
|
@ -59,12 +65,13 @@ impl LinuxUIManager {
|
||||||
let data_dir = crate::context::get_data_dir();
|
let data_dir = crate::context::get_data_dir();
|
||||||
let icon_path = data_dir.join("icon.png");
|
let icon_path = data_dir.join("icon.png");
|
||||||
if !icon_path.exists() {
|
if !icon_path.exists() {
|
||||||
info!("Creating espanso icon in '{}'", icon_path.to_str().unwrap_or_default());
|
info!(
|
||||||
|
"Creating espanso icon in '{}'",
|
||||||
|
icon_path.to_str().unwrap_or_default()
|
||||||
|
);
|
||||||
std::fs::write(&icon_path, LINUX_ICON_CONTENT).expect("Unable to copy espanso icon");
|
std::fs::write(&icon_path, LINUX_ICON_CONTENT).expect("Unable to copy espanso icon");
|
||||||
}
|
}
|
||||||
|
|
||||||
LinuxUIManager{
|
LinuxUIManager { icon_path }
|
||||||
icon_path
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -17,21 +17,21 @@
|
||||||
* along with espanso. If not, see <https://www.gnu.org/licenses/>.
|
* along with espanso. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
use std::{fs, io};
|
use crate::bridge::macos::{show_context_menu, MacMenuItem};
|
||||||
use std::io::{Cursor};
|
use crate::context;
|
||||||
|
use crate::ui::{MenuItem, MenuItemType};
|
||||||
|
use log::{debug, info, warn};
|
||||||
use std::ffi::CString;
|
use std::ffi::CString;
|
||||||
use log::{info, warn, debug};
|
use std::io::Cursor;
|
||||||
|
use std::os::raw::c_char;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
use crate::ui::{MenuItem, MenuItemType};
|
use std::{fs, io};
|
||||||
use crate::bridge::macos::{MacMenuItem, show_context_menu};
|
|
||||||
use std::os::raw::c_char;
|
|
||||||
use crate::context;
|
|
||||||
|
|
||||||
const NOTIFY_HELPER_BINARY : &'static [u8] = include_bytes!("../res/mac/EspansoNotifyHelper.zip");
|
const NOTIFY_HELPER_BINARY: &'static [u8] = include_bytes!("../res/mac/EspansoNotifyHelper.zip");
|
||||||
|
|
||||||
pub struct MacUIManager {
|
pub struct MacUIManager {
|
||||||
notify_helper_path: PathBuf
|
notify_helper_path: PathBuf,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl super::UIManager for MacUIManager {
|
impl super::UIManager for MacUIManager {
|
||||||
|
@ -60,14 +60,14 @@ impl super::UIManager for MacUIManager {
|
||||||
|
|
||||||
for item in menu.iter() {
|
for item in menu.iter() {
|
||||||
let text = CString::new(item.item_name.clone()).unwrap_or_default();
|
let text = CString::new(item.item_name.clone()).unwrap_or_default();
|
||||||
let mut str_buff : [c_char; 100] = [0; 100];
|
let mut str_buff: [c_char; 100] = [0; 100];
|
||||||
unsafe {
|
unsafe {
|
||||||
std::ptr::copy(text.as_ptr(), str_buff.as_mut_ptr(), item.item_name.len());
|
std::ptr::copy(text.as_ptr(), str_buff.as_mut_ptr(), item.item_name.len());
|
||||||
}
|
}
|
||||||
|
|
||||||
let menu_type = match item.item_type {
|
let menu_type = match item.item_type {
|
||||||
MenuItemType::Button => {1},
|
MenuItemType::Button => 1,
|
||||||
MenuItemType::Separator => {2},
|
MenuItemType::Separator => 2,
|
||||||
};
|
};
|
||||||
|
|
||||||
let raw_item = MacMenuItem {
|
let raw_item = MacMenuItem {
|
||||||
|
@ -79,7 +79,9 @@ impl super::UIManager for MacUIManager {
|
||||||
raw_menu.push(raw_item);
|
raw_menu.push(raw_item);
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe { show_context_menu(raw_menu.as_ptr(), raw_menu.len() as i32); }
|
unsafe {
|
||||||
|
show_context_menu(raw_menu.as_ptr(), raw_menu.len() as i32);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn cleanup(&self) {
|
fn cleanup(&self) {
|
||||||
|
@ -91,21 +93,22 @@ impl MacUIManager {
|
||||||
pub fn new() -> MacUIManager {
|
pub fn new() -> MacUIManager {
|
||||||
let notify_helper_path = MacUIManager::initialize_notify_helper();
|
let notify_helper_path = MacUIManager::initialize_notify_helper();
|
||||||
|
|
||||||
MacUIManager{
|
MacUIManager { notify_helper_path }
|
||||||
notify_helper_path
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn initialize_notify_helper() -> PathBuf {
|
fn initialize_notify_helper() -> PathBuf {
|
||||||
let espanso_dir = context::get_data_dir();
|
let espanso_dir = context::get_data_dir();
|
||||||
|
|
||||||
info!("Initializing EspansoNotifyHelper in {}", espanso_dir.as_path().display());
|
info!(
|
||||||
|
"Initializing EspansoNotifyHelper in {}",
|
||||||
|
espanso_dir.as_path().display()
|
||||||
|
);
|
||||||
|
|
||||||
let espanso_target = espanso_dir.join("EspansoNotifyHelper.app");
|
let espanso_target = espanso_dir.join("EspansoNotifyHelper.app");
|
||||||
|
|
||||||
if espanso_target.exists() {
|
if espanso_target.exists() {
|
||||||
info!("EspansoNotifyHelper already initialized, skipping.");
|
info!("EspansoNotifyHelper already initialized, skipping.");
|
||||||
}else{
|
} else {
|
||||||
// Extract zip file
|
// Extract zip file
|
||||||
let reader = Cursor::new(NOTIFY_HELPER_BINARY);
|
let reader = Cursor::new(NOTIFY_HELPER_BINARY);
|
||||||
|
|
||||||
|
@ -123,10 +126,19 @@ impl MacUIManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (&*file.name()).ends_with('/') {
|
if (&*file.name()).ends_with('/') {
|
||||||
debug!("File {} extracted to \"{}\"", i, outpath.as_path().display());
|
debug!(
|
||||||
|
"File {} extracted to \"{}\"",
|
||||||
|
i,
|
||||||
|
outpath.as_path().display()
|
||||||
|
);
|
||||||
fs::create_dir_all(&outpath).unwrap();
|
fs::create_dir_all(&outpath).unwrap();
|
||||||
} else {
|
} else {
|
||||||
debug!("File {} extracted to \"{}\" ({} bytes)", i, outpath.as_path().display(), file.size());
|
debug!(
|
||||||
|
"File {} extracted to \"{}\" ({} bytes)",
|
||||||
|
i,
|
||||||
|
outpath.as_path().display(),
|
||||||
|
file.size()
|
||||||
|
);
|
||||||
if let Some(p) = outpath.parent() {
|
if let Some(p) = outpath.parent() {
|
||||||
if !p.exists() {
|
if !p.exists() {
|
||||||
fs::create_dir_all(&p).unwrap();
|
fs::create_dir_all(&p).unwrap();
|
||||||
|
|
|
@ -17,16 +17,18 @@
|
||||||
* along with espanso. If not, see <https://www.gnu.org/licenses/>.
|
* along with espanso. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
use crate::bridge::windows::{show_notification, close_notification, WindowsMenuItem, show_context_menu, cleanup_ui};
|
use crate::bridge::windows::{
|
||||||
use widestring::U16CString;
|
cleanup_ui, close_notification, show_context_menu, show_notification, WindowsMenuItem,
|
||||||
use std::{thread, time};
|
};
|
||||||
use log::{debug};
|
|
||||||
use std::sync::Mutex;
|
|
||||||
use std::sync::Arc;
|
|
||||||
use crate::ui::{MenuItem, MenuItemType};
|
use crate::ui::{MenuItem, MenuItemType};
|
||||||
|
use log::debug;
|
||||||
|
use std::sync::Arc;
|
||||||
|
use std::sync::Mutex;
|
||||||
|
use std::{thread, time};
|
||||||
|
use widestring::U16CString;
|
||||||
|
|
||||||
pub struct WindowsUIManager {
|
pub struct WindowsUIManager {
|
||||||
id: Arc<Mutex<i32>>
|
id: Arc<Mutex<i32>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl super::UIManager for WindowsUIManager {
|
impl super::UIManager for WindowsUIManager {
|
||||||
|
@ -45,29 +47,30 @@ impl super::UIManager for WindowsUIManager {
|
||||||
|
|
||||||
// Setup a timeout to close the notification
|
// Setup a timeout to close the notification
|
||||||
let id = Arc::clone(&self.id);
|
let id = Arc::clone(&self.id);
|
||||||
let _ = thread::Builder::new().name("notification_thread".to_string()).spawn(move || {
|
let _ = thread::Builder::new()
|
||||||
for _ in 1..10 {
|
.name("notification_thread".to_string())
|
||||||
let duration = time::Duration::from_millis(step as u64);
|
.spawn(move || {
|
||||||
thread::sleep(duration);
|
for _ in 1..10 {
|
||||||
|
let duration = time::Duration::from_millis(step as u64);
|
||||||
|
thread::sleep(duration);
|
||||||
|
|
||||||
let new_id = id.lock().unwrap();
|
let new_id = id.lock().unwrap();
|
||||||
if *new_id != current_id {
|
if *new_id != current_id {
|
||||||
debug!("Cancelling notification close event with id {}", current_id);
|
debug!("Cancelling notification close event with id {}", current_id);
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
unsafe {
|
unsafe {
|
||||||
close_notification();
|
close_notification();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Create and show a window notification
|
// Create and show a window notification
|
||||||
unsafe {
|
unsafe {
|
||||||
let message = U16CString::from_str(message).unwrap();
|
let message = U16CString::from_str(message).unwrap();
|
||||||
show_notification(message.as_ptr());
|
show_notification(message.as_ptr());
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn show_menu(&self, menu: Vec<MenuItem>) {
|
fn show_menu(&self, menu: Vec<MenuItem>) {
|
||||||
|
@ -75,14 +78,14 @@ impl super::UIManager for WindowsUIManager {
|
||||||
|
|
||||||
for item in menu.iter() {
|
for item in menu.iter() {
|
||||||
let text = U16CString::from_str(item.item_name.clone()).unwrap_or_default();
|
let text = U16CString::from_str(item.item_name.clone()).unwrap_or_default();
|
||||||
let mut str_buff : [u16; 100] = [0; 100];
|
let mut str_buff: [u16; 100] = [0; 100];
|
||||||
unsafe {
|
unsafe {
|
||||||
std::ptr::copy(text.as_ptr(), str_buff.as_mut_ptr(), text.len());
|
std::ptr::copy(text.as_ptr(), str_buff.as_mut_ptr(), text.len());
|
||||||
}
|
}
|
||||||
|
|
||||||
let menu_type = match item.item_type {
|
let menu_type = match item.item_type {
|
||||||
MenuItemType::Button => {1},
|
MenuItemType::Button => 1,
|
||||||
MenuItemType::Separator => {2},
|
MenuItemType::Separator => 2,
|
||||||
};
|
};
|
||||||
|
|
||||||
let raw_item = WindowsMenuItem {
|
let raw_item = WindowsMenuItem {
|
||||||
|
@ -94,7 +97,9 @@ impl super::UIManager for WindowsUIManager {
|
||||||
raw_menu.push(raw_item);
|
raw_menu.push(raw_item);
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe { show_context_menu(raw_menu.as_ptr(), raw_menu.len() as i32); }
|
unsafe {
|
||||||
|
show_context_menu(raw_menu.as_ptr(), raw_menu.len() as i32);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn cleanup(&self) {
|
fn cleanup(&self) {
|
||||||
|
@ -108,9 +113,7 @@ impl WindowsUIManager {
|
||||||
pub fn new() -> WindowsUIManager {
|
pub fn new() -> WindowsUIManager {
|
||||||
let id = Arc::new(Mutex::new(0));
|
let id = Arc::new(Mutex::new(0));
|
||||||
|
|
||||||
let manager = WindowsUIManager {
|
let manager = WindowsUIManager { id };
|
||||||
id
|
|
||||||
};
|
|
||||||
|
|
||||||
manager
|
manager
|
||||||
}
|
}
|
||||||
|
|
15
src/utils.rs
15
src/utils.rs
|
@ -17,9 +17,9 @@
|
||||||
* along with espanso. If not, see <https://www.gnu.org/licenses/>.
|
* along with espanso. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
use std::path::Path;
|
|
||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
use std::fs::create_dir;
|
use std::fs::create_dir;
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
pub fn copy_dir(source_dir: &Path, dest_dir: &Path) -> Result<(), Box<dyn Error>> {
|
pub fn copy_dir(source_dir: &Path, dest_dir: &Path) -> Result<(), Box<dyn Error>> {
|
||||||
for entry in std::fs::read_dir(source_dir)? {
|
for entry in std::fs::read_dir(source_dir)? {
|
||||||
|
@ -30,8 +30,9 @@ pub fn copy_dir(source_dir: &Path, dest_dir: &Path) -> Result<(), Box<dyn Error>
|
||||||
let target_dir = dest_dir.join(name);
|
let target_dir = dest_dir.join(name);
|
||||||
create_dir(&target_dir)?;
|
create_dir(&target_dir)?;
|
||||||
copy_dir(&entry, &target_dir)?;
|
copy_dir(&entry, &target_dir)?;
|
||||||
}else if entry.is_file() {
|
} else if entry.is_file() {
|
||||||
let target_entry = dest_dir.join(entry.file_name().expect("Error obtaining the filename"));
|
let target_entry =
|
||||||
|
dest_dir.join(entry.file_name().expect("Error obtaining the filename"));
|
||||||
std::fs::copy(entry, target_entry)?;
|
std::fs::copy(entry, target_entry)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -42,8 +43,8 @@ pub fn copy_dir(source_dir: &Path, dest_dir: &Path) -> Result<(), Box<dyn Error>
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use tempfile::TempDir;
|
|
||||||
use std::fs::create_dir;
|
use std::fs::create_dir;
|
||||||
|
use tempfile::TempDir;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_copy_dir_into() {
|
fn test_copy_dir_into() {
|
||||||
|
@ -88,7 +89,9 @@ mod tests {
|
||||||
assert!(dest_tmp_dir.path().join("source/file2.txt").exists());
|
assert!(dest_tmp_dir.path().join("source/file2.txt").exists());
|
||||||
|
|
||||||
assert!(dest_tmp_dir.path().join("source/nested").exists());
|
assert!(dest_tmp_dir.path().join("source/nested").exists());
|
||||||
assert!(dest_tmp_dir.path().join("source/nested/nestedfile.txt").exists());
|
assert!(dest_tmp_dir
|
||||||
|
.path()
|
||||||
|
.join("source/nested/nestedfile.txt")
|
||||||
|
.exists());
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
Loading…
Reference in New Issue
Block a user