commit
5e4ac5a4c2
2
Cargo.lock
generated
2
Cargo.lock
generated
|
@ -329,7 +329,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "espanso"
|
||||
version = "0.2.4"
|
||||
version = "0.3.0"
|
||||
dependencies = [
|
||||
"backtrace 0.3.37 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"chrono 0.4.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "espanso"
|
||||
version = "0.2.4"
|
||||
version = "0.3.0"
|
||||
authors = ["Federico Terzi <federicoterzi96@gmail.com>"]
|
||||
license = "GPL-3.0"
|
||||
description = "Cross-platform Text Expander written in Rust"
|
||||
|
|
|
@ -48,6 +48,14 @@ please consider making a small donation, it really helps :)
|
|||
|
||||
[![Donate with PayPal](images/donate.gif)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=FHNLR5DRS267E&source=url)
|
||||
|
||||
## Contributors
|
||||
|
||||
Many people helped the project along the way, thanks to all of you. In particular, I want to thank:
|
||||
|
||||
* [Scrumplex](https://scrumplex.net/) - Official AUR repo mantainer and Linux Guru
|
||||
* [Luca Antognetti](https://github.com/luca-ant) - Linux and Windows Tester
|
||||
* [Matteo Pellegrino](https://www.matteopellegrino.me/) - MacOS Tester
|
||||
|
||||
## Remarks
|
||||
|
||||
* Special thanks to the [ModifyPath](https://www.legroom.net/software/modpath)
|
||||
|
|
|
@ -13,6 +13,7 @@ steps:
|
|||
set -e
|
||||
python packager.py build
|
||||
cp target/packager/mac/espanso-*.gz .
|
||||
cp target/packager/mac/espanso-*.txt .
|
||||
cp target/packager/mac/espanso.rb .
|
||||
ls -la
|
||||
displayName: "Cargo build and packaging for MacOS"
|
|
@ -12,6 +12,7 @@ steps:
|
|||
- script: |
|
||||
python packager.py build
|
||||
copy "target\\packager\\win\\espanso-win-installer.exe" "espanso-win-installer.exe"
|
||||
copy "target\\packager\\win\\espanso-win-installer-sha256.txt" "espanso-win-installer-sha256.txt"
|
||||
dir
|
||||
displayName: "Build and packaging for Windows"
|
||||
|
||||
|
|
|
@ -84,6 +84,16 @@ void register_keypress_callback(KeypressCallback callback) {
|
|||
keypress_callback = callback;
|
||||
}
|
||||
|
||||
int32_t check_x11() {
|
||||
Display *check_disp = XOpenDisplay(NULL);
|
||||
|
||||
if (!check_disp) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
XCloseDisplay(check_disp);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int32_t initialize(void * _context_instance) {
|
||||
setlocale(LC_ALL, "");
|
||||
|
@ -139,14 +149,53 @@ int32_t initialize(void * _context_instance) {
|
|||
return -5;
|
||||
}
|
||||
|
||||
if (!XRecordEnableContextAsync(data_disp, context, event_callback, NULL)) {
|
||||
return -6;
|
||||
}
|
||||
|
||||
xdo_context = xdo_new(NULL);
|
||||
|
||||
/**
|
||||
* Note: We might never get a MappingNotify event if the
|
||||
* modifier and keymap information was never cached in Xlib.
|
||||
* The next line makes sure that this happens initially.
|
||||
*/
|
||||
XKeysymToKeycode(ctrl_disp, XK_F1);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
int32_t eventloop() {
|
||||
if (!XRecordEnableContext (data_disp, context, event_callback, NULL)) {
|
||||
return -1;
|
||||
bool running = true;
|
||||
|
||||
int ctrl_fd = XConnectionNumber(ctrl_disp);
|
||||
int data_fd = XConnectionNumber(data_disp);
|
||||
|
||||
while (running)
|
||||
{
|
||||
fd_set fds;
|
||||
FD_ZERO(&fds);
|
||||
FD_SET(ctrl_fd, &fds);
|
||||
FD_SET(data_fd, &fds);
|
||||
timeval timeout;
|
||||
timeout.tv_sec = 2;
|
||||
timeout.tv_usec = 0;
|
||||
int retval = select(max(ctrl_fd, data_fd) + 1,
|
||||
&fds, NULL, NULL, &timeout);
|
||||
|
||||
if (FD_ISSET(data_fd, &fds)) {
|
||||
XRecordProcessReplies(data_disp);
|
||||
}
|
||||
if (FD_ISSET(ctrl_fd, &fds)) {
|
||||
XEvent event;
|
||||
XNextEvent(ctrl_disp, &event);
|
||||
if (event.type == MappingNotify) {
|
||||
XMappingEvent *e = (XMappingEvent *) &event;
|
||||
if (e->request == MappingKeyboard) {
|
||||
XRefreshKeyboardMapping(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 1;
|
||||
|
@ -395,4 +444,4 @@ int32_t is_current_window_terminal() {
|
|||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
|
@ -24,6 +24,11 @@
|
|||
|
||||
extern void * context_instance;
|
||||
|
||||
/*
|
||||
* Check if the X11 context is available
|
||||
*/
|
||||
extern "C" int32_t check_x11();
|
||||
|
||||
/*
|
||||
* Initialize the X11 context and parameters
|
||||
*/
|
||||
|
|
|
@ -250,7 +250,7 @@ LRESULT CALLBACK window_procedure(HWND window, unsigned int msg, WPARAM wp, LPAR
|
|||
// We need to call the callback in two different ways based on the type of key
|
||||
// The only modifier we use that has a result > 0 is the BACKSPACE, so we have to consider it.
|
||||
if (result >= 1 && raw->data.keyboard.VKey != VK_BACK) {
|
||||
keypress_callback(manager_instance, reinterpret_cast<int32_t*>(buffer.data()), buffer.size(), 0, raw->data.keyboard.VKey, is_key_down);
|
||||
keypress_callback(manager_instance, reinterpret_cast<uint16_t*>(buffer.data()), buffer.size(), 0, raw->data.keyboard.VKey, is_key_down);
|
||||
}else{
|
||||
keypress_callback(manager_instance, nullptr, 0, 1, raw->data.keyboard.VKey, is_key_down);
|
||||
}
|
||||
|
|
|
@ -39,7 +39,7 @@ extern "C" int32_t initialize(void * self, wchar_t * ico_path, wchar_t * bmp_pat
|
|||
* Called when a new keypress is made, the first argument is an int array,
|
||||
* while the second is the size of the array.
|
||||
*/
|
||||
typedef void (*KeypressCallback)(void * self, int32_t *buffer, int32_t len, int32_t is_modifier, int32_t key_code, int32_t is_key_down);
|
||||
typedef void (*KeypressCallback)(void * self, uint16_t *buffer, int32_t len, int32_t is_modifier, int32_t key_code, int32_t is_key_down);
|
||||
extern KeypressCallback keypress_callback;
|
||||
|
||||
/*
|
||||
|
|
23
packager.py
23
packager.py
|
@ -6,6 +6,7 @@ import hashlib
|
|||
import click
|
||||
import shutil
|
||||
import toml
|
||||
import hashlib
|
||||
import urllib.request
|
||||
from dataclasses import dataclass
|
||||
|
||||
|
@ -106,6 +107,17 @@ def build_windows(package_info):
|
|||
print("Compiling installer with Inno setup")
|
||||
subprocess.run(["iscc", os.path.abspath(os.path.join(TARGET_DIR, "setupscript.iss"))])
|
||||
|
||||
print("Calculating the SHA256")
|
||||
sha256_hash = hashlib.sha256()
|
||||
with open(os.path.abspath(os.path.join(TARGET_DIR, INSTALLER_NAME+".exe")),"rb") as f:
|
||||
# Read and update hash string value in blocks of 4K
|
||||
for byte_block in iter(lambda: f.read(4096),b""):
|
||||
sha256_hash.update(byte_block)
|
||||
|
||||
hash_file = os.path.abspath(os.path.join(TARGET_DIR, "espanso-win-installer-sha256.txt"))
|
||||
with open(hash_file, "w") as hf:
|
||||
hf.write(sha256_hash.hexdigest())
|
||||
|
||||
|
||||
def build_mac(package_info):
|
||||
print("Starting packaging process for MacOS...")
|
||||
|
@ -131,6 +143,17 @@ def build_mac(package_info):
|
|||
])
|
||||
print(f"Created archive: {archive_target}")
|
||||
|
||||
print("Calculating the SHA256")
|
||||
sha256_hash = hashlib.sha256()
|
||||
with open(archive_target,"rb") as f:
|
||||
# Read and update hash string value in blocks of 4K
|
||||
for byte_block in iter(lambda: f.read(4096),b""):
|
||||
sha256_hash.update(byte_block)
|
||||
|
||||
hash_file = os.path.abspath(os.path.join(TARGET_DIR, "espanso-mac-sha256.txt"))
|
||||
with open(hash_file, "w") as hf:
|
||||
hf.write(sha256_hash.hexdigest())
|
||||
|
||||
print("Processing Homebrew formula template")
|
||||
with open("packager/mac/espanso.rb", "r") as formula_template:
|
||||
content = formula_template.read()
|
||||
|
|
|
@ -22,6 +22,7 @@ use std::os::raw::{c_void, c_char};
|
|||
#[allow(improper_ctypes)]
|
||||
#[link(name="linuxbridge", kind="static")]
|
||||
extern {
|
||||
pub fn check_x11() -> i32;
|
||||
pub fn initialize(s: *const c_void) -> i32;
|
||||
pub fn eventloop();
|
||||
pub fn cleanup();
|
||||
|
|
|
@ -49,7 +49,7 @@ extern {
|
|||
pub fn set_clipboard(payload: *const u16) -> i32;
|
||||
|
||||
// KEYBOARD
|
||||
pub fn register_keypress_callback(cb: extern fn(_self: *mut c_void, *const i32,
|
||||
pub fn register_keypress_callback(cb: extern fn(_self: *mut c_void, *const u16,
|
||||
i32, i32, i32, i32));
|
||||
|
||||
pub fn eventloop();
|
||||
|
|
|
@ -54,6 +54,12 @@ impl super::ClipboardManager for LinuxClipboardManager {
|
|||
if let Err(e) = res {
|
||||
error!("Could not set clipboard: {}", e);
|
||||
}
|
||||
|
||||
let res = child.wait();
|
||||
|
||||
if let Err(e) = res {
|
||||
error!("Could not set clipboard: {}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -38,7 +38,6 @@ const DEFAULT_CONFIG_FILE_CONTENT : &str = include_str!("../res/config.yml");
|
|||
|
||||
const DEFAULT_CONFIG_FILE_NAME : &str = "default.yml";
|
||||
const USER_CONFIGS_FOLDER_NAME: &str = "user";
|
||||
const PACKAGES_FOLDER_NAME : &str = "packages";
|
||||
|
||||
// Default values for primitives
|
||||
fn default_name() -> String{ "default".to_owned() }
|
||||
|
@ -149,9 +148,22 @@ pub enum BackendType {
|
|||
Clipboard
|
||||
}
|
||||
impl Default for BackendType {
|
||||
// The default backend varies based on the operating system.
|
||||
// On Windows and macOS, the Inject backend is working great and should
|
||||
// be preferred as it doesn't override the clipboard.
|
||||
// On the other hand, on linux it has many problems due to the bugs
|
||||
// of the libxdo used. For this reason, Clipboard will be the default
|
||||
// backend on Linux from version v0.3.0
|
||||
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
fn default() -> Self {
|
||||
BackendType::Inject
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
fn default() -> Self {
|
||||
BackendType::Clipboard
|
||||
}
|
||||
}
|
||||
|
||||
impl Configs {
|
||||
|
@ -212,26 +224,25 @@ pub struct ConfigSet {
|
|||
}
|
||||
|
||||
impl ConfigSet {
|
||||
pub fn load(dir_path: &Path) -> Result<ConfigSet, ConfigLoadError> {
|
||||
if !dir_path.is_dir() {
|
||||
pub fn load(config_dir: &Path, package_dir: &Path) -> Result<ConfigSet, ConfigLoadError> {
|
||||
if !config_dir.is_dir() {
|
||||
return Err(ConfigLoadError::InvalidConfigDirectory)
|
||||
}
|
||||
|
||||
// Load default configuration
|
||||
let default_file = dir_path.join(DEFAULT_CONFIG_FILE_NAME);
|
||||
let default_file = config_dir.join(DEFAULT_CONFIG_FILE_NAME);
|
||||
let default = Configs::load_config(default_file.as_path())?;
|
||||
|
||||
// Analyze which config files has to be loaded
|
||||
|
||||
let mut target_files = Vec::new();
|
||||
|
||||
let specific_dir = dir_path.join(USER_CONFIGS_FOLDER_NAME);
|
||||
let specific_dir = config_dir.join(USER_CONFIGS_FOLDER_NAME);
|
||||
if specific_dir.exists() {
|
||||
let dir_entry = WalkDir::new(specific_dir);
|
||||
target_files.extend(dir_entry);
|
||||
}
|
||||
|
||||
let package_dir = dir_path.join(PACKAGES_FOLDER_NAME);
|
||||
if package_dir.exists() {
|
||||
let dir_entry = WalkDir::new(package_dir);
|
||||
target_files.extend(dir_entry);
|
||||
|
@ -320,54 +331,40 @@ impl ConfigSet {
|
|||
}
|
||||
|
||||
pub fn load_default() -> Result<ConfigSet, ConfigLoadError> {
|
||||
let espanso_dir = ConfigSet::get_default_config_dir();
|
||||
// Configuration related
|
||||
|
||||
// Create the espanso dir if id doesn't exist
|
||||
let res = create_dir_all(espanso_dir.as_path());
|
||||
let config_dir = crate::context::get_config_dir();
|
||||
|
||||
if res.is_ok() {
|
||||
let default_file = espanso_dir.join(DEFAULT_CONFIG_FILE_NAME);
|
||||
let default_file = config_dir.join(DEFAULT_CONFIG_FILE_NAME);
|
||||
|
||||
// If config file does not exist, create one from template
|
||||
if !default_file.exists() {
|
||||
let result = fs::write(&default_file, DEFAULT_CONFIG_FILE_CONTENT);
|
||||
if result.is_err() {
|
||||
return Err(ConfigLoadError::UnableToCreateDefaultConfig)
|
||||
}
|
||||
// If config file does not exist, create one from template
|
||||
if !default_file.exists() {
|
||||
let result = fs::write(&default_file, DEFAULT_CONFIG_FILE_CONTENT);
|
||||
if result.is_err() {
|
||||
return Err(ConfigLoadError::UnableToCreateDefaultConfig)
|
||||
}
|
||||
|
||||
// Create auxiliary directories
|
||||
|
||||
let user_config_dir = espanso_dir.join(USER_CONFIGS_FOLDER_NAME);
|
||||
if !user_config_dir.exists() {
|
||||
let res = create_dir_all(user_config_dir.as_path());
|
||||
if res.is_err() {
|
||||
return Err(ConfigLoadError::UnableToCreateDefaultConfig)
|
||||
}
|
||||
}
|
||||
|
||||
let packages_dir = espanso_dir.join(PACKAGES_FOLDER_NAME);
|
||||
if !packages_dir.exists() {
|
||||
let res = create_dir_all(packages_dir.as_path());
|
||||
if res.is_err() {
|
||||
return Err(ConfigLoadError::UnableToCreateDefaultConfig)
|
||||
}
|
||||
}
|
||||
|
||||
return ConfigSet::load(espanso_dir.as_path())
|
||||
}
|
||||
|
||||
Err(ConfigLoadError::UnableToCreateDefaultConfig)
|
||||
}
|
||||
// Create auxiliary directories
|
||||
|
||||
pub fn get_default_config_dir() -> PathBuf {
|
||||
let home_dir = dirs::home_dir().expect("Unable to get home directory");
|
||||
home_dir.join(".espanso")
|
||||
}
|
||||
let user_config_dir = config_dir.join(USER_CONFIGS_FOLDER_NAME);
|
||||
if !user_config_dir.exists() {
|
||||
let res = create_dir_all(user_config_dir.as_path());
|
||||
if res.is_err() {
|
||||
return Err(ConfigLoadError::UnableToCreateDefaultConfig)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_default_packages_dir() -> PathBuf {
|
||||
let espanso_dir = ConfigSet::get_default_config_dir();
|
||||
espanso_dir.join(PACKAGES_FOLDER_NAME)
|
||||
|
||||
// Packages
|
||||
|
||||
let package_dir = crate::context::get_package_dir();
|
||||
let res = create_dir_all(package_dir.as_path());
|
||||
if res.is_err() {
|
||||
return Err(ConfigLoadError::UnableToCreateDefaultConfig) // TODO: change error type
|
||||
}
|
||||
|
||||
return ConfigSet::load(config_dir.as_path(), package_dir.as_path());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -547,94 +544,18 @@ mod tests {
|
|||
|
||||
// Test ConfigSet
|
||||
|
||||
#[test]
|
||||
fn test_config_set_default_content_should_work_correctly() {
|
||||
let tmp_dir = TempDir::new().expect("unable to create temp directory");
|
||||
let default_path = tmp_dir.path().join(DEFAULT_CONFIG_FILE_NAME);
|
||||
fs::write(default_path, DEFAULT_CONFIG_FILE_CONTENT);
|
||||
|
||||
let config_set = ConfigSet::load(tmp_dir.path());
|
||||
assert!(config_set.is_ok());
|
||||
pub fn create_temp_espanso_directories() -> (TempDir, TempDir) {
|
||||
create_temp_espanso_directories_with_default_content(DEFAULT_CONFIG_FILE_CONTENT)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_config_set_load_fail_bad_directory() {
|
||||
let config_set = ConfigSet::load(Path::new("invalid/path"));
|
||||
assert_eq!(config_set.is_err(), true);
|
||||
assert_eq!(config_set.unwrap_err(), ConfigLoadError::InvalidConfigDirectory);
|
||||
}
|
||||
pub fn create_temp_espanso_directories_with_default_content(default_content: &str) -> (TempDir, TempDir) {
|
||||
let data_dir = TempDir::new().expect("unable to create data directory");
|
||||
let package_dir = TempDir::new().expect("unable to create package directory");
|
||||
|
||||
#[test]
|
||||
fn test_config_set_missing_default_file() {
|
||||
let tmp_dir = TempDir::new().expect("unable to create temp directory");
|
||||
|
||||
let config_set = ConfigSet::load(tmp_dir.path());
|
||||
assert_eq!(config_set.is_err(), true);
|
||||
assert_eq!(config_set.unwrap_err(), ConfigLoadError::FileNotFound);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_config_set_invalid_yaml_syntax() {
|
||||
let tmp_dir = TempDir::new().expect("unable to create temp directory");
|
||||
let default_path = tmp_dir.path().join(DEFAULT_CONFIG_FILE_NAME);
|
||||
let default_path_copy = default_path.clone();
|
||||
fs::write(default_path, TEST_CONFIG_FILE_WITH_BAD_YAML);
|
||||
|
||||
let config_set = ConfigSet::load(tmp_dir.path());
|
||||
match config_set {
|
||||
Ok(_) => {assert!(false)},
|
||||
Err(e) => {
|
||||
match e {
|
||||
ConfigLoadError::InvalidYAML(p, _) => assert_eq!(p, default_path_copy),
|
||||
_ => assert!(false),
|
||||
}
|
||||
assert!(true);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_config_set_specific_file_with_reserved_fields() {
|
||||
let tmp_dir = TempDir::new().expect("unable to create temp directory");
|
||||
let default_path = tmp_dir.path().join(DEFAULT_CONFIG_FILE_NAME);
|
||||
fs::write(default_path, DEFAULT_CONFIG_FILE_CONTENT);
|
||||
|
||||
let user_defined_path = create_user_config_file(tmp_dir.path(), "specific.yml", r###"
|
||||
config_caching_interval: 10000
|
||||
"###);
|
||||
let user_defined_path_copy = user_defined_path.clone();
|
||||
|
||||
let config_set = ConfigSet::load(tmp_dir.path());
|
||||
assert!(config_set.is_err());
|
||||
assert_eq!(config_set.unwrap_err(), ConfigLoadError::InvalidParameter(user_defined_path_copy))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_config_set_specific_file_missing_name_auto_generated() {
|
||||
let tmp_dir = TempDir::new().expect("unable to create temp directory");
|
||||
let default_path = tmp_dir.path().join(DEFAULT_CONFIG_FILE_NAME);
|
||||
fs::write(default_path, DEFAULT_CONFIG_FILE_CONTENT);
|
||||
|
||||
let user_defined_path = create_user_config_file(tmp_dir.path(), "specific.yml", r###"
|
||||
backend: Clipboard
|
||||
"###);
|
||||
let user_defined_path_copy = user_defined_path.clone();
|
||||
|
||||
let config_set = ConfigSet::load(tmp_dir.path());
|
||||
assert!(config_set.is_ok());
|
||||
assert_eq!(config_set.unwrap().specific[0].name, user_defined_path_copy.to_str().unwrap_or_default())
|
||||
}
|
||||
|
||||
pub fn create_temp_espanso_directory() -> TempDir {
|
||||
create_temp_espanso_directory_with_default_content(DEFAULT_CONFIG_FILE_CONTENT)
|
||||
}
|
||||
|
||||
pub fn create_temp_espanso_directory_with_default_content(default_content: &str) -> TempDir {
|
||||
let tmp_dir = TempDir::new().expect("unable to create temp directory");
|
||||
let default_path = tmp_dir.path().join(DEFAULT_CONFIG_FILE_NAME);
|
||||
let default_path = data_dir.path().join(DEFAULT_CONFIG_FILE_NAME);
|
||||
fs::write(default_path, default_content);
|
||||
|
||||
tmp_dir
|
||||
(data_dir, package_dir)
|
||||
}
|
||||
|
||||
pub fn create_temp_file_in_dir(tmp_dir: &PathBuf, name: &str, content: &str) -> PathBuf {
|
||||
|
@ -654,9 +575,8 @@ mod tests {
|
|||
create_temp_file_in_dir(&user_config_dir, name, content)
|
||||
}
|
||||
|
||||
pub fn create_package_file(tmp_dir: &Path, package_name: &str, filename: &str, content: &str) -> PathBuf {
|
||||
let package_config_dir = tmp_dir.join(PACKAGES_FOLDER_NAME);
|
||||
let package_dir = package_config_dir.join(package_name);
|
||||
pub fn create_package_file(package_data_dir: &Path, package_name: &str, filename: &str, content: &str) -> PathBuf {
|
||||
let package_dir = package_data_dir.join(package_name);
|
||||
if !package_dir.exists() {
|
||||
create_dir_all(&package_dir);
|
||||
}
|
||||
|
@ -664,28 +584,99 @@ mod tests {
|
|||
create_temp_file_in_dir(&package_dir, filename, content)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_config_set_default_content_should_work_correctly() {
|
||||
let (data_dir, package_dir) = create_temp_espanso_directories();
|
||||
|
||||
let config_set = ConfigSet::load(data_dir.path(), package_dir.path());
|
||||
assert!(config_set.is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_config_set_load_fail_bad_directory() {
|
||||
let config_set = ConfigSet::load(Path::new("invalid/path"), Path::new("invalid/path"));
|
||||
assert_eq!(config_set.is_err(), true);
|
||||
assert_eq!(config_set.unwrap_err(), ConfigLoadError::InvalidConfigDirectory);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_config_set_missing_default_file() {
|
||||
let data_dir = TempDir::new().expect("unable to create temp directory");
|
||||
let package_dir = TempDir::new().expect("unable to create package directory");
|
||||
|
||||
let config_set = ConfigSet::load(data_dir.path(), package_dir.path());
|
||||
assert_eq!(config_set.is_err(), true);
|
||||
assert_eq!(config_set.unwrap_err(), ConfigLoadError::FileNotFound);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_config_set_invalid_yaml_syntax() {
|
||||
let (data_dir, package_dir) = create_temp_espanso_directories_with_default_content(
|
||||
TEST_CONFIG_FILE_WITH_BAD_YAML
|
||||
);
|
||||
let default_path = data_dir.path().join(DEFAULT_CONFIG_FILE_NAME);
|
||||
|
||||
let config_set = ConfigSet::load(data_dir.path(), package_dir.path());
|
||||
match config_set {
|
||||
Ok(_) => {assert!(false)},
|
||||
Err(e) => {
|
||||
match e {
|
||||
ConfigLoadError::InvalidYAML(p, _) => assert_eq!(p, default_path),
|
||||
_ => assert!(false),
|
||||
}
|
||||
assert!(true);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_config_set_specific_file_with_reserved_fields() {
|
||||
let (data_dir, package_dir) = create_temp_espanso_directories();
|
||||
|
||||
let user_defined_path = create_user_config_file(data_dir.path(), "specific.yml", r###"
|
||||
config_caching_interval: 10000
|
||||
"###);
|
||||
let user_defined_path_copy = user_defined_path.clone();
|
||||
|
||||
let config_set = ConfigSet::load(data_dir.path(), package_dir.path());
|
||||
assert!(config_set.is_err());
|
||||
assert_eq!(config_set.unwrap_err(), ConfigLoadError::InvalidParameter(user_defined_path_copy))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_config_set_specific_file_missing_name_auto_generated() {
|
||||
let (data_dir, package_dir) = create_temp_espanso_directories();
|
||||
|
||||
let user_defined_path = create_user_config_file(data_dir.path(), "specific.yml", r###"
|
||||
backend: Clipboard
|
||||
"###);
|
||||
let user_defined_path_copy = user_defined_path.clone();
|
||||
|
||||
let config_set = ConfigSet::load(data_dir.path(), package_dir.path());
|
||||
assert!(config_set.is_ok());
|
||||
assert_eq!(config_set.unwrap().specific[0].name, user_defined_path_copy.to_str().unwrap_or_default())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_config_set_specific_file_duplicate_name() {
|
||||
let tmp_dir = create_temp_espanso_directory();
|
||||
let (data_dir, package_dir) = create_temp_espanso_directories();
|
||||
|
||||
let user_defined_path = create_user_config_file(tmp_dir.path(), "specific.yml", r###"
|
||||
let user_defined_path = create_user_config_file(data_dir.path(), "specific.yml", r###"
|
||||
name: specific1
|
||||
"###);
|
||||
|
||||
let user_defined_path2 = create_user_config_file(tmp_dir.path(), "specific2.yml", r###"
|
||||
let user_defined_path2 = create_user_config_file(data_dir.path(), "specific2.yml", r###"
|
||||
name: specific1
|
||||
"###);
|
||||
|
||||
let config_set = ConfigSet::load(tmp_dir.path());
|
||||
let config_set = ConfigSet::load(data_dir.path(), package_dir.path());
|
||||
assert!(config_set.is_err());
|
||||
assert!(variant_eq(&config_set.unwrap_err(), &ConfigLoadError::NameDuplicate(PathBuf::new())))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_user_defined_config_set_merge_with_parent_matches() {
|
||||
let tmp_dir = TempDir::new().expect("unable to create temp directory");
|
||||
let default_path = tmp_dir.path().join(DEFAULT_CONFIG_FILE_NAME);
|
||||
fs::write(default_path, r###"
|
||||
let (data_dir, package_dir) = create_temp_espanso_directories_with_default_content(r###"
|
||||
matches:
|
||||
- trigger: ":lol"
|
||||
replace: "LOL"
|
||||
|
@ -693,7 +684,7 @@ mod tests {
|
|||
replace: "Bob"
|
||||
"###);
|
||||
|
||||
let user_defined_path = create_user_config_file(tmp_dir.path(), "specific1.yml", r###"
|
||||
let user_defined_path = create_user_config_file(data_dir.path(), "specific1.yml", r###"
|
||||
name: specific1
|
||||
|
||||
matches:
|
||||
|
@ -701,7 +692,7 @@ mod tests {
|
|||
replace: "newstring"
|
||||
"###);
|
||||
|
||||
let config_set = ConfigSet::load(tmp_dir.path()).unwrap();
|
||||
let config_set = ConfigSet::load(data_dir.path(), package_dir.path()).unwrap();
|
||||
assert_eq!(config_set.default.matches.len(), 2);
|
||||
assert_eq!(config_set.specific[0].matches.len(), 3);
|
||||
|
||||
|
@ -712,9 +703,7 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn test_user_defined_config_set_merge_with_parent_matches_child_priority() {
|
||||
let tmp_dir = TempDir::new().expect("unable to create temp directory");
|
||||
let default_path = tmp_dir.path().join(DEFAULT_CONFIG_FILE_NAME);
|
||||
fs::write(default_path, r###"
|
||||
let (data_dir, package_dir) = create_temp_espanso_directories_with_default_content(r###"
|
||||
matches:
|
||||
- trigger: ":lol"
|
||||
replace: "LOL"
|
||||
|
@ -722,7 +711,7 @@ mod tests {
|
|||
replace: "Bob"
|
||||
"###);
|
||||
|
||||
let user_defined_path2 = create_user_config_file(tmp_dir.path(), "specific2.yml", r###"
|
||||
let user_defined_path2 = create_user_config_file(data_dir.path(), "specific2.yml", r###"
|
||||
name: specific1
|
||||
|
||||
matches:
|
||||
|
@ -730,7 +719,7 @@ mod tests {
|
|||
replace: "newstring"
|
||||
"###);
|
||||
|
||||
let config_set = ConfigSet::load(tmp_dir.path()).unwrap();
|
||||
let config_set = ConfigSet::load(data_dir.path(), package_dir.path()).unwrap();
|
||||
assert_eq!(config_set.default.matches.len(), 2);
|
||||
assert_eq!(config_set.specific[0].matches.len(), 2);
|
||||
|
||||
|
@ -740,9 +729,7 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn test_user_defined_config_set_exclude_merge_with_parent_matches() {
|
||||
let tmp_dir = TempDir::new().expect("unable to create temp directory");
|
||||
let default_path = tmp_dir.path().join(DEFAULT_CONFIG_FILE_NAME);
|
||||
fs::write(default_path, r###"
|
||||
let (data_dir, package_dir) = create_temp_espanso_directories_with_default_content(r###"
|
||||
matches:
|
||||
- trigger: ":lol"
|
||||
replace: "LOL"
|
||||
|
@ -750,7 +737,7 @@ mod tests {
|
|||
replace: "Bob"
|
||||
"###);
|
||||
|
||||
let user_defined_path2 = create_user_config_file(tmp_dir.path(), "specific2.yml", r###"
|
||||
let user_defined_path2 = create_user_config_file(data_dir.path(), "specific2.yml", r###"
|
||||
name: specific1
|
||||
|
||||
exclude_default_matches: true
|
||||
|
@ -760,7 +747,7 @@ mod tests {
|
|||
replace: "newstring"
|
||||
"###);
|
||||
|
||||
let config_set = ConfigSet::load(tmp_dir.path()).unwrap();
|
||||
let config_set = ConfigSet::load(data_dir.path(), package_dir.path()).unwrap();
|
||||
assert_eq!(config_set.default.matches.len(), 2);
|
||||
assert_eq!(config_set.specific[0].matches.len(), 1);
|
||||
|
||||
|
@ -769,17 +756,17 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn test_only_yaml_files_are_loaded_from_config() {
|
||||
let tmp_dir = TempDir::new().expect("unable to create temp directory");
|
||||
let default_path = tmp_dir.path().join(DEFAULT_CONFIG_FILE_NAME);
|
||||
fs::write(default_path, r###"
|
||||
matches:
|
||||
- trigger: ":lol"
|
||||
replace: "LOL"
|
||||
- trigger: ":yess"
|
||||
replace: "Bob"
|
||||
"###);
|
||||
let (data_dir, package_dir) = create_temp_espanso_directories_with_default_content(
|
||||
r###"
|
||||
matches:
|
||||
- trigger: ":lol"
|
||||
replace: "LOL"
|
||||
- trigger: ":yess"
|
||||
replace: "Bob"
|
||||
"###
|
||||
);
|
||||
|
||||
let user_defined_path2 = create_user_config_file(tmp_dir.path(), "specific.zzz", r###"
|
||||
let user_defined_path2 = create_user_config_file(data_dir.path(), "specific.zzz", r###"
|
||||
name: specific1
|
||||
|
||||
exclude_default_matches: true
|
||||
|
@ -789,35 +776,35 @@ mod tests {
|
|||
replace: "newstring"
|
||||
"###);
|
||||
|
||||
let config_set = ConfigSet::load(tmp_dir.path()).unwrap();
|
||||
let config_set = ConfigSet::load(data_dir.path(), package_dir.path()).unwrap();
|
||||
assert_eq!(config_set.specific.len(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_config_set_no_parent_configs_works_correctly() {
|
||||
let tmp_dir = create_temp_espanso_directory();
|
||||
let (data_dir, package_dir) = create_temp_espanso_directories();
|
||||
|
||||
let user_defined_path = create_user_config_file(tmp_dir.path(), "specific.yml", r###"
|
||||
let user_defined_path = create_user_config_file(data_dir.path(), "specific.yml", r###"
|
||||
name: specific1
|
||||
"###);
|
||||
|
||||
let user_defined_path2 = create_user_config_file(tmp_dir.path(), "specific2.yml", r###"
|
||||
let user_defined_path2 = create_user_config_file(data_dir.path(), "specific2.yml", r###"
|
||||
name: specific2
|
||||
"###);
|
||||
|
||||
let config_set = ConfigSet::load(tmp_dir.path()).unwrap();
|
||||
let config_set = ConfigSet::load(data_dir.path(), package_dir.path()).unwrap();
|
||||
assert_eq!(config_set.specific.len(), 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_config_set_default_parent_works_correctly() {
|
||||
let tmp_dir = create_temp_espanso_directory_with_default_content(r###"
|
||||
let (data_dir, package_dir) = create_temp_espanso_directories_with_default_content(r###"
|
||||
matches:
|
||||
- trigger: hasta
|
||||
replace: Hasta la vista
|
||||
"###);
|
||||
|
||||
let user_defined_path = create_user_config_file(tmp_dir.path(), "specific.yml", r###"
|
||||
let user_defined_path = create_user_config_file(data_dir.path(), "specific.yml", r###"
|
||||
parent: default
|
||||
|
||||
matches:
|
||||
|
@ -825,7 +812,7 @@ mod tests {
|
|||
replace: "world"
|
||||
"###);
|
||||
|
||||
let config_set = ConfigSet::load(tmp_dir.path()).unwrap();
|
||||
let config_set = ConfigSet::load(data_dir.path(), package_dir.path()).unwrap();
|
||||
assert_eq!(config_set.specific.len(), 0);
|
||||
assert_eq!(config_set.default.matches.len(), 2);
|
||||
assert!(config_set.default.matches.iter().any(|m| m.trigger == "hasta"));
|
||||
|
@ -834,19 +821,19 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn test_config_set_no_parent_should_not_merge() {
|
||||
let tmp_dir = create_temp_espanso_directory_with_default_content(r###"
|
||||
let (data_dir, package_dir)= create_temp_espanso_directories_with_default_content(r###"
|
||||
matches:
|
||||
- trigger: hasta
|
||||
replace: Hasta la vista
|
||||
"###);
|
||||
|
||||
let user_defined_path = create_user_config_file(tmp_dir.path(), "specific.yml", r###"
|
||||
let user_defined_path = create_user_config_file(data_dir.path(), "specific.yml", r###"
|
||||
matches:
|
||||
- trigger: "hello"
|
||||
replace: "world"
|
||||
"###);
|
||||
|
||||
let config_set = ConfigSet::load(tmp_dir.path()).unwrap();
|
||||
let config_set = ConfigSet::load(data_dir.path(), package_dir.path()).unwrap();
|
||||
assert_eq!(config_set.specific.len(), 1);
|
||||
assert_eq!(config_set.default.matches.len(), 1);
|
||||
assert!(config_set.default.matches.iter().any(|m| m.trigger == "hasta"));
|
||||
|
@ -856,13 +843,13 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn test_config_set_default_nested_parent_works_correctly() {
|
||||
let tmp_dir = create_temp_espanso_directory_with_default_content(r###"
|
||||
let (data_dir, package_dir) = create_temp_espanso_directories_with_default_content(r###"
|
||||
matches:
|
||||
- trigger: hasta
|
||||
replace: Hasta la vista
|
||||
"###);
|
||||
|
||||
let user_defined_path = create_user_config_file(tmp_dir.path(), "specific.yml", r###"
|
||||
let user_defined_path = create_user_config_file(data_dir.path(), "specific.yml", r###"
|
||||
name: custom1
|
||||
parent: default
|
||||
|
||||
|
@ -871,7 +858,7 @@ mod tests {
|
|||
replace: "world"
|
||||
"###);
|
||||
|
||||
let user_defined_path2 = create_user_config_file(tmp_dir.path(), "specific2.yml", r###"
|
||||
let user_defined_path2 = create_user_config_file(data_dir.path(), "specific2.yml", r###"
|
||||
parent: custom1
|
||||
|
||||
matches:
|
||||
|
@ -879,7 +866,7 @@ mod tests {
|
|||
replace: "mario"
|
||||
"###);
|
||||
|
||||
let config_set = ConfigSet::load(tmp_dir.path()).unwrap();
|
||||
let config_set = ConfigSet::load(data_dir.path(), package_dir.path()).unwrap();
|
||||
assert_eq!(config_set.specific.len(), 0);
|
||||
assert_eq!(config_set.default.matches.len(), 3);
|
||||
assert!(config_set.default.matches.iter().any(|m| m.trigger == "hasta"));
|
||||
|
@ -889,13 +876,13 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn test_config_set_parent_merge_children_priority_should_be_higher() {
|
||||
let tmp_dir = create_temp_espanso_directory_with_default_content(r###"
|
||||
let (data_dir, package_dir) = create_temp_espanso_directories_with_default_content(r###"
|
||||
matches:
|
||||
- trigger: hasta
|
||||
replace: Hasta la vista
|
||||
"###);
|
||||
|
||||
let user_defined_path = create_user_config_file(tmp_dir.path(), "specific.yml", r###"
|
||||
let user_defined_path = create_user_config_file(data_dir.path(), "specific.yml", r###"
|
||||
parent: default
|
||||
|
||||
matches:
|
||||
|
@ -903,7 +890,7 @@ mod tests {
|
|||
replace: "world"
|
||||
"###);
|
||||
|
||||
let config_set = ConfigSet::load(tmp_dir.path()).unwrap();
|
||||
let config_set = ConfigSet::load(data_dir.path(), package_dir.path()).unwrap();
|
||||
assert_eq!(config_set.specific.len(), 0);
|
||||
assert_eq!(config_set.default.matches.len(), 1);
|
||||
assert!(config_set.default.matches.iter().any(|m| m.trigger == "hasta" && m.replace == "world"));
|
||||
|
@ -911,13 +898,13 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn test_config_set_package_configs_default_merge() {
|
||||
let tmp_dir = create_temp_espanso_directory_with_default_content(r###"
|
||||
let (data_dir, package_dir) = create_temp_espanso_directories_with_default_content(r###"
|
||||
matches:
|
||||
- trigger: hasta
|
||||
replace: Hasta la vista
|
||||
"###);
|
||||
|
||||
let package_path = create_package_file(tmp_dir.path(), "package1", "package.yml", r###"
|
||||
let package_path = create_package_file(package_dir.path(), "package1", "package.yml", r###"
|
||||
parent: default
|
||||
|
||||
matches:
|
||||
|
@ -925,7 +912,7 @@ mod tests {
|
|||
replace: "potter"
|
||||
"###);
|
||||
|
||||
let config_set = ConfigSet::load(tmp_dir.path()).unwrap();
|
||||
let config_set = ConfigSet::load(data_dir.path(), package_dir.path()).unwrap();
|
||||
assert_eq!(config_set.specific.len(), 0);
|
||||
assert_eq!(config_set.default.matches.len(), 2);
|
||||
assert!(config_set.default.matches.iter().any(|m| m.trigger == "hasta"));
|
||||
|
@ -934,19 +921,19 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn test_config_set_package_configs_without_merge() {
|
||||
let tmp_dir = create_temp_espanso_directory_with_default_content(r###"
|
||||
let (data_dir, package_dir) = create_temp_espanso_directories_with_default_content(r###"
|
||||
matches:
|
||||
- trigger: hasta
|
||||
replace: Hasta la vista
|
||||
"###);
|
||||
|
||||
let package_path = create_package_file(tmp_dir.path(), "package1", "package.yml", r###"
|
||||
let package_path = create_package_file(package_dir.path(), "package1", "package.yml", r###"
|
||||
matches:
|
||||
- trigger: "harry"
|
||||
replace: "potter"
|
||||
"###);
|
||||
|
||||
let config_set = ConfigSet::load(tmp_dir.path()).unwrap();
|
||||
let config_set = ConfigSet::load(data_dir.path(), package_dir.path()).unwrap();
|
||||
assert_eq!(config_set.specific.len(), 1);
|
||||
assert_eq!(config_set.default.matches.len(), 1);
|
||||
assert!(config_set.default.matches.iter().any(|m| m.trigger == "hasta"));
|
||||
|
@ -955,13 +942,13 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn test_config_set_package_configs_multiple_files() {
|
||||
let tmp_dir = create_temp_espanso_directory_with_default_content(r###"
|
||||
let (data_dir, package_dir) = create_temp_espanso_directories_with_default_content(r###"
|
||||
matches:
|
||||
- trigger: hasta
|
||||
replace: Hasta la vista
|
||||
"###);
|
||||
|
||||
let package_path = create_package_file(tmp_dir.path(), "package1", "package.yml", r###"
|
||||
let package_path = create_package_file(package_dir.path(), "package1", "package.yml", r###"
|
||||
name: package1
|
||||
|
||||
matches:
|
||||
|
@ -969,7 +956,7 @@ mod tests {
|
|||
replace: "potter"
|
||||
"###);
|
||||
|
||||
let package_path2 = create_package_file(tmp_dir.path(), "package1", "addon.yml", r###"
|
||||
let package_path2 = create_package_file(package_dir.path(), "package1", "addon.yml", r###"
|
||||
parent: package1
|
||||
|
||||
matches:
|
||||
|
@ -977,7 +964,7 @@ mod tests {
|
|||
replace: "weasley"
|
||||
"###);
|
||||
|
||||
let config_set = ConfigSet::load(tmp_dir.path()).unwrap();
|
||||
let config_set = ConfigSet::load(data_dir.path(), package_dir.path()).unwrap();
|
||||
assert_eq!(config_set.specific.len(), 1);
|
||||
assert_eq!(config_set.default.matches.len(), 1);
|
||||
assert!(config_set.default.matches.iter().any(|m| m.trigger == "hasta"));
|
||||
|
|
|
@ -209,7 +209,7 @@ mod tests {
|
|||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
use crate::config::ConfigManager;
|
||||
use crate::config::tests::{create_temp_espanso_directory, create_temp_file_in_dir, create_user_config_file};
|
||||
use crate::config::tests::{create_temp_espanso_directories, create_temp_file_in_dir, create_user_config_file};
|
||||
|
||||
struct DummySystemManager {
|
||||
title: RefCell<String>,
|
||||
|
@ -249,25 +249,25 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn test_runtime_constructor_regex_load_correctly() {
|
||||
let tmp_dir = create_temp_espanso_directory();
|
||||
let (data_dir, package_dir) = create_temp_espanso_directories();
|
||||
|
||||
let specific_path = create_user_config_file(&tmp_dir.path(), "specific.yml", r###"
|
||||
let specific_path = create_user_config_file(&data_dir.path(), "specific.yml", r###"
|
||||
name: myname1
|
||||
filter_exec: "Title"
|
||||
"###);
|
||||
|
||||
let specific_path2 = create_user_config_file(&tmp_dir.path(), "specific2.yml", r###"
|
||||
let specific_path2 = create_user_config_file(&data_dir.path(), "specific2.yml", r###"
|
||||
name: myname2
|
||||
filter_title: "Yeah"
|
||||
filter_class: "Car"
|
||||
"###);
|
||||
|
||||
let specific_path3 = create_user_config_file(&tmp_dir.path(), "specific3.yml", r###"
|
||||
let specific_path3 = create_user_config_file(&data_dir.path(), "specific3.yml", r###"
|
||||
name: myname3
|
||||
filter_title: "Nice"
|
||||
"###);
|
||||
|
||||
let config_set = ConfigSet::load(tmp_dir.path());
|
||||
let config_set = ConfigSet::load(data_dir.path(), package_dir.path());
|
||||
assert!(config_set.is_ok());
|
||||
|
||||
let dummy_system_manager = DummySystemManager::new();
|
||||
|
@ -300,25 +300,25 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn test_runtime_constructor_malformed_regexes_are_ignored() {
|
||||
let tmp_dir = create_temp_espanso_directory();
|
||||
let (data_dir, package_dir) = create_temp_espanso_directories();
|
||||
|
||||
let specific_path = create_user_config_file(&tmp_dir.path(), "specific.yml", r###"
|
||||
let specific_path = create_user_config_file(&data_dir.path(), "specific.yml", r###"
|
||||
name: myname1
|
||||
filter_exec: "[`-_]"
|
||||
"###);
|
||||
|
||||
let specific_path2 = create_user_config_file(&tmp_dir.path(), "specific2.yml", r###"
|
||||
let specific_path2 = create_user_config_file(&data_dir.path(), "specific2.yml", r###"
|
||||
name: myname2
|
||||
filter_title: "[`-_]"
|
||||
filter_class: "Car"
|
||||
"###);
|
||||
|
||||
let specific_path3 = create_user_config_file(&tmp_dir.path(), "specific3.yml", r###"
|
||||
let specific_path3 = create_user_config_file(&data_dir.path(), "specific3.yml", r###"
|
||||
name: myname3
|
||||
filter_title: "Nice"
|
||||
"###);
|
||||
|
||||
let config_set = ConfigSet::load(tmp_dir.path());
|
||||
let config_set = ConfigSet::load(data_dir.path(), package_dir.path());
|
||||
assert!(config_set.is_ok());
|
||||
|
||||
let dummy_system_manager = DummySystemManager::new();
|
||||
|
@ -351,14 +351,14 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn test_runtime_calculate_active_config_specific_title_match() {
|
||||
let tmp_dir = create_temp_espanso_directory();
|
||||
let (data_dir, package_dir) = create_temp_espanso_directories();
|
||||
|
||||
let specific_path = create_user_config_file(&tmp_dir.path(), "specific.yml", r###"
|
||||
let specific_path = create_user_config_file(&data_dir.path(), "specific.yml", r###"
|
||||
name: chrome
|
||||
filter_title: "Chrome"
|
||||
"###);
|
||||
|
||||
let config_set = ConfigSet::load(tmp_dir.path());
|
||||
let config_set = ConfigSet::load(data_dir.path(), package_dir.path());
|
||||
assert!(config_set.is_ok());
|
||||
|
||||
let dummy_system_manager = DummySystemManager::new_custom("Google Chrome", "Chrome", "C:\\Path\\chrome.exe");
|
||||
|
@ -369,14 +369,14 @@ mod tests {
|
|||
}
|
||||
|
||||
fn test_runtime_calculate_active_config_specific_class_match() {
|
||||
let tmp_dir = create_temp_espanso_directory();
|
||||
let (data_dir, package_dir) = create_temp_espanso_directories();
|
||||
|
||||
let specific_path = create_user_config_file(&tmp_dir.path(), "specific.yml", r###"
|
||||
let specific_path = create_user_config_file(&data_dir.path(), "specific.yml", r###"
|
||||
name: chrome
|
||||
filter_class: "Chrome"
|
||||
"###);
|
||||
|
||||
let config_set = ConfigSet::load(tmp_dir.path());
|
||||
let config_set = ConfigSet::load(data_dir.path(), package_dir.path());
|
||||
assert!(config_set.is_ok());
|
||||
|
||||
let dummy_system_manager = DummySystemManager::new_custom("Google Chrome", "Chrome", "C:\\Path\\chrome.exe");
|
||||
|
@ -387,14 +387,14 @@ mod tests {
|
|||
}
|
||||
|
||||
fn test_runtime_calculate_active_config_specific_exec_match() {
|
||||
let tmp_dir = create_temp_espanso_directory();
|
||||
let (data_dir, package_dir) = create_temp_espanso_directories();
|
||||
|
||||
let specific_path = create_user_config_file(&tmp_dir.path(), "specific.yml", r###"
|
||||
let specific_path = create_user_config_file(&data_dir.path(), "specific.yml", r###"
|
||||
name: chrome
|
||||
filter_exec: "chrome.exe"
|
||||
"###);
|
||||
|
||||
let config_set = ConfigSet::load(tmp_dir.path());
|
||||
let config_set = ConfigSet::load(data_dir.path(), package_dir.path());
|
||||
assert!(config_set.is_ok());
|
||||
|
||||
let dummy_system_manager = DummySystemManager::new_custom("Google Chrome", "Chrome", "C:\\Path\\chrome.exe");
|
||||
|
@ -405,15 +405,15 @@ mod tests {
|
|||
}
|
||||
|
||||
fn test_runtime_calculate_active_config_specific_multi_filter_match() {
|
||||
let tmp_dir = create_temp_espanso_directory();
|
||||
let (data_dir, package_dir) = create_temp_espanso_directories();
|
||||
|
||||
let specific_path = create_user_config_file(&tmp_dir.path(), "specific.yml", r###"
|
||||
let specific_path = create_user_config_file(&data_dir.path(), "specific.yml", r###"
|
||||
name: chrome
|
||||
filter_class: Browser
|
||||
filter_exec: "firefox.exe"
|
||||
"###);
|
||||
|
||||
let config_set = ConfigSet::load(tmp_dir.path());
|
||||
let config_set = ConfigSet::load(data_dir.path(), package_dir.path());
|
||||
assert!(config_set.is_ok());
|
||||
|
||||
let dummy_system_manager = DummySystemManager::new_custom("Google Chrome", "Browser", "C:\\Path\\chrome.exe");
|
||||
|
@ -425,14 +425,14 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn test_runtime_calculate_active_config_no_match() {
|
||||
let tmp_dir = create_temp_espanso_directory();
|
||||
let (data_dir, package_dir) = create_temp_espanso_directories();
|
||||
|
||||
let specific_path = create_user_config_file(&tmp_dir.path(), "specific.yml", r###"
|
||||
let specific_path = create_user_config_file(&data_dir.path(), "specific.yml", r###"
|
||||
name: firefox
|
||||
filter_title: "Firefox"
|
||||
"###);
|
||||
|
||||
let config_set = ConfigSet::load(tmp_dir.path());
|
||||
let config_set = ConfigSet::load(data_dir.path(), package_dir.path());
|
||||
assert!(config_set.is_ok());
|
||||
|
||||
let dummy_system_manager = DummySystemManager::new_custom("Google Chrome", "Chrome", "C:\\Path\\chrome.exe");
|
||||
|
@ -444,14 +444,14 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn test_runtime_active_config_cache() {
|
||||
let tmp_dir = create_temp_espanso_directory();
|
||||
let (data_dir, package_dir) = create_temp_espanso_directories();
|
||||
|
||||
let specific_path = create_user_config_file(&tmp_dir.path(), "specific.yml", r###"
|
||||
let specific_path = create_user_config_file(&data_dir.path(), "specific.yml", r###"
|
||||
name: firefox
|
||||
filter_title: "Firefox"
|
||||
"###);
|
||||
|
||||
let config_set = ConfigSet::load(tmp_dir.path());
|
||||
let config_set = ConfigSet::load(data_dir.path(), package_dir.path());
|
||||
assert!(config_set.is_ok());
|
||||
|
||||
let dummy_system_manager = DummySystemManager::new_custom("Google Chrome", "Chrome", "C:\\Path\\chrome.exe");
|
||||
|
|
|
@ -18,12 +18,14 @@
|
|||
*/
|
||||
|
||||
use std::sync::mpsc::Sender;
|
||||
use std::os::raw::c_void;
|
||||
use std::os::raw::{c_void, c_char};
|
||||
use crate::event::*;
|
||||
use crate::event::KeyModifier::*;
|
||||
use crate::bridge::linux::*;
|
||||
use std::process::exit;
|
||||
use log::error;
|
||||
use log::{error, info};
|
||||
use std::ffi::CStr;
|
||||
use std::{thread, time};
|
||||
|
||||
#[repr(C)]
|
||||
pub struct LinuxContext {
|
||||
|
@ -32,6 +34,16 @@ pub struct LinuxContext {
|
|||
|
||||
impl LinuxContext {
|
||||
pub fn new(send_channel: Sender<Event>) -> Box<LinuxContext> {
|
||||
// Check if the X11 context is available
|
||||
let x11_available = unsafe {
|
||||
check_x11()
|
||||
};
|
||||
|
||||
if x11_available < 0 {
|
||||
error!("Error, can't connect to X11 context");
|
||||
std::process::exit(100);
|
||||
}
|
||||
|
||||
let context = Box::new(LinuxContext {
|
||||
send_channel,
|
||||
});
|
||||
|
@ -74,14 +86,19 @@ extern fn keypress_callback(_self: *mut c_void, raw_buffer: *const u8, len: i32,
|
|||
let _self = _self as *mut LinuxContext;
|
||||
|
||||
if is_modifier == 0 { // Char event
|
||||
// Convert the received buffer to a character
|
||||
let buffer = std::slice::from_raw_parts(raw_buffer, len as usize);
|
||||
let r = String::from_utf8_lossy(buffer).chars().nth(0);
|
||||
// Convert the received buffer to a string
|
||||
let c_str = CStr::from_ptr(raw_buffer as (*const c_char));
|
||||
let char_str = c_str.to_str();
|
||||
|
||||
// Send the char through the channel
|
||||
if let Some(c) = r {
|
||||
let event = Event::Key(KeyEvent::Char(c));
|
||||
(*_self).send_channel.send(event).unwrap();
|
||||
match char_str {
|
||||
Ok(char_str) => {
|
||||
let event = Event::Key(KeyEvent::Char(char_str.to_owned()));
|
||||
(*_self).send_channel.send(event).unwrap();
|
||||
},
|
||||
Err(e) => {
|
||||
error!("Unable to receive char: {}",e);
|
||||
},
|
||||
}
|
||||
}else{ // Modifier event
|
||||
let modifier: Option<KeyModifier> = match key_code {
|
||||
|
|
|
@ -18,11 +18,11 @@
|
|||
*/
|
||||
|
||||
use std::sync::mpsc::Sender;
|
||||
use std::os::raw::c_void;
|
||||
use std::os::raw::{c_void, c_char};
|
||||
use crate::bridge::macos::*;
|
||||
use crate::event::{Event, KeyEvent, KeyModifier, ActionType};
|
||||
use crate::event::KeyModifier::*;
|
||||
use std::ffi::CString;
|
||||
use std::ffi::{CString, CStr};
|
||||
use std::fs;
|
||||
use log::{info, error};
|
||||
use std::process::exit;
|
||||
|
@ -94,14 +94,19 @@ extern fn keypress_callback(_self: *mut c_void, raw_buffer: *const u8, len: i32,
|
|||
let _self = _self as *mut MacContext;
|
||||
|
||||
if is_modifier == 0 { // Char event
|
||||
// Convert the received buffer to a character
|
||||
let buffer = std::slice::from_raw_parts(raw_buffer, len as usize);
|
||||
let r = String::from_utf8_lossy(buffer).chars().nth(0);
|
||||
// Convert the received buffer to a string
|
||||
let c_str = CStr::from_ptr(raw_buffer as (*const c_char));
|
||||
let char_str = c_str.to_str();
|
||||
|
||||
// Send the char through the channel
|
||||
if let Some(c) = r {
|
||||
let event = Event::Key(KeyEvent::Char(c));
|
||||
(*_self).send_channel.send(event).unwrap();
|
||||
match char_str {
|
||||
Ok(char_str) => {
|
||||
let event = Event::Key(KeyEvent::Char(char_str.to_owned()));
|
||||
(*_self).send_channel.send(event).unwrap();
|
||||
},
|
||||
Err(e) => {
|
||||
error!("Unable to receive char: {}",e);
|
||||
},
|
||||
}
|
||||
}else{ // Modifier event
|
||||
let modifier: Option<KeyModifier> = match key_code {
|
||||
|
|
|
@ -35,13 +35,6 @@ pub trait Context {
|
|||
fn eventloop(&self);
|
||||
}
|
||||
|
||||
pub fn get_data_dir() -> PathBuf {
|
||||
let data_dir = dirs::data_dir().expect("Can't obtain data_dir(), terminating.");
|
||||
let espanso_dir = data_dir.join("espanso");
|
||||
create_dir_all(&espanso_dir).expect("Error creating espanso data directory");
|
||||
espanso_dir
|
||||
}
|
||||
|
||||
// MAC IMPLEMENTATION
|
||||
#[cfg(target_os = "macos")]
|
||||
pub fn new(send_channel: Sender<Event>) -> Box<dyn Context> {
|
||||
|
@ -58,4 +51,62 @@ pub fn new(send_channel: Sender<Event>) -> Box<dyn Context> {
|
|||
#[cfg(target_os = "windows")]
|
||||
pub fn new(send_channel: Sender<Event>) -> Box<dyn Context> {
|
||||
windows::WindowsContext::new(send_channel)
|
||||
}
|
||||
|
||||
// espanso directories
|
||||
|
||||
pub fn get_data_dir() -> PathBuf {
|
||||
let data_dir = dirs::data_local_dir().expect("Can't obtain data_local_dir(), terminating.");
|
||||
let espanso_dir = data_dir.join("espanso");
|
||||
create_dir_all(&espanso_dir).expect("Error creating espanso data directory");
|
||||
espanso_dir
|
||||
}
|
||||
|
||||
pub fn get_config_dir() -> PathBuf {
|
||||
// Portable mode check
|
||||
// Get the espanso executable path
|
||||
let espanso_exe_path = std::env::current_exe().expect("Could not get espanso executable path");
|
||||
let exe_dir = espanso_exe_path.parent();
|
||||
if let Some(parent) = exe_dir {
|
||||
let config_dir = parent.join(".espanso");
|
||||
if config_dir.exists() {
|
||||
println!("PORTABLE MODE, using config folder: '{}'", config_dir.to_string_lossy());
|
||||
return config_dir;
|
||||
}
|
||||
}
|
||||
|
||||
// For compatibility purposes, check if the $HOME/.espanso directory is available
|
||||
let home_dir = dirs::home_dir().expect("Can't obtain the user home directory, terminating.");
|
||||
let legacy_espanso_dir = home_dir.join(".espanso");
|
||||
if legacy_espanso_dir.exists() {
|
||||
eprintln!("WARNING: using legacy espanso config location in $HOME/.espanso is DEPRECATED");
|
||||
eprintln!("Starting from espanso v0.3.0, espanso config location is changed.");
|
||||
eprintln!("Please check out the documentation to find out more: https://espanso.org/docs/configuration/");
|
||||
|
||||
return legacy_espanso_dir;
|
||||
}
|
||||
|
||||
// New config location, from version v0.3.0
|
||||
// Refer to issue #73 for more information: https://github.com/federico-terzi/espanso/issues/73
|
||||
let config_dir = dirs::config_dir().expect("Can't obtain config_dir(), terminating.");
|
||||
let espanso_dir = config_dir.join("espanso");
|
||||
create_dir_all(&espanso_dir).expect("Error creating espanso config directory");
|
||||
espanso_dir
|
||||
}
|
||||
|
||||
const PACKAGES_FOLDER_NAME : &str = "packages";
|
||||
|
||||
pub fn get_package_dir() -> PathBuf {
|
||||
// Deprecated $HOME/.espanso/packages directory compatibility check
|
||||
let config_dir = get_config_dir();
|
||||
let legacy_package_dir = config_dir.join(PACKAGES_FOLDER_NAME);
|
||||
if legacy_package_dir.exists() {
|
||||
return legacy_package_dir;
|
||||
}
|
||||
|
||||
// New package location, starting from version v0.3.0
|
||||
let data_dir = get_data_dir();
|
||||
let package_dir = data_dir.join(PACKAGES_FOLDER_NAME);
|
||||
create_dir_all(&package_dir).expect("Error creating espanso packages directory");
|
||||
package_dir
|
||||
}
|
|
@ -23,8 +23,8 @@ use crate::event::{Event, KeyEvent, KeyModifier, ActionType};
|
|||
use crate::event::KeyModifier::*;
|
||||
use std::ffi::c_void;
|
||||
use std::{fs};
|
||||
use widestring::U16CString;
|
||||
use log::{info};
|
||||
use widestring::{U16CString, U16CStr};
|
||||
use log::{info, error};
|
||||
|
||||
const BMP_BINARY : &[u8] = include_bytes!("../res/win/espanso.bmp");
|
||||
const ICO_BINARY : &[u8] = include_bytes!("../res/win/espanso.ico");
|
||||
|
@ -102,20 +102,31 @@ impl super::Context for WindowsContext {
|
|||
|
||||
// Native bridge code
|
||||
|
||||
extern fn keypress_callback(_self: *mut c_void, raw_buffer: *const i32, len: i32,
|
||||
extern fn keypress_callback(_self: *mut c_void, raw_buffer: *const u16, len: i32,
|
||||
is_modifier: i32, key_code: i32, is_key_down: i32) {
|
||||
unsafe {
|
||||
let _self = _self as *mut WindowsContext;
|
||||
if is_key_down != 0 { // KEY DOWN EVENT
|
||||
if is_modifier == 0 { // Char event
|
||||
// Convert the received buffer to a character
|
||||
// Convert the received buffer to a string
|
||||
let buffer = std::slice::from_raw_parts(raw_buffer, len as usize);
|
||||
let r = std::char::from_u32(buffer[0] as u32);
|
||||
let c_string = U16CStr::from_slice_with_nul(buffer);
|
||||
|
||||
// Send the char through the channel
|
||||
if let Some(c) = r {
|
||||
let event = Event::Key(KeyEvent::Char(c));
|
||||
(*_self).send_channel.send(event).unwrap();
|
||||
if let Ok(c_string) = c_string {
|
||||
let string = c_string.to_string();
|
||||
|
||||
// Send the char through the channel
|
||||
match string {
|
||||
Ok(string) => {
|
||||
let event = Event::Key(KeyEvent::Char(string));
|
||||
(*_self).send_channel.send(event).unwrap();
|
||||
},
|
||||
Err(e) => {
|
||||
error!("Unable to receive char: {}",e);
|
||||
},
|
||||
}
|
||||
}else{
|
||||
error!("unable to decode widechar");
|
||||
}
|
||||
}
|
||||
}else{ // KEY UP event
|
||||
|
|
|
@ -110,7 +110,7 @@ impl <'a, S: KeyboardManager, C: ClipboardManager, M: ConfigManager<'a>, U: UIMa
|
|||
return;
|
||||
}
|
||||
|
||||
self.keyboard_manager.delete_string(m.trigger.len() as i32);
|
||||
self.keyboard_manager.delete_string(m.trigger.chars().count() as i32);
|
||||
|
||||
let target_string = if m._has_vars {
|
||||
let mut output_map = HashMap::new();
|
||||
|
|
|
@ -52,7 +52,7 @@ impl From<i32> for ActionType {
|
|||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum KeyEvent {
|
||||
Char(char),
|
||||
Char(String),
|
||||
Modifier(KeyModifier)
|
||||
}
|
||||
|
||||
|
|
90
src/main.rs
90
src/main.rs
|
@ -22,7 +22,6 @@ extern crate lazy_static;
|
|||
|
||||
use std::thread;
|
||||
use std::fs::{File, OpenOptions};
|
||||
use std::path::Path;
|
||||
use std::process::exit;
|
||||
use std::sync::mpsc;
|
||||
use std::sync::mpsc::Receiver;
|
||||
|
@ -81,12 +80,6 @@ fn main() {
|
|||
.version(VERSION)
|
||||
.author("Federico Terzi")
|
||||
.about("Cross-platform Text Expander written in Rust")
|
||||
.arg(Arg::with_name("config")
|
||||
.short("c")
|
||||
.long("config")
|
||||
.value_name("FILE")
|
||||
.help("Sets a custom config directory. If not specified, reads the default $HOME/.espanso/default.yml file, creating it if not present.")
|
||||
.takes_value(true))
|
||||
.arg(Arg::with_name("v")
|
||||
.short("v")
|
||||
.multiple(true)
|
||||
|
@ -109,9 +102,9 @@ fn main() {
|
|||
.subcommand(SubCommand::with_name("daemon")
|
||||
.about("Start the daemon without spawning a new process."))
|
||||
.subcommand(SubCommand::with_name("register")
|
||||
.about("MacOS only. Register espanso in the system daemon manager."))
|
||||
.about("MacOS and Linux only. Register espanso in the system daemon manager."))
|
||||
.subcommand(SubCommand::with_name("unregister")
|
||||
.about("MacOS only. Unregister espanso from the system daemon manager."))
|
||||
.about("MacOS and Linux only. Unregister espanso from the system daemon manager."))
|
||||
.subcommand(SubCommand::with_name("log")
|
||||
.about("Print the latest daemon logs."))
|
||||
.subcommand(SubCommand::with_name("start")
|
||||
|
@ -122,6 +115,8 @@ fn main() {
|
|||
.about("Restart the espanso daemon."))
|
||||
.subcommand(SubCommand::with_name("status")
|
||||
.about("Check if the espanso daemon is running or not."))
|
||||
.subcommand(SubCommand::with_name("path")
|
||||
.about("Prints all the current espanso directory paths, to easily locate configuration and data paths."))
|
||||
|
||||
// Package manager
|
||||
.subcommand(SubCommand::with_name("package")
|
||||
|
@ -145,20 +140,7 @@ fn main() {
|
|||
let log_level = matches.occurrences_of("v") as i32;
|
||||
|
||||
// Load the configuration
|
||||
let mut config_set = match matches.value_of("config") {
|
||||
None => {
|
||||
if log_level > 1 {
|
||||
println!("loading configuration from default location...");
|
||||
}
|
||||
ConfigSet::load_default()
|
||||
},
|
||||
Some(path) => {
|
||||
if log_level > 1 {
|
||||
println!("loading configuration from custom location: {}", path);
|
||||
}
|
||||
ConfigSet::load(Path::new(path))
|
||||
},
|
||||
}.unwrap_or_else(|e| {
|
||||
let mut config_set = ConfigSet::load_default().unwrap_or_else(|e| {
|
||||
println!("{}", e);
|
||||
exit(1);
|
||||
});
|
||||
|
@ -232,6 +214,11 @@ fn main() {
|
|||
return;
|
||||
}
|
||||
|
||||
if matches.subcommand_matches("path").is_some() {
|
||||
path_main(config_set);
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some(matches) = matches.subcommand_matches("package") {
|
||||
if let Some(matches) = matches.subcommand_matches("install") {
|
||||
install_main(config_set, matches);
|
||||
|
@ -304,6 +291,8 @@ fn daemon_main(config_set: ConfigSet) {
|
|||
log_panics::init();
|
||||
|
||||
info!("espanso version {}", VERSION);
|
||||
info!("using config path: {}", context::get_config_dir().to_string_lossy());
|
||||
info!("using package path: {}", context::get_package_dir().to_string_lossy());
|
||||
info!("starting daemon...");
|
||||
|
||||
let (send_channel, receive_channel) = mpsc::channel();
|
||||
|
@ -393,10 +382,10 @@ fn start_daemon(config_set: ConfigSet) {
|
|||
if status.success() {
|
||||
println!("Daemon started correctly!")
|
||||
}else{
|
||||
println!("Error starting launchd daemon with status: {}", status);
|
||||
eprintln!("Error starting launchd daemon with status: {}", status);
|
||||
}
|
||||
}else{
|
||||
println!("Error starting launchd daemon: {}", res.unwrap_err());
|
||||
eprintln!("Error starting launchd daemon: {}", res.unwrap_err());
|
||||
}
|
||||
}else{
|
||||
fork_daemon(config_set);
|
||||
|
@ -405,7 +394,50 @@ fn start_daemon(config_set: ConfigSet) {
|
|||
|
||||
#[cfg(target_os = "linux")]
|
||||
fn start_daemon(config_set: ConfigSet) {
|
||||
fork_daemon(config_set);
|
||||
if config_set.default.use_system_agent {
|
||||
use std::process::Command;
|
||||
|
||||
// Make sure espanso is currently registered in systemd
|
||||
let res = Command::new("systemctl")
|
||||
.args(&["--user", "is-enabled", "espanso.service"])
|
||||
.status();
|
||||
if !res.unwrap().success() {
|
||||
use std::io::{self, BufRead};
|
||||
eprintln!("espanso must be registered to systemd (user level) first.");
|
||||
eprint!("Do you want to proceed? [Y/n]: ");
|
||||
|
||||
let mut line = String::new();
|
||||
let stdin = io::stdin();
|
||||
stdin.lock().read_line(&mut line).unwrap();
|
||||
let answer = line.trim().to_lowercase();
|
||||
if answer != "n" {
|
||||
register_main(config_set);
|
||||
}else{
|
||||
eprintln!("Please register espanso to systemd with this command:");
|
||||
eprintln!(" espanso register");
|
||||
// TODO: enable flag to use non-managed daemon mode
|
||||
|
||||
std::process::exit(4);
|
||||
}
|
||||
}
|
||||
|
||||
// Start the espanso service
|
||||
let res = Command::new("systemctl")
|
||||
.args(&["--user", "start", "espanso.service"])
|
||||
.status();
|
||||
|
||||
if let Ok(status) = res {
|
||||
if status.success() {
|
||||
println!("Daemon started correctly!")
|
||||
}else{
|
||||
eprintln!("Error starting systemd daemon with status: {}", status);
|
||||
}
|
||||
}else{
|
||||
eprintln!("Error starting systemd daemon: {}", res.unwrap_err());
|
||||
}
|
||||
}else{
|
||||
fork_daemon(config_set);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
|
@ -744,6 +776,12 @@ fn list_package_main(_config_set: ConfigSet, matches: &ArgMatches) {
|
|||
}
|
||||
}
|
||||
|
||||
fn path_main(_config_set: ConfigSet) {
|
||||
println!("Config: {}", crate::context::get_config_dir().to_string_lossy());
|
||||
println!("Packages: {}", crate::context::get_package_dir().to_string_lossy());
|
||||
println!("Data: {}", crate::context::get_config_dir().to_string_lossy());
|
||||
}
|
||||
|
||||
|
||||
fn acquire_lock() -> Option<File> {
|
||||
let espanso_dir = context::get_data_dir();
|
||||
|
|
|
@ -90,7 +90,7 @@ pub trait MatchReceiver {
|
|||
}
|
||||
|
||||
pub trait Matcher : KeyEventReceiver {
|
||||
fn handle_char(&self, c: char);
|
||||
fn handle_char(&self, c: &str);
|
||||
fn handle_modifier(&self, m: KeyModifier);
|
||||
}
|
||||
|
||||
|
@ -98,7 +98,7 @@ impl <M: Matcher> KeyEventReceiver for M {
|
|||
fn on_key_event(&self, e: KeyEvent) {
|
||||
match e {
|
||||
KeyEvent::Char(c) => {
|
||||
self.handle_char(c);
|
||||
self.handle_char(&c);
|
||||
},
|
||||
KeyEvent::Modifier(m) => {
|
||||
self.handle_modifier(m);
|
||||
|
|
|
@ -35,6 +35,7 @@ pub struct ScrollingMatcher<'a, R: MatchReceiver, M: ConfigManager<'a>> {
|
|||
|
||||
struct MatchEntry<'a> {
|
||||
start: usize,
|
||||
count: usize,
|
||||
_match: &'a Match
|
||||
}
|
||||
|
||||
|
@ -68,7 +69,7 @@ impl <'a, R: MatchReceiver, M: ConfigManager<'a>> ScrollingMatcher<'a, R, M> {
|
|||
}
|
||||
|
||||
impl <'a, R: MatchReceiver, M: ConfigManager<'a>> super::Matcher for ScrollingMatcher<'a, R, M> {
|
||||
fn handle_char(&self, c: char) {
|
||||
fn handle_char(&self, c: &str) {
|
||||
// if not enabled, avoid any processing
|
||||
if !*(self.is_enabled.borrow()) {
|
||||
return;
|
||||
|
@ -77,8 +78,12 @@ impl <'a, R: MatchReceiver, M: ConfigManager<'a>> super::Matcher for ScrollingMa
|
|||
let mut current_set_queue = self.current_set_queue.borrow_mut();
|
||||
|
||||
let new_matches: Vec<MatchEntry> = self.config_manager.matches().iter()
|
||||
.filter(|&x| x.trigger.chars().nth(0).unwrap() == c)
|
||||
.map(|x | MatchEntry{start: 1, _match: &x})
|
||||
.filter(|&x| x.trigger.starts_with(c))
|
||||
.map(|x | MatchEntry{
|
||||
start: 1,
|
||||
count: x.trigger.chars().count(),
|
||||
_match: &x
|
||||
})
|
||||
.collect();
|
||||
// TODO: use an associative structure to improve the efficiency of this first "new_matches" lookup.
|
||||
|
||||
|
@ -86,9 +91,18 @@ impl <'a, R: MatchReceiver, M: ConfigManager<'a>> super::Matcher for ScrollingMa
|
|||
Some(last_matches) => {
|
||||
let mut updated: Vec<MatchEntry> = last_matches.iter()
|
||||
.filter(|&x| {
|
||||
x._match.trigger[x.start..].chars().nth(0).unwrap() == c
|
||||
let nchar = x._match.trigger.chars().nth(x.start);
|
||||
if let Some(nchar) = nchar {
|
||||
c.starts_with(nchar)
|
||||
}else{
|
||||
false
|
||||
}
|
||||
})
|
||||
.map(|x | MatchEntry{
|
||||
start: x.start+1,
|
||||
count: x.count,
|
||||
_match: &x._match
|
||||
})
|
||||
.map(|x | MatchEntry{start: x.start+1, _match: &x._match})
|
||||
.collect();
|
||||
|
||||
updated.extend(new_matches);
|
||||
|
@ -100,7 +114,7 @@ impl <'a, R: MatchReceiver, M: ConfigManager<'a>> super::Matcher for ScrollingMa
|
|||
let mut found_match = None;
|
||||
|
||||
for entry in combined_matches.iter() {
|
||||
if entry.start == entry._match.trigger.len() {
|
||||
if entry.start == entry.count {
|
||||
found_match = Some(entry._match);
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -54,7 +54,7 @@ impl DefaultPackageManager {
|
|||
|
||||
pub fn new_default() -> DefaultPackageManager {
|
||||
DefaultPackageManager::new(
|
||||
crate::config::ConfigSet::get_default_packages_dir(),
|
||||
crate::context::get_package_dir(),
|
||||
crate::context::get_data_dir()
|
||||
)
|
||||
}
|
||||
|
|
11
src/res/linux/systemd.service
Normal file
11
src/res/linux/systemd.service
Normal file
|
@ -0,0 +1,11 @@
|
|||
[Unit]
|
||||
Description=espanso daemon
|
||||
|
||||
[Service]
|
||||
ExecStart={{{espanso_path}}} daemon
|
||||
Restart=on-failure
|
||||
RestartSec=3
|
||||
|
||||
[Install]
|
||||
WantedBy=default.target
|
||||
|
|
@ -102,13 +102,101 @@ pub fn unregister(_config_set: ConfigSet) {
|
|||
// LINUX
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
pub fn register(_config_set: ConfigSet) {
|
||||
println!("Linux does not support automatic system daemon integration.");
|
||||
const LINUX_SERVICE_CONTENT : &str = include_str!("res/linux/systemd.service");
|
||||
#[cfg(target_os = "linux")]
|
||||
const LINUX_SERVICE_FILENAME : &str = "espanso.service";
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
pub fn register(config_set: ConfigSet) {
|
||||
use std::fs::create_dir_all;
|
||||
use std::process::{Command, ExitStatus};
|
||||
|
||||
// Check if espanso service is already registered
|
||||
let res = Command::new("systemctl")
|
||||
.args(&["--user", "is-enabled", "espanso"])
|
||||
.output();
|
||||
if let Ok(res) = res {
|
||||
let output = String::from_utf8_lossy(res.stdout.as_slice());
|
||||
let output = output.trim();
|
||||
if res.status.success() && output == "enabled" {
|
||||
eprintln!("espanso service is already registered to systemd");
|
||||
eprintln!("If you want to register it again, please uninstall it first with:");
|
||||
eprintln!(" espanso unregister");
|
||||
std::process::exit(5);
|
||||
}
|
||||
}
|
||||
|
||||
// User level systemd services should be placed in this directory:
|
||||
// $XDG_CONFIG_HOME/systemd/user/, usually: ~/.config/systemd/user/
|
||||
let config_dir = dirs::config_dir().expect("Could not get configuration directory");
|
||||
let systemd_dir = config_dir.join("systemd");
|
||||
let user_dir = systemd_dir.join("user");
|
||||
|
||||
// Make sure the directory exists
|
||||
if !user_dir.exists() {
|
||||
create_dir_all(user_dir.clone()).expect("Could not create systemd user directory");
|
||||
}
|
||||
|
||||
let service_file = user_dir.join(LINUX_SERVICE_FILENAME);
|
||||
if !service_file.exists() {
|
||||
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");
|
||||
println!("Entry will point to: {}", espanso_path.to_str().unwrap_or_default());
|
||||
|
||||
let service_content = String::from(LINUX_SERVICE_CONTENT)
|
||||
.replace("{{{espanso_path}}}", espanso_path.to_str().unwrap_or_default());
|
||||
|
||||
std::fs::write(service_file.clone(), service_content).expect("Unable to write service file");
|
||||
|
||||
println!("Service file created correctly!")
|
||||
}
|
||||
|
||||
println!("Enabling espanso for systemd...");
|
||||
|
||||
let res = Command::new("systemctl")
|
||||
.args(&["--user", "enable", "espanso"])
|
||||
.status();
|
||||
|
||||
if let Ok(status) = res {
|
||||
if status.success() {
|
||||
println!("Service registered correctly!")
|
||||
}
|
||||
}else{
|
||||
println!("Error loading espanso service");
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
pub fn unregister(_config_set: ConfigSet) {
|
||||
println!("Linux does not support automatic system daemon integration.");
|
||||
pub fn unregister(config_set: ConfigSet) {
|
||||
use std::process::{Command, ExitStatus};
|
||||
|
||||
// Disable the service first
|
||||
let res = Command::new("systemctl")
|
||||
.args(&["--user", "disable", "espanso"])
|
||||
.status();
|
||||
|
||||
// Then delete the espanso.service entry
|
||||
let config_dir = dirs::config_dir().expect("Could not get configuration directory");
|
||||
let systemd_dir = config_dir.join("systemd");
|
||||
let user_dir = systemd_dir.join("user");
|
||||
let service_file = user_dir.join(LINUX_SERVICE_FILENAME);
|
||||
|
||||
if service_file.exists() {
|
||||
let res = std::fs::remove_file(&service_file);
|
||||
match res {
|
||||
Ok(_) => {
|
||||
println!("Deleted entry at {}", service_file.to_string_lossy());
|
||||
println!("Service unregistered successfully!");
|
||||
},
|
||||
Err(e) => {
|
||||
println!("Error, could not delete service entry at {} with error {}",
|
||||
service_file.to_string_lossy(), e);
|
||||
},
|
||||
}
|
||||
}else{
|
||||
eprintln!("Error, could not find espanso service file");
|
||||
}
|
||||
}
|
||||
|
||||
// WINDOWS
|
||||
|
|
Loading…
Reference in New Issue
Block a user