commit
f8788060c0
73
Cargo.lock
generated
73
Cargo.lock
generated
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
1
build.rs
1
build.rs
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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"
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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;
|
||||
|
|
23
src/check.rs
23
src/check.rs
|
@ -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
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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"
|
||||
"###);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
|
|
107
src/engine.rs
107
src/engine.rs
|
@ -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.");
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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),
|
||||
|
|
|
@ -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)]
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
|
70
src/main.rs
70
src/main.rs
|
@ -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.");
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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)]
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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{
|
||||
|
|
|
@ -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
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
}
|
||||
]}
|
|
@ -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
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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>;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
24
src/utils.rs
24
src/utils.rs
|
@ -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());
|
||||
|
|
Loading…
Reference in New Issue
Block a user