From 0c59d79a0b4851e3961de304ba4057ba79d7a735 Mon Sep 17 00:00:00 2001 From: Federico Terzi Date: Thu, 3 Sep 2020 21:37:15 +0200 Subject: [PATCH 01/11] Fix #422 --- native/libwinbridge/bridge.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/native/libwinbridge/bridge.cpp b/native/libwinbridge/bridge.cpp index 6db15c9..6a57002 100644 --- a/native/libwinbridge/bridge.cpp +++ b/native/libwinbridge/bridge.cpp @@ -808,7 +808,7 @@ int32_t set_clipboard(wchar_t *text) { } int32_t get_clipboard(wchar_t *buffer, int32_t size) { - int32_t result = 0; + int32_t result = 1; if (!OpenClipboard(NULL)) { return -1; } From 66f4d0964b0b896cf7dcafaac438a7c64714cb3f Mon Sep 17 00:00:00 2001 From: Federico Terzi Date: Thu, 3 Sep 2020 21:45:25 +0200 Subject: [PATCH 02/11] Version bump --- Cargo.lock | 2 +- Cargo.toml | 2 +- snapcraft.yaml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9f83030..1aa2556 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -371,7 +371,7 @@ dependencies = [ [[package]] name = "espanso" -version = "0.7.1" +version = "0.7.2" 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)", diff --git a/Cargo.toml b/Cargo.toml index 88e884c..14d9039 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "espanso" -version = "0.7.1" +version = "0.7.2" authors = ["Federico Terzi "] license = "GPL-3.0" description = "Cross-platform Text Expander written in Rust" diff --git a/snapcraft.yaml b/snapcraft.yaml index 5af5017..43e79e5 100644 --- a/snapcraft.yaml +++ b/snapcraft.yaml @@ -1,5 +1,5 @@ name: espanso -version: 0.7.1 +version: 0.7.2 summary: A Cross-platform Text Expander written in Rust description: | espanso is a Cross-platform, Text Expander written in Rust. From e94567b37b00a51c740066e453c132339e3d1e06 Mon Sep 17 00:00:00 2001 From: Federico Terzi Date: Tue, 8 Sep 2020 21:40:35 +0200 Subject: [PATCH 03/11] Add modulo app stub auto-generation draft. #430 --- src/res/mac/modulo.plist | 28 ++++++++++++++++++++++++++++ src/ui/modulo/mac.rs | 38 ++++++++++++++++++++++++++++++++++++++ src/ui/modulo/mod.rs | 11 +++++++++++ 3 files changed, 77 insertions(+) create mode 100644 src/res/mac/modulo.plist create mode 100644 src/ui/modulo/mac.rs diff --git a/src/res/mac/modulo.plist b/src/res/mac/modulo.plist new file mode 100644 index 0000000..dd37ecd --- /dev/null +++ b/src/res/mac/modulo.plist @@ -0,0 +1,28 @@ + + + + + CFBundleDevelopmentRegion + English + CFBundleExecutable + {{{modulo_path}}} + CFBundleIconFile + + CFBundleIdentifier + com.federicoterzi.modulo + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + Modulo + CFBundlePackageType + APPL + CFBundleSignature + ???? + CFBundleVersion + 1.0 + NSMainNibFile + MainMenu + NSPrincipalClass + NSApplication + + diff --git a/src/ui/modulo/mac.rs b/src/ui/modulo/mac.rs new file mode 100644 index 0000000..1fc150b --- /dev/null +++ b/src/ui/modulo/mac.rs @@ -0,0 +1,38 @@ +use log::info; +use std::os::unix::fs::symlink; + +const MODULO_APP_BUNDLE_NAME: &str = "Modulo.app"; +const MODULO_APP_BUNDLE_PLIST_CONTENT: &'static str = include_str!("../../res/mac/modulo.plist"); + +pub fn generate_modulo_app_bundle(modulo_path: &str) -> Result { + let data_dir = crate::context::get_data_dir(); + + let modulo_app_dir = data_dir.join(MODULO_APP_BUNDLE_NAME); + + // Remove previous bundle if present + if modulo_app_dir.exists() { + std::fs::remove_dir_all(&modulo_app_dir)?; + } + + // Recreate the App bundle stub + std::fs::create_dir(&modulo_app_dir)?; + + let contents_dir = modulo_app_dir.join("Contents"); + std::fs::create_dir(&contents_dir)?; + + let macos_dir = contents_dir.join("MacOS"); + std::fs::create_dir(&macos_dir)?; + + // Generate the Plist file + let plist_content = MODULO_APP_BUNDLE_PLIST_CONTENT.replace("{{{modulo_path}}}", modulo_path); + let plist_file = contents_dir.join("Info.plist"); + std::fs::write(plist_file, plist_content)?; + + // Generate the symbolic link to the modulo binary + let target_link = macos_dir.join("modulo"); + symlink(modulo_path, &target_link)?; + + info!("Created Modulo APP stub at: {:?}", &target_link); + + Ok(target_link) +} \ No newline at end of file diff --git a/src/ui/modulo/mod.rs b/src/ui/modulo/mod.rs index 1a19d2d..279f00d 100644 --- a/src/ui/modulo/mod.rs +++ b/src/ui/modulo/mod.rs @@ -3,6 +3,9 @@ use log::{error, info}; use std::io::{Error, Write}; use std::process::{Child, Command, Output}; +#[cfg(target_os = "macos")] +mod mac; + pub struct ModuloManager { modulo_path: Option, } @@ -41,6 +44,14 @@ impl ModuloManager { } } + // TODO: explain why + #[cfg(target_os = "macos")] + { + modulo_path = generate_modulo_app_bundle(&modulo_path).expect("unable to generate modulo app stub") + .to_string_lossy().to_string(); + } + + if let Some(ref modulo_path) = modulo_path { info!("Using modulo at {:?}", modulo_path); } From fbfafe3564f3f8e213f2e2aa7e563d9e89f641fb Mon Sep 17 00:00:00 2001 From: Freddy Date: Tue, 8 Sep 2020 22:00:24 +0200 Subject: [PATCH 04/11] Improve modulo app bundle generation. Fix #430 --- Cargo.toml | 2 +- src/ui/modulo/mac.rs | 17 ++++++++++++++++- src/ui/modulo/mod.rs | 21 ++++++++++++--------- 3 files changed, 29 insertions(+), 11 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 14d9039..c9d4457 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,7 +10,7 @@ edition = "2018" build="build.rs" [modulo] -version = "0.1.0" +version = "0.1.1" [dependencies] widestring = "0.4.0" diff --git a/src/ui/modulo/mac.rs b/src/ui/modulo/mac.rs index 1fc150b..e6bd7e9 100644 --- a/src/ui/modulo/mac.rs +++ b/src/ui/modulo/mac.rs @@ -1,10 +1,25 @@ use log::info; +use std::path::PathBuf; use std::os::unix::fs::symlink; const MODULO_APP_BUNDLE_NAME: &str = "Modulo.app"; const MODULO_APP_BUNDLE_PLIST_CONTENT: &'static str = include_str!("../../res/mac/modulo.plist"); pub fn generate_modulo_app_bundle(modulo_path: &str) -> Result { + let modulo_pathbuf = PathBuf::from(modulo_path); + let modulo_path: String = if !modulo_pathbuf.exists() { + // If modulo was taken from the PATH, we need to calculate the absolute path + // To do so, we use the `which` command + let output = std::process::Command::new("which").arg("modulo").output().expect("unable to call 'which' command to determine modulo's full path"); + let path = String::from_utf8_lossy(output.stdout.as_slice()); + let path = path.trim(); + + info!("Detected modulo's full path: {:?}", &path); + path.to_string() + }else{ + modulo_path.to_owned() + }; + let data_dir = crate::context::get_data_dir(); let modulo_app_dir = data_dir.join(MODULO_APP_BUNDLE_NAME); @@ -24,7 +39,7 @@ pub fn generate_modulo_app_bundle(modulo_path: &str) -> Result Date: Wed, 9 Sep 2020 19:55:05 +0200 Subject: [PATCH 05/11] Wait until modifier keys are released after form on Windows. Fix #440 --- native/libwinbridge/bridge.cpp | 19 +++++++++++++++++++ native/libwinbridge/bridge.h | 5 +++++ src/bridge/windows.rs | 1 + src/extension/form.rs | 14 +++++++++++--- src/keyboard/mod.rs | 2 +- src/keyboard/windows.rs | 12 ++++++++++++ 6 files changed, 49 insertions(+), 4 deletions(-) diff --git a/native/libwinbridge/bridge.cpp b/native/libwinbridge/bridge.cpp index 6a57002..ede2528 100644 --- a/native/libwinbridge/bridge.cpp +++ b/native/libwinbridge/bridge.cpp @@ -663,6 +663,25 @@ void trigger_copy() { SendInput(vec.size(), vec.data(), sizeof(INPUT)); } +int32_t are_modifiers_pressed() { + short ctrl_pressed = GetAsyncKeyState(VK_CONTROL); + short enter_pressed = GetAsyncKeyState(VK_RETURN); + short alt_pressed = GetAsyncKeyState(VK_MENU); + short shift_pressed = GetAsyncKeyState(VK_SHIFT); + short meta_pressed = GetAsyncKeyState(VK_LWIN); + short rmeta_pressed = GetAsyncKeyState(VK_RWIN); + if (((ctrl_pressed & 0x8000) + + (enter_pressed & 0x8000) + + (alt_pressed & 0x8000) + + (shift_pressed & 0x8000) + + (meta_pressed & 0x8000) + + (rmeta_pressed & 0x8000)) != 0) { + return 1; + } + + return 0; +} + // SYSTEM diff --git a/native/libwinbridge/bridge.h b/native/libwinbridge/bridge.h index 7db802a..ae57213 100644 --- a/native/libwinbridge/bridge.h +++ b/native/libwinbridge/bridge.h @@ -97,6 +97,11 @@ extern "C" void trigger_shift_paste(); */ extern "C" void trigger_copy(); +/* + * Check whether keyboard modifiers (CTRL, CMD, SHIFT, ecc) are pressed + */ +extern "C" int32_t are_modifiers_pressed(); + // Detect current application commands /* diff --git a/src/bridge/windows.rs b/src/bridge/windows.rs index dd7ed32..554056f 100644 --- a/src/bridge/windows.rs +++ b/src/bridge/windows.rs @@ -69,6 +69,7 @@ extern "C" { pub fn trigger_paste(); pub fn trigger_shift_paste(); pub fn trigger_copy(); + pub fn are_modifiers_pressed() -> i32; // PROCESSES diff --git a/src/extension/form.rs b/src/extension/form.rs index c0a3361..00ac091 100644 --- a/src/extension/form.rs +++ b/src/extension/form.rs @@ -74,7 +74,7 @@ impl super::Extension for FormExtension { .manager .invoke(&["form", "-i", "-"], &serialized_config); - // On macOS, after the form closes we have to wait until the user releases the modifier keys + // On macOS and Windows, after the form closes we have to wait until the user releases the modifier keys on_form_close(); if let Some(output) = output { @@ -95,9 +95,17 @@ impl super::Extension for FormExtension { } } -#[cfg(not(target_os = "macos"))] +#[cfg(target_os = "linux")] fn on_form_close() { - // NOOP on Windows and Linux + // NOOP on Linux +} + +#[cfg(target_os = "windows")] +fn on_form_close() { + let released = crate::keyboard::windows::wait_for_modifiers_release(); + if !released { + warn!("Wait for modifiers release timed out! Please after closing the form, release your modifiers keys (CTRL, CMD, ALT, SHIFT)"); + } } #[cfg(target_os = "macos")] diff --git a/src/keyboard/mod.rs b/src/keyboard/mod.rs index dece480..7a5c2ca 100644 --- a/src/keyboard/mod.rs +++ b/src/keyboard/mod.rs @@ -21,7 +21,7 @@ use crate::config::Configs; use serde::{Deserialize, Serialize}; #[cfg(target_os = "windows")] -mod windows; +pub mod windows; #[cfg(target_os = "linux")] mod linux; diff --git a/src/keyboard/windows.rs b/src/keyboard/windows.rs index be2cbb2..55b1b1d 100644 --- a/src/keyboard/windows.rs +++ b/src/keyboard/windows.rs @@ -78,3 +78,15 @@ impl super::KeyboardManager for WindowsKeyboardManager { } } } + +pub fn wait_for_modifiers_release() -> bool { + let start = std::time::SystemTime::now(); + while start.elapsed().unwrap_or_default().as_millis() < 3000 { + let pressed = unsafe { crate::bridge::windows::are_modifiers_pressed() }; + if pressed == 0 { + return true; + } + std::thread::sleep(std::time::Duration::from_millis(100)); + } + false +} \ No newline at end of file From 8f8d5f2f1c717303eff9feb729e09d40c98380af Mon Sep 17 00:00:00 2001 From: Federico Terzi Date: Wed, 16 Sep 2020 19:49:12 +0200 Subject: [PATCH 06/11] Add proxy setting for install command. Fix #408 --- src/main.rs | 16 +++++++++++++--- src/package/default.rs | 22 ++++++++++++---------- src/package/git.rs | 33 --------------------------------- src/package/mod.rs | 4 +++- src/package/zip.rs | 15 ++++++++++++--- 5 files changed, 40 insertions(+), 50 deletions(-) delete mode 100644 src/package/git.rs diff --git a/src/main.rs b/src/main.rs index 6158d38..f35abe6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -97,7 +97,9 @@ fn main() { .help("(Optional) Link to GitHub repository") .required(false) .default_value("hub"), - ); + ) + .arg(Arg::with_name("proxy").help("Use a proxy, should be used as --proxy=https://proxy:1234") + .required(false).long("proxy").takes_value(true)); let uninstall_subcommand = SubCommand::with_name("uninstall") .about("Remove an installed package. Equivalent to 'espanso package uninstall'") @@ -1096,6 +1098,14 @@ fn install_main(_config_set: ConfigSet, matches: &ArgMatches) { repository = repository.trim_end_matches(".git") } + let proxy = match matches.value_of("proxy") { + Some(proxy) => { + println!("Using proxy: {}", proxy); + Some(proxy.to_string()) + } + None => {None} + }; + let package_resolver = Box::new(ZipPackageResolver::new()); let allow_external: bool = if matches.is_present("external") { @@ -1131,7 +1141,7 @@ fn install_main(_config_set: ConfigSet, matches: &ArgMatches) { println!("Using cached package index, run 'espanso package refresh' to update it.") } - package_manager.install_package(package_name, allow_external) + package_manager.install_package(package_name, allow_external, proxy) } else { // Make sure the repo is a valid github url lazy_static! { @@ -1147,7 +1157,7 @@ fn install_main(_config_set: ConfigSet, matches: &ArgMatches) { if !allow_external { Ok(InstallResult::BlockedExternalPackage(repository.to_owned())) } else { - package_manager.install_package_from_repo(package_name, repository) + package_manager.install_package_from_repo(package_name, repository, proxy) } }; diff --git a/src/package/default.rs b/src/package/default.rs index e2cc3c9..69182e3 100644 --- a/src/package/default.rs +++ b/src/package/default.rs @@ -264,12 +264,13 @@ impl super::PackageManager for DefaultPackageManager { &self, name: &str, allow_external: bool, + proxy: Option, ) -> Result> { let package = self.get_package(name); match package { Some(package) => { if package.is_core || allow_external { - self.install_package_from_repo(name, &package.repo) + self.install_package_from_repo(name, &package.repo, proxy) } else { Ok(BlockedExternalPackage(package.original_repo)) } @@ -282,6 +283,7 @@ impl super::PackageManager for DefaultPackageManager { &self, name: &str, repo_url: &str, + proxy: Option, ) -> Result> { // Check if package is already installed let packages = self.list_local_packages_names(); @@ -294,7 +296,7 @@ impl super::PackageManager for DefaultPackageManager { .package_resolver .as_ref() .unwrap() - .clone_repo_to_temp(repo_url)?; + .clone_repo_to_temp(repo_url, proxy)?; let temp_package_dir = temp_dir.path().join(name); if !temp_package_dir.exists() { @@ -532,7 +534,7 @@ mod tests { assert_eq!( temp.package_manager - .install_package("doesnotexist", false) + .install_package("doesnotexist", false, None) .unwrap(), NotFoundInIndex ); @@ -548,7 +550,7 @@ mod tests { assert_eq!( temp.package_manager - .install_package("italian-accents", false) + .install_package("italian-accents", false, None) .unwrap(), AlreadyInstalled ); @@ -563,7 +565,7 @@ mod tests { assert_eq!( temp.package_manager - .install_package("dummy-package", false) + .install_package("dummy-package", false, None) .unwrap(), Installed ); @@ -589,7 +591,7 @@ mod tests { assert_eq!( temp.package_manager - .install_package("not-existing", false) + .install_package("not-existing", false, None) .unwrap(), NotFoundInRepo ); @@ -604,7 +606,7 @@ mod tests { assert_eq!( temp.package_manager - .install_package("dummy-package2", false) + .install_package("dummy-package2", false, None) .unwrap(), MissingPackageVersion ); @@ -619,7 +621,7 @@ mod tests { assert_eq!( temp.package_manager - .install_package("dummy-package3", false) + .install_package("dummy-package3", false, None) .unwrap(), UnableToParsePackageInfo ); @@ -634,7 +636,7 @@ mod tests { assert_eq!( temp.package_manager - .install_package("dummy-package4", false) + .install_package("dummy-package4", false, None) .unwrap(), UnableToParsePackageInfo ); @@ -649,7 +651,7 @@ mod tests { assert_eq!( temp.package_manager - .install_package("dummy-package", false) + .install_package("dummy-package", false, None) .unwrap(), Installed ); diff --git a/src/package/git.rs b/src/package/git.rs deleted file mode 100644 index f8d132b..0000000 --- a/src/package/git.rs +++ /dev/null @@ -1,33 +0,0 @@ -use tempfile::TempDir; -use std::error::Error; -use git2::Repository; -use super::PackageResolver; - -pub struct GitPackageResolver; - -impl GitPackageResolver { - pub fn new() -> GitPackageResolver { - return GitPackageResolver{}; - } -} - -impl super::PackageResolver for GitPackageResolver { - fn clone_repo_to_temp(&self, repo_url: &str) -> Result> { - let temp_dir = TempDir::new()?; - let _repo = Repository::clone(repo_url, temp_dir.path())?; - Ok(temp_dir) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use tempfile::{TempDir, NamedTempFile}; - - #[test] - fn test_clone_temp_repository() { - let resolver = GitPackageResolver::new(); - let cloned_dir = resolver.clone_repo_to_temp("https://github.com/federico-terzi/espanso-hub-core").unwrap(); - assert!(cloned_dir.path().join("LICENSE").exists()); - } -} \ No newline at end of file diff --git a/src/package/mod.rs b/src/package/mod.rs index 1d99210..8906c8c 100644 --- a/src/package/mod.rs +++ b/src/package/mod.rs @@ -34,11 +34,13 @@ pub trait PackageManager { &self, name: &str, allow_external: bool, + proxy: Option, ) -> Result>; fn install_package_from_repo( &self, name: &str, repo_url: &str, + proxy: Option, ) -> Result>; fn remove_package(&self, name: &str) -> Result>; @@ -47,7 +49,7 @@ pub trait PackageManager { } pub trait PackageResolver { - fn clone_repo_to_temp(&self, repo_url: &str) -> Result>; + fn clone_repo_to_temp(&self, repo_url: &str, proxy: Option) -> Result>; } #[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] diff --git a/src/package/zip.rs b/src/package/zip.rs index 52fcf9d..8b90ab4 100644 --- a/src/package/zip.rs +++ b/src/package/zip.rs @@ -13,13 +13,22 @@ impl ZipPackageResolver { } impl super::PackageResolver for ZipPackageResolver { - fn clone_repo_to_temp(&self, repo_url: &str) -> Result> { + fn clone_repo_to_temp(&self, repo_url: &str, proxy: Option) -> Result> { let temp_dir = TempDir::new()?; let zip_url = repo_url.to_owned() + "/archive/master.zip"; + let mut client = reqwest::Client::builder(); + + if let Some(proxy) = proxy { + let proxy = reqwest::Proxy::https(&proxy).expect("unable to setup https proxy"); + client = client.proxy(proxy); + }; + + let client = client.build().expect("unable to create http client"); + // Download the archive from GitHub - let mut response = reqwest::get(&zip_url)?; + let mut response = client.get(&zip_url).send()?; // Extract zip file let mut buffer = Vec::new(); @@ -90,7 +99,7 @@ mod tests { fn test_clone_temp_repository() { let resolver = ZipPackageResolver::new(); let cloned_dir = resolver - .clone_repo_to_temp("https://github.com/federico-terzi/espanso-hub-core") + .clone_repo_to_temp("https://github.com/federico-terzi/espanso-hub-core", None) .unwrap(); assert!(cloned_dir.path().join("LICENSE").exists()); } From d43f7ae205669cc055291a3ba25f66dd46595672 Mon Sep 17 00:00:00 2001 From: Federico Terzi Date: Wed, 16 Sep 2020 20:00:04 +0200 Subject: [PATCH 07/11] Add icon to modulo stub app --- src/res/mac/AppIcon.icns | Bin 0 -> 40048 bytes src/res/mac/modulo.plist | 4 +++- src/ui/modulo/mac.rs | 8 ++++++++ 3 files changed, 11 insertions(+), 1 deletion(-) create mode 100644 src/res/mac/AppIcon.icns diff --git a/src/res/mac/AppIcon.icns b/src/res/mac/AppIcon.icns new file mode 100644 index 0000000000000000000000000000000000000000..d3f07c39c1a784e458922afa7a3f83c6c6d63a13 GIT binary patch literal 40048 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 literal 0 HcmV?d00001 diff --git a/src/res/mac/modulo.plist b/src/res/mac/modulo.plist index dd37ecd..9883bd4 100644 --- a/src/res/mac/modulo.plist +++ b/src/res/mac/modulo.plist @@ -7,7 +7,9 @@ CFBundleExecutable {{{modulo_path}}} CFBundleIconFile - + AppIcon + CFBundleIconName + AppIcon> CFBundleIdentifier com.federicoterzi.modulo CFBundleInfoDictionaryVersion diff --git a/src/ui/modulo/mac.rs b/src/ui/modulo/mac.rs index e6bd7e9..db0e480 100644 --- a/src/ui/modulo/mac.rs +++ b/src/ui/modulo/mac.rs @@ -4,6 +4,7 @@ use std::os::unix::fs::symlink; const MODULO_APP_BUNDLE_NAME: &str = "Modulo.app"; const MODULO_APP_BUNDLE_PLIST_CONTENT: &'static str = include_str!("../../res/mac/modulo.plist"); +const MODULO_APP_BUNDLE_ICON: &'static str = include_str!("../../res/mac/AppIcon.icns"); pub fn generate_modulo_app_bundle(modulo_path: &str) -> Result { let modulo_pathbuf = PathBuf::from(modulo_path); @@ -37,12 +38,19 @@ pub fn generate_modulo_app_bundle(modulo_path: &str) -> Result Date: Wed, 16 Sep 2020 20:01:19 +0200 Subject: [PATCH 08/11] Fix modulo stub creation process --- src/ui/modulo/mac.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ui/modulo/mac.rs b/src/ui/modulo/mac.rs index db0e480..ca3f72f 100644 --- a/src/ui/modulo/mac.rs +++ b/src/ui/modulo/mac.rs @@ -4,7 +4,7 @@ use std::os::unix::fs::symlink; const MODULO_APP_BUNDLE_NAME: &str = "Modulo.app"; const MODULO_APP_BUNDLE_PLIST_CONTENT: &'static str = include_str!("../../res/mac/modulo.plist"); -const MODULO_APP_BUNDLE_ICON: &'static str = include_str!("../../res/mac/AppIcon.icns"); +const MODULO_APP_BUNDLE_ICON: &'static str = include_bytes!("../../res/mac/AppIcon.icns"); pub fn generate_modulo_app_bundle(modulo_path: &str) -> Result { let modulo_pathbuf = PathBuf::from(modulo_path); From b13745ccd25953a4d4f7cb72d7d0e9be14dd9315 Mon Sep 17 00:00:00 2001 From: Federico Terzi Date: Wed, 16 Sep 2020 20:02:02 +0200 Subject: [PATCH 09/11] Fix modulo stub creation process --- src/ui/modulo/mac.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ui/modulo/mac.rs b/src/ui/modulo/mac.rs index ca3f72f..4919ca4 100644 --- a/src/ui/modulo/mac.rs +++ b/src/ui/modulo/mac.rs @@ -4,7 +4,7 @@ use std::os::unix::fs::symlink; const MODULO_APP_BUNDLE_NAME: &str = "Modulo.app"; const MODULO_APP_BUNDLE_PLIST_CONTENT: &'static str = include_str!("../../res/mac/modulo.plist"); -const MODULO_APP_BUNDLE_ICON: &'static str = include_bytes!("../../res/mac/AppIcon.icns"); +const MODULO_APP_BUNDLE_ICON: &[u8] = include_bytes!("../../res/mac/AppIcon.icns"); pub fn generate_modulo_app_bundle(modulo_path: &str) -> Result { let modulo_pathbuf = PathBuf::from(modulo_path); From ff13da9a85b111a803a5cb057ca64e905867bf47 Mon Sep 17 00:00:00 2001 From: Federico Terzi Date: Tue, 22 Sep 2020 21:46:39 +0200 Subject: [PATCH 10/11] Fix formatting --- src/keyboard/windows.rs | 2 +- src/main.rs | 11 ++++++++--- src/package/mod.rs | 6 +++++- src/package/zip.rs | 6 +++++- src/ui/modulo/mac.rs | 13 ++++++++----- src/ui/modulo/mod.rs | 8 ++++++-- 6 files changed, 33 insertions(+), 13 deletions(-) diff --git a/src/keyboard/windows.rs b/src/keyboard/windows.rs index 55b1b1d..507d4dd 100644 --- a/src/keyboard/windows.rs +++ b/src/keyboard/windows.rs @@ -89,4 +89,4 @@ pub fn wait_for_modifiers_release() -> bool { std::thread::sleep(std::time::Duration::from_millis(100)); } false -} \ No newline at end of file +} diff --git a/src/main.rs b/src/main.rs index f35abe6..294ad3c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -98,8 +98,13 @@ fn main() { .required(false) .default_value("hub"), ) - .arg(Arg::with_name("proxy").help("Use a proxy, should be used as --proxy=https://proxy:1234") - .required(false).long("proxy").takes_value(true)); + .arg( + Arg::with_name("proxy") + .help("Use a proxy, should be used as --proxy=https://proxy:1234") + .required(false) + .long("proxy") + .takes_value(true), + ); let uninstall_subcommand = SubCommand::with_name("uninstall") .about("Remove an installed package. Equivalent to 'espanso package uninstall'") @@ -1103,7 +1108,7 @@ fn install_main(_config_set: ConfigSet, matches: &ArgMatches) { println!("Using proxy: {}", proxy); Some(proxy.to_string()) } - None => {None} + None => None, }; let package_resolver = Box::new(ZipPackageResolver::new()); diff --git a/src/package/mod.rs b/src/package/mod.rs index 8906c8c..ea6d23d 100644 --- a/src/package/mod.rs +++ b/src/package/mod.rs @@ -49,7 +49,11 @@ pub trait PackageManager { } pub trait PackageResolver { - fn clone_repo_to_temp(&self, repo_url: &str, proxy: Option) -> Result>; + fn clone_repo_to_temp( + &self, + repo_url: &str, + proxy: Option, + ) -> Result>; } #[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] diff --git a/src/package/zip.rs b/src/package/zip.rs index 8b90ab4..4912e5f 100644 --- a/src/package/zip.rs +++ b/src/package/zip.rs @@ -13,7 +13,11 @@ impl ZipPackageResolver { } impl super::PackageResolver for ZipPackageResolver { - fn clone_repo_to_temp(&self, repo_url: &str, proxy: Option) -> Result> { + fn clone_repo_to_temp( + &self, + repo_url: &str, + proxy: Option, + ) -> Result> { let temp_dir = TempDir::new()?; let zip_url = repo_url.to_owned() + "/archive/master.zip"; diff --git a/src/ui/modulo/mac.rs b/src/ui/modulo/mac.rs index 4919ca4..1b55b2e 100644 --- a/src/ui/modulo/mac.rs +++ b/src/ui/modulo/mac.rs @@ -1,6 +1,6 @@ use log::info; -use std::path::PathBuf; use std::os::unix::fs::symlink; +use std::path::PathBuf; const MODULO_APP_BUNDLE_NAME: &str = "Modulo.app"; const MODULO_APP_BUNDLE_PLIST_CONTENT: &'static str = include_str!("../../res/mac/modulo.plist"); @@ -11,13 +11,16 @@ pub fn generate_modulo_app_bundle(modulo_path: &str) -> Result Result Result Date: Tue, 22 Sep 2020 22:23:39 +0200 Subject: [PATCH 11/11] Change menu icon on macOS when disabled. Fix #432 --- native/libmacbridge/AppDelegate.h | 2 ++ native/libmacbridge/AppDelegate.mm | 34 +++++++++++++++++++++-------- native/libmacbridge/bridge.h | 8 ++++++- native/libmacbridge/bridge.mm | 16 +++++++++++++- src/bridge/macos.rs | 8 ++++++- src/context/macos.rs | 31 +++++++++++++++++++++++++- src/context/mod.rs | 2 +- src/engine.rs | 5 +++++ src/res/mac/icondisabled.png | Bin 0 -> 4099 bytes 9 files changed, 92 insertions(+), 14 deletions(-) create mode 100644 src/res/mac/icondisabled.png diff --git a/native/libmacbridge/AppDelegate.h b/native/libmacbridge/AppDelegate.h index f35f5d1..e37bb71 100644 --- a/native/libmacbridge/AppDelegate.h +++ b/native/libmacbridge/AppDelegate.h @@ -29,5 +29,7 @@ - (void)applicationDidFinishLaunching:(NSNotification *)aNotification; - (IBAction) statusIconClick: (id) sender; - (IBAction) contextMenuClick: (id) sender; +- (void) updateIcon: (char *)iconPath; +- (void) setIcon: (char *)iconPath; @end \ No newline at end of file diff --git a/native/libmacbridge/AppDelegate.mm b/native/libmacbridge/AppDelegate.mm index cd24e8b..5bfb256 100644 --- a/native/libmacbridge/AppDelegate.mm +++ b/native/libmacbridge/AppDelegate.mm @@ -26,15 +26,8 @@ // Setup status icon if (show_icon) { myStatusItem = [[[NSStatusBar systemStatusBar] statusItemWithLength:NSSquareStatusItemLength] retain]; - - NSString *nsIconPath = [NSString stringWithUTF8String:icon_path]; - NSImage *statusImage = [[NSImage alloc] initWithContentsOfFile:nsIconPath]; - [statusImage setTemplate:YES]; - - [myStatusItem.button setImage:statusImage]; - [myStatusItem setHighlightMode:YES]; - [myStatusItem.button setAction:@selector(statusIconClick:)]; - [myStatusItem.button setTarget:self]; + + [self setIcon: icon_path]; } // Setup key listener @@ -66,6 +59,29 @@ }]; } +- (void) updateIcon: (char *)iconPath { + if (show_icon) { + [myStatusItem release]; + + [self setIcon: iconPath]; + } +} + +- (void) setIcon: (char *)iconPath { + if (show_icon) { + myStatusItem = [[[NSStatusBar systemStatusBar] statusItemWithLength:NSSquareStatusItemLength] retain]; + + NSString *nsIconPath = [NSString stringWithUTF8String:iconPath]; + NSImage *statusImage = [[NSImage alloc] initWithContentsOfFile:nsIconPath]; + [statusImage setTemplate:YES]; + + [myStatusItem.button setImage:statusImage]; + [myStatusItem setHighlightMode:YES]; + [myStatusItem.button setAction:@selector(statusIconClick:)]; + [myStatusItem.button setTarget:self]; + } +} + - (IBAction) statusIconClick: (id) sender { icon_click_callback(context_instance); } diff --git a/native/libmacbridge/bridge.h b/native/libmacbridge/bridge.h index 169c3cf..a50212c 100644 --- a/native/libmacbridge/bridge.h +++ b/native/libmacbridge/bridge.h @@ -26,12 +26,13 @@ extern "C" { extern void * context_instance; extern char * icon_path; +extern char * disabled_icon_path; extern int32_t show_icon; /* * Initialize the AppDelegate and check for accessibility permissions */ -int32_t initialize(void * context, const char * icon_path, int32_t show_icon); +int32_t initialize(void * context, const char * icon_path, const char * disabled_icon_path, int32_t show_icon); /* * Start the event loop indefinitely. Blocking call. @@ -117,6 +118,11 @@ typedef void (*ContextMenuClickCallback)(void * self, int32_t id); extern ContextMenuClickCallback context_menu_click_callback; extern "C" void register_context_menu_click_callback(ContextMenuClickCallback callback); +/* + * Update the tray icon status + */ +extern "C" void update_tray_icon(int32_t enabled); + // SYSTEM /* diff --git a/native/libmacbridge/bridge.mm b/native/libmacbridge/bridge.mm index bafe1ae..27cd5b1 100644 --- a/native/libmacbridge/bridge.mm +++ b/native/libmacbridge/bridge.mm @@ -33,6 +33,7 @@ extern "C" { void * context_instance; char * icon_path; +char * disabled_icon_path; int32_t show_icon; AppDelegate * delegate_ptr; @@ -40,9 +41,10 @@ KeypressCallback keypress_callback; IconClickCallback icon_click_callback; ContextMenuClickCallback context_menu_click_callback; -int32_t initialize(void * context, const char * _icon_path, int32_t _show_icon) { +int32_t initialize(void * context, const char * _icon_path, const char * _disabled_icon_path, int32_t _show_icon) { context_instance = context; icon_path = strdup(_icon_path); + disabled_icon_path = strdup(_disabled_icon_path); show_icon = _show_icon; AppDelegate *delegate = [[AppDelegate alloc] init]; @@ -74,6 +76,18 @@ int32_t headless_eventloop() { return 0; } +void update_tray_icon(int32_t enabled) { + dispatch_async(dispatch_get_main_queue(), ^(void) { + NSApplication * application = [NSApplication sharedApplication]; + char * iconPath = icon_path; + if (!enabled) { + iconPath = disabled_icon_path; + } + + [[application delegate] updateIcon: iconPath]; + }); +} + void send_string(const char * string) { char * stringCopy = strdup(string); dispatch_async(dispatch_get_main_queue(), ^(void) { diff --git a/src/bridge/macos.rs b/src/bridge/macos.rs index c9f8c9f..01846d7 100644 --- a/src/bridge/macos.rs +++ b/src/bridge/macos.rs @@ -29,7 +29,12 @@ pub struct MacMenuItem { #[allow(improper_ctypes)] #[link(name = "macbridge", kind = "static")] extern "C" { - pub fn initialize(s: *const c_void, icon_path: *const c_char, show_icon: i32); + pub fn initialize( + s: *const c_void, + icon_path: *const c_char, + disabled_icon_path: *const c_char, + show_icon: i32, + ); pub fn eventloop(); pub fn headless_eventloop(); @@ -51,6 +56,7 @@ extern "C" { pub fn register_icon_click_callback(cb: extern "C" fn(_self: *mut c_void)); pub fn show_context_menu(items: *const MacMenuItem, count: i32) -> i32; pub fn register_context_menu_click_callback(cb: extern "C" fn(_self: *mut c_void, id: i32)); + pub fn update_tray_icon(enabled: i32); // Keyboard pub fn register_keypress_callback( diff --git a/src/context/macos.rs b/src/context/macos.rs index a5c9b54..284e0c7 100644 --- a/src/context/macos.rs +++ b/src/context/macos.rs @@ -34,6 +34,7 @@ use std::sync::Arc; use std::{fs, thread}; const STATUS_ICON_BINARY: &[u8] = include_bytes!("../res/mac/icon.png"); +const DISABLED_STATUS_ICON_BINARY: &[u8] = include_bytes!("../res/mac/icondisabled.png"); pub struct MacContext { pub send_channel: Sender, @@ -72,6 +73,7 @@ impl MacContext { // Initialize the status icon path let espanso_dir = super::get_data_dir(); let status_icon_target = espanso_dir.join("icon.png"); + let disabled_status_icon_target = espanso_dir.join("icondisabled.png"); if status_icon_target.exists() { info!("Status icon already initialized, skipping."); @@ -84,6 +86,19 @@ impl MacContext { }); } + if disabled_status_icon_target.exists() { + info!("Status icon (disabled) already initialized, skipping."); + } else { + fs::write(&disabled_status_icon_target, DISABLED_STATUS_ICON_BINARY).unwrap_or_else( + |e| { + error!( + "Error copying the Status Icon (disabled) to the espanso data directory: {}", + e + ); + }, + ); + } + unsafe { let context_ptr = &*context as *const MacContext as *const c_void; @@ -93,9 +108,17 @@ impl MacContext { let status_icon_path = CString::new(status_icon_target.to_str().unwrap_or_default()).unwrap_or_default(); + let disabled_status_icon_path = + CString::new(disabled_status_icon_target.to_str().unwrap_or_default()) + .unwrap_or_default(); let show_icon = if config.show_icon { 1 } else { 0 }; - initialize(context_ptr, status_icon_path.as_ptr(), show_icon); + initialize( + context_ptr, + status_icon_path.as_ptr(), + disabled_status_icon_path.as_ptr(), + show_icon, + ); } context @@ -146,6 +169,12 @@ impl MacContext { } } +pub fn update_icon(enabled: bool) { + unsafe { + crate::bridge::macos::update_tray_icon(if enabled { 1 } else { 0 }); + } +} + impl super::Context for MacContext { fn eventloop(&self) { // Start the SecureInput watcher thread diff --git a/src/context/mod.rs b/src/context/mod.rs index ea656fe..0f95483 100644 --- a/src/context/mod.rs +++ b/src/context/mod.rs @@ -50,7 +50,7 @@ pub fn new( #[cfg(target_os = "macos")] pub fn update_icon(enabled: bool) { - // TODO: add update icon on macOS + macos::update_icon(enabled); } #[cfg(target_os = "macos")] diff --git a/src/engine.rs b/src/engine.rs index 3406181..7bd7ce9 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -498,9 +498,14 @@ impl< if config.secure_input_notification && config.show_notifications { self.ui_manager.notify_delay(&format!("{} has activated SecureInput. Espanso won't work until you disable it.", app_name), 5000); } + + crate::context::update_icon(false); } SystemEvent::SecureInputDisabled => { info!("SecureInput has been disabled."); + + let is_enabled = self.enabled.borrow(); + crate::context::update_icon(*is_enabled); } SystemEvent::NotifyRequest(message) => { let config = self.config_manager.default_config(); diff --git a/src/res/mac/icondisabled.png b/src/res/mac/icondisabled.png new file mode 100644 index 0000000000000000000000000000000000000000..a289156c84f899726076871f472156bb5f81b4b0 GIT binary patch literal 4099 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