commit
						4bd5f4c6c5
					
				
							
								
								
									
										2
									
								
								.github/scripts/ubuntu/Dockerfile
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/scripts/ubuntu/Dockerfile
									
									
									
									
										vendored
									
									
								
							|  | @ -31,7 +31,7 @@ RUN set -eux; \ | ||||||
|     cargo --version; \ |     cargo --version; \ | ||||||
|     rustc --version; |     rustc --version; | ||||||
| 
 | 
 | ||||||
| RUN mkdir espanso && cargo install --force cargo-make | RUN mkdir espanso && cargo install --force cargo-make --version 0.34.0 | ||||||
| 
 | 
 | ||||||
| COPY . espanso | COPY . espanso | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
							
								
								
									
										6
									
								
								.github/workflows/ci.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										6
									
								
								.github/workflows/ci.yml
									
									
									
									
										vendored
									
									
								
							|  | @ -35,7 +35,7 @@ jobs: | ||||||
|           MACOSX_DEPLOYMENT_TARGET: "10.13" |           MACOSX_DEPLOYMENT_TARGET: "10.13" | ||||||
|       - name: Install cargo-make |       - name: Install cargo-make | ||||||
|         run: | |         run: | | ||||||
|           cargo install --force cargo-make |           cargo install --force cargo-make --version 0.34.0 | ||||||
|       - name: Run test suite |       - name: Run test suite | ||||||
|         run: cargo make test-binary |         run: cargo make test-binary | ||||||
|       - name: Build |       - name: Build | ||||||
|  | @ -60,7 +60,7 @@ jobs: | ||||||
|           cargo clippy -p espanso --features wayland -- -D warnings |           cargo clippy -p espanso --features wayland -- -D warnings | ||||||
|       - name: Install cargo-make |       - name: Install cargo-make | ||||||
|         run: | |         run: | | ||||||
|           cargo install --force cargo-make |           cargo install --force cargo-make --version 0.34.0 | ||||||
|       - name: Run test suite |       - name: Run test suite | ||||||
|         run: cargo make test-binary --env NO_X11=true |         run: cargo make test-binary --env NO_X11=true | ||||||
|       - name: Build |       - name: Build | ||||||
|  | @ -74,7 +74,7 @@ jobs: | ||||||
|         run: rustup update && rustup target add aarch64-apple-darwin |         run: rustup update && rustup target add aarch64-apple-darwin | ||||||
|       - name: Install cargo-make |       - name: Install cargo-make | ||||||
|         run: | |         run: | | ||||||
|           cargo install --force cargo-make |           cargo install --force cargo-make --version 0.34.0 | ||||||
|       - name: Build |       - name: Build | ||||||
|         run: | |         run: | | ||||||
|           cargo make build-macos-arm-binary |           cargo make build-macos-arm-binary | ||||||
|  |  | ||||||
							
								
								
									
										6
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										6
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
								
							|  | @ -61,7 +61,7 @@ jobs: | ||||||
|           echo Using version ${{ needs.extract-version.outputs.espanso_version }} |           echo Using version ${{ needs.extract-version.outputs.espanso_version }} | ||||||
|       - name: Install cargo-make |       - name: Install cargo-make | ||||||
|         run: | |         run: | | ||||||
|           cargo install --force cargo-make |           cargo install --force cargo-make --version 0.34.0 | ||||||
|       - name: Test |       - name: Test | ||||||
|         run: cargo make test-binary --profile release |         run: cargo make test-binary --profile release | ||||||
|       - name: Build |       - name: Build | ||||||
|  | @ -128,7 +128,7 @@ jobs: | ||||||
|           echo Using version ${{ needs.extract-version.outputs.espanso_version }} |           echo Using version ${{ needs.extract-version.outputs.espanso_version }} | ||||||
|       - name: Install cargo-make |       - name: Install cargo-make | ||||||
|         run: | |         run: | | ||||||
|           cargo install --force cargo-make |           cargo install --force cargo-make --version 0.34.0 | ||||||
|       - name: Test |       - name: Test | ||||||
|         run: cargo make test-binary --profile release |         run: cargo make test-binary --profile release | ||||||
|         env: |         env: | ||||||
|  | @ -168,7 +168,7 @@ jobs: | ||||||
|         run: rustup update && rustup target add aarch64-apple-darwin |         run: rustup update && rustup target add aarch64-apple-darwin | ||||||
|       - name: Install cargo-make |       - name: Install cargo-make | ||||||
|         run: | |         run: | | ||||||
|           cargo install --force cargo-make |           cargo install --force cargo-make --version 0.34.0 | ||||||
|       - name: Build |       - name: Build | ||||||
|         run: cargo make create-bundle --profile release --env BUILD_ARCH=aarch64-apple-darwin |         run: cargo make create-bundle --profile release --env BUILD_ARCH=aarch64-apple-darwin | ||||||
|       - name: Codesign executable |       - name: Codesign executable | ||||||
|  |  | ||||||
							
								
								
									
										2
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										2
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							|  | @ -572,7 +572,7 @@ dependencies = [ | ||||||
| 
 | 
 | ||||||
| [[package]] | [[package]] | ||||||
| name = "espanso" | name = "espanso" | ||||||
| version = "2.0.3-alpha" | version = "2.0.4-alpha" | ||||||
| dependencies = [ | dependencies = [ | ||||||
|  "anyhow", |  "anyhow", | ||||||
|  "caps", |  "caps", | ||||||
|  |  | ||||||
|  | @ -16,7 +16,7 @@ These are the basic tools required to build espanso: | ||||||
| steps. You can install it by running: | steps. You can install it by running: | ||||||
| 
 | 
 | ||||||
| ``` | ``` | ||||||
| cargo install --force cargo-make | cargo install --force cargo-make --version 0.34.0 | ||||||
| ``` | ``` | ||||||
| 
 | 
 | ||||||
| # Linux | # Linux | ||||||
|  |  | ||||||
|  | @ -156,6 +156,12 @@ pub trait Config: Send + Sync { | ||||||
|   // Disabling this option might conflict with the undo feature.
 |   // Disabling this option might conflict with the undo feature.
 | ||||||
|   fn win32_exclude_orphan_events(&self) -> bool; |   fn win32_exclude_orphan_events(&self) -> bool; | ||||||
| 
 | 
 | ||||||
|  |   // The maximum interval (in milliseconds) for which a keyboard layout
 | ||||||
|  |   // can be cached. If switching often between different layouts, you
 | ||||||
|  |   // could lower this amount to avoid the "lost detection" effect described
 | ||||||
|  |   // in this issue: https://github.com/federico-terzi/espanso/issues/745
 | ||||||
|  |   fn win32_keyboard_layout_cache_interval(&self) -> i64; | ||||||
|  | 
 | ||||||
|   fn is_match<'a>(&self, app: &AppProperties<'a>) -> bool; |   fn is_match<'a>(&self, app: &AppProperties<'a>) -> bool; | ||||||
| 
 | 
 | ||||||
|   fn pretty_dump(&self) -> String { |   fn pretty_dump(&self) -> String { | ||||||
|  | @ -187,6 +193,9 @@ pub trait Config: Send + Sync { | ||||||
|         show_notifications: {:?} |         show_notifications: {:?} | ||||||
|         secure_input_notification: {:?} |         secure_input_notification: {:?} | ||||||
| 
 | 
 | ||||||
|  |         win32_exclude_orphan_events: {:?} | ||||||
|  |         win32_keyboard_layout_cache_interval: {:?} | ||||||
|  | 
 | ||||||
|         match_paths: {:#?} |         match_paths: {:#?} | ||||||
|       ", 
 |       ", 
 | ||||||
|       self.label(), |       self.label(), | ||||||
|  | @ -215,6 +224,9 @@ pub trait Config: Send + Sync { | ||||||
|       self.show_notifications(), |       self.show_notifications(), | ||||||
|       self.secure_input_notification(), |       self.secure_input_notification(), | ||||||
| 
 | 
 | ||||||
|  |       self.win32_exclude_orphan_events(), | ||||||
|  |       self.win32_keyboard_layout_cache_interval(), | ||||||
|  | 
 | ||||||
|       self.match_paths(), |       self.match_paths(), | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  | @ -45,6 +45,7 @@ pub(crate) struct ParsedConfig { | ||||||
|   pub show_icon: Option<bool>, |   pub show_icon: Option<bool>, | ||||||
|   pub secure_input_notification: Option<bool>, |   pub secure_input_notification: Option<bool>, | ||||||
|   pub win32_exclude_orphan_events: Option<bool>, |   pub win32_exclude_orphan_events: Option<bool>, | ||||||
|  |   pub win32_keyboard_layout_cache_interval: Option<i64>, | ||||||
| 
 | 
 | ||||||
|   pub pre_paste_delay: Option<usize>, |   pub pre_paste_delay: Option<usize>, | ||||||
|   pub restore_clipboard_delay: Option<usize>, |   pub restore_clipboard_delay: Option<usize>, | ||||||
|  |  | ||||||
|  | @ -109,6 +109,9 @@ pub(crate) struct YAMLConfig { | ||||||
|   #[serde(default)] |   #[serde(default)] | ||||||
|   pub win32_exclude_orphan_events: Option<bool>, |   pub win32_exclude_orphan_events: Option<bool>, | ||||||
| 
 | 
 | ||||||
|  |   #[serde(default)] | ||||||
|  |   pub win32_keyboard_layout_cache_interval: Option<i64>, | ||||||
|  | 
 | ||||||
|   // Include/Exclude
 |   // Include/Exclude
 | ||||||
|   #[serde(default)] |   #[serde(default)] | ||||||
|   pub includes: Option<Vec<String>>, |   pub includes: Option<Vec<String>>, | ||||||
|  | @ -197,6 +200,7 @@ impl TryFrom<YAMLConfig> for ParsedConfig { | ||||||
|       paste_shortcut_event_delay: yaml_config.paste_shortcut_event_delay, |       paste_shortcut_event_delay: yaml_config.paste_shortcut_event_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, | ||||||
| 
 | 
 | ||||||
|       use_standard_includes: yaml_config.use_standard_includes, |       use_standard_includes: yaml_config.use_standard_includes, | ||||||
|       includes: yaml_config.includes, |       includes: yaml_config.includes, | ||||||
|  | @ -253,6 +257,7 @@ mod tests { | ||||||
|     show_notifications: false |     show_notifications: false | ||||||
|     secure_input_notification: false |     secure_input_notification: false | ||||||
|     win32_exclude_orphan_events: false |     win32_exclude_orphan_events: false | ||||||
|  |     win32_keyboard_layout_cache_interval: 300 | ||||||
|       
 |       
 | ||||||
|     use_standard_includes: true |     use_standard_includes: true | ||||||
|     includes: ["test1"] |     includes: ["test1"] | ||||||
|  | @ -305,6 +310,7 @@ mod tests { | ||||||
|         show_notifications: Some(false), |         show_notifications: Some(false), | ||||||
|         secure_input_notification: Some(false), |         secure_input_notification: Some(false), | ||||||
|         win32_exclude_orphan_events: Some(false), |         win32_exclude_orphan_events: Some(false), | ||||||
|  |         win32_keyboard_layout_cache_interval: Some(300), | ||||||
| 
 | 
 | ||||||
|         pre_paste_delay: Some(300), |         pre_paste_delay: Some(300), | ||||||
|         evdev_modifier_delay: Some(40), |         evdev_modifier_delay: Some(40), | ||||||
|  |  | ||||||
|  | @ -323,6 +323,13 @@ impl Config for ResolvedConfig { | ||||||
|   fn evdev_modifier_delay(&self) -> Option<usize> { |   fn evdev_modifier_delay(&self) -> Option<usize> { | ||||||
|     self.parsed.evdev_modifier_delay |     self.parsed.evdev_modifier_delay | ||||||
|   } |   } | ||||||
|  | 
 | ||||||
|  |   fn win32_keyboard_layout_cache_interval(&self) -> i64 { | ||||||
|  |     self | ||||||
|  |       .parsed | ||||||
|  |       .win32_keyboard_layout_cache_interval | ||||||
|  |       .unwrap_or(2000) | ||||||
|  |   } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| impl ResolvedConfig { | impl ResolvedConfig { | ||||||
|  | @ -405,6 +412,7 @@ impl ResolvedConfig { | ||||||
|       show_notifications, |       show_notifications, | ||||||
|       secure_input_notification, |       secure_input_notification, | ||||||
|       win32_exclude_orphan_events, |       win32_exclude_orphan_events, | ||||||
|  |       win32_keyboard_layout_cache_interval, | ||||||
|       includes, |       includes, | ||||||
|       excludes, |       excludes, | ||||||
|       extra_includes, |       extra_includes, | ||||||
|  |  | ||||||
|  | @ -394,6 +394,10 @@ impl Config for LegacyInteropConfig { | ||||||
|   fn evdev_modifier_delay(&self) -> Option<usize> { |   fn evdev_modifier_delay(&self) -> Option<usize> { | ||||||
|     Some(10) |     Some(10) | ||||||
|   } |   } | ||||||
|  | 
 | ||||||
|  |   fn win32_keyboard_layout_cache_interval(&self) -> i64 { | ||||||
|  |     2000 | ||||||
|  |   } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| struct LegacyMatchGroup { | struct LegacyMatchGroup { | ||||||
|  |  | ||||||
|  | @ -6,6 +6,7 @@ use libc::{input_event, size_t, ssize_t, EWOULDBLOCK, O_CLOEXEC, O_NONBLOCK, O_R | ||||||
| use log::trace; | use log::trace; | ||||||
| use scopeguard::ScopeGuard; | use scopeguard::ScopeGuard; | ||||||
| use std::collections::HashMap; | use std::collections::HashMap; | ||||||
|  | use std::os::raw::c_char; | ||||||
| use std::os::unix::io::AsRawFd; | use std::os::unix::io::AsRawFd; | ||||||
| use std::{ | use std::{ | ||||||
|   ffi::{c_void, CStr}, |   ffi::{c_void, CStr}, | ||||||
|  | @ -160,16 +161,16 @@ impl Device { | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     // Extract the utf8 char
 |     // Extract the utf8 char
 | ||||||
|     let mut buffer: [u8; 16] = [0; 16]; |     let mut buffer: [c_char; 16] = [0; 16]; | ||||||
|     unsafe { |     unsafe { | ||||||
|       xkb_state_key_get_utf8( |       xkb_state_key_get_utf8( | ||||||
|         self.get_state(), |         self.get_state(), | ||||||
|         keycode, |         keycode, | ||||||
|         buffer.as_mut_ptr() as *mut i8, |         buffer.as_mut_ptr(), | ||||||
|         std::mem::size_of_val(&buffer), |         std::mem::size_of_val(&buffer), | ||||||
|       ) |       ) | ||||||
|     }; |     }; | ||||||
|     let content_raw = unsafe { CStr::from_ptr(buffer.as_ptr() as *mut i8) }; |     let content_raw = unsafe { CStr::from_ptr(buffer.as_ptr()) }; | ||||||
|     let content = content_raw.to_string_lossy().to_string(); |     let content = content_raw.to_string_lossy().to_string(); | ||||||
| 
 | 
 | ||||||
|     let event = RawKeyboardEvent { |     let event = RawKeyboardEvent { | ||||||
|  |  | ||||||
|  | @ -66,6 +66,12 @@ pub struct SourceCreationOptions { | ||||||
|   // those from espanso, but might need to be disabled when using some software-level keyboards.
 |   // those from espanso, but might need to be disabled when using some software-level keyboards.
 | ||||||
|   // Disabling this option might conflict with the undo feature.
 |   // Disabling this option might conflict with the undo feature.
 | ||||||
|   pub win32_exclude_orphan_events: bool, |   pub win32_exclude_orphan_events: bool, | ||||||
|  | 
 | ||||||
|  |   // The maximum interval (in milliseconds) for which a keyboard layout
 | ||||||
|  |   // can be cached. If switching often between different layouts, you
 | ||||||
|  |   // could lower this amount to avoid the "lost detection" effect described
 | ||||||
|  |   // in this issue: https://github.com/federico-terzi/espanso/issues/745
 | ||||||
|  |   pub win32_keyboard_layout_cache_interval: i64, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // This struct identifies the keyboard layout that
 | // This struct identifies the keyboard layout that
 | ||||||
|  | @ -87,6 +93,7 @@ impl Default for SourceCreationOptions { | ||||||
|       evdev_keyboard_rmlvo: None, |       evdev_keyboard_rmlvo: None, | ||||||
|       hotkeys: Vec::new(), |       hotkeys: Vec::new(), | ||||||
|       win32_exclude_orphan_events: true, |       win32_exclude_orphan_events: true, | ||||||
|  |       win32_keyboard_layout_cache_interval: 2000, | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  | @ -97,6 +104,7 @@ pub fn get_source(options: SourceCreationOptions) -> Result<Box<dyn Source>> { | ||||||
|   Ok(Box::new(win32::Win32Source::new( |   Ok(Box::new(win32::Win32Source::new( | ||||||
|     &options.hotkeys, |     &options.hotkeys, | ||||||
|     options.win32_exclude_orphan_events, |     options.win32_exclude_orphan_events, | ||||||
|  |     options.win32_keyboard_layout_cache_interval, | ||||||
|   ))) |   ))) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -17,6 +17,7 @@ | ||||||
|  * along with espanso.  If not, see <https://www.gnu.org/licenses/>.
 |  * along with espanso.  If not, see <https://www.gnu.org/licenses/>.
 | ||||||
|  */ |  */ | ||||||
| 
 | 
 | ||||||
|  | use std::os::raw::c_long; | ||||||
| use std::{convert::TryInto, ffi::c_void}; | use std::{convert::TryInto, ffi::c_void}; | ||||||
| 
 | 
 | ||||||
| use lazycell::LazyCell; | use lazycell::LazyCell; | ||||||
|  | @ -79,10 +80,19 @@ pub struct RawHotKey { | ||||||
|   pub flags: u32, |   pub flags: u32, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | #[repr(C)] | ||||||
|  | pub struct InitOptions { | ||||||
|  |   pub keyboard_layout_cache_interval: c_long, | ||||||
|  | } | ||||||
|  | 
 | ||||||
| #[allow(improper_ctypes)] | #[allow(improper_ctypes)] | ||||||
| #[link(name = "espansodetect", kind = "static")] | #[link(name = "espansodetect", kind = "static")] | ||||||
| extern "C" { | extern "C" { | ||||||
|   pub fn detect_initialize(_self: *const Win32Source, error_code: *mut i32) -> *mut c_void; |   pub fn detect_initialize( | ||||||
|  |     _self: *const Win32Source, | ||||||
|  |     options: *const InitOptions, | ||||||
|  |     error_code: *mut i32, | ||||||
|  |   ) -> *mut c_void; | ||||||
|   pub fn detect_register_hotkey(window: *const c_void, hotkey: RawHotKey) -> i32; |   pub fn detect_register_hotkey(window: *const c_void, hotkey: RawHotKey) -> i32; | ||||||
| 
 | 
 | ||||||
|   pub fn detect_eventloop( |   pub fn detect_eventloop( | ||||||
|  | @ -99,24 +109,35 @@ pub struct Win32Source { | ||||||
|   hotkeys: Vec<HotKey>, |   hotkeys: Vec<HotKey>, | ||||||
| 
 | 
 | ||||||
|   exclude_orphan_events: bool, |   exclude_orphan_events: bool, | ||||||
|  |   keyboard_layout_cache_interval: i64, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| #[allow(clippy::new_without_default)] | #[allow(clippy::new_without_default)] | ||||||
| impl Win32Source { | impl Win32Source { | ||||||
|   pub fn new(hotkeys: &[HotKey], exclude_orphan_events: bool) -> Win32Source { |   pub fn new( | ||||||
|  |     hotkeys: &[HotKey], | ||||||
|  |     exclude_orphan_events: bool, | ||||||
|  |     keyboard_layout_cache_interval: i64, | ||||||
|  |   ) -> Win32Source { | ||||||
|     Self { |     Self { | ||||||
|       handle: std::ptr::null_mut(), |       handle: std::ptr::null_mut(), | ||||||
|       callback: LazyCell::new(), |       callback: LazyCell::new(), | ||||||
|       hotkeys: hotkeys.to_vec(), |       hotkeys: hotkeys.to_vec(), | ||||||
|       exclude_orphan_events, |       exclude_orphan_events, | ||||||
|  |       keyboard_layout_cache_interval, | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| impl Source for Win32Source { | impl Source for Win32Source { | ||||||
|   fn initialize(&mut self) -> Result<()> { |   fn initialize(&mut self) -> Result<()> { | ||||||
|  |     let options = InitOptions { | ||||||
|  |       keyboard_layout_cache_interval: self.keyboard_layout_cache_interval.try_into().unwrap(), | ||||||
|  |     }; | ||||||
|  | 
 | ||||||
|     let mut error_code = 0; |     let mut error_code = 0; | ||||||
|     let handle = unsafe { detect_initialize(self as *const Win32Source, &mut error_code) }; |     let handle = | ||||||
|  |       unsafe { detect_initialize(self as *const Win32Source, &options, &mut error_code) }; | ||||||
| 
 | 
 | ||||||
|     if handle.is_null() { |     if handle.is_null() { | ||||||
|       let error = match error_code { |       let error = match error_code { | ||||||
|  |  | ||||||
|  | @ -39,8 +39,6 @@ | ||||||
| #include <strsafe.h> | #include <strsafe.h> | ||||||
| #include <Windows.h> | #include <Windows.h> | ||||||
| 
 | 
 | ||||||
| // How many milliseconds must pass between events before refreshing the keyboard layout
 |  | ||||||
| const long DETECT_REFRESH_KEYBOARD_LAYOUT_INTERVAL = 2000; |  | ||||||
| const wchar_t *const DETECT_WINCLASS = L"EspansoDetect"; | const wchar_t *const DETECT_WINCLASS = L"EspansoDetect"; | ||||||
| const USHORT MOUSE_DOWN_FLAGS = RI_MOUSE_LEFT_BUTTON_DOWN | RI_MOUSE_RIGHT_BUTTON_DOWN | RI_MOUSE_MIDDLE_BUTTON_DOWN | | const USHORT MOUSE_DOWN_FLAGS = RI_MOUSE_LEFT_BUTTON_DOWN | RI_MOUSE_RIGHT_BUTTON_DOWN | RI_MOUSE_MIDDLE_BUTTON_DOWN | | ||||||
|                         RI_MOUSE_BUTTON_1_DOWN | RI_MOUSE_BUTTON_2_DOWN | RI_MOUSE_BUTTON_3_DOWN | |                         RI_MOUSE_BUTTON_1_DOWN | RI_MOUSE_BUTTON_2_DOWN | RI_MOUSE_BUTTON_3_DOWN | | ||||||
|  | @ -52,6 +50,8 @@ const USHORT MOUSE_UP_FLAGS = RI_MOUSE_LEFT_BUTTON_UP | RI_MOUSE_RIGHT_BUTTON_UP | ||||||
| typedef struct { | typedef struct { | ||||||
|   HKL current_keyboard_layout; |   HKL current_keyboard_layout; | ||||||
|   DWORD last_key_press_tick; |   DWORD last_key_press_tick; | ||||||
|  |   // How many milliseconds must pass between events before refreshing the keyboard layout
 | ||||||
|  |   long keyboard_layout_cache_interval; | ||||||
| 
 | 
 | ||||||
|   // Rust interop
 |   // Rust interop
 | ||||||
|   void * rust_instance; |   void * rust_instance; | ||||||
|  | @ -130,7 +130,7 @@ LRESULT CALLBACK detect_window_procedure(HWND window, unsigned int msg, WPARAM w | ||||||
|       DWORD currentTick = GetTickCount(); |       DWORD currentTick = GetTickCount(); | ||||||
| 
 | 
 | ||||||
|       // If enough time has passed between the last keypress and now, refresh the keyboard layout
 |       // If enough time has passed between the last keypress and now, refresh the keyboard layout
 | ||||||
|       if ((currentTick - variables->last_key_press_tick) > DETECT_REFRESH_KEYBOARD_LAYOUT_INTERVAL) |       if ((currentTick - variables->last_key_press_tick) > variables->keyboard_layout_cache_interval) | ||||||
|       { |       { | ||||||
| 
 | 
 | ||||||
|         // Because keyboard layouts on windows are Window-specific, to get the current
 |         // Because keyboard layouts on windows are Window-specific, to get the current
 | ||||||
|  | @ -269,7 +269,7 @@ LRESULT CALLBACK detect_window_procedure(HWND window, unsigned int msg, WPARAM w | ||||||
|   } |   } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void * detect_initialize(void *_self, int32_t *error_code) | void * detect_initialize(void *_self, InitOptions * options, int32_t *error_code) | ||||||
| { | { | ||||||
|   HWND window = NULL; |   HWND window = NULL; | ||||||
| 
 | 
 | ||||||
|  | @ -297,6 +297,7 @@ void * detect_initialize(void *_self, int32_t *error_code) | ||||||
| 
 | 
 | ||||||
|     // Initialize the default keyboard layout
 |     // Initialize the default keyboard layout
 | ||||||
|     variables->current_keyboard_layout = GetKeyboardLayout(0); |     variables->current_keyboard_layout = GetKeyboardLayout(0); | ||||||
|  |     variables->keyboard_layout_cache_interval = options->keyboard_layout_cache_interval; | ||||||
| 
 | 
 | ||||||
|     // Docs: https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-createwindowexw
 |     // Docs: https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-createwindowexw
 | ||||||
|     window = CreateWindowEx( |     window = CreateWindowEx( | ||||||
|  |  | ||||||
|  | @ -76,9 +76,12 @@ typedef struct { | ||||||
| 
 | 
 | ||||||
| typedef void (*EventCallback)(void * rust_istance, InputEvent data); | typedef void (*EventCallback)(void * rust_istance, InputEvent data); | ||||||
| 
 | 
 | ||||||
|  | typedef struct { | ||||||
|  |   long keyboard_layout_cache_interval; | ||||||
|  | } InitOptions; | ||||||
| 
 | 
 | ||||||
| // Initialize the Raw Input API and the Window.
 | // Initialize the Raw Input API and the Window.
 | ||||||
| extern "C" void * detect_initialize(void * rust_istance, int32_t *error_code); | extern "C" void * detect_initialize(void * rust_istance, InitOptions * options, int32_t *error_code); | ||||||
| 
 | 
 | ||||||
| // Register the given hotkey, return a non-zero code if successful
 | // Register the given hotkey, return a non-zero code if successful
 | ||||||
| extern "C" int32_t detect_register_hotkey(void * window, HotKey hotkey); | extern "C" int32_t detect_register_hotkey(void * window, HotKey hotkey); | ||||||
|  |  | ||||||
|  | @ -64,18 +64,24 @@ impl Middleware for DisableMiddleware { | ||||||
| 
 | 
 | ||||||
|     match &event.etype { |     match &event.etype { | ||||||
|       EventType::Keyboard(m_event) => { |       EventType::Keyboard(m_event) => { | ||||||
|         if is_toggle_key(m_event, &self.options) { |         if m_event.status == Status::Released { | ||||||
|           let mut last_toggle_press = self.last_toggle_press.borrow_mut(); |           let mut last_toggle_press = self.last_toggle_press.borrow_mut(); | ||||||
|           if let Some(previous_press) = *last_toggle_press { |           if is_toggle_key(m_event, &self.options) { | ||||||
|             if previous_press.elapsed() < self.options.toggle_key_maximum_window { |             if let Some(previous_press) = *last_toggle_press { | ||||||
|               *enabled = !*enabled; |               if previous_press.elapsed() < self.options.toggle_key_maximum_window { | ||||||
|               *last_toggle_press = None; |                 *enabled = !*enabled; | ||||||
|               has_status_changed = true; |                 *last_toggle_press = None; | ||||||
|  |                 has_status_changed = true; | ||||||
|  |               } else { | ||||||
|  |                 *last_toggle_press = Some(Instant::now()); | ||||||
|  |               } | ||||||
|             } else { |             } else { | ||||||
|               *last_toggle_press = Some(Instant::now()); |               *last_toggle_press = Some(Instant::now()); | ||||||
|             } |             } | ||||||
|           } else { |           } else { | ||||||
|             *last_toggle_press = Some(Instant::now()); |             // If another key is pressed (not the toggle key), we should reset the window
 | ||||||
|  |             // For more information, see: https://github.com/federico-terzi/espanso/issues/815
 | ||||||
|  |             *last_toggle_press = None; | ||||||
|           } |           } | ||||||
|         } |         } | ||||||
|       } |       } | ||||||
|  | @ -115,10 +121,6 @@ impl Middleware for DisableMiddleware { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| fn is_toggle_key(event: &KeyboardEvent, options: &DisableOptions) -> bool { | fn is_toggle_key(event: &KeyboardEvent, options: &DisableOptions) -> bool { | ||||||
|   if event.status != Status::Released { |  | ||||||
|     return false; |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   if options |   if options | ||||||
|     .toggle_key |     .toggle_key | ||||||
|     .as_ref() |     .as_ref() | ||||||
|  |  | ||||||
|  | @ -17,6 +17,8 @@ | ||||||
|  * along with espanso.  If not, see <https://www.gnu.org/licenses/>.
 |  * along with espanso.  If not, see <https://www.gnu.org/licenses/>.
 | ||||||
|  */ |  */ | ||||||
| 
 | 
 | ||||||
|  | use log::error; | ||||||
|  | 
 | ||||||
| use super::super::Middleware; | use super::super::Middleware; | ||||||
| use crate::event::{effect::HtmlInjectRequest, Event, EventType}; | use crate::event::{effect::HtmlInjectRequest, Event, EventType}; | ||||||
| 
 | 
 | ||||||
|  | @ -37,23 +39,33 @@ impl Middleware for MarkdownMiddleware { | ||||||
|   fn next(&self, event: Event, _: &mut dyn FnMut(Event)) -> Event { |   fn next(&self, event: Event, _: &mut dyn FnMut(Event)) -> Event { | ||||||
|     if let EventType::MarkdownInject(m_event) = &event.etype { |     if let EventType::MarkdownInject(m_event) = &event.etype { | ||||||
|       // Render the markdown into HTML
 |       // Render the markdown into HTML
 | ||||||
|       let html = markdown::to_html(&m_event.markdown); |       // NOTE: we wrap the `to_html` call between catch_unwind because if the markdown is malformed,
 | ||||||
|       let mut html = html.trim(); |       // the library panics. Ideally, the library would return a Result::Err in that case, but
 | ||||||
|  |       // for now it doesn't, so we employ that workaround.
 | ||||||
|  |       // See also: https://github.com/federico-terzi/espanso/issues/759
 | ||||||
|  |       let html = std::panic::catch_unwind(|| markdown::to_html(&m_event.markdown)); | ||||||
|  |       if let Ok(html) = html { | ||||||
|  |         let mut html = html.trim(); | ||||||
| 
 | 
 | ||||||
|       // Remove the surrounding paragraph
 |         // Remove the surrounding paragraph
 | ||||||
|       if html.starts_with("<p>") { |         if html.starts_with("<p>") { | ||||||
|         html = html.trim_start_matches("<p>"); |           html = html.trim_start_matches("<p>"); | ||||||
|       } |         } | ||||||
|       if html.ends_with("</p>") { |         if html.ends_with("</p>") { | ||||||
|         html = html.trim_end_matches("</p>"); |           html = html.trim_end_matches("</p>"); | ||||||
|       } |         } | ||||||
| 
 | 
 | ||||||
|       return Event::caused_by( |         return Event::caused_by( | ||||||
|         event.source_id, |           event.source_id, | ||||||
|         EventType::HtmlInject(HtmlInjectRequest { |           EventType::HtmlInject(HtmlInjectRequest { | ||||||
|           html: html.to_owned(), |             html: html.to_owned(), | ||||||
|         }), |           }), | ||||||
|       ); |         ); | ||||||
|  |       } else { | ||||||
|  |         error!("unable to convert markdown to HTML, is it malformed?"); | ||||||
|  | 
 | ||||||
|  |         return Event::caused_by(event.source_id, EventType::NOOP); | ||||||
|  |       } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     event |     event | ||||||
|  |  | ||||||
|  | @ -191,7 +191,7 @@ fn is_event_of_interest(event_type: &EventType) -> bool { | ||||||
|         // In hex, they have the byte 3 = 0xfe
 |         // In hex, they have the byte 3 = 0xfe
 | ||||||
|         // See list in "keysymdef.h" file
 |         // See list in "keysymdef.h" file
 | ||||||
|         if cfg!(target_os = "linux") { |         if cfg!(target_os = "linux") { | ||||||
|           if let Key::Other(raw_code) = &keyboard_event.key { |           if let (Key::Other(raw_code), None) = (&keyboard_event.key, &keyboard_event.value) { | ||||||
|             if (65025..=65276).contains(raw_code) { |             if (65025..=65276).contains(raw_code) { | ||||||
|               return false; |               return false; | ||||||
|             } |             } | ||||||
|  |  | ||||||
|  | @ -457,22 +457,5 @@ fn build_native() { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| fn main() { | fn main() { | ||||||
|   println!("cargo:rerun-if-changed=src/x11/native/native.h"); |  | ||||||
|   println!("cargo:rerun-if-changed=src/sys/interop/interop.h"); |  | ||||||
|   println!("cargo:rerun-if-changed=src/sys/form/form.cpp"); |  | ||||||
|   println!("cargo:rerun-if-changed=src/sys/common/mac.h"); |  | ||||||
|   println!("cargo:rerun-if-changed=src/sys/common/mac.mm"); |  | ||||||
|   println!("cargo:rerun-if-changed=src/sys/common/common.h"); |  | ||||||
|   println!("cargo:rerun-if-changed=src/sys/common/common.cpp"); |  | ||||||
|   println!("cargo:rerun-if-changed=src/sys/welcome/welcome_gui.h"); |  | ||||||
|   println!("cargo:rerun-if-changed=src/sys/welcome/welcome_gui.cpp"); |  | ||||||
|   println!("cargo:rerun-if-changed=src/sys/welcome/welcome.cpp"); |  | ||||||
|   println!("cargo:rerun-if-changed=src/sys/troubleshooting/troubleshooting_gui.h"); |  | ||||||
|   println!("cargo:rerun-if-changed=src/sys/troubleshooting/troubleshooting_gui.cpp"); |  | ||||||
|   println!("cargo:rerun-if-changed=src/sys/troubleshooting/troubleshooting.cpp"); |  | ||||||
|   println!("cargo:rerun-if-changed=src/sys/search/search.cpp"); |  | ||||||
|   println!("cargo:rerun-if-changed=src/sys/wizard/wizard.cpp"); |  | ||||||
|   println!("cargo:rerun-if-changed=src/sys/wizard/wizard_gui.cpp"); |  | ||||||
|   println!("cargo:rerun-if-changed=src/sys/wizard/wizard_gui.h"); |  | ||||||
|   build_native(); |   build_native(); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -1,6 +1,6 @@ | ||||||
| [package] | [package] | ||||||
| name = "espanso" | name = "espanso" | ||||||
| version = "2.0.3-alpha" | version = "2.0.4-alpha" | ||||||
| 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" | ||||||
|  |  | ||||||
|  | @ -126,6 +126,8 @@ pub fn initialize_and_spawn( | ||||||
|           ), |           ), | ||||||
|           hotkeys: match_converter.get_hotkeys(), |           hotkeys: match_converter.get_hotkeys(), | ||||||
|           win32_exclude_orphan_events: default_config.win32_exclude_orphan_events(), |           win32_exclude_orphan_events: default_config.win32_exclude_orphan_events(), | ||||||
|  |           win32_keyboard_layout_cache_interval: default_config | ||||||
|  |             .win32_keyboard_layout_cache_interval(), | ||||||
|         }) |         }) | ||||||
|         .expect("failed to initialize detector module"); |         .expect("failed to initialize detector module"); | ||||||
|       let exit_source = super::engine::funnel::exit::ExitSource::new(exit_signal, &sequencer); |       let exit_source = super::engine::funnel::exit::ExitSource::new(exit_signal, &sequencer); | ||||||
|  |  | ||||||
|  | @ -55,55 +55,53 @@ fn convert_fields(fields: &Params) -> HashMap<String, FormField> { | ||||||
|     let mut form_field = None; |     let mut form_field = None; | ||||||
| 
 | 
 | ||||||
|     if let Value::Object(params) = field { |     if let Value::Object(params) = field { | ||||||
|       if let Some(Value::String(field_type)) = params.get("type") { |       form_field = match params.get("type") { | ||||||
|         form_field = match field_type.as_str() { |         Some(Value::String(field_type)) if field_type == "choice" => Some(FormField::Choice { | ||||||
|           "text" => Some(FormField::Text { |           default: params | ||||||
|             default: params |             .get("default") | ||||||
|               .get("default") |             .and_then(|val| val.as_string()) | ||||||
|               .and_then(|val| val.as_string()) |             .cloned(), | ||||||
|               .cloned(), |           values: params | ||||||
|             multiline: params |             .get("values") | ||||||
|               .get("multiline") |             .and_then(|val| val.as_array()) | ||||||
|               .and_then(|val| val.as_bool()) |             .map(|arr| { | ||||||
|               .cloned() |               arr | ||||||
|               .unwrap_or(false), |                 .iter() | ||||||
|           }), |                 .flat_map(|choice| choice.as_string()) | ||||||
|           "choice" => Some(FormField::Choice { |                 .cloned() | ||||||
|             default: params |                 .collect() | ||||||
|               .get("default") |             }) | ||||||
|               .and_then(|val| val.as_string()) |             .unwrap_or_default(), | ||||||
|               .cloned(), |         }), | ||||||
|             values: params |         Some(Value::String(field_type)) if field_type == "list" => Some(FormField::List { | ||||||
|               .get("values") |           default: params | ||||||
|               .and_then(|val| val.as_array()) |             .get("default") | ||||||
|               .map(|arr| { |             .and_then(|val| val.as_string()) | ||||||
|                 arr |             .cloned(), | ||||||
|                   .iter() |           values: params | ||||||
|                   .flat_map(|choice| choice.as_string()) |             .get("values") | ||||||
|                   .cloned() |             .and_then(|val| val.as_array()) | ||||||
|                   .collect() |             .map(|arr| { | ||||||
|               }) |               arr | ||||||
|               .unwrap_or_default(), |                 .iter() | ||||||
|           }), |                 .flat_map(|choice| choice.as_string()) | ||||||
|           "list" => Some(FormField::List { |                 .cloned() | ||||||
|             default: params |                 .collect() | ||||||
|               .get("default") |             }) | ||||||
|               .and_then(|val| val.as_string()) |             .unwrap_or_default(), | ||||||
|               .cloned(), |         }), | ||||||
|             values: params |         // By default, it's considered type 'text'
 | ||||||
|               .get("values") |         _ => Some(FormField::Text { | ||||||
|               .and_then(|val| val.as_array()) |           default: params | ||||||
|               .map(|arr| { |             .get("default") | ||||||
|                 arr |             .and_then(|val| val.as_string()) | ||||||
|                   .iter() |             .cloned(), | ||||||
|                   .flat_map(|choice| choice.as_string()) |           multiline: params | ||||||
|                   .cloned() |             .get("multiline") | ||||||
|                   .collect() |             .and_then(|val| val.as_bool()) | ||||||
|               }) |             .cloned() | ||||||
|               .unwrap_or_default(), |             .unwrap_or(false), | ||||||
|           }), |         }), | ||||||
|           _ => None, |  | ||||||
|         } |  | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -55,6 +55,7 @@ fn get_builtin_patches() -> Vec<PatchDefinition> { | ||||||
|     patches::linux::urxvt_terminal_x11::patch(), |     patches::linux::urxvt_terminal_x11::patch(), | ||||||
|     patches::linux::xterm_terminal_x11::patch(), |     patches::linux::xterm_terminal_x11::patch(), | ||||||
|     patches::linux::yakuake_terminal_x11::patch(), |     patches::linux::yakuake_terminal_x11::patch(), | ||||||
|  |     patches::linux::virtualbox_x11::patch(), | ||||||
|   ]; |   ]; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -19,6 +19,8 @@ | ||||||
| 
 | 
 | ||||||
| use std::sync::Arc; | use std::sync::Arc; | ||||||
| 
 | 
 | ||||||
|  | use espanso_config::config::Backend; | ||||||
|  | 
 | ||||||
| use crate::patch::patches::{PatchedConfig, Patches}; | use crate::patch::patches::{PatchedConfig, Patches}; | ||||||
| use crate::patch::PatchDefinition; | use crate::patch::PatchDefinition; | ||||||
| 
 | 
 | ||||||
|  | @ -33,6 +35,7 @@ pub fn patch() -> PatchDefinition { | ||||||
|         name, |         name, | ||||||
|         Patches { |         Patches { | ||||||
|           paste_shortcut: Some(Some("CTRL+SHIFT+V".to_string())), |           paste_shortcut: Some(Some("CTRL+SHIFT+V".to_string())), | ||||||
|  |           backend: Some(Backend::Clipboard), | ||||||
|           ..Default::default() |           ..Default::default() | ||||||
|         }, |         }, | ||||||
|       )) |       )) | ||||||
|  |  | ||||||
|  | @ -30,6 +30,7 @@ pub mod termite_terminal_x11; | ||||||
| pub mod thunderbird_x11; | pub mod thunderbird_x11; | ||||||
| pub mod tilix_terminal_x11; | pub mod tilix_terminal_x11; | ||||||
| pub mod urxvt_terminal_x11; | pub mod urxvt_terminal_x11; | ||||||
|  | pub mod virtualbox_x11; | ||||||
| pub mod xterm_terminal_x11; | pub mod xterm_terminal_x11; | ||||||
| pub mod yakuake_terminal_x11; | pub mod yakuake_terminal_x11; | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
							
								
								
									
										46
									
								
								espanso/src/patch/patches/linux/virtualbox_x11.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								espanso/src/patch/patches/linux/virtualbox_x11.rs
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,46 @@ | ||||||
|  | /* | ||||||
|  |  * This file is part of espanso. | ||||||
|  |  * | ||||||
|  |  * Copyright (C) 2019-2021 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/>.
 | ||||||
|  |  */ | ||||||
|  | 
 | ||||||
|  | use std::sync::Arc; | ||||||
|  | 
 | ||||||
|  | use espanso_config::config::Backend; | ||||||
|  | 
 | ||||||
|  | use crate::patch::patches::{PatchedConfig, Patches}; | ||||||
|  | use crate::patch::PatchDefinition; | ||||||
|  | 
 | ||||||
|  | pub fn patch() -> PatchDefinition { | ||||||
|  |   PatchDefinition { | ||||||
|  |     name: module_path!().split(':').last().unwrap_or("unknown"), | ||||||
|  |     is_enabled: || cfg!(target_os = "linux") && !super::util::is_wayland(), | ||||||
|  |     should_patch: |app| app.class.unwrap_or_default().contains("VirtualBox Machine"), | ||||||
|  |     apply: |base, name| { | ||||||
|  |       Arc::new(PatchedConfig::patch( | ||||||
|  |         base, | ||||||
|  |         name, | ||||||
|  |         Patches { | ||||||
|  |           backend: Some(Backend::Inject), | ||||||
|  |           key_delay: Some(Some(10)), | ||||||
|  |           inject_delay: Some(Some(15)), | ||||||
|  |           disable_x11_fast_inject: Some(true), | ||||||
|  |           ..Default::default() | ||||||
|  |         }, | ||||||
|  |       )) | ||||||
|  |     }, | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | @ -49,5 +49,6 @@ generate_patchable_config!( | ||||||
|   apply_patch -> bool, |   apply_patch -> bool, | ||||||
|   undo_backspace -> bool, |   undo_backspace -> bool, | ||||||
|   win32_exclude_orphan_events -> bool, |   win32_exclude_orphan_events -> bool, | ||||||
|  |   win32_keyboard_layout_cache_interval -> i64, | ||||||
|   keyboard_layout -> Option<RMLVOConfig> |   keyboard_layout -> Option<RMLVOConfig> | ||||||
| ); | ); | ||||||
|  |  | ||||||
|  | @ -1,5 +1,5 @@ | ||||||
| name: espanso | name: espanso | ||||||
| version: 2.0.3-alpha | version: 2.0.4-alpha | ||||||
| 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. | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue
	
	Block a user