Merge pull request #1022 from federico-terzi/dev

Version 2.1.4-beta
This commit is contained in:
Federico Terzi 2022-03-19 12:51:42 +01:00 committed by GitHub
commit e37aa98616
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
35 changed files with 849 additions and 119 deletions

View File

@ -9,7 +9,7 @@ RUN apt-get update \
ENV RUSTUP_HOME=/usr/local/rustup \ ENV RUSTUP_HOME=/usr/local/rustup \
CARGO_HOME=/usr/local/cargo \ CARGO_HOME=/usr/local/cargo \
PATH=/usr/local/cargo/bin:$PATH \ PATH=/usr/local/cargo/bin:$PATH \
RUST_VERSION=1.55.0 RUST_VERSION=1.57.0
RUN set -eux; \ RUN set -eux; \
dpkgArch="$(dpkg --print-architecture)"; \ dpkgArch="$(dpkg --print-architecture)"; \

43
Cargo.lock generated
View File

@ -187,9 +187,9 @@ dependencies = [
[[package]] [[package]]
name = "cc" name = "cc"
version = "1.0.66" version = "1.0.73"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c0496836a84f8d0495758516b8621a622beb77c0fed418570e50764093ced48" checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11"
[[package]] [[package]]
name = "cfg-if" name = "cfg-if"
@ -212,6 +212,7 @@ dependencies = [
"libc", "libc",
"num-integer", "num-integer",
"num-traits", "num-traits",
"pure-rust-locales",
"time", "time",
"winapi 0.3.9", "winapi 0.3.9",
] ]
@ -399,6 +400,16 @@ dependencies = [
"lazy_static", "lazy_static",
] ]
[[package]]
name = "cstr_core"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "644828c273c063ab0d39486ba42a5d1f3a499d35529c759e763a9c6cb8a0fb08"
dependencies = [
"cty",
"memchr",
]
[[package]] [[package]]
name = "ctor" name = "ctor"
version = "0.1.20" version = "0.1.20"
@ -409,6 +420,12 @@ dependencies = [
"syn 1.0.67", "syn 1.0.67",
] ]
[[package]]
name = "cty"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b365fabc795046672053e29c954733ec3b05e4be654ab130fe8f1f94d7051f35"
[[package]] [[package]]
name = "dbus" name = "dbus"
version = "0.9.1" version = "0.9.1"
@ -572,7 +589,7 @@ dependencies = [
[[package]] [[package]]
name = "espanso" name = "espanso"
version = "2.1.3-alpha" version = "2.1.4-beta"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"caps", "caps",
@ -851,6 +868,7 @@ dependencies = [
"log", "log",
"rand 0.8.3", "rand 0.8.3",
"regex", "regex",
"sys-locale",
"thiserror", "thiserror",
] ]
@ -2040,6 +2058,12 @@ dependencies = [
"unicode-xid 0.2.1", "unicode-xid 0.2.1",
] ]
[[package]]
name = "pure-rust-locales"
version = "0.5.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b45c49fc4f91f35bae654f85ebb3a44d60ac64f11b3166ffa609def390c732d8"
[[package]] [[package]]
name = "quote" name = "quote"
version = "0.3.15" version = "0.3.15"
@ -2577,6 +2601,19 @@ dependencies = [
"unicode-xid 0.0.4", "unicode-xid 0.0.4",
] ]
[[package]]
name = "sys-locale"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "91f89ebb59fa30d4f65fafc2d68e94f6975256fd87e812dd99cb6e020c8563df"
dependencies = [
"cc",
"cstr_core",
"libc",
"web-sys",
"winapi 0.3.9",
]
[[package]] [[package]]
name = "tempdir" name = "tempdir"
version = "0.3.7" version = "0.3.7"

View File

@ -28,4 +28,4 @@ widestring = "0.4.3"
wait-timeout = { version = "0.2.0", optional = true } wait-timeout = { version = "0.2.0", optional = true }
[build-dependencies] [build-dependencies]
cc = "1.0.66" cc = "1.0.73"

View File

@ -30,8 +30,6 @@ int32_t clipboard_get_text(char * buffer, int32_t buffer_size) {
const char * text = [string UTF8String]; const char * text = [string UTF8String];
strncpy(buffer, text, buffer_size); strncpy(buffer, text, buffer_size);
[string release];
return 1; return 1;
} }
} }

View File

@ -21,3 +21,5 @@ pub(crate) const DEFAULT_CLIPBOARD_THRESHOLD: usize = 100;
pub(crate) const DEFAULT_PRE_PASTE_DELAY: usize = 100; pub(crate) const DEFAULT_PRE_PASTE_DELAY: usize = 100;
pub(crate) const DEFAULT_SHORTCUT_EVENT_DELAY: usize = 10; pub(crate) const DEFAULT_SHORTCUT_EVENT_DELAY: usize = 10;
pub(crate) const DEFAULT_RESTORE_CLIPBOARD_DELAY: usize = 300; pub(crate) const DEFAULT_RESTORE_CLIPBOARD_DELAY: usize = 300;
pub(crate) const DEFAULT_POST_FORM_DELAY: usize = 200;
pub(crate) const DEFAULT_POST_SEARCH_DELAY: usize = 200;

View File

@ -150,6 +150,18 @@ pub trait Config: Send + Sync {
// If false, avoid showing the SecureInput notification on macOS // If false, avoid showing the SecureInput notification on macOS
fn secure_input_notification(&self) -> bool; fn secure_input_notification(&self) -> bool;
// The number of milliseconds to wait after a form has been closed.
// This is useful to let the target application regain focus
// after a form has been closed, otherwise the injection might
// not be targeted to the right application.
fn post_form_delay(&self) -> usize;
// The number of milliseconds to wait after the search bar has been closed.
// This is useful to let the target application regain focus
// after the search bar has been closed, otherwise the injection might
// not be targeted to the right application.
fn post_search_delay(&self) -> usize;
// If true, use the `xclip` command to implement the clipboard instead of // If true, use the `xclip` command to implement the clipboard instead of
// the built-in native module on X11. // the built-in native module on X11.
fn x11_use_xclip_backend(&self) -> bool; fn x11_use_xclip_backend(&self) -> bool;
@ -188,6 +200,8 @@ pub trait Config: Send + Sync {
toggle_key: {:?} toggle_key: {:?}
auto_restart: {:?} auto_restart: {:?}
restore_clipboard_delay: {:?} restore_clipboard_delay: {:?}
post_form_delay: {:?}
post_search_delay: {:?}
backspace_limit: {} backspace_limit: {}
search_trigger: {:?} search_trigger: {:?}
search_shortcut: {:?} search_shortcut: {:?}
@ -220,6 +234,8 @@ pub trait Config: Send + Sync {
self.toggle_key(), self.toggle_key(),
self.auto_restart(), self.auto_restart(),
self.restore_clipboard_delay(), self.restore_clipboard_delay(),
self.post_form_delay(),
self.post_search_delay(),
self.backspace_limit(), self.backspace_limit(),
self.search_trigger(), self.search_trigger(),
self.search_shortcut(), self.search_shortcut(),

View File

@ -44,6 +44,8 @@ pub(crate) struct ParsedConfig {
pub show_notifications: Option<bool>, pub show_notifications: Option<bool>,
pub show_icon: Option<bool>, pub show_icon: Option<bool>,
pub secure_input_notification: Option<bool>, pub secure_input_notification: Option<bool>,
pub post_form_delay: Option<usize>,
pub post_search_delay: Option<usize>,
pub win32_exclude_orphan_events: Option<bool>, pub win32_exclude_orphan_events: Option<bool>,
pub win32_keyboard_layout_cache_interval: Option<i64>, pub win32_keyboard_layout_cache_interval: Option<i64>,
pub x11_use_xclip_backend: Option<bool>, pub x11_use_xclip_backend: Option<bool>,

View File

@ -103,6 +103,12 @@ pub(crate) struct YAMLConfig {
#[serde(default)] #[serde(default)]
pub show_icon: Option<bool>, pub show_icon: Option<bool>,
#[serde(default)]
pub post_form_delay: Option<usize>,
#[serde(default)]
pub post_search_delay: Option<usize>,
#[serde(default)] #[serde(default)]
pub secure_input_notification: Option<bool>, pub secure_input_notification: Option<bool>,
@ -201,6 +207,8 @@ impl TryFrom<YAMLConfig> for ParsedConfig {
pre_paste_delay: yaml_config.pre_paste_delay, pre_paste_delay: yaml_config.pre_paste_delay,
restore_clipboard_delay: yaml_config.restore_clipboard_delay, restore_clipboard_delay: yaml_config.restore_clipboard_delay,
paste_shortcut_event_delay: yaml_config.paste_shortcut_event_delay, paste_shortcut_event_delay: yaml_config.paste_shortcut_event_delay,
post_form_delay: yaml_config.post_form_delay,
post_search_delay: yaml_config.post_search_delay,
win32_exclude_orphan_events: yaml_config.win32_exclude_orphan_events, win32_exclude_orphan_events: yaml_config.win32_exclude_orphan_events,
win32_keyboard_layout_cache_interval: yaml_config.win32_keyboard_layout_cache_interval, win32_keyboard_layout_cache_interval: yaml_config.win32_keyboard_layout_cache_interval,
@ -260,6 +268,8 @@ mod tests {
show_icon: false show_icon: false
show_notifications: false show_notifications: false
secure_input_notification: false secure_input_notification: false
post_form_delay: 300
post_search_delay: 400
win32_exclude_orphan_events: false win32_exclude_orphan_events: false
win32_keyboard_layout_cache_interval: 300 win32_keyboard_layout_cache_interval: 300
x11_use_xclip_backend: true x11_use_xclip_backend: true
@ -314,6 +324,8 @@ mod tests {
show_icon: Some(false), show_icon: Some(false),
show_notifications: Some(false), show_notifications: Some(false),
secure_input_notification: Some(false), secure_input_notification: Some(false),
post_form_delay: Some(300),
post_search_delay: Some(400),
win32_exclude_orphan_events: Some(false), win32_exclude_orphan_events: Some(false),
win32_keyboard_layout_cache_interval: Some(300), win32_keyboard_layout_cache_interval: Some(300),
x11_use_xclip_backend: Some(true), x11_use_xclip_backend: Some(true),

View File

@ -19,8 +19,8 @@
use super::{ use super::{
default::{ default::{
DEFAULT_CLIPBOARD_THRESHOLD, DEFAULT_PRE_PASTE_DELAY, DEFAULT_RESTORE_CLIPBOARD_DELAY, DEFAULT_CLIPBOARD_THRESHOLD, DEFAULT_POST_FORM_DELAY, DEFAULT_POST_SEARCH_DELAY,
DEFAULT_SHORTCUT_EVENT_DELAY, DEFAULT_PRE_PASTE_DELAY, DEFAULT_RESTORE_CLIPBOARD_DELAY, DEFAULT_SHORTCUT_EVENT_DELAY,
}, },
parse::ParsedConfig, parse::ParsedConfig,
path::calculate_paths, path::calculate_paths,
@ -299,6 +299,20 @@ impl Config for ResolvedConfig {
self.parsed.secure_input_notification.unwrap_or(true) self.parsed.secure_input_notification.unwrap_or(true)
} }
fn post_form_delay(&self) -> usize {
self
.parsed
.post_form_delay
.unwrap_or(DEFAULT_POST_FORM_DELAY)
}
fn post_search_delay(&self) -> usize {
self
.parsed
.post_search_delay
.unwrap_or(DEFAULT_POST_SEARCH_DELAY)
}
fn win32_exclude_orphan_events(&self) -> bool { fn win32_exclude_orphan_events(&self) -> bool {
self.parsed.win32_exclude_orphan_events.unwrap_or(true) self.parsed.win32_exclude_orphan_events.unwrap_or(true)
} }
@ -398,6 +412,8 @@ impl ResolvedConfig {
show_icon, show_icon,
show_notifications, show_notifications,
secure_input_notification, secure_input_notification,
post_form_delay,
post_search_delay,
win32_exclude_orphan_events, win32_exclude_orphan_events,
win32_keyboard_layout_cache_interval, win32_keyboard_layout_cache_interval,
x11_use_xclip_backend, x11_use_xclip_backend,

View File

@ -387,6 +387,14 @@ impl Config for LegacyInteropConfig {
self.config.enable_active self.config.enable_active
} }
fn post_form_delay(&self) -> usize {
crate::config::default::DEFAULT_POST_FORM_DELAY
}
fn post_search_delay(&self) -> usize {
crate::config::default::DEFAULT_POST_SEARCH_DELAY
}
fn win32_exclude_orphan_events(&self) -> bool { fn win32_exclude_orphan_events(&self) -> bool {
true true
} }

View File

@ -27,7 +27,7 @@ scopeguard = "1.1.0"
sctk = { package = "smithay-client-toolkit", version = "0.14.0", optional = true } sctk = { package = "smithay-client-toolkit", version = "0.14.0", optional = true }
[build-dependencies] [build-dependencies]
cc = "1.0.66" cc = "1.0.73"
[dev-dependencies] [dev-dependencies]
enum-as-inner = "0.3.3" enum-as-inner = "0.3.3"

View File

@ -37,7 +37,6 @@ pub fn get_active_layout() -> Option<String> {
if gnome::is_gnome() { if gnome::is_gnome() {
gnome::get_active_layout() gnome::get_active_layout()
} else { } else {
log::warn!("unable to determine the currently active layout, you might need to explicitly specify the layout in the config for espanso to work correctly.");
None None
} }
} }

View File

@ -47,7 +47,7 @@ impl<'a> Middleware for ImageResolverMiddleware<'a> {
if let EventType::ImageRequested(m_event) = &event.etype { if let EventType::ImageRequested(m_event) = &event.etype {
// On Windows, we have to replace the forward / with the backslash \ in the path // On Windows, we have to replace the forward / with the backslash \ in the path
let path = if cfg!(target_os = "windows") { let path = if cfg!(target_os = "windows") {
m_event.image_path.replace("/", "\\") m_event.image_path.replace('/', "\\")
} else { } else {
m_event.image_path.to_owned() m_event.image_path.to_owned()
}; };

View File

@ -20,4 +20,4 @@ lazy_static = "1.4.0"
widestring = "0.4.3" widestring = "0.4.3"
[build-dependencies] [build-dependencies]
cc = "1.0.66" cc = "1.0.73"

View File

@ -23,7 +23,6 @@
int32_t info_get_title(char *buffer, int32_t buffer_size) int32_t info_get_title(char *buffer, int32_t buffer_size)
{ {
@autoreleasepool {
CFArrayRef windows = CGWindowListCopyWindowInfo(kCGWindowListExcludeDesktopElements | kCGWindowListOptionOnScreenOnly, kCGNullWindowID); CFArrayRef windows = CGWindowListCopyWindowInfo(kCGWindowListExcludeDesktopElements | kCGWindowListOptionOnScreenOnly, kCGNullWindowID);
int32_t result = 0; int32_t result = 0;
@ -46,7 +45,6 @@ int32_t info_get_title(char *buffer, int32_t buffer_size)
CFRelease(windows); CFRelease(windows);
} }
}
return 0; return 0;
} }
@ -54,7 +52,6 @@ int32_t info_get_title(char *buffer, int32_t buffer_size)
// Partially taken from: https://stackoverflow.com/questions/480866/get-the-title-of-the-current-active-window-document-in-mac-os-x/23451568#23451568 // Partially taken from: https://stackoverflow.com/questions/480866/get-the-title-of-the-current-active-window-document-in-mac-os-x/23451568#23451568
int32_t info_get_title_fallback(char *buffer, int32_t buffer_size) int32_t info_get_title_fallback(char *buffer, int32_t buffer_size)
{ {
@autoreleasepool {
// Get the process ID of the frontmost application. // Get the process ID of the frontmost application.
NSRunningApplication* app = [[NSWorkspace sharedWorkspace] frontmostApplication]; NSRunningApplication* app = [[NSWorkspace sharedWorkspace] frontmostApplication];
pid_t pid = [app processIdentifier]; pid_t pid = [app processIdentifier];
@ -93,31 +90,26 @@ int32_t info_get_title_fallback(char *buffer, int32_t buffer_size)
} else { } else {
return -4; return -4;
} }
}
} }
int32_t info_get_exec(char *buffer, int32_t buffer_size) int32_t info_get_exec(char *buffer, int32_t buffer_size)
{ {
@autoreleasepool {
NSRunningApplication *frontApp = [[NSWorkspace sharedWorkspace] frontmostApplication]; NSRunningApplication *frontApp = [[NSWorkspace sharedWorkspace] frontmostApplication];
NSString *bundlePath = [frontApp bundleURL].path; NSString *bundlePath = [frontApp bundleURL].path;
const char * path = [bundlePath UTF8String]; const char * path = [bundlePath UTF8String];
snprintf(buffer, buffer_size, "%s", path); snprintf(buffer, buffer_size, "%s", path);
}
return 1; return 1;
} }
int32_t info_get_class(char *buffer, int32_t buffer_size) int32_t info_get_class(char *buffer, int32_t buffer_size)
{ {
@autoreleasepool {
NSRunningApplication *frontApp = [[NSWorkspace sharedWorkspace] frontmostApplication]; NSRunningApplication *frontApp = [[NSWorkspace sharedWorkspace] frontmostApplication];
NSString *bundleId = frontApp.bundleIdentifier; NSString *bundleId = frontApp.bundleIdentifier;
const char * bundle = [bundleId UTF8String]; const char * bundle = [bundleId UTF8String];
snprintf(buffer, buffer_size, "%s", bundle); snprintf(buffer, buffer_size, "%s", bundle);
}
return 1; return 1;
} }

View File

@ -27,7 +27,7 @@ scopeguard = "1.1.0"
itertools = "0.10.0" itertools = "0.10.0"
[build-dependencies] [build-dependencies]
cc = "1.0.66" cc = "1.0.73"
[dev-dependencies] [dev-dependencies]
enum-as-inner = "0.3.3" enum-as-inner = "0.3.3"

View File

@ -14,4 +14,4 @@ lazy_static = "1.4.0"
regex = "1.4.3" regex = "1.4.3"
[build-dependencies] [build-dependencies]
cc = "1.0.66" cc = "1.0.73"

View File

@ -15,7 +15,7 @@ lazy_static = "1.4.0"
regex = "1.4.3" regex = "1.4.3"
[build-dependencies] [build-dependencies]
cc = "1.0.66" cc = "1.0.73"
regex = "1.4.3" regex = "1.4.3"
zip = "0.5.12" zip = "0.5.12"
winres = "0.1.11" winres = "0.1.11"

View File

@ -49,7 +49,7 @@ fn build_native() {
.expect("unable to extract wxWidgets source dir"); .expect("unable to extract wxWidgets source dir");
// Compile wxWidgets // Compile wxWidgets
let tool = cc::windows_registry::find_tool("msvc", "msbuild") let tool = cc::windows_registry::find_tool("msvc", "devenv")
.expect("unable to locate MSVC compiler, did you install Visual Studio?"); .expect("unable to locate MSVC compiler, did you install Visual Studio?");
let mut vcvars_path = None; let mut vcvars_path = None;
let mut current_root = tool.path(); let mut current_root = tool.path();

View File

@ -41,7 +41,7 @@ const long DEFAULT_STYLE = wxSTAY_ON_TOP | wxFRAME_TOOL_WINDOW | wxRESIZE_BORDER
#endif #endif
#ifdef __LINUX__ #ifdef __LINUX__
const int SEARCH_BAR_FONT_SIZE = 20; const int SEARCH_BAR_FONT_SIZE = 20;
const long DEFAULT_STYLE = wxSTAY_ON_TOP | wxFRAME_TOOL_WINDOW | wxBORDER_NONE; const long DEFAULT_STYLE = wxSTAY_ON_TOP;
#endif #endif
const int HELP_TEXT_FONT_SIZE = 10; const int HELP_TEXT_FONT_SIZE = 10;

View File

@ -10,6 +10,7 @@ anyhow = "1.0.38"
thiserror = "1.0.23" thiserror = "1.0.23"
regex = "1.4.3" regex = "1.4.3"
lazy_static = "1.4.0" lazy_static = "1.4.0"
chrono = "0.4.19" chrono = {version = "0.4.19", features=["unstable-locales"]}
enum-as-inner = "0.3.3" enum-as-inner = "0.3.3"
rand = "0.8.3" rand = "0.8.3"
sys-locale = "0.1.0"

View File

@ -17,22 +17,30 @@
* along with espanso. If not, see <https://www.gnu.org/licenses/>. * along with espanso. If not, see <https://www.gnu.org/licenses/>.
*/ */
use chrono::{DateTime, Duration, Local}; use chrono::{DateTime, Duration, Local, Locale};
use crate::{Extension, ExtensionOutput, ExtensionResult, Number, Params, Value}; use crate::{Extension, ExtensionOutput, ExtensionResult, Number, Params, Value};
pub struct DateExtension { pub trait LocaleProvider {
fn get_system_locale(&self) -> String;
}
pub struct DateExtension<'a> {
fixed_date: Option<DateTime<Local>>, fixed_date: Option<DateTime<Local>>,
locale_provider: &'a dyn LocaleProvider,
} }
#[allow(clippy::new_without_default)] #[allow(clippy::new_without_default)]
impl DateExtension { impl<'a> DateExtension<'a> {
pub fn new() -> Self { pub fn new(locale_provider: &'a dyn LocaleProvider) -> Self {
Self { fixed_date: None } Self {
fixed_date: None,
locale_provider,
}
} }
} }
impl Extension for DateExtension { impl<'a> Extension for DateExtension<'a> {
fn name(&self) -> &str { fn name(&self) -> &str {
"date" "date"
} }
@ -53,9 +61,14 @@ impl Extension for DateExtension {
} }
let format = params.get("format"); let format = params.get("format");
let locale = params
.get("locale")
.and_then(|val| val.as_string())
.map(String::from)
.unwrap_or_else(|| self.locale_provider.get_system_locale());
let date = if let Some(Value::String(format)) = format { let date = if let Some(Value::String(format)) = format {
now.format(format).to_string() DateExtension::format_date_with_locale_string(now, format, &locale)
} else { } else {
now.to_rfc2822() now.to_rfc2822()
}; };
@ -64,7 +77,7 @@ impl Extension for DateExtension {
} }
} }
impl DateExtension { impl<'a> DateExtension<'a> {
fn get_date(&self) -> DateTime<Local> { fn get_date(&self) -> DateTime<Local> {
if let Some(fixed_date) = self.fixed_date { if let Some(fixed_date) = self.fixed_date {
fixed_date fixed_date
@ -72,6 +85,340 @@ impl DateExtension {
Local::now() Local::now()
} }
} }
fn format_date_with_locale(date: DateTime<Local>, format: &str, locale: Locale) -> String {
date.format_localized(format, locale).to_string()
}
fn format_date_with_locale_string(
date: DateTime<Local>,
format: &str,
locale_str: &str,
) -> String {
let locale = convert_locale_string_to_locale(locale_str).unwrap_or(Locale::en_US);
Self::format_date_with_locale(date, format, locale)
}
}
fn convert_locale_string_to_locale(locale_str: &str) -> Option<Locale> {
match locale_str {
"aa-DJ" => Some(Locale::aa_DJ),
"aa-ER" => Some(Locale::aa_ER),
"aa-ET" => Some(Locale::aa_ET),
"af-ZA" => Some(Locale::af_ZA),
"agr-PE" => Some(Locale::agr_PE),
"ak-GH" => Some(Locale::ak_GH),
"am-ET" => Some(Locale::am_ET),
"an-ES" => Some(Locale::an_ES),
"anp-IN" => Some(Locale::anp_IN),
"ar-AE" => Some(Locale::ar_AE),
"ar-BH" => Some(Locale::ar_BH),
"ar-DZ" => Some(Locale::ar_DZ),
"ar-EG" => Some(Locale::ar_EG),
"ar-IN" => Some(Locale::ar_IN),
"ar-IQ" => Some(Locale::ar_IQ),
"ar-JO" => Some(Locale::ar_JO),
"ar-KW" => Some(Locale::ar_KW),
"ar-LB" => Some(Locale::ar_LB),
"ar-LY" => Some(Locale::ar_LY),
"ar-MA" => Some(Locale::ar_MA),
"ar-OM" => Some(Locale::ar_OM),
"ar-QA" => Some(Locale::ar_QA),
"ar-SA" => Some(Locale::ar_SA),
"ar-SD" => Some(Locale::ar_SD),
"ar-SS" => Some(Locale::ar_SS),
"ar-SY" => Some(Locale::ar_SY),
"ar-TN" => Some(Locale::ar_TN),
"ar-YE" => Some(Locale::ar_YE),
"as-IN" => Some(Locale::as_IN),
"ast-ES" => Some(Locale::ast_ES),
"ayc-PE" => Some(Locale::ayc_PE),
"az-AZ" => Some(Locale::az_AZ),
"az-IR" => Some(Locale::az_IR),
"be-BY" => Some(Locale::be_BY),
"bem-ZM" => Some(Locale::bem_ZM),
"ber-DZ" => Some(Locale::ber_DZ),
"ber-MA" => Some(Locale::ber_MA),
"bg-BG" => Some(Locale::bg_BG),
"bhb-IN" => Some(Locale::bhb_IN),
"bho-IN" => Some(Locale::bho_IN),
"bho-NP" => Some(Locale::bho_NP),
"bi-VU" => Some(Locale::bi_VU),
"bn-BD" => Some(Locale::bn_BD),
"bn-IN" => Some(Locale::bn_IN),
"bo-CN" => Some(Locale::bo_CN),
"bo-IN" => Some(Locale::bo_IN),
"br-FR" => Some(Locale::br_FR),
"brx-IN" => Some(Locale::brx_IN),
"bs-BA" => Some(Locale::bs_BA),
"byn-ER" => Some(Locale::byn_ER),
"ca-AD" => Some(Locale::ca_AD),
"ca-ES" => Some(Locale::ca_ES),
"ca-FR" => Some(Locale::ca_FR),
"ca-IT" => Some(Locale::ca_IT),
"ce-RU" => Some(Locale::ce_RU),
"chr-US" => Some(Locale::chr_US),
"cmn-TW" => Some(Locale::cmn_TW),
"crh-UA" => Some(Locale::crh_UA),
"cs-CZ" => Some(Locale::cs_CZ),
"csb-PL" => Some(Locale::csb_PL),
"cv-RU" => Some(Locale::cv_RU),
"cy-GB" => Some(Locale::cy_GB),
"da-DK" => Some(Locale::da_DK),
"de-AT" => Some(Locale::de_AT),
"de-BE" => Some(Locale::de_BE),
"de-CH" => Some(Locale::de_CH),
"de-DE" => Some(Locale::de_DE),
"de-IT" => Some(Locale::de_IT),
"de-LI" => Some(Locale::de_LI),
"de-LU" => Some(Locale::de_LU),
"doi-IN" => Some(Locale::doi_IN),
"dsb-DE" => Some(Locale::dsb_DE),
"dv-MV" => Some(Locale::dv_MV),
"dz-BT" => Some(Locale::dz_BT),
"el-CY" => Some(Locale::el_CY),
"el-GR" => Some(Locale::el_GR),
"en-AG" => Some(Locale::en_AG),
"en-AU" => Some(Locale::en_AU),
"en-BW" => Some(Locale::en_BW),
"en-CA" => Some(Locale::en_CA),
"en-DK" => Some(Locale::en_DK),
"en-GB" => Some(Locale::en_GB),
"en-HK" => Some(Locale::en_HK),
"en-IE" => Some(Locale::en_IE),
"en-IL" => Some(Locale::en_IL),
"en-IN" => Some(Locale::en_IN),
"en-NG" => Some(Locale::en_NG),
"en-NZ" => Some(Locale::en_NZ),
"en-PH" => Some(Locale::en_PH),
"en-SC" => Some(Locale::en_SC),
"en-SG" => Some(Locale::en_SG),
"en-US" => Some(Locale::en_US),
"en-ZA" => Some(Locale::en_ZA),
"en-ZM" => Some(Locale::en_ZM),
"en-ZW" => Some(Locale::en_ZW),
"eo" => Some(Locale::eo),
"es-AR" => Some(Locale::es_AR),
"es-BO" => Some(Locale::es_BO),
"es-CL" => Some(Locale::es_CL),
"es-CO" => Some(Locale::es_CO),
"es-CR" => Some(Locale::es_CR),
"es-CU" => Some(Locale::es_CU),
"es-DO" => Some(Locale::es_DO),
"es-EC" => Some(Locale::es_EC),
"es-ES" => Some(Locale::es_ES),
"es-GT" => Some(Locale::es_GT),
"es-HN" => Some(Locale::es_HN),
"es-MX" => Some(Locale::es_MX),
"es-NI" => Some(Locale::es_NI),
"es-PA" => Some(Locale::es_PA),
"es-PE" => Some(Locale::es_PE),
"es-PR" => Some(Locale::es_PR),
"es-PY" => Some(Locale::es_PY),
"es-SV" => Some(Locale::es_SV),
"es-US" => Some(Locale::es_US),
"es-UY" => Some(Locale::es_UY),
"es-VE" => Some(Locale::es_VE),
"et-EE" => Some(Locale::et_EE),
"eu-ES" => Some(Locale::eu_ES),
"fa-IR" => Some(Locale::fa_IR),
"ff-SN" => Some(Locale::ff_SN),
"fi-FI" => Some(Locale::fi_FI),
"fil-PH" => Some(Locale::fil_PH),
"fo-FO" => Some(Locale::fo_FO),
"fr-BE" => Some(Locale::fr_BE),
"fr-CA" => Some(Locale::fr_CA),
"fr-CH" => Some(Locale::fr_CH),
"fr-FR" => Some(Locale::fr_FR),
"fr-LU" => Some(Locale::fr_LU),
"fur-IT" => Some(Locale::fur_IT),
"fy-DE" => Some(Locale::fy_DE),
"fy-NL" => Some(Locale::fy_NL),
"ga-IE" => Some(Locale::ga_IE),
"gd-GB" => Some(Locale::gd_GB),
"gez-ER" => Some(Locale::gez_ER),
"gez-ET" => Some(Locale::gez_ET),
"gl-ES" => Some(Locale::gl_ES),
"gu-IN" => Some(Locale::gu_IN),
"gv-GB" => Some(Locale::gv_GB),
"ha-NG" => Some(Locale::ha_NG),
"hak-TW" => Some(Locale::hak_TW),
"he-IL" => Some(Locale::he_IL),
"hi-IN" => Some(Locale::hi_IN),
"hif-FJ" => Some(Locale::hif_FJ),
"hne-IN" => Some(Locale::hne_IN),
"hr-HR" => Some(Locale::hr_HR),
"hsb-DE" => Some(Locale::hsb_DE),
"ht-HT" => Some(Locale::ht_HT),
"hu-HU" => Some(Locale::hu_HU),
"hy-AM" => Some(Locale::hy_AM),
"ia-FR" => Some(Locale::ia_FR),
"id-ID" => Some(Locale::id_ID),
"ig-NG" => Some(Locale::ig_NG),
"ik-CA" => Some(Locale::ik_CA),
"is-IS" => Some(Locale::is_IS),
"it-CH" => Some(Locale::it_CH),
"it-IT" => Some(Locale::it_IT),
"iu-CA" => Some(Locale::iu_CA),
"ja-JP" => Some(Locale::ja_JP),
"ka-GE" => Some(Locale::ka_GE),
"kab-DZ" => Some(Locale::kab_DZ),
"kk-KZ" => Some(Locale::kk_KZ),
"kl-GL" => Some(Locale::kl_GL),
"km-KH" => Some(Locale::km_KH),
"kn-IN" => Some(Locale::kn_IN),
"ko-KR" => Some(Locale::ko_KR),
"kok-IN" => Some(Locale::kok_IN),
"ks-IN" => Some(Locale::ks_IN),
"ku-TR" => Some(Locale::ku_TR),
"kw-GB" => Some(Locale::kw_GB),
"ky-KG" => Some(Locale::ky_KG),
"lb-LU" => Some(Locale::lb_LU),
"lg-UG" => Some(Locale::lg_UG),
"li-BE" => Some(Locale::li_BE),
"li-NL" => Some(Locale::li_NL),
"lij-IT" => Some(Locale::lij_IT),
"ln-CD" => Some(Locale::ln_CD),
"lo-LA" => Some(Locale::lo_LA),
"lt-LT" => Some(Locale::lt_LT),
"lv-LV" => Some(Locale::lv_LV),
"lzh-TW" => Some(Locale::lzh_TW),
"mag-IN" => Some(Locale::mag_IN),
"mai-IN" => Some(Locale::mai_IN),
"mai-NP" => Some(Locale::mai_NP),
"mfe-MU" => Some(Locale::mfe_MU),
"mg-MG" => Some(Locale::mg_MG),
"mhr-RU" => Some(Locale::mhr_RU),
"mi-NZ" => Some(Locale::mi_NZ),
"miq-NI" => Some(Locale::miq_NI),
"mjw-IN" => Some(Locale::mjw_IN),
"mk-MK" => Some(Locale::mk_MK),
"ml-IN" => Some(Locale::ml_IN),
"mn-MN" => Some(Locale::mn_MN),
"mni-IN" => Some(Locale::mni_IN),
"mnw-MM" => Some(Locale::mnw_MM),
"mr-IN" => Some(Locale::mr_IN),
"ms-MY" => Some(Locale::ms_MY),
"mt-MT" => Some(Locale::mt_MT),
"my-MM" => Some(Locale::my_MM),
"nan-TW" => Some(Locale::nan_TW),
"nb-NO" => Some(Locale::nb_NO),
"nds-DE" => Some(Locale::nds_DE),
"nds-NL" => Some(Locale::nds_NL),
"ne-NP" => Some(Locale::ne_NP),
"nhn-MX" => Some(Locale::nhn_MX),
"niu-NU" => Some(Locale::niu_NU),
"niu-NZ" => Some(Locale::niu_NZ),
"nl-AW" => Some(Locale::nl_AW),
"nl-BE" => Some(Locale::nl_BE),
"nl-NL" => Some(Locale::nl_NL),
"nn-NO" => Some(Locale::nn_NO),
"nr-ZA" => Some(Locale::nr_ZA),
"nso-ZA" => Some(Locale::nso_ZA),
"oc-FR" => Some(Locale::oc_FR),
"om-ET" => Some(Locale::om_ET),
"om-KE" => Some(Locale::om_KE),
"or-IN" => Some(Locale::or_IN),
"os-RU" => Some(Locale::os_RU),
"pa-IN" => Some(Locale::pa_IN),
"pa-PK" => Some(Locale::pa_PK),
"pap-AW" => Some(Locale::pap_AW),
"pap-CW" => Some(Locale::pap_CW),
"pl-PL" => Some(Locale::pl_PL),
"ps-AF" => Some(Locale::ps_AF),
"pt-BR" => Some(Locale::pt_BR),
"pt-PT" => Some(Locale::pt_PT),
"quz-PE" => Some(Locale::quz_PE),
"raj-IN" => Some(Locale::raj_IN),
"ro-RO" => Some(Locale::ro_RO),
"ru-RU" => Some(Locale::ru_RU),
"ru-UA" => Some(Locale::ru_UA),
"rw-RW" => Some(Locale::rw_RW),
"sa-IN" => Some(Locale::sa_IN),
"sah-RU" => Some(Locale::sah_RU),
"sat-IN" => Some(Locale::sat_IN),
"sc-IT" => Some(Locale::sc_IT),
"sd-IN" => Some(Locale::sd_IN),
"se-NO" => Some(Locale::se_NO),
"sgs-LT" => Some(Locale::sgs_LT),
"shn-MM" => Some(Locale::shn_MM),
"shs-CA" => Some(Locale::shs_CA),
"si-LK" => Some(Locale::si_LK),
"sid-ET" => Some(Locale::sid_ET),
"sk-SK" => Some(Locale::sk_SK),
"sl-SI" => Some(Locale::sl_SI),
"sm-WS" => Some(Locale::sm_WS),
"so-DJ" => Some(Locale::so_DJ),
"so-ET" => Some(Locale::so_ET),
"so-KE" => Some(Locale::so_KE),
"so-SO" => Some(Locale::so_SO),
"sq-AL" => Some(Locale::sq_AL),
"sq-MK" => Some(Locale::sq_MK),
"sr-ME" => Some(Locale::sr_ME),
"sr-RS" => Some(Locale::sr_RS),
"ss-ZA" => Some(Locale::ss_ZA),
"st-ZA" => Some(Locale::st_ZA),
"sv-FI" => Some(Locale::sv_FI),
"sv-SE" => Some(Locale::sv_SE),
"sw-KE" => Some(Locale::sw_KE),
"sw-TZ" => Some(Locale::sw_TZ),
"szl-PL" => Some(Locale::szl_PL),
"ta-IN" => Some(Locale::ta_IN),
"ta-LK" => Some(Locale::ta_LK),
"tcy-IN" => Some(Locale::tcy_IN),
"te-IN" => Some(Locale::te_IN),
"tg-TJ" => Some(Locale::tg_TJ),
"th-TH" => Some(Locale::th_TH),
"the-NP" => Some(Locale::the_NP),
"ti-ER" => Some(Locale::ti_ER),
"ti-ET" => Some(Locale::ti_ET),
"tig-ER" => Some(Locale::tig_ER),
"tk-TM" => Some(Locale::tk_TM),
"tl-PH" => Some(Locale::tl_PH),
"tn-ZA" => Some(Locale::tn_ZA),
"to-TO" => Some(Locale::to_TO),
"tpi-PG" => Some(Locale::tpi_PG),
"tr-CY" => Some(Locale::tr_CY),
"tr-TR" => Some(Locale::tr_TR),
"ts-ZA" => Some(Locale::ts_ZA),
"tt-RU" => Some(Locale::tt_RU),
"ug-CN" => Some(Locale::ug_CN),
"uk-UA" => Some(Locale::uk_UA),
"unm-US" => Some(Locale::unm_US),
"ur-IN" => Some(Locale::ur_IN),
"ur-PK" => Some(Locale::ur_PK),
"uz-UZ" => Some(Locale::uz_UZ),
"ve-ZA" => Some(Locale::ve_ZA),
"vi-VN" => Some(Locale::vi_VN),
"wa-BE" => Some(Locale::wa_BE),
"wae-CH" => Some(Locale::wae_CH),
"wal-ET" => Some(Locale::wal_ET),
"wo-SN" => Some(Locale::wo_SN),
"xh-ZA" => Some(Locale::xh_ZA),
"yi-US" => Some(Locale::yi_US),
"yo-NG" => Some(Locale::yo_NG),
"yue-HK" => Some(Locale::yue_HK),
"yuw-PG" => Some(Locale::yuw_PG),
"zh-CN" => Some(Locale::zh_CN),
"zh-HK" => Some(Locale::zh_HK),
"zh-SG" => Some(Locale::zh_SG),
"zh-TW" => Some(Locale::zh_TW),
"zu-ZA" => Some(Locale::zu_ZA),
_ => None,
}
}
pub struct DefaultLocaleProvider {}
impl LocaleProvider for DefaultLocaleProvider {
fn get_system_locale(&self) -> String {
sys_locale::get_locale().unwrap_or_else(|| String::from("en-US"))
}
}
#[allow(clippy::new_without_default)]
impl DefaultLocaleProvider {
pub fn new() -> Self {
Self {}
}
} }
#[cfg(test)] #[cfg(test)]
@ -79,9 +426,30 @@ mod tests {
use super::*; use super::*;
use chrono::offset::TimeZone; use chrono::offset::TimeZone;
struct MockLocaleProvider {
locale: String,
}
impl LocaleProvider for MockLocaleProvider {
fn get_system_locale(&self) -> String {
self.locale.clone()
}
}
impl MockLocaleProvider {
pub fn new() -> Self {
Self {
locale: "en-US".to_string(),
}
}
pub fn new_with_locale(locale: String) -> Self {
Self { locale }
}
}
#[test] #[test]
fn date_formatted_correctly() { fn date_formatted_correctly() {
let mut extension = DateExtension::new(); let locale_provider = MockLocaleProvider::new();
let mut extension = DateExtension::new(&locale_provider);
extension.fixed_date = Some(Local.ymd(2014, 7, 8).and_hms(9, 10, 11)); extension.fixed_date = Some(Local.ymd(2014, 7, 8).and_hms(9, 10, 11));
let param = vec![("format".to_string(), Value::String("%H:%M:%S".to_string()))] let param = vec![("format".to_string(), Value::String("%H:%M:%S".to_string()))]
@ -98,7 +466,8 @@ mod tests {
#[test] #[test]
fn offset_works_correctly() { fn offset_works_correctly() {
let mut extension = DateExtension::new(); let locale_provider = MockLocaleProvider::new();
let mut extension = DateExtension::new(&locale_provider);
extension.fixed_date = Some(Local.ymd(2014, 7, 8).and_hms(9, 10, 11)); extension.fixed_date = Some(Local.ymd(2014, 7, 8).and_hms(9, 10, 11));
let param = vec![ let param = vec![
@ -115,4 +484,61 @@ mod tests {
ExtensionOutput::Single("10:10:11".to_string()) ExtensionOutput::Single("10:10:11".to_string())
); );
} }
#[test]
fn default_locale_works_correctly() {
let locale_provider = MockLocaleProvider::new_with_locale("it-IT".to_string());
let mut extension = DateExtension::new(&locale_provider);
extension.fixed_date = Some(Local.ymd(2014, 7, 8).and_hms(9, 10, 11));
let param = vec![("format".to_string(), Value::String("%A".to_string()))]
.into_iter()
.collect::<Params>();
assert_eq!(
extension
.calculate(&Default::default(), &Default::default(), &param)
.into_success()
.unwrap(),
ExtensionOutput::Single("martedì".to_string())
);
}
#[test]
fn invalid_locale_should_default_to_en_us() {
let locale_provider = MockLocaleProvider::new_with_locale("invalid".to_string());
let mut extension = DateExtension::new(&locale_provider);
extension.fixed_date = Some(Local.ymd(2014, 7, 8).and_hms(9, 10, 11));
let param = vec![("format".to_string(), Value::String("%A".to_string()))]
.into_iter()
.collect::<Params>();
assert_eq!(
extension
.calculate(&Default::default(), &Default::default(), &param)
.into_success()
.unwrap(),
ExtensionOutput::Single("Tuesday".to_string())
);
}
#[test]
fn override_locale() {
let locale_provider = MockLocaleProvider::new();
let mut extension = DateExtension::new(&locale_provider);
extension.fixed_date = Some(Local.ymd(2014, 7, 8).and_hms(9, 10, 11));
let param = vec![
("format".to_string(), Value::String("%A".to_string())),
("locale".to_string(), Value::String("it-IT".to_string())),
]
.into_iter()
.collect::<Params>();
assert_eq!(
extension
.calculate(&Default::default(), &Default::default(), &param)
.into_success()
.unwrap(),
ExtensionOutput::Single("martedì".to_string())
);
}
} }

View File

@ -0,0 +1,99 @@
/*
* This file is part of espanso.
*
* Copyright (C) 2019-2022 Federico Terzi
*
* espanso is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* espanso is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with espanso. If not, see <https://www.gnu.org/licenses/>.
*/
pub enum MacShell {
Bash,
Sh,
Zsh,
}
// Determine the PATH env variable value available inside a regular terminal session
#[cfg(target_os = "macos")]
pub fn determine_path_env_variable_override(explicit_shell: Option<MacShell>) -> Option<String> {
let shell: MacShell = explicit_shell.or_else(determine_default_macos_shell)?;
match shell {
MacShell::Bash => {
launch_command_and_get_output("bash", &["--login", "-c", "source ~/.bashrc; echo $PATH"])
}
MacShell::Sh => launch_command_and_get_output("sh", &["--login", "-c", "echo $PATH"]),
MacShell::Zsh => {
launch_command_and_get_output("zsh", &["--login", "-c", "source ~/.zshrc; echo $PATH"])
}
}
}
#[cfg(not(target_os = "macos"))]
pub fn determine_path_env_variable_override(_: Option<MacShell>) -> Option<String> {
None
}
#[cfg(target_os = "macos")]
pub fn determine_default_macos_shell() -> Option<MacShell> {
use regex::Regex;
use std::process::Command;
let output = Command::new("sh")
.args(&["--login", "-c", "dscl . -read ~/ UserShell"])
.output()
.ok()?;
lazy_static! {
static ref EXTRACT_SHELL_REGEX: Regex =
Regex::new(r"UserShell:\s(.*)$").expect("unable to generate regex to extract default shell");
}
if !output.status.success() {
return None;
}
let output_str = String::from_utf8_lossy(&output.stdout);
let captures = EXTRACT_SHELL_REGEX.captures(output_str.trim())?;
let shell = captures.get(1)?.as_str().trim();
if shell.ends_with("/bash") {
Some(MacShell::Bash)
} else if shell.ends_with("/zsh") {
Some(MacShell::Zsh)
} else if shell.ends_with("/sh") {
Some(MacShell::Sh)
} else {
None
}
}
#[cfg(not(target_os = "macos"))]
pub fn determine_default_macos_shell() -> Option<MacShell> {
None
}
#[cfg(target_os = "macos")]
fn launch_command_and_get_output(command: &str, args: &[&str]) -> Option<String> {
use std::process::Command;
let output = Command::new(command).args(args).output().ok()?;
if !output.status.success() {
return None;
}
let output_str = String::from_utf8_lossy(&output.stdout);
Some(output_str.to_string())
}

View File

@ -1,7 +1,7 @@
/* /*
* This file is part of espanso. * This file is part of espanso.
* *
* Copyright (C) 2019-2021 Federico Terzi * Copyright (C) 2019-2022 Federico Terzi
* *
* espanso is free software: you can redistribute it and/or modify * espanso is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -21,6 +21,7 @@ pub mod choice;
pub mod clipboard; pub mod clipboard;
pub mod date; pub mod date;
pub mod echo; pub mod echo;
mod exec_util;
pub mod form; pub mod form;
pub mod random; pub mod random;
pub mod script; pub mod script;

View File

@ -1,7 +1,7 @@
/* /*
* This file is part of espanso. * This file is part of espanso.
* *
* Copyright (C) 2019-2021 Federico Terzi * Copyright (C) 2019-2022 Federico Terzi
* *
* espanso is free software: you can redistribute it and/or modify * espanso is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -24,7 +24,7 @@ use std::{
}; };
use crate::{Extension, ExtensionOutput, ExtensionResult, Params, Value}; use crate::{Extension, ExtensionOutput, ExtensionResult, Params, Value};
use log::{error, info}; use log::{debug, error, info};
use thiserror::Error; use thiserror::Error;
#[allow(clippy::upper_case_acronyms)] #[allow(clippy::upper_case_acronyms)]
@ -35,10 +35,16 @@ pub enum Shell {
WSL2, WSL2,
Bash, Bash,
Sh, Sh,
Zsh,
} }
impl Shell { impl Shell {
fn execute_cmd(&self, cmd: &str, vars: &HashMap<String, String>) -> std::io::Result<Output> { fn execute_cmd(
&self,
cmd: &str,
vars: &HashMap<String, String>,
override_path_on_macos: bool,
) -> std::io::Result<Output> {
let mut is_wsl = false; let mut is_wsl = false;
let mut command = match self { let mut command = match self {
@ -74,6 +80,11 @@ impl Shell {
command.args(&["-c", cmd]); command.args(&["-c", cmd]);
command command
} }
Shell::Zsh => {
let mut command = Command::new("zsh");
command.args(&["-c", cmd]);
command
}
}; };
// Set the OS-specific flags // Set the OS-specific flags
@ -84,6 +95,27 @@ impl Shell {
command.env(key, value); command.env(key, value);
} }
// If Espanso is executed as an app bundle on macOS, it doesn't inherit the PATH
// environment variables that are available inside a terminal, and this can be confusing for users.
// For example, one might use "jq" inside the terminal but then it throws an error with "command not found"
// if launched through the Espanso shell extension.
// For this reason, Espanso tries to obtain the same PATH value by spawning a login shell and extracting
// the PATH after the processing.
if cfg!(target_os = "macos") && override_path_on_macos {
let supported_mac_shell = match self {
Shell::Bash => Some(super::exec_util::MacShell::Bash),
Shell::Sh => Some(super::exec_util::MacShell::Sh),
Shell::Zsh => Some(super::exec_util::MacShell::Zsh),
_ => None,
};
if let Some(path_env_override) =
super::exec_util::determine_path_env_variable_override(supported_mac_shell)
{
debug!("overriding PATH env variable with: {}", path_env_override);
command.env("PATH", path_env_override);
}
}
// In WSL environment, we have to specify which ENV variables // In WSL environment, we have to specify which ENV variables
// should be passed to linux. // should be passed to linux.
// For more information: https://devblogs.microsoft.com/commandline/share-environment-vars-between-wsl-and-windows/ // For more information: https://devblogs.microsoft.com/commandline/share-environment-vars-between-wsl-and-windows/
@ -110,6 +142,7 @@ impl Shell {
"wsl2" => Some(Shell::WSL2), "wsl2" => Some(Shell::WSL2),
"bash" => Some(Shell::Bash), "bash" => Some(Shell::Bash),
"sh" => Some(Shell::Sh), "sh" => Some(Shell::Sh),
"zsh" => Some(Shell::Zsh),
_ => None, _ => None,
} }
} }
@ -120,7 +153,17 @@ impl Default for Shell {
if cfg!(target_os = "windows") { if cfg!(target_os = "windows") {
Shell::Powershell Shell::Powershell
} else if cfg!(target_os = "macos") { } else if cfg!(target_os = "macos") {
Shell::Sh lazy_static! {
static ref DEFAULT_MACOS_SHELL: Option<super::exec_util::MacShell> =
super::exec_util::determine_default_macos_shell();
}
match *DEFAULT_MACOS_SHELL {
Some(super::exec_util::MacShell::Bash) => Shell::Bash,
Some(super::exec_util::MacShell::Sh) => Shell::Sh,
Some(super::exec_util::MacShell::Zsh) => Shell::Zsh,
None => Shell::Sh,
}
} else if cfg!(target_os = "linux") { } else if cfg!(target_os = "linux") {
Shell::Bash Shell::Bash
} else { } else {
@ -172,7 +215,13 @@ impl Extension for ShellExtension {
self.config_path.to_string_lossy().to_string(), self.config_path.to_string_lossy().to_string(),
); );
match shell.execute_cmd(cmd, &env_variables) { let macos_override_path = params
.get("macos_override_path")
.and_then(|v| v.as_bool())
.copied()
.unwrap_or(true);
match shell.execute_cmd(cmd, &env_variables, macos_override_path) {
Ok(output) => { Ok(output) => {
let output_str = String::from_utf8_lossy(&output.stdout); let output_str = String::from_utf8_lossy(&output.stdout);
let error_str = String::from_utf8_lossy(&output.stderr); let error_str = String::from_utf8_lossy(&output.stderr);

View File

@ -31,4 +31,4 @@ notify-rust = "4.2.2"
crossbeam = "0.8.0" crossbeam = "0.8.0"
[build-dependencies] [build-dependencies]
cc = "1.0.66" cc = "1.0.73"

View File

@ -1,6 +1,6 @@
[package] [package]
name = "espanso" name = "espanso"
version = "2.1.3-alpha" version = "2.1.4-beta"
authors = ["Federico Terzi <federicoterzi96@gmail.com>"] authors = ["Federico Terzi <federicoterzi96@gmail.com>"]
license = "GPL-3.0" license = "GPL-3.0"
description = "Cross-platform Text Expander written in Rust" description = "Cross-platform Text Expander written in Rust"
@ -81,7 +81,7 @@ section = "utility"
license-file = ["../LICENSE", "1"] license-file = ["../LICENSE", "1"]
[package.metadata.deb.variants.wayland] [package.metadata.deb.variants.wayland]
depends = "$auto, systemd, libnotify-bin, libxkbcommon0, libwxgtk3.0-gtk3-0v5" depends = "$auto, systemd, libnotify-bin, libxkbcommon0, libwxgtk3.0-gtk3-0v5, wl-clipboard"
# TODO: once this issue [1] is fixed, we should create a variant for # TODO: once this issue [1] is fixed, we should create a variant for
# wayland to automatically run the setcap script. # wayland to automatically run the setcap script.
# [1]: https://github.com/mmstick/cargo-deb/issues/151 # [1]: https://github.com/mmstick/cargo-deb/issues/151

View File

@ -197,3 +197,15 @@ impl<'a> espanso_engine::process::EnabledStatusProvider for ConfigManager<'a> {
self.active().enable() self.active().enable()
} }
} }
impl<'a> crate::gui::modulo::form::ModuloFormUIOptionProvider for ConfigManager<'a> {
fn get_post_form_delay(&self) -> usize {
self.active().post_form_delay()
}
}
impl<'a> crate::gui::modulo::search::ModuloSearchUIOptionProvider for ConfigManager<'a> {
fn get_post_search_delay(&self) -> usize {
self.active().post_search_delay()
}
}

View File

@ -107,8 +107,10 @@ pub fn initialize_and_spawn(
let default_config = &*config_manager.default(); let default_config = &*config_manager.default();
let modulo_manager = crate::gui::modulo::manager::ModuloManager::new(); let modulo_manager = crate::gui::modulo::manager::ModuloManager::new();
let modulo_form_ui = crate::gui::modulo::form::ModuloFormUI::new(&modulo_manager); let modulo_form_ui =
let modulo_search_ui = crate::gui::modulo::search::ModuloSearchUI::new(&modulo_manager); crate::gui::modulo::form::ModuloFormUI::new(&modulo_manager, &config_manager);
let modulo_search_ui =
crate::gui::modulo::search::ModuloSearchUI::new(&modulo_manager, &config_manager);
let modulo_text_ui = crate::gui::modulo::textview::ModuloTextUI::new(&modulo_manager); let modulo_text_ui = crate::gui::modulo::textview::ModuloTextUI::new(&modulo_manager);
let context: Box<dyn Context> = Box::new(super::context::DefaultContext::new( let context: Box<dyn Context> = Box::new(super::context::DefaultContext::new(
@ -186,7 +188,8 @@ pub fn initialize_and_spawn(
let clipboard_adapter = ClipboardAdapter::new(&*clipboard, &config_manager); let clipboard_adapter = ClipboardAdapter::new(&*clipboard, &config_manager);
let clipboard_extension = let clipboard_extension =
espanso_render::extension::clipboard::ClipboardExtension::new(&clipboard_adapter); espanso_render::extension::clipboard::ClipboardExtension::new(&clipboard_adapter);
let date_extension = espanso_render::extension::date::DateExtension::new(); let locale_provider = espanso_render::extension::date::DefaultLocaleProvider::new();
let date_extension = espanso_render::extension::date::DateExtension::new(&locale_provider);
let echo_extension = espanso_render::extension::echo::EchoExtension::new(); let echo_extension = espanso_render::extension::echo::EchoExtension::new();
// For backwards compatiblity purposes, the echo extension can also be called with "dummy" type // For backwards compatiblity purposes, the echo extension can also be called with "dummy" type
let dummy_extension = espanso_render::extension::echo::EchoExtension::new_with_alias("dummy"); let dummy_extension = espanso_render::extension::echo::EchoExtension::new_with_alias("dummy");

View File

@ -20,18 +20,30 @@
use serde::Serialize; use serde::Serialize;
use serde_json::{json, Map, Value}; use serde_json::{json, Map, Value};
use std::collections::HashMap; use std::collections::HashMap;
use std::convert::TryInto;
use crate::gui::{FormField, FormUI}; use crate::gui::{FormField, FormUI};
use super::manager::ModuloManager; use super::manager::ModuloManager;
pub trait ModuloFormUIOptionProvider {
fn get_post_form_delay(&self) -> usize;
}
pub struct ModuloFormUI<'a> { pub struct ModuloFormUI<'a> {
manager: &'a ModuloManager, manager: &'a ModuloManager,
option_provider: &'a dyn ModuloFormUIOptionProvider,
} }
impl<'a> ModuloFormUI<'a> { impl<'a> ModuloFormUI<'a> {
pub fn new(manager: &'a ModuloManager) -> Self { pub fn new(
Self { manager } manager: &'a ModuloManager,
option_provider: &'a dyn ModuloFormUIOptionProvider,
) -> Self {
Self {
manager,
option_provider,
}
} }
} }
@ -52,7 +64,7 @@ impl<'a> FormUI for ModuloFormUI<'a> {
.manager .manager
.invoke(&["form", "-j", "-i", "-"], &json_config)?; .invoke(&["form", "-j", "-i", "-"], &json_config)?;
let json: Result<HashMap<String, String>, _> = serde_json::from_str(&output); let json: Result<HashMap<String, String>, _> = serde_json::from_str(&output);
match json { let result = match json {
Ok(json) => { Ok(json) => {
if json.is_empty() { if json.is_empty() {
Ok(None) Ok(None)
@ -61,7 +73,16 @@ impl<'a> FormUI for ModuloFormUI<'a> {
} }
} }
Err(error) => Err(error.into()), Err(error) => Err(error.into()),
};
let post_form_delay = self.option_provider.get_post_form_delay();
if post_form_delay > 0 {
std::thread::sleep(std::time::Duration::from_millis(
post_form_delay.try_into().unwrap(),
));
} }
result
} }
} }

View File

@ -105,6 +105,13 @@ impl ModuloManager {
error!("modulo reported an error: {}", error); error!("modulo reported an error: {}", error);
} }
if !child_output.status.success() {
error!(
"modulo exited with non-zero status code: {:?}",
child_output.status.code()
)
}
if !output.trim().is_empty() { if !output.trim().is_empty() {
Ok(output.to_string()) Ok(output.to_string())
} else { } else {

View File

@ -19,19 +19,30 @@
use serde::Serialize; use serde::Serialize;
use serde_json::Value; use serde_json::Value;
use std::collections::HashMap; use std::{collections::HashMap, convert::TryInto};
use crate::gui::{SearchItem, SearchUI}; use crate::gui::{SearchItem, SearchUI};
use super::manager::ModuloManager; use super::manager::ModuloManager;
pub trait ModuloSearchUIOptionProvider {
fn get_post_search_delay(&self) -> usize;
}
pub struct ModuloSearchUI<'a> { pub struct ModuloSearchUI<'a> {
manager: &'a ModuloManager, manager: &'a ModuloManager,
option_provider: &'a dyn ModuloSearchUIOptionProvider,
} }
impl<'a> ModuloSearchUI<'a> { impl<'a> ModuloSearchUI<'a> {
pub fn new(manager: &'a ModuloManager) -> Self { pub fn new(
Self { manager } manager: &'a ModuloManager,
option_provider: &'a dyn ModuloSearchUIOptionProvider,
) -> Self {
Self {
manager,
option_provider,
}
} }
} }
@ -48,7 +59,7 @@ impl<'a> SearchUI for ModuloSearchUI<'a> {
.manager .manager
.invoke(&["search", "-j", "-i", "-"], &json_config)?; .invoke(&["search", "-j", "-i", "-"], &json_config)?;
let json: Result<HashMap<String, Value>, _> = serde_json::from_str(&output); let json: Result<HashMap<String, Value>, _> = serde_json::from_str(&output);
match json { let result = match json {
Ok(json) => { Ok(json) => {
if let Some(Value::String(selected_id)) = json.get("selected") { if let Some(Value::String(selected_id)) = json.get("selected") {
Ok(Some(selected_id.clone())) Ok(Some(selected_id.clone()))
@ -57,7 +68,16 @@ impl<'a> SearchUI for ModuloSearchUI<'a> {
} }
} }
Err(error) => Err(error.into()), Err(error) => Err(error.into()),
};
let post_search_delay = self.option_provider.get_post_search_delay();
if post_search_delay > 0 {
std::thread::sleep(std::time::Duration::from_millis(
post_search_delay.try_into().unwrap(),
));
} }
result
} }
} }

View File

@ -48,6 +48,8 @@ generate_patchable_config!(
backspace_limit -> usize, backspace_limit -> usize,
apply_patch -> bool, apply_patch -> bool,
undo_backspace -> bool, undo_backspace -> bool,
post_form_delay -> usize,
post_search_delay -> usize,
win32_exclude_orphan_events -> bool, win32_exclude_orphan_events -> bool,
win32_keyboard_layout_cache_interval -> i64, win32_keyboard_layout_cache_interval -> i64,
x11_use_xclip_backend -> bool, x11_use_xclip_backend -> bool,

View File

@ -1,6 +1,6 @@
//! ```cargo //! ```cargo
//! [dependencies] //! [dependencies]
//! cc = "1.0.66" //! cc = "1.0.73"
//! glob = "0.3.0" //! glob = "0.3.0"
//! envmnt = "*" //! envmnt = "*"
//! ``` //! ```
@ -26,7 +26,7 @@ fn main() {
// First, we try to find the directory containing the various versions: // First, we try to find the directory containing the various versions:
// C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Redist\MSVC\ // C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Redist\MSVC\
let tool = cc::windows_registry::find_tool("msvc", "msbuild") let tool = cc::windows_registry::find_tool("msvc", "devenv")
.expect("unable to locate MSVC compiler, did you install Visual Studio?"); .expect("unable to locate MSVC compiler, did you install Visual Studio?");
let mut versions_dir = None; let mut versions_dir = None;
let mut current_root = tool.path(); let mut current_root = tool.path();
@ -56,8 +56,15 @@ fn main() {
.expect("unable to extract path of vcruntime140_1.dll file"); .expect("unable to extract path of vcruntime140_1.dll file");
// Copy the DLLs in the target directory // Copy the DLLs in the target directory
let parent_dir = target_file.parent().expect("unable to obtain directory containing DLLs"); let parent_dir = target_file
for entry in glob::glob(&format!(r"{}\*.dll", parent_dir.to_string_lossy().to_string())).expect("unable to glob over DLLs") { .parent()
.expect("unable to obtain directory containing DLLs");
for entry in glob::glob(&format!(
r"{}\*.dll",
parent_dir.to_string_lossy().to_string()
))
.expect("unable to glob over DLLs")
{
let entry = entry.expect("unable to unwrap DLL entry"); let entry = entry.expect("unable to unwrap DLL entry");
let filename = entry.file_name().expect("unable to obtain filename"); let filename = entry.file_name().expect("unable to obtain filename");
std::fs::copy(&entry, target_dir.join(filename)).expect("unable to copy DLL"); std::fs::copy(&entry, target_dir.join(filename)).expect("unable to copy DLL");

View File

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