Merge pull request #219 from federico-terzi/dev

Version 0.5.4
This commit is contained in:
Federico Terzi 2020-04-03 20:47:28 +02:00 committed by GitHub
commit f8788060c0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
40 changed files with 599 additions and 338 deletions

73
Cargo.lock generated
View File

@ -137,10 +137,6 @@ dependencies = [
name = "cc"
version = "1.0.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"jobserver 0.1.17 (registry+https://github.com/rust-lang/crates.io-index)",
"num_cpus 1.10.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "cfg-if"
@ -370,7 +366,7 @@ dependencies = [
[[package]]
name = "espanso"
version = "0.5.3"
version = "0.5.4"
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)",
@ -379,7 +375,6 @@ dependencies = [
"dialoguer 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"dirs 2.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
"fs2 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)",
"git2 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
@ -497,20 +492,6 @@ dependencies = [
"wasi 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "git2"
version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
"libgit2-sys 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
"openssl-probe 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
"openssl-sys 0.9.49 (registry+https://github.com/rust-lang/crates.io-index)",
"url 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "h2"
version = "0.1.26"
@ -634,16 +615,6 @@ name = "itoa"
version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "jobserver"
version = "0.1.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"getrandom 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "kernel32-sys"
version = "0.2.2"
@ -663,43 +634,6 @@ name = "libc"
version = "0.2.62"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "libgit2-sys"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"cc 1.0.45 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
"libssh2-sys 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)",
"libz-sys 1.0.25 (registry+https://github.com/rust-lang/crates.io-index)",
"openssl-sys 0.9.49 (registry+https://github.com/rust-lang/crates.io-index)",
"pkg-config 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "libssh2-sys"
version = "0.2.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"cc 1.0.45 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
"libz-sys 1.0.25 (registry+https://github.com/rust-lang/crates.io-index)",
"openssl-sys 0.9.49 (registry+https://github.com/rust-lang/crates.io-index)",
"pkg-config 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)",
"vcpkg 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "libz-sys"
version = "1.0.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"cc 1.0.45 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
"pkg-config 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)",
"vcpkg 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "linked-hash-map"
version = "0.5.2"
@ -1839,7 +1773,6 @@ dependencies = [
"checksum futures 0.1.29 (registry+https://github.com/rust-lang/crates.io-index)" = "1b980f2816d6ee8673b6517b52cb0e808a180efc92e5c19d02cdda79066703ef"
"checksum futures-cpupool 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "ab90cde24b3319636588d0c35fe03b1333857621051837ed769faefb4c2162e4"
"checksum getrandom 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)" = "473a1265acc8ff1e808cd0a1af8cee3c2ee5200916058a2ca113c29f2d903571"
"checksum git2 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "39f27186fbb5ec67ece9a56990292bc5aed3c3fc51b9b07b0b52446b1dfb4a82"
"checksum h2 0.1.26 (registry+https://github.com/rust-lang/crates.io-index)" = "a5b34c246847f938a410a03c5458c7fee2274436675e76d8b903c08efc29c462"
"checksum http 0.1.18 (registry+https://github.com/rust-lang/crates.io-index)" = "372bcb56f939e449117fb0869c2e8fd8753a8223d92a172c6e808cf123a5b6e4"
"checksum http-body 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6741c859c1b2463a423a1dbce98d418e6c3c3fc720fb0d45528657320920292d"
@ -1851,13 +1784,9 @@ dependencies = [
"checksum indexmap 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a61202fbe46c4a951e9404a720a0180bcf3212c750d735cb5c4ba4dc551299f3"
"checksum iovec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "dbe6e417e7d0975db6512b90796e8ce223145ac4e33c377e4a42882a0e88bb08"
"checksum itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "501266b7edd0174f8530248f87f99c88fbe60ca4ef3dd486835b8d8d53136f7f"
"checksum jobserver 0.1.17 (registry+https://github.com/rust-lang/crates.io-index)" = "f2b1d42ef453b30b7387e113da1c83ab1605d90c5b4e0eb8e96d016ed3b8c160"
"checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d"
"checksum lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
"checksum libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)" = "34fcd2c08d2f832f376f4173a231990fa5aef4e99fb569867318a227ef4c06ba"
"checksum libgit2-sys 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a30f8637eb59616ee3b8a00f6adff781ee4ddd8343a615b8238de756060cc1b3"
"checksum libssh2-sys 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)" = "8914d10b159fc288f2b6f253c94bd0c15a777fd5a297691141d89674b87e66fd"
"checksum libz-sys 1.0.25 (registry+https://github.com/rust-lang/crates.io-index)" = "2eb5e43362e38e2bca2fd5f5134c4d4564a23a5c28e9b95411652021a8675ebe"
"checksum linked-hash-map 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "ae91b68aebc4ddb91978b11a1b02ddd8602a05ec19002801c5666000e05e0f83"
"checksum lock_api 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "62ebf1391f6acad60e5c8b43706dde4582df75c06698ab44511d15016bc2442c"
"checksum log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)" = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7"

View File

@ -1,6 +1,6 @@
[package]
name = "espanso"
version = "0.5.3"
version = "0.5.4"
authors = ["Federico Terzi <federicoterzi96@gmail.com>"]
license = "GPL-3.0"
description = "Cross-platform Text Expander written in Rust"
@ -26,7 +26,6 @@ chrono = "0.4.9"
lazy_static = "1.4.0"
walkdir = "2.2.9"
reqwest = "0.9.20"
git2 = {version = "0.10.1", features = ["https"]}
tempfile = "3.1.0"
dialoguer = "0.4.0"
rand = "0.7.2"

View File

@ -44,6 +44,7 @@ fn print_config() {
println!("cargo:rustc-link-lib=dylib=c++");
println!("cargo:rustc-link-lib=static=macbridge");
println!("cargo:rustc-link-lib=framework=Cocoa");
println!("cargo:rustc-link-lib=framework=IOKit");
}
fn main()

View File

@ -158,6 +158,15 @@ int32_t set_clipboard(char * text);
*/
int32_t set_clipboard_image(char * path);
/*
* If a process is currently holding SecureInput, then return 1 and set the pid pointer to the corresponding PID.
*/
int32_t get_secure_input_process(int64_t *pid);
/*
* Find the executable path corresponding to the given PID, return 0 if no process was found.
*/
int32_t get_path_from_pid(int64_t pid, char *buff, int buff_size);
};
#endif //ESPANSO_BRIDGE_H

View File

@ -20,9 +20,11 @@
#include "bridge.h"
#import <Foundation/Foundation.h>
#include <IOKit/IOKitLib.h>
#include "AppDelegate.h"
#include <stdio.h>
#include <string.h>
#include <libproc.h>
extern "C" {
}
@ -334,3 +336,47 @@ void open_settings_panel() {
[[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:urlString]];
}
// Taken (with a few modifications) from the MagicKeys project: https://github.com/zsszatmari/MagicKeys
int32_t get_secure_input_process(int64_t *pid) {
NSArray *consoleUsersArray;
io_service_t rootService;
int32_t result = 0;
if ((rootService = IORegistryGetRootEntry(kIOMasterPortDefault)) != 0)
{
if ((consoleUsersArray = (NSArray *)IORegistryEntryCreateCFProperty((io_registry_entry_t)rootService, CFSTR("IOConsoleUsers"), kCFAllocatorDefault, 0)) != nil)
{
if ([consoleUsersArray isKindOfClass:[NSArray class]]) // Be careful - ensure this really is an array
{
for (NSDictionary *consoleUserDict in consoleUsersArray) {
NSNumber *secureInputPID;
if ((secureInputPID = [consoleUserDict objectForKey:@"kCGSSessionSecureInputPID"]) != nil)
{
if ([secureInputPID isKindOfClass:[NSNumber class]])
{
*pid = ((UInt64) [secureInputPID intValue]);
result = 1;
break;
}
}
}
}
CFRelease((CFTypeRef)consoleUsersArray);
}
IOObjectRelease((io_object_t) rootService);
}
return result;
}
int32_t get_path_from_pid(int64_t pid, char *buff, int buff_size) {
int res = proc_pidpath((pid_t) pid, buff, buff_size);
if ( res <= 0 ) {
return 0;
} else {
return 1;
}
}

View File

@ -7,7 +7,6 @@ class Espanso < Formula
url "https://github.com/federico-terzi/espanso/releases/latest/download/espanso-mac.tar.gz"
sha256 "{{{release_hash}}}"
version "{{{app_version}}}"
depends_on "openssl@1.1"
def install
bin.install "espanso"

View File

@ -1,5 +1,5 @@
name: espanso
version: 0.5.3
version: 0.5.4
summary: A Cross-platform Text Expander written in Rust
description: |
espanso is a Cross-platform, Text Expander written in Rust.

View File

@ -39,6 +39,8 @@ extern {
pub fn open_settings_panel();
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_secure_input_process(pid:*mut i64) -> i32;
pub fn get_path_from_pid(pid:i64, buffer: *mut c_char, size: i32) -> i32;
// Clipboard
pub fn get_clipboard(buffer: *mut c_char, size: i32) -> i32;

View File

@ -17,11 +17,11 @@
* along with espanso. If not, see <https://www.gnu.org/licenses/>.
*/
// This functions are used to check if the required dependencies are satisfied
// This functions are used to check if the required dependencies and conditions are satisfied
// before starting espanso
#[cfg(target_os = "linux")]
pub fn check_dependencies() -> bool {
pub fn check_preconditions() -> bool {
use std::process::Command;
let mut result = true;
@ -48,13 +48,26 @@ pub fn check_dependencies() -> bool {
}
#[cfg(target_os = "macos")]
pub fn check_dependencies() -> bool {
// Nothing to do here
pub fn check_preconditions() -> bool {
// Make sure no app is currently using secure input.
let secure_input_app = crate::system::macos::MacSystemManager::get_secure_input_application();
if let Some((app_name, process)) = secure_input_app {
eprintln!("WARNING: An application is currently using SecureInput and might prevent espanso from working correctly.");
eprintln!();
eprintln!("APP: {}", app_name);
eprintln!("PROC: {}", process);
eprintln!();
eprintln!("Please close it or disable SecureInput for that application (most apps that use it have a");
eprintln!("setting to disable it).");
eprintln!("Until then, espanso might not work as expected.");
}
true
}
#[cfg(target_os = "windows")]
pub fn check_dependencies() -> bool {
pub fn check_preconditions() -> bool {
// Nothing needed on windows
true
}

View File

@ -19,7 +19,7 @@
use std::process::{Command, Stdio};
use std::io::{Write};
use log::{error, warn};
use log::{error};
use std::path::Path;
pub struct LinuxClipboardManager {}
@ -86,6 +86,10 @@ impl super::ClipboardManager for LinuxClipboardManager {
let res = Command::new("xclip")
.args(&["-selection", "clipboard", "-t", mime, "-i", &image_path])
.spawn();
if let Err(e) = res {
error!("Could not set image clipboard: {}", e);
}
}
}

View File

@ -64,6 +64,9 @@ fn default_enable_active() -> bool { true }
fn default_backspace_limit() -> i32 { 3 }
fn default_restore_clipboard_delay() -> i32 { 300 }
fn default_exclude_default_entries() -> bool {false}
fn default_secure_input_watcher_enabled() -> bool {true}
fn default_secure_input_notification() -> bool {true}
fn default_secure_input_watcher_interval() -> i32 {5000}
fn default_matches() -> Vec<Match> { Vec::new() }
fn default_global_vars() -> Vec<MatchVariable> { Vec::new() }
@ -138,6 +141,15 @@ pub struct Configs {
#[serde(default = "default_restore_clipboard_delay")]
pub restore_clipboard_delay: i32,
#[serde(default = "default_secure_input_watcher_enabled")]
pub secure_input_watcher_enabled: bool,
#[serde(default = "default_secure_input_watcher_interval")]
pub secure_input_watcher_interval: i32,
#[serde(default = "default_secure_input_notification")]
pub secure_input_notification: bool,
#[serde(default)]
pub backend: BackendType,
@ -190,6 +202,9 @@ impl Configs {
validate_field!(result, self.passive_arg_escape, default_passive_arg_escape());
validate_field!(result, self.passive_key, default_passive_key());
validate_field!(result, self.restore_clipboard_delay, default_restore_clipboard_delay());
validate_field!(result, self.secure_input_watcher_enabled, default_secure_input_watcher_enabled());
validate_field!(result, self.secure_input_watcher_interval, default_secure_input_watcher_interval());
validate_field!(result, self.secure_input_notification, default_secure_input_notification());
result
}
@ -412,12 +427,10 @@ impl ConfigSet {
// Check if some triggers are conflicting with each other
// For more information, see: https://github.com/federico-terzi/espanso/issues/135
if default.conflict_check {
for s in specific.iter() {
let has_conflicts = Self::has_conflicts(&default, &specific);
if has_conflicts {
eprintln!("Warning: some triggers had conflicts and may not behave as intended");
eprintln!("To turn off this check, add \"conflict_check: false\" in the configuration");
}
let has_conflicts = Self::has_conflicts(&default, &specific);
if has_conflicts {
eprintln!("Warning: some triggers had conflicts and may not behave as intended");
eprintln!("To turn off this check, add \"conflict_check: false\" in the configuration");
}
}
@ -568,8 +581,7 @@ mod tests {
use super::*;
use std::io::Write;
use tempfile::{NamedTempFile, TempDir};
use std::any::Any;
use crate::matcher::{TextContent, MatchContentType};
use crate::matcher::{MatchContentType};
const TEST_WORKING_CONFIG_FILE : &str = include_str!("../res/test/working_config.yml");
const TEST_CONFIG_FILE_WITH_BAD_YAML : &str = include_str!("../res/test/config_with_bad_yaml.yml");
@ -578,7 +590,7 @@ mod tests {
fn create_tmp_file(string: &str) -> NamedTempFile {
let file = NamedTempFile::new().unwrap();
file.as_file().write_all(string.as_bytes());
file.as_file().write_all(string.as_bytes()).unwrap();
file
}
@ -701,7 +713,7 @@ mod tests {
let package_dir = TempDir::new().expect("unable to create package directory");
let default_path = data_dir.path().join(DEFAULT_CONFIG_FILE_NAME);
fs::write(default_path, default_content);
fs::write(default_path, default_content).unwrap();
(data_dir, package_dir)
}
@ -709,7 +721,7 @@ mod tests {
pub fn create_temp_file_in_dir(tmp_dir: &PathBuf, name: &str, content: &str) -> PathBuf {
let user_defined_path = tmp_dir.join(name);
let user_defined_path_copy = user_defined_path.clone();
fs::write(user_defined_path, content);
fs::write(user_defined_path, content).unwrap();
user_defined_path_copy
}
@ -717,7 +729,7 @@ mod tests {
pub fn create_user_config_file(tmp_dir: &Path, name: &str, content: &str) -> PathBuf {
let user_config_dir = tmp_dir.join(USER_CONFIGS_FOLDER_NAME);
if !user_config_dir.exists() {
create_dir_all(&user_config_dir);
create_dir_all(&user_config_dir).unwrap();
}
create_temp_file_in_dir(&user_config_dir, name, content)
@ -726,7 +738,7 @@ mod tests {
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);
create_dir_all(&package_dir).unwrap();
}
create_temp_file_in_dir(&package_dir, filename, content)
@ -809,11 +821,11 @@ mod tests {
fn test_config_set_specific_file_duplicate_name() {
let (data_dir, package_dir) = create_temp_espanso_directories();
let user_defined_path = create_user_config_file(data_dir.path(), "specific.yml", r###"
create_user_config_file(data_dir.path(), "specific.yml", r###"
name: specific1
"###);
let user_defined_path2 = create_user_config_file(data_dir.path(), "specific2.yml", r###"
create_user_config_file(data_dir.path(), "specific2.yml", r###"
name: specific1
"###);
@ -832,7 +844,7 @@ mod tests {
replace: "Bob"
"###);
let user_defined_path = create_user_config_file(data_dir.path(), "specific1.yml", r###"
create_user_config_file(data_dir.path(), "specific1.yml", r###"
name: specific1
matches:
@ -859,7 +871,7 @@ mod tests {
replace: "Bob"
"###);
let user_defined_path2 = create_user_config_file(data_dir.path(), "specific2.yml", r###"
create_user_config_file(data_dir.path(), "specific2.yml", r###"
name: specific1
matches:
@ -891,7 +903,7 @@ mod tests {
replace: "Bob"
"###);
let user_defined_path2 = create_user_config_file(data_dir.path(), "specific2.yml", r###"
create_user_config_file(data_dir.path(), "specific2.yml", r###"
name: specific1
exclude_default_entries: true
@ -926,7 +938,7 @@ mod tests {
"###
);
let user_defined_path2 = create_user_config_file(data_dir.path(), "specific.zzz", r###"
create_user_config_file(data_dir.path(), "specific.zzz", r###"
name: specific1
exclude_default_entries: true
@ -944,11 +956,11 @@ mod tests {
fn test_config_set_no_parent_configs_works_correctly() {
let (data_dir, package_dir) = create_temp_espanso_directories();
let user_defined_path = create_user_config_file(data_dir.path(), "specific.yml", r###"
create_user_config_file(data_dir.path(), "specific.yml", r###"
name: specific1
"###);
let user_defined_path2 = create_user_config_file(data_dir.path(), "specific2.yml", r###"
create_user_config_file(data_dir.path(), "specific2.yml", r###"
name: specific2
"###);
@ -964,7 +976,7 @@ mod tests {
replace: Hasta la vista
"###);
let user_defined_path = create_user_config_file(data_dir.path(), "specific.yml", r###"
create_user_config_file(data_dir.path(), "specific.yml", r###"
parent: default
matches:
@ -987,7 +999,7 @@ mod tests {
replace: Hasta la vista
"###);
let user_defined_path = create_user_config_file(data_dir.path(), "specific.yml", r###"
create_user_config_file(data_dir.path(), "specific.yml", r###"
matches:
- trigger: "hello"
replace: "world"
@ -1009,7 +1021,7 @@ mod tests {
replace: Hasta la vista
"###);
let user_defined_path = create_user_config_file(data_dir.path(), "specific.yml", r###"
create_user_config_file(data_dir.path(), "specific.yml", r###"
name: custom1
parent: default
@ -1018,7 +1030,7 @@ mod tests {
replace: "world"
"###);
let user_defined_path2 = create_user_config_file(data_dir.path(), "specific2.yml", r###"
create_user_config_file(data_dir.path(), "specific2.yml", r###"
parent: custom1
matches:
@ -1042,7 +1054,7 @@ mod tests {
replace: Hasta la vista
"###);
let user_defined_path = create_user_config_file(data_dir.path(), "specific.yml", r###"
create_user_config_file(data_dir.path(), "specific.yml", r###"
parent: default
matches:
@ -1070,7 +1082,7 @@ mod tests {
replace: Hasta la vista
"###);
let package_path = create_package_file(package_dir.path(), "package1", "package.yml", r###"
create_package_file(package_dir.path(), "package1", "package.yml", r###"
parent: default
matches:
@ -1093,7 +1105,7 @@ mod tests {
replace: Hasta la vista
"###);
let package_path = create_package_file(package_dir.path(), "package1", "package.yml", r###"
create_package_file(package_dir.path(), "package1", "package.yml", r###"
matches:
- trigger: "harry"
replace: "potter"
@ -1114,7 +1126,7 @@ mod tests {
replace: Hasta la vista
"###);
let package_path = create_package_file(package_dir.path(), "package1", "package.yml", r###"
create_package_file(package_dir.path(), "package1", "package.yml", r###"
name: package1
matches:
@ -1122,7 +1134,7 @@ mod tests {
replace: "potter"
"###);
let package_path2 = create_package_file(package_dir.path(), "package1", "addon.yml", r###"
create_package_file(package_dir.path(), "package1", "addon.yml", r###"
parent: package1
matches:
@ -1160,7 +1172,7 @@ mod tests {
replace: Jon
"###);
let user_defined_path = create_user_config_file(data_dir.path(), "specific.yml", r###"
create_user_config_file(data_dir.path(), "specific.yml", r###"
name: specific1
matches:
@ -1184,7 +1196,7 @@ mod tests {
replace: Error
"###);
let user_defined_path = create_user_config_file(data_dir.path(), "specific.yml", r###"
create_user_config_file(data_dir.path(), "specific.yml", r###"
name: specific1
matches:
@ -1206,7 +1218,7 @@ mod tests {
replace: Jon
"###);
let user_defined_path = create_user_config_file(data_dir.path(), "specific.yml", r###"
create_user_config_file(data_dir.path(), "specific.yml", r###"
name: specific1
matches:
@ -1228,14 +1240,14 @@ mod tests {
replace: Jon
"###);
let user_defined_path = create_user_config_file(data_dir.path(), "specific.yml", r###"
create_user_config_file(data_dir.path(), "specific.yml", r###"
name: specific1
matches:
- trigger: "bad"
replace: "Conflict"
"###);
let user_defined_path2 = create_user_config_file(data_dir.path(), "specific2.yml", r###"
create_user_config_file(data_dir.path(), "specific2.yml", r###"
name: specific2
matches:
@ -1257,7 +1269,7 @@ mod tests {
format: "%m"
"###);
let user_defined_path = create_user_config_file(data_dir.path(), "specific.yml", r###"
create_user_config_file(data_dir.path(), "specific.yml", r###"
global_vars:
- name: specificvar
type: date
@ -1283,7 +1295,7 @@ mod tests {
format: "%m"
"###);
let user_defined_path = create_user_config_file(data_dir.path(), "specific.yml", r###"
create_user_config_file(data_dir.path(), "specific.yml", r###"
parent: default
global_vars:
- name: specificvar
@ -1309,7 +1321,7 @@ mod tests {
format: "%m"
"###);
let user_defined_path = create_user_config_file(data_dir.path(), "specific.yml", r###"
create_user_config_file(data_dir.path(), "specific.yml", r###"
exclude_default_entries: true
global_vars:

View File

@ -204,12 +204,8 @@ impl <'a, S: SystemManager> super::ConfigManager<'a> for RuntimeConfigManager<'a
#[cfg(test)]
mod tests {
use super::*;
use tempfile::{NamedTempFile, TempDir};
use crate::config::{DEFAULT_CONFIG_FILE_NAME, DEFAULT_CONFIG_FILE_CONTENT};
use std::fs;
use std::path::PathBuf;
use crate::config::ConfigManager;
use crate::config::tests::{create_temp_espanso_directories, create_temp_file_in_dir, create_user_config_file};
use crate::config::tests::{create_temp_espanso_directories, create_user_config_file};
struct DummySystemManager {
title: RefCell<String>,
@ -251,18 +247,18 @@ mod tests {
fn test_runtime_constructor_regex_load_correctly() {
let (data_dir, package_dir) = create_temp_espanso_directories();
let specific_path = create_user_config_file(&data_dir.path(), "specific.yml", r###"
create_user_config_file(&data_dir.path(), "specific.yml", r###"
name: myname1
filter_exec: "Title"
"###);
let specific_path2 = create_user_config_file(&data_dir.path(), "specific2.yml", r###"
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(&data_dir.path(), "specific3.yml", r###"
create_user_config_file(&data_dir.path(), "specific3.yml", r###"
name: myname3
filter_title: "Nice"
"###);
@ -302,18 +298,18 @@ mod tests {
fn test_runtime_constructor_malformed_regexes_are_ignored() {
let (data_dir, package_dir) = create_temp_espanso_directories();
let specific_path = create_user_config_file(&data_dir.path(), "specific.yml", r###"
create_user_config_file(&data_dir.path(), "specific.yml", r###"
name: myname1
filter_exec: "[`-_]"
"###);
let specific_path2 = create_user_config_file(&data_dir.path(), "specific2.yml", r###"
create_user_config_file(&data_dir.path(), "specific2.yml", r###"
name: myname2
filter_title: "[`-_]"
filter_class: "Car"
"###);
let specific_path3 = create_user_config_file(&data_dir.path(), "specific3.yml", r###"
create_user_config_file(&data_dir.path(), "specific3.yml", r###"
name: myname3
filter_title: "Nice"
"###);
@ -353,7 +349,7 @@ mod tests {
fn test_runtime_calculate_active_config_specific_title_match() {
let (data_dir, package_dir) = create_temp_espanso_directories();
let specific_path = create_user_config_file(&data_dir.path(), "specific.yml", r###"
create_user_config_file(&data_dir.path(), "specific.yml", r###"
name: chrome
filter_title: "Chrome"
"###);
@ -368,10 +364,11 @@ mod tests {
assert_eq!(config_manager.calculate_active_config().name, "chrome");
}
#[test]
fn test_runtime_calculate_active_config_specific_class_match() {
let (data_dir, package_dir) = create_temp_espanso_directories();
let specific_path = create_user_config_file(&data_dir.path(), "specific.yml", r###"
create_user_config_file(&data_dir.path(), "specific.yml", r###"
name: chrome
filter_class: "Chrome"
"###);
@ -386,10 +383,11 @@ mod tests {
assert_eq!(config_manager.calculate_active_config().name, "chrome");
}
#[test]
fn test_runtime_calculate_active_config_specific_exec_match() {
let (data_dir, package_dir) = create_temp_espanso_directories();
let specific_path = create_user_config_file(&data_dir.path(), "specific.yml", r###"
create_user_config_file(&data_dir.path(), "specific.yml", r###"
name: chrome
filter_exec: "chrome.exe"
"###);
@ -404,10 +402,11 @@ mod tests {
assert_eq!(config_manager.calculate_active_config().name, "chrome");
}
#[test]
fn test_runtime_calculate_active_config_specific_multi_filter_match() {
let (data_dir, package_dir) = create_temp_espanso_directories();
let specific_path = create_user_config_file(&data_dir.path(), "specific.yml", r###"
create_user_config_file(&data_dir.path(), "specific.yml", r###"
name: chrome
filter_class: Browser
filter_exec: "firefox.exe"
@ -427,7 +426,7 @@ mod tests {
fn test_runtime_calculate_active_config_no_match() {
let (data_dir, package_dir) = create_temp_espanso_directories();
let specific_path = create_user_config_file(&data_dir.path(), "specific.yml", r###"
create_user_config_file(&data_dir.path(), "specific.yml", r###"
name: firefox
filter_title: "Firefox"
"###);
@ -446,7 +445,7 @@ mod tests {
fn test_runtime_active_config_cache() {
let (data_dir, package_dir) = create_temp_espanso_directories();
let specific_path = create_user_config_file(&data_dir.path(), "specific.yml", r###"
create_user_config_file(&data_dir.path(), "specific.yml", r###"
name: firefox
filter_title: "Firefox"
"###);

View File

@ -23,12 +23,12 @@ use crate::event::*;
use crate::event::KeyModifier::*;
use crate::bridge::linux::*;
use std::process::exit;
use log::{debug, error, info};
use log::{debug, error};
use std::ffi::CStr;
use std::{thread, time};
use std::sync::atomic::AtomicBool;
use std::sync::Arc;
use std::sync::atomic::Ordering::Acquire;
use crate::config::Configs;
#[repr(C)]
pub struct LinuxContext {
@ -37,7 +37,7 @@ pub struct LinuxContext {
}
impl LinuxContext {
pub fn new(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
let x11_available = unsafe {
check_x11()
@ -85,7 +85,7 @@ impl Drop for LinuxContext {
// Native bridge code
extern fn keypress_callback(_self: *mut c_void, raw_buffer: *const u8, len: i32,
extern fn keypress_callback(_self: *mut c_void, raw_buffer: *const u8, _len: i32,
event_type: i32, key_code: i32) {
unsafe {
let _self = _self as *mut LinuxContext;
@ -100,7 +100,7 @@ extern fn keypress_callback(_self: *mut c_void, raw_buffer: *const u8, len: i32,
if event_type == 0 { // Char event
// 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();
// Send the char through the channel

View File

@ -20,25 +20,30 @@
use std::sync::mpsc::Sender;
use std::os::raw::{c_void, c_char};
use crate::bridge::macos::*;
use crate::event::{Event, KeyEvent, KeyModifier, ActionType};
use crate::event::{Event, KeyEvent, KeyModifier, ActionType, SystemEvent};
use crate::event::KeyModifier::*;
use std::ffi::{CString, CStr};
use std::fs;
use std::{fs, thread};
use log::{info, error, debug};
use std::process::exit;
use std::sync::atomic::AtomicBool;
use std::sync::Arc;
use std::sync::atomic::Ordering::Acquire;
use crate::config::Configs;
use std::cell::RefCell;
use crate::system::macos::MacSystemManager;
const STATUS_ICON_BINARY : &[u8] = include_bytes!("../res/mac/icon.png");
pub struct MacContext {
pub send_channel: Sender<Event>,
is_injecting: Arc<AtomicBool>,
secure_input_watcher_enabled: bool,
secure_input_watcher_interval: i32,
}
impl MacContext {
pub fn new(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
unsafe {
let res = prompt_accessibility();
@ -53,7 +58,9 @@ impl MacContext {
let context = Box::new(MacContext {
send_channel,
is_injecting
is_injecting,
secure_input_watcher_enabled: config.secure_input_watcher_enabled,
secure_input_watcher_interval: config.secure_input_watcher_interval,
});
// Initialize the status icon path
@ -81,10 +88,59 @@ impl MacContext {
context
}
fn start_secure_input_watcher(&self) {
let send_channel = self.send_channel.clone();
let secure_input_watcher_interval = self.secure_input_watcher_interval as u64;
let secure_input_watcher = thread::Builder::new().name("secure_input_watcher".to_string()).spawn(move || {
let mut last_secure_input_pid: Option<i64> = None;
loop {
let pid = MacSystemManager::get_secure_input_pid();
if let Some(pid) = pid { // Some application is currently on SecureInput
let should_notify = if let Some(old_pid) = last_secure_input_pid { // We already detected a SecureInput app
if old_pid != pid { // The old app is different from the current one, we should take action
true
}else{ // We already notified this application before
false
}
}else{ // First time we see this SecureInput app, we should take action
true
};
if should_notify {
let secure_input_app = crate::system::macos::MacSystemManager::get_secure_input_application();
if let Some((app_name, path)) = secure_input_app {
let event = Event::System(SystemEvent::SecureInputEnabled(app_name, path));
send_channel.send(event);
}
}
last_secure_input_pid = Some(pid);
}else{ // No app is currently keeping SecureInput
if let Some(old_pid) = last_secure_input_pid { // If there was an app with SecureInput, notify that is now free
let event = Event::System(SystemEvent::SecureInputDisabled);
send_channel.send(event);
}
last_secure_input_pid = None
}
thread::sleep(std::time::Duration::from_millis(secure_input_watcher_interval));
}
});
}
}
impl super::Context for MacContext {
fn eventloop(&self) {
// Start the SecureInput watcher thread
if self.secure_input_watcher_enabled {
self.start_secure_input_watcher();
}
unsafe {
eventloop();
}

View File

@ -32,6 +32,7 @@ use std::path::PathBuf;
use std::fs::create_dir_all;
use std::sync::{Once, Arc};
use std::sync::atomic::AtomicBool;
use crate::config::Configs;
pub trait Context {
fn eventloop(&self);
@ -39,20 +40,20 @@ pub trait Context {
// MAC IMPLEMENTATION
#[cfg(target_os = "macos")]
pub fn new(send_channel: Sender<Event>, is_injecting: Arc<AtomicBool>) -> Box<dyn Context> {
macos::MacContext::new(send_channel, is_injecting)
pub fn new(config: Configs, send_channel: Sender<Event>, is_injecting: Arc<AtomicBool>) -> Box<dyn Context> {
macos::MacContext::new(config, send_channel, is_injecting)
}
// LINUX IMPLEMENTATION
#[cfg(target_os = "linux")]
pub fn new(send_channel: Sender<Event>, is_injecting: Arc<AtomicBool>) -> Box<dyn Context> {
linux::LinuxContext::new(send_channel, is_injecting)
pub fn new(config: Configs, send_channel: Sender<Event>, is_injecting: Arc<AtomicBool>) -> Box<dyn Context> {
linux::LinuxContext::new(config, send_channel, is_injecting)
}
// WINDOWS IMPLEMENTATION
#[cfg(target_os = "windows")]
pub fn new(send_channel: Sender<Event>, is_injecting: Arc<AtomicBool>) -> Box<dyn Context> {
windows::WindowsContext::new(send_channel, is_injecting)
pub fn new(config: Configs, send_channel: Sender<Event>, is_injecting: Arc<AtomicBool>) -> Box<dyn Context> {
windows::WindowsContext::new(config, send_channel, is_injecting)
}
// espanso directories

View File

@ -28,6 +28,7 @@ use log::{info, error, debug};
use std::sync::atomic::AtomicBool;
use std::sync::Arc;
use std::sync::atomic::Ordering::Acquire;
use crate::config::Configs;
const BMP_BINARY : &[u8] = include_bytes!("../res/win/espanso.bmp");
const ICO_BINARY : &[u8] = include_bytes!("../res/win/espanso.ico");
@ -38,7 +39,7 @@ pub struct WindowsContext {
}
impl WindowsContext {
pub fn new(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
let espanso_dir = super::get_data_dir();

View File

@ -17,25 +17,21 @@
* along with espanso. If not, see <https://www.gnu.org/licenses/>.
*/
use crate::matcher::{Match, MatchReceiver, MatchContentType};
use crate::matcher::{Match, MatchReceiver};
use crate::keyboard::KeyboardManager;
use crate::config::ConfigManager;
use crate::config::BackendType;
use crate::clipboard::ClipboardManager;
use log::{info, warn, debug, error};
use crate::ui::{UIManager, MenuItem, MenuItemType};
use crate::event::{ActionEventReceiver, ActionType};
use crate::extension::Extension;
use crate::event::{ActionEventReceiver, ActionType, SystemEventReceiver, SystemEvent};
use crate::render::{Renderer, RenderResult};
use std::cell::RefCell;
use std::process::exit;
use std::collections::HashMap;
use std::path::PathBuf;
use regex::{Regex, Captures};
use std::time::SystemTime;
use regex::{Regex};
use std::sync::Arc;
use std::sync::atomic::AtomicBool;
use std::sync::atomic::Ordering::{Relaxed, Release, Acquire, AcqRel, SeqCst};
use std::sync::atomic::Ordering::Release;
pub struct Engine<'a, S: KeyboardManager, C: ClipboardManager, M: ConfigManager<'a>,
U: UIManager, R: Renderer> {
@ -47,7 +43,6 @@ pub struct Engine<'a, S: KeyboardManager, C: ClipboardManager, M: ConfigManager<
is_injecting: Arc<AtomicBool>,
enabled: RefCell<bool>,
last_action_time: RefCell<SystemTime>, // Used to block espanso from re-interpreting it's own inputs
}
impl <'a, S: KeyboardManager, C: ClipboardManager, M: ConfigManager<'a>, U: UIManager, R: Renderer>
@ -56,7 +51,6 @@ impl <'a, S: KeyboardManager, C: ClipboardManager, M: ConfigManager<'a>, U: UIMa
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 last_action_time = RefCell::new(SystemTime::now());
Engine{keyboard_manager,
clipboard_manager,
@ -65,7 +59,6 @@ impl <'a, S: KeyboardManager, C: ClipboardManager, M: ConfigManager<'a>, U: UIMa
renderer,
is_injecting,
enabled,
last_action_time,
}
}
@ -92,7 +85,7 @@ impl <'a, S: KeyboardManager, C: ClipboardManager, M: ConfigManager<'a>, U: UIMa
menu.push(MenuItem{
item_type: MenuItemType::Button,
item_name: "Exit".to_owned(),
item_name: "Exit espanso".to_owned(),
item_id: ActionType::Exit as i32,
});
@ -111,20 +104,6 @@ impl <'a, S: KeyboardManager, C: ClipboardManager, M: ConfigManager<'a>, U: UIMa
None
}
}
/// Used to check if the last action has been executed within a specified interval.
/// If so, return true (blocking the action), otherwise false.
fn check_last_action_and_set(&self, interval: u128) -> bool {
let mut last_action_time = self.last_action_time.borrow_mut();
if let Ok(elapsed) = last_action_time.elapsed() {
if elapsed.as_millis() < interval {
return true;
}
}
(*last_action_time) = SystemTime::now();
return false;
}
}
lazy_static! {
@ -189,7 +168,9 @@ impl <'a, S: KeyboardManager, C: ClipboardManager, M: ConfigManager<'a>, U: UIMa
None
};
let backend = if config.backend == BackendType::Auto {
let backend = if m.force_clipboard {
&BackendType::Clipboard
}else if config.backend == BackendType::Auto {
if cfg!(target_os = "linux") {
let all_ascii = target_string.chars().all(|c| c.is_ascii());
if all_ascii {
@ -286,7 +267,16 @@ impl <'a, S: KeyboardManager, C: ClipboardManager, M: ConfigManager<'a>, U: UIMa
return;
}
info!("Passive mode activated");
// Block espanso from reinterpreting its own actions
self.is_injecting.store(true, Release);
// In order to avoid pasting previous clipboard contents, we need to check if
// a new clipboard was effectively copied.
// See issue: https://github.com/federico-terzi/espanso/issues/213
let previous_clipboard = self.clipboard_manager.get_clipboard();
// Sleep for a while, giving time to effectively copy the text
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
self.keyboard_manager.trigger_copy();
@ -298,22 +288,39 @@ impl <'a, S: KeyboardManager, C: ClipboardManager, M: ConfigManager<'a>, U: UIMa
let clipboard = self.clipboard_manager.get_clipboard();
if let Some(clipboard) = clipboard {
let rendered = self.renderer.render_passive(&clipboard,
&config);
// Don't expand empty clipboards, as usually they are the result of an empty passive selection
if clipboard.trim().is_empty() {
info!("Avoiding passive expansion, as the user didn't select anything");
}else{
if let Some(previous_content) = previous_clipboard {
// Because of issue #213, we need to make sure the user selected something.
if clipboard == previous_content {
info!("Avoiding passive expansion, as the user didn't select anything");
} else {
info!("Passive mode activated");
match rendered {
RenderResult::Text(payload) => {
// Paste back the result in the field
self.clipboard_manager.set_clipboard(&payload);
let rendered = self.renderer.render_passive(&clipboard,
&config);
std::thread::sleep(std::time::Duration::from_millis(100)); // TODO: avoid hardcoding
self.keyboard_manager.trigger_paste(&config.paste_shortcut);
},
_ => {
warn!("Cannot expand passive match")
},
match rendered {
RenderResult::Text(payload) => {
// Paste back the result in the field
self.clipboard_manager.set_clipboard(&payload);
std::thread::sleep(std::time::Duration::from_millis(100)); // TODO: avoid hardcoding
self.keyboard_manager.trigger_paste(&config.paste_shortcut);
},
_ => {
warn!("Cannot expand passive match")
},
}
}
}
}
}
// Re-allow espanso to interpret actions
self.is_injecting.store(false, Release);
}
}
@ -333,4 +340,24 @@ impl <'a, S: KeyboardManager, C: ClipboardManager,
_ => {}
}
}
}
impl <'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) {
match e {
// MacOS specific
SystemEvent::SecureInputEnabled(app_name, path) => {
info!("SecureInput has been acquired by {}, preventing espanso from working correctly. Full path: {}", app_name, path);
if self.config_manager.default_config().secure_input_notification {
self.ui_manager.notify_delay(&format!("{} has activated SecureInput. Espanso won't work until you disable it.", app_name), 5000);
}
},
SystemEvent::SecureInputDisabled => {
info!("SecureInput has been disabled.");
},
}
}
}

View File

@ -17,7 +17,7 @@
* along with espanso. If not, see <https://www.gnu.org/licenses/>.
*/
use crate::event::{KeyEventReceiver, ActionEventReceiver, Event};
use crate::event::{KeyEventReceiver, ActionEventReceiver, Event, SystemEventReceiver};
use std::sync::mpsc::Receiver;
pub trait EventManager {
@ -28,15 +28,18 @@ pub struct DefaultEventManager<'a> {
receive_channel: Receiver<Event>,
key_receivers: Vec<&'a dyn KeyEventReceiver>,
action_receivers: Vec<&'a dyn ActionEventReceiver>,
system_receivers: Vec<&'a dyn SystemEventReceiver>,
}
impl<'a> DefaultEventManager<'a> {
pub fn new(receive_channel: Receiver<Event>, key_receivers: Vec<&'a dyn KeyEventReceiver>,
action_receivers: Vec<&'a dyn ActionEventReceiver>) -> DefaultEventManager<'a> {
action_receivers: Vec<&'a dyn ActionEventReceiver>,
system_receivers: Vec<&'a dyn SystemEventReceiver>) -> DefaultEventManager<'a> {
DefaultEventManager {
receive_channel,
key_receivers,
action_receivers,
system_receivers
}
}
}
@ -53,6 +56,9 @@ impl <'a> EventManager for DefaultEventManager<'a> {
Event::Action(action_event) => {
self.action_receivers.iter().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),

View File

@ -21,10 +21,12 @@ pub(crate) mod manager;
use serde::{Serialize, Deserialize};
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub enum Event {
Action(ActionType),
Key(KeyEvent)
Key(KeyEvent),
System(SystemEvent),
}
#[derive(Debug, Clone)]
@ -57,7 +59,7 @@ pub enum KeyEvent {
Other
}
#[warn(non_camel_case_types)]
#[allow(non_camel_case_types)]
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum KeyModifier {
CTRL,
@ -132,6 +134,14 @@ impl KeyModifier {
}
}
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub enum SystemEvent {
// MacOS specific
SecureInputEnabled(String, String), // AppName, App Path
SecureInputDisabled,
}
// Receivers
pub trait KeyEventReceiver {
@ -142,6 +152,10 @@ pub trait ActionEventReceiver {
fn on_action_event(&self, e: ActionType);
}
pub trait SystemEventReceiver {
fn on_system_event(&self, e: SystemEvent);
}
// TESTS
#[cfg(test)]

View File

@ -17,7 +17,7 @@
* along with espanso. If not, see <https://www.gnu.org/licenses/>.
*/
use serde_yaml::{Mapping, Value};
use serde_yaml::{Mapping};
use crate::clipboard::ClipboardManager;
pub struct ClipboardExtension {
@ -37,7 +37,7 @@ impl super::Extension for ClipboardExtension {
String::from("clipboard")
}
fn calculate(&self, params: &Mapping, _: &Vec<String>) -> Option<String> {
fn calculate(&self, _: &Mapping, _: &Vec<String>) -> Option<String> {
self.clipboard_manager.get_clipboard()
}
}

View File

@ -17,7 +17,7 @@
* along with espanso. If not, see <https://www.gnu.org/licenses/>.
*/
use serde::{Serialize, Deserialize, Deserializer};
use serde::{Serialize, Deserialize};
#[cfg(target_os = "windows")]
mod windows;

View File

@ -20,33 +20,32 @@
#[macro_use]
extern crate lazy_static;
use std::thread;
use std::fs::{File, OpenOptions};
use std::io::{BufRead, BufReader};
use std::process::exit;
use std::sync::{mpsc, Arc};
use std::sync::{Arc, mpsc};
use std::sync::atomic::AtomicBool;
use std::sync::mpsc::Receiver;
use std::thread;
use std::time::Duration;
use clap::{App, Arg, SubCommand, ArgMatches};
use clap::{App, Arg, ArgMatches, SubCommand};
use fs2::FileExt;
use log::{info, warn, LevelFilter};
use log::{info, LevelFilter, warn};
use simplelog::{CombinedLogger, SharedLogger, TerminalMode, TermLogger, WriteLogger};
use crate::config::{ConfigSet, ConfigManager};
use crate::config::{ConfigManager, ConfigSet};
use crate::config::runtime::RuntimeConfigManager;
use crate::engine::Engine;
use crate::event::*;
use crate::event::manager::{DefaultEventManager, EventManager};
use crate::matcher::scrolling::ScrollingMatcher;
use crate::package::{InstallResult, PackageManager, RemoveResult, UpdateResult};
use crate::package::default::DefaultPackageManager;
use crate::package::zip::ZipPackageResolver;
use crate::protocol::*;
use crate::system::SystemManager;
use crate::ui::UIManager;
use crate::protocol::*;
use std::io::{BufReader, BufRead};
use crate::package::default::DefaultPackageManager;
use crate::package::{PackageManager, InstallResult, UpdateResult, RemoveResult, PackageResolver};
use std::sync::atomic::AtomicBool;
use crate::package::git::GitPackageResolver;
use crate::package::zip::ZipPackageResolver;
mod ui;
mod edit;
@ -73,12 +72,12 @@ const LOG_FILE: &str = "espanso.log";
fn main() {
let install_subcommand = SubCommand::with_name("install")
.about("Install a package. Equivalent to 'espanso package install'")
.arg(Arg::with_name("no-git")
.short("g")
.long("no-git")
.arg(Arg::with_name("external")
.short("e")
.long("external")
.required(false)
.takes_value(false)
.help("Install packages avoiding the GIT package provider. Try this flag if the default mode is not working."))
.help("Allow installing packages from non-verified repositories."))
.arg(Arg::with_name("package_name")
.help("Package name"));
@ -332,7 +331,7 @@ fn daemon_main(config_set: ConfigSet) {
// we could reinterpret the characters we are injecting
let is_injecting = Arc::new(std::sync::atomic::AtomicBool::new(false));
let context = context::new(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();
thread::Builder::new().name("daemon_background".to_string()).spawn(move || {
@ -376,6 +375,7 @@ fn daemon_background(receive_channel: Receiver<Event>, config_set: ConfigSet, is
receive_channel,
vec!(&matcher),
vec!(&engine, &matcher),
vec!(&engine),
);
info!("espanso is running!");
@ -445,7 +445,7 @@ fn start_daemon(config_set: ConfigSet) {
// If Systemd is not available in the system, espanso should default to unmanaged mode
// See issue https://github.com/federico-terzi/espanso/issues/139
let force_unmanaged = if let Err(status) = status {
let force_unmanaged = if let Err(_) = status {
true
} else {
false
@ -759,11 +759,13 @@ fn install_main(_config_set: ConfigSet, matches: &ArgMatches) {
exit(1);
});
let package_resolver: Box<dyn PackageResolver> = if matches.is_present("no-git") {
println!("Using alternative package provider");
Box::new(ZipPackageResolver::new())
let package_resolver= Box::new(ZipPackageResolver::new());
let allow_external: bool = if matches.is_present("external") {
println!("Allowing external repositories");
true
}else{
Box::new(GitPackageResolver::new())
false
};
let mut package_manager = DefaultPackageManager::new_default(Some(package_resolver));
@ -792,7 +794,7 @@ fn install_main(_config_set: ConfigSet, matches: &ArgMatches) {
println!("Using cached package index, run 'espanso package refresh' to update it.")
}
let res = package_manager.install_package(package_name);
let res = package_manager.install_package(package_name, allow_external);
match res {
Ok(install_result) => {
@ -812,6 +814,22 @@ fn install_main(_config_set: ConfigSet, matches: &ArgMatches) {
InstallResult::AlreadyInstalled => {
eprintln!("{} already installed!", package_name);
},
InstallResult::BlockedExternalPackage(repo_url) => {
eprintln!("Warning: the requested package is hosted on an external repository:");
eprintln!();
eprintln!("{}", repo_url);
eprintln!();
eprintln!("and its contents may not have been verified by espanso.");
eprintln!();
eprintln!("For your security, espanso blocks packages that are not verified.");
eprintln!("If you want to install the package anyway, you can force espanso");
eprintln!("to install it with the following command, but please do it only");
eprintln!("if you trust the source or you verified the contents of the package");
eprintln!("by checking out the repository listed above.");
eprintln!();
eprintln!("espanso install {} --external", package_name);
eprintln!();
}
InstallResult::Installed => {
println!("{} successfully installed!", package_name);
println!();
@ -975,7 +993,7 @@ fn edit_main(matches: &ArgMatches) {
if should_reload {
// Load the configuration
let mut config_set = ConfigSet::load_default().unwrap_or_else(|e| {
let config_set = ConfigSet::load_default().unwrap_or_else(|e| {
eprintln!("{}", e);
eprintln!("Unable to reload espanso due to previous configuration error.");
exit(1);
@ -1008,9 +1026,9 @@ fn release_lock(lock_file: File) {
lock_file.unlock().unwrap()
}
/// Used to make sure all the required dependencies are present before starting espanso.
/// Used to make sure all the required dependencies and conditions are satisfied before starting espanso.
fn precheck_guard() {
let satisfied = check::check_dependencies();
let satisfied = check::check_preconditions();
if !satisfied {
println!();
println!("Pre-check was not successful, espanso could not be started.");

View File

@ -34,6 +34,7 @@ pub struct Match {
pub word: bool,
pub passive_only: bool,
pub propagate_case: bool,
pub force_clipboard: bool,
// Automatically calculated from the triggers, used by the matcher to check for correspondences.
#[serde(skip_serializing)]
@ -89,7 +90,7 @@ impl<'a> From<&'a AutoMatch> for Match{
if other.propagate_case {
// List with first letter capitalized
let first_capitalized : Vec<String> = triggers.iter().map(|trigger| {
let mut capitalized = trigger.clone();
let capitalized = trigger.clone();
let mut v: Vec<char> = capitalized.chars().collect();
v[0] = v[0].to_uppercase().nth(0).unwrap();
v.into_iter().collect()
@ -170,6 +171,7 @@ impl<'a> From<&'a AutoMatch> for Match{
passive_only: other.passive_only,
_trigger_sequences: trigger_sequences,
propagate_case: other.propagate_case,
force_clipboard: other.force_clipboard,
}
}
}
@ -200,6 +202,9 @@ struct AutoMatch {
#[serde(default = "default_propagate_case")]
pub propagate_case: bool,
#[serde(default = "default_force_clipboard")]
pub force_clipboard: bool,
}
fn default_trigger() -> String {"".to_owned()}
@ -210,6 +215,7 @@ 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)]
pub struct MatchVariable {

View File

@ -18,7 +18,7 @@
*/
use crate::matcher::{Match, MatchReceiver, TriggerEntry};
use std::cell::{RefCell, Ref};
use std::cell::{RefCell};
use crate::event::{KeyModifier, ActionEventReceiver, ActionType};
use crate::config::ConfigManager;
use crate::event::KeyModifier::BACKSPACE;

View File

@ -24,14 +24,11 @@ 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};
use crate::package::InstallResult::{NotFoundInIndex, AlreadyInstalled, BlockedExternalPackage};
use std::fs;
use tempfile::TempDir;
use git2::Repository;
use regex::Regex;
use crate::package::RemoveResult::Removed;
use std::collections::HashMap;
use super::git::GitPackageResolver;
const DEFAULT_PACKAGE_INDEX_FILE : &str = "package_index.json";
@ -138,13 +135,31 @@ impl DefaultPackageManager {
return None
}
let original_repo = if fields.contains_key("package_original_repo") {
fields.get("package_original_repo").unwrap().clone()
}else{
fields.get("package_repo").unwrap().clone()
};
let is_core = if fields.contains_key("is_core") {
match fields.get("is_core").unwrap().clone().as_ref() {
"true" => true,
"false" => false,
_ => false,
}
}else{
false
};
let package = Package {
name: fields.get("package_name").unwrap().clone(),
title: fields.get("package_title").unwrap().clone(),
version: fields.get("package_version").unwrap().clone(),
repo: fields.get("package_repo").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,
original_repo
};
Some(package)
@ -228,11 +243,15 @@ impl super::PackageManager for DefaultPackageManager {
None
}
fn install_package(&self, name: &str) -> 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);
match package {
Some(package) => {
self.install_package_from_repo(name, &package.repo)
if package.is_core || allow_external {
self.install_package_from_repo(name, &package.repo)
}else{
Ok(BlockedExternalPackage(package.original_repo))
}
},
None => {
Ok(NotFoundInIndex)
@ -315,7 +334,7 @@ mod tests {
use crate::package::PackageManager;
use std::fs::{create_dir, create_dir_all};
use crate::package::InstallResult::*;
use std::io::Write;
use crate::package::zip::ZipPackageResolver;
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");
@ -337,7 +356,7 @@ mod tests {
let package_manager = DefaultPackageManager::new(
package_dir.path().clone().to_path_buf(),
data_dir.path().clone().to_path_buf(),
Some(Box::new(GitPackageResolver::new())),
Some(Box::new(ZipPackageResolver::new())),
);
TempPackageManager {
@ -349,7 +368,7 @@ mod tests {
#[test]
fn test_download_index() {
let temp = create_temp_package_manager(|_, _| {});
create_temp_package_manager(|_, _| {});
let index = DefaultPackageManager::request_index();
assert!(index.is_ok());
@ -360,7 +379,7 @@ mod tests {
fn test_outdated_index() {
let temp = create_temp_package_manager(|_, data_dir| {
let index_file = data_dir.join(DEFAULT_PACKAGE_INDEX_FILE);
std::fs::write(index_file, OUTDATED_INDEX_CONTENT);
std::fs::write(index_file, OUTDATED_INDEX_CONTENT).unwrap();
});
assert!(temp.package_manager.is_index_outdated());
@ -373,7 +392,7 @@ mod tests {
let current_time = SystemTime::now().duration_since(UNIX_EPOCH).expect("Time went backwards");
let current_timestamp = current_time.as_secs();
let new_contents = INDEX_CONTENT_WITHOUT_UPDATE.replace("XXXX", &format!("{}", current_timestamp));
std::fs::write(index_file, new_contents);
std::fs::write(index_file, new_contents).unwrap();
});
assert_eq!(temp.package_manager.update_index(false).unwrap(), UpdateResult::NotOutdated);
@ -386,7 +405,7 @@ mod tests {
let current_time = SystemTime::now().duration_since(UNIX_EPOCH).expect("Time went backwards");
let current_timestamp = current_time.as_secs();
let new_contents = INDEX_CONTENT_WITHOUT_UPDATE.replace("XXXX", &format!("{}", current_timestamp));
std::fs::write(index_file, new_contents);
std::fs::write(index_file, new_contents).unwrap();
});
assert_eq!(temp.package_manager.update_index(true).unwrap(), UpdateResult::Updated);
@ -396,7 +415,7 @@ mod tests {
fn test_outdated_index_should_be_updated() {
let mut temp = create_temp_package_manager(|_, data_dir| {
let index_file = data_dir.join(DEFAULT_PACKAGE_INDEX_FILE);
std::fs::write(index_file, OUTDATED_INDEX_CONTENT);
std::fs::write(index_file, OUTDATED_INDEX_CONTENT).unwrap();
});
assert_eq!(temp.package_manager.update_index(false).unwrap(), UpdateResult::Updated);
@ -412,9 +431,9 @@ mod tests {
#[test]
fn test_get_package_should_be_found() {
let mut temp = create_temp_package_manager(|_, data_dir| {
let temp = create_temp_package_manager(|_, data_dir| {
let index_file = data_dir.join(DEFAULT_PACKAGE_INDEX_FILE);
std::fs::write(index_file, GET_PACKAGE_INDEX);
std::fs::write(index_file, GET_PACKAGE_INDEX).unwrap();
});
assert_eq!(temp.package_manager.get_package("italian-accents").unwrap().title, "Italian Accents");
@ -422,9 +441,9 @@ mod tests {
#[test]
fn test_get_package_should_not_be_found() {
let mut temp = create_temp_package_manager(|_, data_dir| {
let temp = create_temp_package_manager(|_, data_dir| {
let index_file = data_dir.join(DEFAULT_PACKAGE_INDEX_FILE);
std::fs::write(index_file, GET_PACKAGE_INDEX);
std::fs::write(index_file, GET_PACKAGE_INDEX).unwrap();
});
assert!(temp.package_manager.get_package("not-existing").is_none());
@ -432,10 +451,10 @@ mod tests {
#[test]
fn test_list_local_packages_names() {
let mut temp = create_temp_package_manager(|package_dir, _| {
create_dir(package_dir.join("package-1"));
create_dir(package_dir.join("package2"));
std::fs::write(package_dir.join("dummyfile.txt"), "test");
let temp = create_temp_package_manager(|package_dir, _| {
create_dir(package_dir.join("package-1")).unwrap();
create_dir(package_dir.join("package2")).unwrap();
std::fs::write(package_dir.join("dummyfile.txt"), "test").unwrap();
});
let packages = temp.package_manager.list_local_packages_names();
@ -446,33 +465,33 @@ mod tests {
#[test]
fn test_install_package_not_found() {
let mut temp = create_temp_package_manager(|package_dir, data_dir| {
let temp = create_temp_package_manager(|_, data_dir| {
let index_file = data_dir.join(DEFAULT_PACKAGE_INDEX_FILE);
std::fs::write(index_file, INSTALL_PACKAGE_INDEX);
std::fs::write(index_file, INSTALL_PACKAGE_INDEX).unwrap();
});
assert_eq!(temp.package_manager.install_package("doesnotexist").unwrap(), NotFoundInIndex);
assert_eq!(temp.package_manager.install_package("doesnotexist", false).unwrap(), NotFoundInIndex);
}
#[test]
fn test_install_package_already_installed() {
let mut temp = create_temp_package_manager(|package_dir, data_dir| {
create_dir(package_dir.join("italian-accents"));
let temp = create_temp_package_manager(|package_dir, data_dir| {
create_dir(package_dir.join("italian-accents")).unwrap();
let index_file = data_dir.join(DEFAULT_PACKAGE_INDEX_FILE);
std::fs::write(index_file, INSTALL_PACKAGE_INDEX);
std::fs::write(index_file, INSTALL_PACKAGE_INDEX).unwrap();
});
assert_eq!(temp.package_manager.install_package("italian-accents").unwrap(), AlreadyInstalled);
assert_eq!(temp.package_manager.install_package("italian-accents", false).unwrap(), AlreadyInstalled);
}
#[test]
fn test_install_package() {
let mut temp = create_temp_package_manager(|_, data_dir| {
let temp = create_temp_package_manager(|_, data_dir| {
let index_file = data_dir.join(DEFAULT_PACKAGE_INDEX_FILE);
std::fs::write(index_file, INSTALL_PACKAGE_INDEX);
std::fs::write(index_file, INSTALL_PACKAGE_INDEX).unwrap();
});
assert_eq!(temp.package_manager.install_package("dummy-package").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/README.md").exists());
assert!(temp.package_dir.path().join("dummy-package/package.yml").exists());
@ -480,52 +499,52 @@ mod tests {
#[test]
fn test_install_package_does_not_exist_in_repo() {
let mut temp = create_temp_package_manager(|_, data_dir| {
let temp = create_temp_package_manager(|_, data_dir| {
let index_file = data_dir.join(DEFAULT_PACKAGE_INDEX_FILE);
std::fs::write(index_file, INSTALL_PACKAGE_INDEX);
std::fs::write(index_file, INSTALL_PACKAGE_INDEX).unwrap();
});
assert_eq!(temp.package_manager.install_package("not-existing").unwrap(), NotFoundInRepo);
assert_eq!(temp.package_manager.install_package("not-existing", false).unwrap(), NotFoundInRepo);
}
#[test]
fn test_install_package_missing_version() {
let mut temp = create_temp_package_manager(|_, data_dir| {
let temp = create_temp_package_manager(|_, data_dir| {
let index_file = data_dir.join(DEFAULT_PACKAGE_INDEX_FILE);
std::fs::write(index_file, INSTALL_PACKAGE_INDEX);
std::fs::write(index_file, INSTALL_PACKAGE_INDEX).unwrap();
});
assert_eq!(temp.package_manager.install_package("dummy-package2").unwrap(), MissingPackageVersion);
assert_eq!(temp.package_manager.install_package("dummy-package2", false).unwrap(), MissingPackageVersion);
}
#[test]
fn test_install_package_missing_readme_unable_to_parse_package_info() {
let mut temp = create_temp_package_manager(|_, data_dir| {
let temp = create_temp_package_manager(|_, data_dir| {
let index_file = data_dir.join(DEFAULT_PACKAGE_INDEX_FILE);
std::fs::write(index_file, INSTALL_PACKAGE_INDEX);
std::fs::write(index_file, INSTALL_PACKAGE_INDEX).unwrap();
});
assert_eq!(temp.package_manager.install_package("dummy-package3").unwrap(), UnableToParsePackageInfo);
assert_eq!(temp.package_manager.install_package("dummy-package3", false).unwrap(), UnableToParsePackageInfo);
}
#[test]
fn test_install_package_bad_readme_unable_to_parse_package_info() {
let mut temp = create_temp_package_manager(|_, data_dir| {
let temp = create_temp_package_manager(|_, data_dir| {
let index_file = data_dir.join(DEFAULT_PACKAGE_INDEX_FILE);
std::fs::write(index_file, INSTALL_PACKAGE_INDEX);
std::fs::write(index_file, INSTALL_PACKAGE_INDEX).unwrap();
});
assert_eq!(temp.package_manager.install_package("dummy-package4").unwrap(), UnableToParsePackageInfo);
assert_eq!(temp.package_manager.install_package("dummy-package4", false).unwrap(), UnableToParsePackageInfo);
}
#[test]
fn test_list_local_packages() {
let mut temp = create_temp_package_manager(|_, data_dir| {
let temp = create_temp_package_manager(|_, data_dir| {
let index_file = data_dir.join(DEFAULT_PACKAGE_INDEX_FILE);
std::fs::write(index_file, INSTALL_PACKAGE_INDEX);
std::fs::write(index_file, INSTALL_PACKAGE_INDEX).unwrap();
});
assert_eq!(temp.package_manager.install_package("dummy-package").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/README.md").exists());
assert!(temp.package_dir.path().join("dummy-package/package.yml").exists());
@ -537,11 +556,11 @@ mod tests {
#[test]
fn test_remove_package() {
let mut temp = create_temp_package_manager(|package_dir, _| {
let temp = create_temp_package_manager(|package_dir, _| {
let dummy_package_dir = package_dir.join("dummy-package");
create_dir_all(&dummy_package_dir);
std::fs::write(dummy_package_dir.join("README.md"), "readme");
std::fs::write(dummy_package_dir.join("package.yml"), "name: package");
create_dir_all(&dummy_package_dir).unwrap();
std::fs::write(dummy_package_dir.join("README.md"), "readme").unwrap();
std::fs::write(dummy_package_dir.join("package.yml"), "name: package").unwrap();
});
assert!(temp.package_dir.path().join("dummy-package").exists());
@ -555,7 +574,7 @@ mod tests {
#[test]
fn test_remove_package_not_found() {
let mut temp = create_temp_package_manager(|_, _| {});
let temp = create_temp_package_manager(|_, _| {});
assert_eq!(temp.package_manager.remove_package("not-existing").unwrap(), RemoveResult::NotFound);
}
@ -571,8 +590,9 @@ mod tests {
package_version: "0.1.0"
package_author: "Federico Terzi"
package_repo: "https://github.com/federico-terzi/espanso-hub-core"
is_core: true
---
"###);
"###).unwrap();
let package = DefaultPackageManager::parse_package_from_readme(file.path()).unwrap();
@ -582,7 +602,9 @@ mod tests {
version: "0.1.0".to_string(),
repo: "https://github.com/federico-terzi/espanso-hub-core".to_string(),
desc: "Include Italian accents substitutions to espanso.".to_string(),
author: "Federico Terzi".to_string()
author: "Federico Terzi".to_string(),
original_repo: "https://github.com/federico-terzi/espanso-hub-core".to_string(),
is_core: true,
};
assert_eq!(package, target_package);
@ -599,9 +621,10 @@ mod tests {
package_version:"0.1.0"
package_author:Federico Terzi
package_repo: "https://github.com/federico-terzi/espanso-hub-core"
is_core: true
---
Readme text
"###);
"###).unwrap();
let package = DefaultPackageManager::parse_package_from_readme(file.path()).unwrap();
@ -611,7 +634,9 @@ mod tests {
version: "0.1.0".to_string(),
repo: "https://github.com/federico-terzi/espanso-hub-core".to_string(),
desc: "Include Italian accents substitutions to espanso.".to_string(),
author: "Federico Terzi".to_string()
author: "Federico Terzi".to_string(),
original_repo: "https://github.com/federico-terzi/espanso-hub-core".to_string(),
is_core: true,
};
assert_eq!(package, target_package);

View File

@ -17,7 +17,6 @@
* along with espanso. If not, see <https://www.gnu.org/licenses/>.
*/
pub(crate) mod git;
pub(crate) mod zip;
pub(crate) mod default;
@ -31,7 +30,7 @@ pub trait PackageManager {
fn get_package(&self, name: &str) -> Option<Package>;
fn install_package(&self, name: &str) -> Result<InstallResult, Box<dyn Error>>;
fn install_package(&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>>;
@ -50,9 +49,17 @@ pub struct Package {
pub version: String,
pub repo: String,
pub desc: String,
pub author: String
pub author: String,
#[serde(default = "default_is_core")]
pub is_core: bool,
#[serde(default = "default_original_repo")]
pub original_repo: String,
}
fn default_is_core() -> bool {false}
fn default_original_repo() -> String {"".to_owned()}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
pub struct PackageIndex {
#[serde(rename = "lastUpdate")]
@ -75,7 +82,8 @@ pub enum InstallResult {
UnableToParsePackageInfo,
MissingPackageVersion,
AlreadyInstalled,
Installed
Installed,
BlockedExternalPackage(String)
}
#[derive(Clone, Debug, PartialEq)]

View File

@ -1,9 +1,7 @@
use tempfile::TempDir;
use std::error::Error;
use super::PackageResolver;
use std::io::{Cursor, copy, Read};
use std::io::{Cursor, copy};
use std::{fs, io};
use std::fs::File;
use log::debug;
pub struct ZipPackageResolver;
@ -33,7 +31,7 @@ impl super::PackageResolver for ZipPackageResolver {
// Find the root folder name
let mut root_folder = {
let mut root_folder = archive.by_index(0).unwrap();
let root_folder = archive.by_index(0).unwrap();
let root_folder = root_folder.sanitized_name();
root_folder.to_str().unwrap().to_owned()
};
@ -77,7 +75,7 @@ impl super::PackageResolver for ZipPackageResolver {
#[cfg(test)]
mod tests {
use super::*;
use tempfile::{TempDir, NamedTempFile};
use super::super::PackageResolver;
#[test]
fn test_clone_temp_repository() {

View File

@ -17,8 +17,7 @@
* along with espanso. If not, see <https://www.gnu.org/licenses/>.
*/
use serde_yaml::{Mapping, Value};
use std::path::PathBuf;
use serde_yaml::{Value};
use std::collections::HashMap;
use regex::{Regex, Captures};
use log::{warn, error};
@ -50,7 +49,7 @@ impl DefaultRenderer {
// Compile the regexes
let passive_match_regex = Regex::new(&config.passive_match_regex)
.unwrap_or_else(|e| {
panic!("Invalid passive match regex");
panic!("Invalid passive match regex: {:?}", e);
});
DefaultRenderer{

View File

@ -8,7 +8,8 @@
"version": "0.1.0",
"repo": "https://github.com/federico-terzi/espanso-hub-core",
"desc": "A package to include some basic emojis in espanso.",
"author": "Federico Terzi"
"author": "Federico Terzi",
"is_core": true
},
@ -19,7 +20,8 @@
"version": "0.1.0",
"repo": "https://github.com/federico-terzi/espanso-hub-core",
"desc": "Include Italian accents substitutions to espanso.",
"author": "Federico Terzi"
"author": "Federico Terzi",
"is_core": true
}

View File

@ -8,7 +8,8 @@
"version": "0.1.0",
"repo": "https://github.com/federico-terzi/espanso-hub-core",
"desc": "A package to include some basic emojis in espanso.",
"author": "Federico Terzi"
"author": "Federico Terzi",
"is_core": true
},
@ -19,7 +20,8 @@
"version": "0.1.0",
"repo": "https://github.com/federico-terzi/espanso-hub-core",
"desc": "Include Italian accents substitutions to espanso.",
"author": "Federico Terzi"
"author": "Federico Terzi",
"is_core": true
}

View File

@ -8,8 +8,8 @@
"version": "0.1.0",
"repo": "https://github.com/federico-terzi/espanso-hub-core",
"desc": "Dummy package",
"author": "Federico Terzi"
"author": "Federico Terzi",
"is_core": true
},
{
@ -18,7 +18,8 @@
"version": "9.9.9",
"repo": "https://github.com/federico-terzi/espanso-hub-core",
"desc": "Dummy package",
"author": "Federico Terzi"
"author": "Federico Terzi",
"is_core": true
},
@ -28,7 +29,8 @@
"version": "0.1.0",
"repo": "https://github.com/federico-terzi/espanso-hub-core",
"desc": "Dummy package",
"author": "Federico Terzi"
"author": "Federico Terzi",
"is_core": true
},
@ -38,7 +40,8 @@
"version": "0.1.0",
"repo": "https://github.com/federico-terzi/espanso-hub-core",
"desc": "Dummy package",
"author": "Federico Terzi"
"author": "Federico Terzi",
"is_core": true
},
@ -49,7 +52,8 @@
"version": "0.1.0",
"repo": "https://github.com/federico-terzi/espanso-hub-core",
"desc": "Include Italian accents substitutions to espanso.",
"author": "Federico Terzi"
"author": "Federico Terzi",
"is_core": true
},
@ -59,7 +63,8 @@
"version": "0.1.0",
"repo": "https://github.com/federico-terzi/espanso-hub-core",
"desc": "Package that does not exist in the repo",
"author": "Federico Terzi"
"author": "Federico Terzi",
"is_core": true
}
]}

View File

@ -8,7 +8,8 @@
"version": "0.1.0",
"repo": "https://github.com/federico-terzi/espanso-hub-core",
"desc": "A package to include some basic emojis in espanso.",
"author": "Federico Terzi"
"author": "Federico Terzi",
"is_core": true
},
@ -19,7 +20,8 @@
"version": "0.1.0",
"repo": "https://github.com/federico-terzi/espanso-hub-core",
"desc": "Include Italian accents substitutions to espanso.",
"author": "Federico Terzi"
"author": "Federico Terzi",
"is_core": true
}

View File

@ -108,9 +108,9 @@ const LINUX_SERVICE_CONTENT : &str = include_str!("res/linux/systemd.service");
const LINUX_SERVICE_FILENAME : &str = "espanso.service";
#[cfg(target_os = "linux")]
pub fn register(config_set: ConfigSet) {
pub fn register(_: ConfigSet) {
use std::fs::create_dir_all;
use std::process::{Command, ExitStatus};
use std::process::{Command};
// Check if espanso service is already registered
let res = Command::new("systemctl")
@ -191,7 +191,7 @@ pub enum VerifyResult {
#[cfg(target_os = "linux")]
pub fn verify() -> VerifyResult {
use regex::Regex;
use std::process::{Command, ExitStatus};
use std::process::{Command};
// Check if espanso service is already registered
let res = Command::new("systemctl")
@ -206,7 +206,7 @@ pub fn verify() -> VerifyResult {
}
lazy_static! {
static ref ExecPathRegex: Regex = Regex::new("ExecStart=(?P<path>.*?)\\s").unwrap();
static ref EXEC_PATH_REGEX: Regex = Regex::new("ExecStart=(?P<path>.*?)\\s").unwrap();
}
// Check if the currently registered path is valid
@ -217,7 +217,7 @@ pub fn verify() -> VerifyResult {
let output = String::from_utf8_lossy(res.stdout.as_slice());
let output = output.trim();
if res.status.success() {
let caps = ExecPathRegex.captures(output).unwrap();
let caps = EXEC_PATH_REGEX.captures(output).unwrap();
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");
@ -231,13 +231,13 @@ pub fn verify() -> VerifyResult {
}
#[cfg(target_os = "linux")]
pub fn unregister(config_set: ConfigSet) {
use std::process::{Command, ExitStatus};
pub fn unregister(_: ConfigSet) {
use std::process::{Command};
// Disable the service first
let res = Command::new("systemctl")
Command::new("systemctl")
.args(&["--user", "disable", "espanso"])
.status();
.status().expect("Unable to invoke systemctl");
// Then delete the espanso.service entry
let config_dir = dirs::config_dir().expect("Could not get configuration directory");

View File

@ -20,7 +20,7 @@
use std::os::raw::c_char;
use std::ffi::CStr;
use crate::bridge::macos::{get_active_app_bundle, get_active_app_identifier};
use crate::bridge::macos::{get_active_app_bundle, get_active_app_identifier, get_secure_input_process, get_path_from_pid};
pub struct MacSystemManager {
@ -74,4 +74,66 @@ impl MacSystemManager {
}
}
/// Check whether an application is currently holding the Secure Input.
/// Return None if no application has claimed SecureInput, its PID otherwise.
pub fn get_secure_input_pid() -> Option<i64> {
unsafe {
let mut pid: i64 = -1;
let res = get_secure_input_process(&mut pid as *mut i64);
if res > 0{
Some(pid)
}else{
None
}
}
}
/// Check whether an application is currently holding the Secure Input.
/// Return None if no application has claimed SecureInput, Some((AppName, AppPath)) otherwise.
pub fn get_secure_input_application() -> Option<(String, String)> {
use regex::Regex;
lazy_static! {
static ref APP_REGEX: Regex = Regex::new("/([^/]+).app/").unwrap();
};
unsafe {
let pid = MacSystemManager::get_secure_input_pid();
if let Some(pid) = pid {
// 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.
let mut buffer : [c_char; 4096] = [0; 4096];
let res = get_path_from_pid(pid, buffer.as_mut_ptr(), buffer.len() as i32);
if res > 0 {
let c_string = CStr::from_ptr(buffer.as_ptr());
let string = c_string.to_str();
if let Ok(path) = string {
if !path.trim().is_empty() {
let process = path.trim().to_string();
let caps = APP_REGEX.captures(&process);
let app_name = if let Some(caps) = caps {
caps.get(1).map_or("", |m| m.as_str()).to_owned()
}else{
process.to_owned()
};
Some((app_name, process))
}else{
None
}
}else{
None
}
}else{
None
}
}else{
None
}
}
}
}

View File

@ -24,7 +24,7 @@ mod windows;
mod linux;
#[cfg(target_os = "macos")]
mod macos;
pub mod macos;
pub trait SystemManager {
fn get_current_window_title(&self) -> Option<String>;

View File

@ -30,10 +30,14 @@ pub struct LinuxUIManager {
impl super::UIManager for LinuxUIManager {
fn notify(&self, message: &str) {
self.notify_delay(message, 2000);
}
fn notify_delay(&self, message: &str, duration: i32) {
let res = Command::new("notify-send")
.args(&["-i", self.icon_path.to_str().unwrap_or_default(),
"-t", "2000", "espanso", message])
.output();
.args(&["-i", self.icon_path.to_str().unwrap_or_default(),
"-t", &duration.to_string(), "espanso", message])
.output();
if let Err(e) = res {
error!("Could not send a notification, error: {}", e);

View File

@ -29,7 +29,6 @@ use std::os::raw::c_char;
use crate::context;
const NOTIFY_HELPER_BINARY : &'static [u8] = include_bytes!("../res/mac/EspansoNotifyHelper.zip");
const DEFAULT_NOTIFICATION_DELAY : f64 = 1.5;
pub struct MacUIManager {
notify_helper_path: PathBuf
@ -37,12 +36,18 @@ pub struct MacUIManager {
impl super::UIManager for MacUIManager {
fn notify(&self, message: &str) {
self.notify_delay(message, 1500);
}
fn notify_delay(&self, message: &str, duration: i32) {
let executable_path = self.notify_helper_path.join("Contents");
let executable_path = executable_path.join("MacOS");
let executable_path = executable_path.join("EspansoNotifyHelper");
let duration_float = duration as f64 / 1000.0;
let res = Command::new(executable_path)
.args(&["espanso", message, &DEFAULT_NOTIFICATION_DELAY.to_string()])
.args(&["espanso", message, &duration_float.to_string()])
.spawn();
if let Err(e) = res {

View File

@ -28,6 +28,7 @@ mod macos;
pub trait UIManager {
fn notify(&self, message: &str);
fn notify_delay(&self, message: &str, duration: i32);
fn show_menu(&self, menu: Vec<MenuItem>);
fn cleanup(&self);
}

View File

@ -31,17 +31,23 @@ pub struct WindowsUIManager {
impl super::UIManager for WindowsUIManager {
fn notify(&self, message: &str) {
self.notify_delay(message, 2000);
}
fn notify_delay(&self, message: &str, duration: i32) {
let current_id: i32 = {
let mut id = self.id.lock().unwrap();
*id += 1;
*id
};
let step = duration / 10;
// Setup a timeout to close the notification
let id = Arc::clone(&self.id);
let _ = thread::Builder::new().name("notification_thread".to_string()).spawn(move || {
for _ in 1..10 {
let duration = time::Duration::from_millis(200);
let duration = time::Duration::from_millis(step as u64);
thread::sleep(duration);
let new_id = id.lock().unwrap();

View File

@ -51,14 +51,14 @@ mod tests {
let dest_tmp_dir = TempDir::new().expect("Error creating temp directory");
let source_dir = source_tmp_dir.path().join("source");
create_dir(&source_dir);
std::fs::write(source_dir.join("file1.txt"), "file1");
std::fs::write(source_dir.join("file2.txt"), "file2");
create_dir(&source_dir).unwrap();
std::fs::write(source_dir.join("file1.txt"), "file1").unwrap();
std::fs::write(source_dir.join("file2.txt"), "file2").unwrap();
let target_dir = dest_tmp_dir.path().join("source");
create_dir(&target_dir);
create_dir(&target_dir).unwrap();
copy_dir(&source_dir, &target_dir);
copy_dir(&source_dir, &target_dir).unwrap();
assert!(dest_tmp_dir.path().join("source").exists());
assert!(dest_tmp_dir.path().join("source/file1.txt").exists());
@ -71,17 +71,17 @@ mod tests {
let dest_tmp_dir = TempDir::new().expect("Error creating temp directory");
let source_dir = source_tmp_dir.path().join("source");
create_dir(&source_dir);
std::fs::write(source_dir.join("file1.txt"), "file1");
std::fs::write(source_dir.join("file2.txt"), "file2");
create_dir(&source_dir).unwrap();
std::fs::write(source_dir.join("file1.txt"), "file1").unwrap();
std::fs::write(source_dir.join("file2.txt"), "file2").unwrap();
let nested_dir = source_dir.join("nested");
create_dir(&nested_dir);
std::fs::write(nested_dir.join("nestedfile.txt"), "nestedfile1");
create_dir(&nested_dir).unwrap();
std::fs::write(nested_dir.join("nestedfile.txt"), "nestedfile1").unwrap();
let target_dir = dest_tmp_dir.path().join("source");
create_dir(&target_dir);
create_dir(&target_dir).unwrap();
copy_dir(&source_dir, &target_dir);
copy_dir(&source_dir, &target_dir).unwrap();
assert!(dest_tmp_dir.path().join("source").exists());
assert!(dest_tmp_dir.path().join("source/file1.txt").exists());