commit
						b7e91d9b5d
					
				
							
								
								
									
										8
									
								
								.github/workflows/ci.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										8
									
								
								.github/workflows/ci.yml
									
									
									
									
										vendored
									
									
								
							| 
						 | 
					@ -25,11 +25,14 @@ jobs:
 | 
				
			||||||
      - name: Install Linux dependencies
 | 
					      - name: Install Linux dependencies
 | 
				
			||||||
        if: ${{ runner.os == 'Linux' }}
 | 
					        if: ${{ runner.os == 'Linux' }}
 | 
				
			||||||
        run: |
 | 
					        run: |
 | 
				
			||||||
          sudo apt install libx11-dev libxtst-dev libxkbcommon-dev libdbus-1-dev libwxgtk3.0-gtk3-dev
 | 
					          sudo apt-get update
 | 
				
			||||||
 | 
					          sudo apt-get install -y libx11-dev libxtst-dev libxkbcommon-dev libdbus-1-dev libwxgtk3.0-gtk3-dev
 | 
				
			||||||
      - name: Check clippy
 | 
					      - name: Check clippy
 | 
				
			||||||
        run: |
 | 
					        run: |
 | 
				
			||||||
          rustup component add clippy
 | 
					          rustup component add clippy
 | 
				
			||||||
          cargo clippy -- -D warnings
 | 
					          cargo clippy -- -D warnings
 | 
				
			||||||
 | 
					        env:
 | 
				
			||||||
 | 
					          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
 | 
				
			||||||
| 
						 | 
					@ -49,7 +52,8 @@ jobs:
 | 
				
			||||||
          cargo fmt --all -- --check
 | 
					          cargo fmt --all -- --check
 | 
				
			||||||
      - name: Install Linux dependencies
 | 
					      - name: Install Linux dependencies
 | 
				
			||||||
        run: |
 | 
					        run: |
 | 
				
			||||||
          sudo apt install libxkbcommon-dev libwxgtk3.0-gtk3-dev libdbus-1-dev
 | 
					          sudo apt-get update
 | 
				
			||||||
 | 
					          sudo apt-get install -y libxkbcommon-dev libwxgtk3.0-gtk3-dev libdbus-1-dev
 | 
				
			||||||
      - name: Check clippy
 | 
					      - name: Check clippy
 | 
				
			||||||
        run: |
 | 
					        run: |
 | 
				
			||||||
          rustup component add clippy
 | 
					          rustup component add clippy
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										4
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
								
							| 
						 | 
					@ -131,8 +131,12 @@ jobs:
 | 
				
			||||||
          cargo install --force cargo-make
 | 
					          cargo install --force cargo-make
 | 
				
			||||||
      - name: Test
 | 
					      - name: Test
 | 
				
			||||||
        run: cargo make test-binary --profile release
 | 
					        run: cargo make test-binary --profile release
 | 
				
			||||||
 | 
					        env:
 | 
				
			||||||
 | 
					          MACOSX_DEPLOYMENT_TARGET: "10.13"
 | 
				
			||||||
      - name: Build
 | 
					      - name: Build
 | 
				
			||||||
        run: cargo make create-bundle --profile release
 | 
					        run: cargo make create-bundle --profile release
 | 
				
			||||||
 | 
					        env:
 | 
				
			||||||
 | 
					          MACOSX_DEPLOYMENT_TARGET: "10.13"
 | 
				
			||||||
      - name: Create ZIP archive
 | 
					      - name: Create ZIP archive
 | 
				
			||||||
        run: |
 | 
					        run: |
 | 
				
			||||||
          ditto -c -k --sequesterRsrc --keepParent target/mac/Espanso.app Espanso-Mac-Intel.zip
 | 
					          ditto -c -k --sequesterRsrc --keepParent target/mac/Espanso.app Espanso-Mac-Intel.zip
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										2
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										2
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							| 
						 | 
					@ -572,7 +572,7 @@ dependencies = [
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[[package]]
 | 
					[[package]]
 | 
				
			||||||
name = "espanso"
 | 
					name = "espanso"
 | 
				
			||||||
version = "2.0.2-alpha"
 | 
					version = "2.0.3-alpha"
 | 
				
			||||||
dependencies = [
 | 
					dependencies = [
 | 
				
			||||||
 "anyhow",
 | 
					 "anyhow",
 | 
				
			||||||
 "caps",
 | 
					 "caps",
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -25,6 +25,11 @@ pub enum InputEvent {
 | 
				
			||||||
  Mouse(MouseEvent),
 | 
					  Mouse(MouseEvent),
 | 
				
			||||||
  Keyboard(KeyboardEvent),
 | 
					  Keyboard(KeyboardEvent),
 | 
				
			||||||
  HotKey(HotKeyEvent),
 | 
					  HotKey(HotKeyEvent),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // Special event type only used on macOS
 | 
				
			||||||
 | 
					  // This is sent after a global keyboard shortcut is released
 | 
				
			||||||
 | 
					  // See https://github.com/federico-terzi/espanso/issues/791
 | 
				
			||||||
 | 
					  AllModifiersReleased,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(Debug, PartialEq)]
 | 
					#[derive(Debug, PartialEq)]
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -241,6 +241,13 @@ impl From<RawInputEvent> for Option<InputEvent> {
 | 
				
			||||||
      INPUT_EVENT_TYPE_KEYBOARD => {
 | 
					      INPUT_EVENT_TYPE_KEYBOARD => {
 | 
				
			||||||
        let (key, variant) = key_code_to_key(raw.key_code);
 | 
					        let (key, variant) = key_code_to_key(raw.key_code);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // When a global keyboard shortcut is relased, the callback returns an event with keycode 0
 | 
				
			||||||
 | 
					        // and status 0.
 | 
				
			||||||
 | 
					        // We need to handle it for this reason: https://github.com/federico-terzi/espanso/issues/791
 | 
				
			||||||
 | 
					        if raw.key_code == 0 && raw.status == 0 {
 | 
				
			||||||
 | 
					          return Some(InputEvent::AllModifiersReleased);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        let value = if raw.buffer_len > 0 {
 | 
					        let value = if raw.buffer_len > 0 {
 | 
				
			||||||
          let raw_string_result =
 | 
					          let raw_string_result =
 | 
				
			||||||
            CStr::from_bytes_with_nul(&raw.buffer[..((raw.buffer_len + 1) as usize)]);
 | 
					            CStr::from_bytes_with_nul(&raw.buffer[..((raw.buffer_len + 1) as usize)]);
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -116,7 +116,7 @@ LRESULT CALLBACK detect_window_procedure(HWND window, unsigned int msg, WPARAM w
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
      // We only want KEY UP AND KEY DOWN events
 | 
					      // We only want KEY UP AND KEY DOWN events
 | 
				
			||||||
      if (raw->data.keyboard.Message != WM_KEYDOWN && raw->data.keyboard.Message != WM_KEYUP &&
 | 
					      if (raw->data.keyboard.Message != WM_KEYDOWN && raw->data.keyboard.Message != WM_KEYUP &&
 | 
				
			||||||
          raw->data.keyboard.Message != WM_SYSKEYDOWN)
 | 
					          raw->data.keyboard.Message != WM_SYSKEYDOWN && raw->data.keyboard.Message != WM_SYSKEYUP)
 | 
				
			||||||
      {
 | 
					      {
 | 
				
			||||||
        return 0;
 | 
					        return 0;
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
| 
						 | 
					@ -156,9 +156,10 @@ LRESULT CALLBACK detect_window_procedure(HWND window, unsigned int msg, WPARAM w
 | 
				
			||||||
      std::vector<BYTE> lpKeyState(256);
 | 
					      std::vector<BYTE> lpKeyState(256);
 | 
				
			||||||
      if (GetKeyboardState(lpKeyState.data()))
 | 
					      if (GetKeyboardState(lpKeyState.data()))
 | 
				
			||||||
      {
 | 
					      {
 | 
				
			||||||
        // This flag is needed to avoid chaning the keyboard state for some layouts.
 | 
					        // This flag is needed to avoid changing the keyboard state for some layouts.
 | 
				
			||||||
        // Refer to issue: https://github.com/federico-terzi/espanso/issues/86
 | 
					        // The 1 << 2 (setting bit 2) part is needed due to this issue: https://github.com/federico-terzi/espanso/issues/86
 | 
				
			||||||
        UINT flags = 1 << 2;
 | 
					        // while the 1 (setting bit 0) part is needed due to this issue: https://github.com/federico-terzi/espanso/issues/552
 | 
				
			||||||
 | 
					        UINT flags = 1 << 2 | 1;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        int result = ToUnicodeEx(raw->data.keyboard.VKey, raw->data.keyboard.MakeCode, lpKeyState.data(), reinterpret_cast<LPWSTR>(event.buffer), (sizeof(event.buffer)/sizeof(event.buffer[0])) - 1, flags, variables->current_keyboard_layout);
 | 
					        int result = ToUnicodeEx(raw->data.keyboard.VKey, raw->data.keyboard.MakeCode, lpKeyState.data(), reinterpret_cast<LPWSTR>(event.buffer), (sizeof(event.buffer)/sizeof(event.buffer[0])) - 1, flags, variables->current_keyboard_layout);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -166,6 +167,15 @@ LRESULT CALLBACK detect_window_procedure(HWND window, unsigned int msg, WPARAM w
 | 
				
			||||||
        if (result >= 1)
 | 
					        if (result >= 1)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
          event.buffer_len = result;
 | 
					          event.buffer_len = result;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          // Filter out the value if the key was pressed while the ALT key was down
 | 
				
			||||||
 | 
					          // but not if AltGr is down (which is a shortcut to ALT+CTRL on some keyboards, such 
 | 
				
			||||||
 | 
					          // as the italian one).
 | 
				
			||||||
 | 
					          // This is needed in conjunction with the fix for: https://github.com/federico-terzi/espanso/issues/725
 | 
				
			||||||
 | 
					          if ((lpKeyState[VK_MENU] & 0x80) != 0 && (lpKeyState[VK_CONTROL] & 0x80) == 0) {
 | 
				
			||||||
 | 
					            memset(event.buffer, 0, sizeof(event.buffer));
 | 
				
			||||||
 | 
					            event.buffer_len = 0;
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        else
 | 
					        else
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -85,6 +85,14 @@ pub struct DiscardPreviousEvent {
 | 
				
			||||||
  pub minimum_source_id: u32,
 | 
					  pub minimum_source_id: u32,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Debug, Clone, PartialEq)]
 | 
				
			||||||
 | 
					pub struct DiscardBetweenEvent {
 | 
				
			||||||
 | 
					  // All Events with a source_id between start_id (included) and end_id (excluded)
 | 
				
			||||||
 | 
					  // will be discarded
 | 
				
			||||||
 | 
					  pub start_id: u32,
 | 
				
			||||||
 | 
					  pub end_id: u32,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(Debug, Clone, PartialEq)]
 | 
					#[derive(Debug, Clone, PartialEq)]
 | 
				
			||||||
pub struct SecureInputEnabledEvent {
 | 
					pub struct SecureInputEnabledEvent {
 | 
				
			||||||
  pub app_name: String,
 | 
					  pub app_name: String,
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -71,6 +71,7 @@ pub enum EventType {
 | 
				
			||||||
  ImageResolved(internal::ImageResolvedEvent),
 | 
					  ImageResolved(internal::ImageResolvedEvent),
 | 
				
			||||||
  MatchInjected,
 | 
					  MatchInjected,
 | 
				
			||||||
  DiscardPrevious(internal::DiscardPreviousEvent),
 | 
					  DiscardPrevious(internal::DiscardPreviousEvent),
 | 
				
			||||||
 | 
					  DiscardBetween(internal::DiscardBetweenEvent),
 | 
				
			||||||
  Undo(internal::UndoEvent),
 | 
					  Undo(internal::UndoEvent),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  Disabled,
 | 
					  Disabled,
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -48,7 +48,10 @@ impl<'a> Funnel for DefaultFunnel<'a> {
 | 
				
			||||||
      .expect("invalid source index returned by select operation");
 | 
					      .expect("invalid source index returned by select operation");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Receive (and convert) the event
 | 
					    // Receive (and convert) the event
 | 
				
			||||||
    let event = source.receive(op);
 | 
					    if let Some(event) = source.receive(op) {
 | 
				
			||||||
      FunnelResult::Event(event)
 | 
					      FunnelResult::Event(event)
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      FunnelResult::Skipped
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -27,7 +27,7 @@ mod default;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub trait Source<'a> {
 | 
					pub trait Source<'a> {
 | 
				
			||||||
  fn register(&'a self, select: &mut Select<'a>) -> usize;
 | 
					  fn register(&'a self, select: &mut Select<'a>) -> usize;
 | 
				
			||||||
  fn receive(&'a self, op: SelectedOperation) -> Event;
 | 
					  fn receive(&'a self, op: SelectedOperation) -> Option<Event>;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub trait Funnel {
 | 
					pub trait Funnel {
 | 
				
			||||||
| 
						 | 
					@ -36,6 +36,7 @@ pub trait Funnel {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub enum FunnelResult {
 | 
					pub enum FunnelResult {
 | 
				
			||||||
  Event(Event),
 | 
					  Event(Event),
 | 
				
			||||||
 | 
					  Skipped,
 | 
				
			||||||
  EndOfStream,
 | 
					  EndOfStream,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -68,6 +68,9 @@ impl<'a> Engine<'a> {
 | 
				
			||||||
          debug!("end of stream received");
 | 
					          debug!("end of stream received");
 | 
				
			||||||
          return ExitMode::Exit;
 | 
					          return ExitMode::Exit;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					        FunnelResult::Skipped => {
 | 
				
			||||||
 | 
					          // This event has been skipped, no need to handle it
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -25,16 +25,16 @@ use super::{
 | 
				
			||||||
    cause::CauseCompensateMiddleware,
 | 
					    cause::CauseCompensateMiddleware,
 | 
				
			||||||
    cursor_hint::CursorHintMiddleware,
 | 
					    cursor_hint::CursorHintMiddleware,
 | 
				
			||||||
    delay_modifiers::{DelayForModifierReleaseMiddleware, ModifierStatusProvider},
 | 
					    delay_modifiers::{DelayForModifierReleaseMiddleware, ModifierStatusProvider},
 | 
				
			||||||
 | 
					    discard::EventsDiscardMiddleware,
 | 
				
			||||||
    markdown::MarkdownMiddleware,
 | 
					    markdown::MarkdownMiddleware,
 | 
				
			||||||
    match_select::MatchSelectMiddleware,
 | 
					    match_select::MatchSelectMiddleware,
 | 
				
			||||||
    matcher::MatcherMiddleware,
 | 
					    matcher::MatcherMiddleware,
 | 
				
			||||||
    multiplex::MultiplexMiddleware,
 | 
					    multiplex::MultiplexMiddleware,
 | 
				
			||||||
    past_discard::PastEventsDiscardMiddleware,
 | 
					 | 
				
			||||||
    render::RenderMiddleware,
 | 
					    render::RenderMiddleware,
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  DisableOptions, EnabledStatusProvider, MatchFilter, MatchInfoProvider, MatchProvider,
 | 
					  DisableOptions, EnabledStatusProvider, MatchFilter, MatchInfoProvider, MatchProvider,
 | 
				
			||||||
  MatchSelector, Matcher, MatcherMiddlewareConfigProvider, Middleware, Multiplexer, PathProvider,
 | 
					  MatchSelector, Matcher, MatcherMiddlewareConfigProvider, Middleware, ModifierStateProvider,
 | 
				
			||||||
  Processor, Renderer, UndoEnabledProvider,
 | 
					  Multiplexer, PathProvider, Processor, Renderer, UndoEnabledProvider,
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
use crate::{
 | 
					use crate::{
 | 
				
			||||||
  event::{Event, EventType},
 | 
					  event::{Event, EventType},
 | 
				
			||||||
| 
						 | 
					@ -69,18 +69,27 @@ impl<'a> DefaultProcessor<'a> {
 | 
				
			||||||
    match_provider: &'a dyn MatchProvider,
 | 
					    match_provider: &'a dyn MatchProvider,
 | 
				
			||||||
    undo_enabled_provider: &'a dyn UndoEnabledProvider,
 | 
					    undo_enabled_provider: &'a dyn UndoEnabledProvider,
 | 
				
			||||||
    enabled_status_provider: &'a dyn EnabledStatusProvider,
 | 
					    enabled_status_provider: &'a dyn EnabledStatusProvider,
 | 
				
			||||||
 | 
					    modifier_state_provider: &'a dyn ModifierStateProvider,
 | 
				
			||||||
  ) -> DefaultProcessor<'a> {
 | 
					  ) -> DefaultProcessor<'a> {
 | 
				
			||||||
    Self {
 | 
					    Self {
 | 
				
			||||||
      event_queue: VecDeque::new(),
 | 
					      event_queue: VecDeque::new(),
 | 
				
			||||||
      middleware: vec![
 | 
					      middleware: vec![
 | 
				
			||||||
        Box::new(PastEventsDiscardMiddleware::new()),
 | 
					        Box::new(EventsDiscardMiddleware::new()),
 | 
				
			||||||
        Box::new(DisableMiddleware::new(disable_options)),
 | 
					        Box::new(DisableMiddleware::new(disable_options)),
 | 
				
			||||||
        Box::new(IconStatusMiddleware::new()),
 | 
					        Box::new(IconStatusMiddleware::new()),
 | 
				
			||||||
        Box::new(MatcherMiddleware::new(matchers, matcher_options_provider)),
 | 
					        Box::new(MatcherMiddleware::new(
 | 
				
			||||||
 | 
					          matchers,
 | 
				
			||||||
 | 
					          matcher_options_provider,
 | 
				
			||||||
 | 
					          modifier_state_provider,
 | 
				
			||||||
 | 
					        )),
 | 
				
			||||||
        Box::new(SuppressMiddleware::new(enabled_status_provider)),
 | 
					        Box::new(SuppressMiddleware::new(enabled_status_provider)),
 | 
				
			||||||
        Box::new(ContextMenuMiddleware::new()),
 | 
					        Box::new(ContextMenuMiddleware::new()),
 | 
				
			||||||
        Box::new(HotKeyMiddleware::new()),
 | 
					        Box::new(HotKeyMiddleware::new()),
 | 
				
			||||||
        Box::new(MatchSelectMiddleware::new(match_filter, match_selector)),
 | 
					        Box::new(MatchSelectMiddleware::new(
 | 
				
			||||||
 | 
					          match_filter,
 | 
				
			||||||
 | 
					          match_selector,
 | 
				
			||||||
 | 
					          event_sequence_provider,
 | 
				
			||||||
 | 
					        )),
 | 
				
			||||||
        Box::new(CauseCompensateMiddleware::new()),
 | 
					        Box::new(CauseCompensateMiddleware::new()),
 | 
				
			||||||
        Box::new(MultiplexMiddleware::new(multiplexer)),
 | 
					        Box::new(MultiplexMiddleware::new(multiplexer)),
 | 
				
			||||||
        Box::new(RenderMiddleware::new(renderer)),
 | 
					        Box::new(RenderMiddleware::new(renderer)),
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -24,42 +24,54 @@ use log::trace;
 | 
				
			||||||
use super::super::Middleware;
 | 
					use super::super::Middleware;
 | 
				
			||||||
use crate::event::{Event, EventType, SourceId};
 | 
					use crate::event::{Event, EventType, SourceId};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// This middleware discards all events that have a source_id smaller than its
 | 
					/// This middleware discards all events that have a source_id between
 | 
				
			||||||
/// configured threshold. This useful to discard past events that might have
 | 
					/// the given maximum and minimum.
 | 
				
			||||||
/// been stuck in the event queue for too long.
 | 
					/// This useful to discard past events that might have been stuck in the
 | 
				
			||||||
pub struct PastEventsDiscardMiddleware {
 | 
					/// event queue for too long, or events generated while the search bar was open.
 | 
				
			||||||
  source_id_threshold: RefCell<SourceId>,
 | 
					pub struct EventsDiscardMiddleware {
 | 
				
			||||||
 | 
					  min_id_threshold: RefCell<SourceId>,
 | 
				
			||||||
 | 
					  max_id_threshold: RefCell<SourceId>,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl PastEventsDiscardMiddleware {
 | 
					impl EventsDiscardMiddleware {
 | 
				
			||||||
  pub fn new() -> Self {
 | 
					  pub fn new() -> Self {
 | 
				
			||||||
    Self {
 | 
					    Self {
 | 
				
			||||||
      source_id_threshold: RefCell::new(0),
 | 
					      min_id_threshold: RefCell::new(0),
 | 
				
			||||||
 | 
					      max_id_threshold: RefCell::new(0),
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl Middleware for PastEventsDiscardMiddleware {
 | 
					impl Middleware for EventsDiscardMiddleware {
 | 
				
			||||||
  fn name(&self) -> &'static str {
 | 
					  fn name(&self) -> &'static str {
 | 
				
			||||||
    "past_discard"
 | 
					    "discard"
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  fn next(&self, event: Event, _: &mut dyn FnMut(Event)) -> Event {
 | 
					  fn next(&self, event: Event, _: &mut dyn FnMut(Event)) -> Event {
 | 
				
			||||||
    let mut source_id_threshold = self.source_id_threshold.borrow_mut();
 | 
					    let mut min_id_threshold = self.min_id_threshold.borrow_mut();
 | 
				
			||||||
 | 
					    let mut max_id_threshold = self.max_id_threshold.borrow_mut();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Filter out previous events
 | 
					    // Filter out previous events
 | 
				
			||||||
    if event.source_id < *source_id_threshold {
 | 
					    if event.source_id < *max_id_threshold && event.source_id >= *min_id_threshold {
 | 
				
			||||||
      trace!("discarding previous event: {:?}", event);
 | 
					      trace!("discarding previous event: {:?}", event);
 | 
				
			||||||
      return Event::caused_by(event.source_id, EventType::NOOP);
 | 
					      return Event::caused_by(event.source_id, EventType::NOOP);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Update the minimum threshold
 | 
					    // Update the thresholds
 | 
				
			||||||
    if let EventType::DiscardPrevious(m_event) = &event.etype {
 | 
					    if let EventType::DiscardPrevious(m_event) = &event.etype {
 | 
				
			||||||
      trace!(
 | 
					      trace!(
 | 
				
			||||||
        "updating minimum source id threshold for events to: {}",
 | 
					        "updating discard max_id_threshold threshold for events to: {}",
 | 
				
			||||||
        m_event.minimum_source_id
 | 
					        m_event.minimum_source_id
 | 
				
			||||||
      );
 | 
					      );
 | 
				
			||||||
      *source_id_threshold = m_event.minimum_source_id;
 | 
					      *max_id_threshold = m_event.minimum_source_id;
 | 
				
			||||||
 | 
					    } else if let EventType::DiscardBetween(m_event) = &event.etype {
 | 
				
			||||||
 | 
					      trace!(
 | 
				
			||||||
 | 
					        "updating discard thresholds for events to: max={} min={}",
 | 
				
			||||||
 | 
					        m_event.end_id,
 | 
				
			||||||
 | 
					        m_event.start_id
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					      *max_id_threshold = m_event.end_id;
 | 
				
			||||||
 | 
					      *min_id_threshold = m_event.start_id;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    event
 | 
					    event
 | 
				
			||||||
| 
						 | 
					@ -20,7 +20,13 @@
 | 
				
			||||||
use log::{debug, error};
 | 
					use log::{debug, error};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use super::super::Middleware;
 | 
					use super::super::Middleware;
 | 
				
			||||||
use crate::event::{internal::MatchSelectedEvent, Event, EventType};
 | 
					use crate::{
 | 
				
			||||||
 | 
					  event::{
 | 
				
			||||||
 | 
					    internal::{DiscardBetweenEvent, MatchSelectedEvent},
 | 
				
			||||||
 | 
					    Event, EventType,
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  process::EventSequenceProvider,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub trait MatchFilter {
 | 
					pub trait MatchFilter {
 | 
				
			||||||
  fn filter_active(&self, matches_ids: &[i32]) -> Vec<i32>;
 | 
					  fn filter_active(&self, matches_ids: &[i32]) -> Vec<i32>;
 | 
				
			||||||
| 
						 | 
					@ -33,13 +39,19 @@ pub trait MatchSelector {
 | 
				
			||||||
pub struct MatchSelectMiddleware<'a> {
 | 
					pub struct MatchSelectMiddleware<'a> {
 | 
				
			||||||
  match_filter: &'a dyn MatchFilter,
 | 
					  match_filter: &'a dyn MatchFilter,
 | 
				
			||||||
  match_selector: &'a dyn MatchSelector,
 | 
					  match_selector: &'a dyn MatchSelector,
 | 
				
			||||||
 | 
					  event_sequence_provider: &'a dyn EventSequenceProvider,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl<'a> MatchSelectMiddleware<'a> {
 | 
					impl<'a> MatchSelectMiddleware<'a> {
 | 
				
			||||||
  pub fn new(match_filter: &'a dyn MatchFilter, match_selector: &'a dyn MatchSelector) -> Self {
 | 
					  pub fn new(
 | 
				
			||||||
 | 
					    match_filter: &'a dyn MatchFilter,
 | 
				
			||||||
 | 
					    match_selector: &'a dyn MatchSelector,
 | 
				
			||||||
 | 
					    event_sequence_provider: &'a dyn EventSequenceProvider,
 | 
				
			||||||
 | 
					  ) -> Self {
 | 
				
			||||||
    Self {
 | 
					    Self {
 | 
				
			||||||
      match_filter,
 | 
					      match_filter,
 | 
				
			||||||
      match_selector,
 | 
					      match_selector,
 | 
				
			||||||
 | 
					      event_sequence_provider,
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -49,7 +61,7 @@ impl<'a> Middleware for MatchSelectMiddleware<'a> {
 | 
				
			||||||
    "match_select"
 | 
					    "match_select"
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  fn next(&self, event: Event, _: &mut dyn FnMut(Event)) -> Event {
 | 
					  fn next(&self, event: Event, dispatch: &mut dyn FnMut(Event)) -> Event {
 | 
				
			||||||
    if let EventType::MatchesDetected(m_event) = event.etype {
 | 
					    if let EventType::MatchesDetected(m_event) = event.etype {
 | 
				
			||||||
      let matches_ids: Vec<i32> = m_event.matches.iter().map(|m| m.id).collect();
 | 
					      let matches_ids: Vec<i32> = m_event.matches.iter().map(|m| m.id).collect();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -75,7 +87,10 @@ impl<'a> Middleware for MatchSelectMiddleware<'a> {
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        _ => {
 | 
					        _ => {
 | 
				
			||||||
 | 
					          let start_event_id = self.event_sequence_provider.get_next_id();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
          // Multiple matches, we need to ask the user which one to use
 | 
					          // Multiple matches, we need to ask the user which one to use
 | 
				
			||||||
 | 
					          let next_event =
 | 
				
			||||||
            if let Some(selected_id) = self.match_selector.select(&valid_ids, m_event.is_search) {
 | 
					            if let Some(selected_id) = self.match_selector.select(&valid_ids, m_event.is_search) {
 | 
				
			||||||
              let m = m_event.matches.into_iter().find(|m| m.id == selected_id);
 | 
					              let m = m_event.matches.into_iter().find(|m| m.id == selected_id);
 | 
				
			||||||
              if let Some(m) = m {
 | 
					              if let Some(m) = m {
 | 
				
			||||||
| 
						 | 
					@ -90,7 +105,22 @@ impl<'a> Middleware for MatchSelectMiddleware<'a> {
 | 
				
			||||||
            } else {
 | 
					            } else {
 | 
				
			||||||
              debug!("MatchSelectMiddleware did not receive any match selection");
 | 
					              debug!("MatchSelectMiddleware did not receive any match selection");
 | 
				
			||||||
              Event::caused_by(event.source_id, EventType::NOOP)
 | 
					              Event::caused_by(event.source_id, EventType::NOOP)
 | 
				
			||||||
          }
 | 
					            };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          let end_event_id = self.event_sequence_provider.get_next_id();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          // We want to prevent espanso from "stacking up" events while the search bar is open,
 | 
				
			||||||
 | 
					          // therefore we filter out all events that were generated while the search bar was open.
 | 
				
			||||||
 | 
					          // See also: https://github.com/federico-terzi/espanso/issues/781
 | 
				
			||||||
 | 
					          dispatch(Event::caused_by(
 | 
				
			||||||
 | 
					            event.source_id,
 | 
				
			||||||
 | 
					            EventType::DiscardBetween(DiscardBetweenEvent {
 | 
				
			||||||
 | 
					              start_id: start_event_id,
 | 
				
			||||||
 | 
					              end_id: end_event_id,
 | 
				
			||||||
 | 
					            }),
 | 
				
			||||||
 | 
					          ));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          next_event
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      };
 | 
					      };
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -57,18 +57,32 @@ pub trait MatcherMiddlewareConfigProvider {
 | 
				
			||||||
  fn max_history_size(&self) -> usize;
 | 
					  fn max_history_size(&self) -> usize;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub trait ModifierStateProvider {
 | 
				
			||||||
 | 
					  fn get_modifier_state(&self) -> ModifierState;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Debug, Clone)]
 | 
				
			||||||
 | 
					pub struct ModifierState {
 | 
				
			||||||
 | 
					  pub is_ctrl_down: bool,
 | 
				
			||||||
 | 
					  pub is_alt_down: bool,
 | 
				
			||||||
 | 
					  pub is_meta_down: bool,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub struct MatcherMiddleware<'a, State> {
 | 
					pub struct MatcherMiddleware<'a, State> {
 | 
				
			||||||
  matchers: &'a [&'a dyn Matcher<'a, State>],
 | 
					  matchers: &'a [&'a dyn Matcher<'a, State>],
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  matcher_states: RefCell<VecDeque<Vec<State>>>,
 | 
					  matcher_states: RefCell<VecDeque<Vec<State>>>,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  max_history_size: usize,
 | 
					  max_history_size: usize,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  modifier_status_provider: &'a dyn ModifierStateProvider,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl<'a, State> MatcherMiddleware<'a, State> {
 | 
					impl<'a, State> MatcherMiddleware<'a, State> {
 | 
				
			||||||
  pub fn new(
 | 
					  pub fn new(
 | 
				
			||||||
    matchers: &'a [&'a dyn Matcher<'a, State>],
 | 
					    matchers: &'a [&'a dyn Matcher<'a, State>],
 | 
				
			||||||
    options_provider: &'a dyn MatcherMiddlewareConfigProvider,
 | 
					    options_provider: &'a dyn MatcherMiddlewareConfigProvider,
 | 
				
			||||||
 | 
					    modifier_status_provider: &'a dyn ModifierStateProvider,
 | 
				
			||||||
  ) -> Self {
 | 
					  ) -> Self {
 | 
				
			||||||
    let max_history_size = options_provider.max_history_size();
 | 
					    let max_history_size = options_provider.max_history_size();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -76,6 +90,7 @@ impl<'a, State> MatcherMiddleware<'a, State> {
 | 
				
			||||||
      matchers,
 | 
					      matchers,
 | 
				
			||||||
      matcher_states: RefCell::new(VecDeque::new()),
 | 
					      matcher_states: RefCell::new(VecDeque::new()),
 | 
				
			||||||
      max_history_size,
 | 
					      max_history_size,
 | 
				
			||||||
 | 
					      modifier_status_provider,
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -101,6 +116,17 @@ impl<'a, State> Middleware for MatcherMiddleware<'a, State> {
 | 
				
			||||||
          matcher_states.pop_back();
 | 
					          matcher_states.pop_back();
 | 
				
			||||||
          return event;
 | 
					          return event;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // We need to filter out some keyboard events if they are generated
 | 
				
			||||||
 | 
					        // while some modifier keys are pressed, otherwise we could have
 | 
				
			||||||
 | 
					        // wrong matches being detected.
 | 
				
			||||||
 | 
					        // See: https://github.com/federico-terzi/espanso/issues/725
 | 
				
			||||||
 | 
					        if should_skip_key_event_due_to_modifier_press(
 | 
				
			||||||
 | 
					          &self.modifier_status_provider.get_modifier_state(),
 | 
				
			||||||
 | 
					        ) {
 | 
				
			||||||
 | 
					          trace!("skipping keyboard event because incompatible modifiers are pressed");
 | 
				
			||||||
 | 
					          return event;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      // Some keys (such as the arrow keys) and mouse clicks prevent espanso from building
 | 
					      // Some keys (such as the arrow keys) and mouse clicks prevent espanso from building
 | 
				
			||||||
| 
						 | 
					@ -161,6 +187,17 @@ fn is_event_of_interest(event_type: &EventType) -> bool {
 | 
				
			||||||
        // Skip non-press events
 | 
					        // Skip non-press events
 | 
				
			||||||
        false
 | 
					        false
 | 
				
			||||||
      } else {
 | 
					      } else {
 | 
				
			||||||
 | 
					        // Skip linux Keyboard (XKB) Extension function and modifier keys
 | 
				
			||||||
 | 
					        // In hex, they have the byte 3 = 0xfe
 | 
				
			||||||
 | 
					        // See list in "keysymdef.h" file
 | 
				
			||||||
 | 
					        if cfg!(target_os = "linux") {
 | 
				
			||||||
 | 
					          if let Key::Other(raw_code) = &keyboard_event.key {
 | 
				
			||||||
 | 
					            if (65025..=65276).contains(raw_code) {
 | 
				
			||||||
 | 
					              return false;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // Skip modifier keys
 | 
					        // Skip modifier keys
 | 
				
			||||||
        !matches!(
 | 
					        !matches!(
 | 
				
			||||||
          keyboard_event.key,
 | 
					          keyboard_event.key,
 | 
				
			||||||
| 
						 | 
					@ -205,4 +242,16 @@ fn is_invalidating_event(event_type: &EventType) -> bool {
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn should_skip_key_event_due_to_modifier_press(modifier_state: &ModifierState) -> bool {
 | 
				
			||||||
 | 
					  if cfg!(target_os = "macos") {
 | 
				
			||||||
 | 
					    modifier_state.is_meta_down
 | 
				
			||||||
 | 
					  } else if cfg!(target_os = "windows") {
 | 
				
			||||||
 | 
					    false
 | 
				
			||||||
 | 
					  } else if cfg!(target_os = "linux") {
 | 
				
			||||||
 | 
					    modifier_state.is_alt_down || modifier_state.is_meta_down
 | 
				
			||||||
 | 
					  } else {
 | 
				
			||||||
 | 
					    unreachable!()
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// TODO: test
 | 
					// TODO: test
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -23,6 +23,7 @@ pub mod context_menu;
 | 
				
			||||||
pub mod cursor_hint;
 | 
					pub mod cursor_hint;
 | 
				
			||||||
pub mod delay_modifiers;
 | 
					pub mod delay_modifiers;
 | 
				
			||||||
pub mod disable;
 | 
					pub mod disable;
 | 
				
			||||||
 | 
					pub mod discard;
 | 
				
			||||||
pub mod exit;
 | 
					pub mod exit;
 | 
				
			||||||
pub mod hotkey;
 | 
					pub mod hotkey;
 | 
				
			||||||
pub mod icon_status;
 | 
					pub mod icon_status;
 | 
				
			||||||
| 
						 | 
					@ -31,7 +32,6 @@ pub mod markdown;
 | 
				
			||||||
pub mod match_select;
 | 
					pub mod match_select;
 | 
				
			||||||
pub mod matcher;
 | 
					pub mod matcher;
 | 
				
			||||||
pub mod multiplex;
 | 
					pub mod multiplex;
 | 
				
			||||||
pub mod past_discard;
 | 
					 | 
				
			||||||
pub mod render;
 | 
					pub mod render;
 | 
				
			||||||
pub mod search;
 | 
					pub mod search;
 | 
				
			||||||
pub mod suppress;
 | 
					pub mod suppress;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -39,7 +39,8 @@ pub use middleware::disable::DisableOptions;
 | 
				
			||||||
pub use middleware::image_resolve::PathProvider;
 | 
					pub use middleware::image_resolve::PathProvider;
 | 
				
			||||||
pub use middleware::match_select::{MatchFilter, MatchSelector};
 | 
					pub use middleware::match_select::{MatchFilter, MatchSelector};
 | 
				
			||||||
pub use middleware::matcher::{
 | 
					pub use middleware::matcher::{
 | 
				
			||||||
  MatchResult, Matcher, MatcherEvent, MatcherMiddlewareConfigProvider,
 | 
					  MatchResult, Matcher, MatcherEvent, MatcherMiddlewareConfigProvider, ModifierState,
 | 
				
			||||||
 | 
					  ModifierStateProvider,
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
pub use middleware::multiplex::Multiplexer;
 | 
					pub use middleware::multiplex::Multiplexer;
 | 
				
			||||||
pub use middleware::render::{Renderer, RendererError};
 | 
					pub use middleware::render::{Renderer, RendererError};
 | 
				
			||||||
| 
						 | 
					@ -63,6 +64,7 @@ pub fn default<'a, MatcherState>(
 | 
				
			||||||
  match_provider: &'a dyn MatchProvider,
 | 
					  match_provider: &'a dyn MatchProvider,
 | 
				
			||||||
  undo_enabled_provider: &'a dyn UndoEnabledProvider,
 | 
					  undo_enabled_provider: &'a dyn UndoEnabledProvider,
 | 
				
			||||||
  enabled_status_provider: &'a dyn EnabledStatusProvider,
 | 
					  enabled_status_provider: &'a dyn EnabledStatusProvider,
 | 
				
			||||||
 | 
					  modifier_state_provider: &'a dyn ModifierStateProvider,
 | 
				
			||||||
) -> impl Processor + 'a {
 | 
					) -> impl Processor + 'a {
 | 
				
			||||||
  default::DefaultProcessor::new(
 | 
					  default::DefaultProcessor::new(
 | 
				
			||||||
    matchers,
 | 
					    matchers,
 | 
				
			||||||
| 
						 | 
					@ -79,5 +81,6 @@ pub fn default<'a, MatcherState>(
 | 
				
			||||||
    match_provider,
 | 
					    match_provider,
 | 
				
			||||||
    undo_enabled_provider,
 | 
					    undo_enabled_provider,
 | 
				
			||||||
    enabled_status_provider,
 | 
					    enabled_status_provider,
 | 
				
			||||||
 | 
					    modifier_state_provider,
 | 
				
			||||||
  )
 | 
					  )
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -29,4 +29,6 @@ extern "C" {
 | 
				
			||||||
  pub fn mac_utils_prompt_accessibility() -> i32;
 | 
					  pub fn mac_utils_prompt_accessibility() -> i32;
 | 
				
			||||||
  pub fn mac_utils_transition_to_foreground_app();
 | 
					  pub fn mac_utils_transition_to_foreground_app();
 | 
				
			||||||
  pub fn mac_utils_transition_to_background_app();
 | 
					  pub fn mac_utils_transition_to_background_app();
 | 
				
			||||||
 | 
					  pub fn mac_utils_start_headless_eventloop();
 | 
				
			||||||
 | 
					  pub fn mac_utils_exit_headless_eventloop();
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -113,6 +113,20 @@ pub fn convert_to_background_app() {
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[cfg(target_os = "macos")]
 | 
				
			||||||
 | 
					pub fn start_headless_eventloop() {
 | 
				
			||||||
 | 
					  unsafe {
 | 
				
			||||||
 | 
					    ffi::mac_utils_start_headless_eventloop();
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[cfg(target_os = "macos")]
 | 
				
			||||||
 | 
					pub fn exit_headless_eventloop() {
 | 
				
			||||||
 | 
					  unsafe {
 | 
				
			||||||
 | 
					    ffi::mac_utils_exit_headless_eventloop();
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[cfg(test)]
 | 
					#[cfg(test)]
 | 
				
			||||||
#[cfg(target_os = "macos")]
 | 
					#[cfg(target_os = "macos")]
 | 
				
			||||||
mod tests {
 | 
					mod tests {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -40,4 +40,8 @@ extern "C" void mac_utils_transition_to_foreground_app();
 | 
				
			||||||
// When called, convert the current process to a background app (hide the dock icon).
 | 
					// When called, convert the current process to a background app (hide the dock icon).
 | 
				
			||||||
extern "C" void mac_utils_transition_to_background_app();
 | 
					extern "C" void mac_utils_transition_to_background_app();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Start and stop a "headless" eventloop to receive NSApplication events.
 | 
				
			||||||
 | 
					extern "C" void mac_utils_start_headless_eventloop();
 | 
				
			||||||
 | 
					extern "C" void mac_utils_exit_headless_eventloop();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#endif //ESPANSO_MAC_UTILS_H
 | 
					#endif //ESPANSO_MAC_UTILS_H
 | 
				
			||||||
| 
						 | 
					@ -89,3 +89,15 @@ void mac_utils_transition_to_background_app() {
 | 
				
			||||||
  ProcessSerialNumber psn = { 0, kCurrentProcess };
 | 
					  ProcessSerialNumber psn = { 0, kCurrentProcess };
 | 
				
			||||||
  TransformProcessType(&psn, kProcessTransformToUIElementApplication);
 | 
					  TransformProcessType(&psn, kProcessTransformToUIElementApplication);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void mac_utils_start_headless_eventloop() {
 | 
				
			||||||
 | 
					  NSApplication * application = [NSApplication sharedApplication];
 | 
				
			||||||
 | 
					  [NSApp run];
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void mac_utils_exit_headless_eventloop() {
 | 
				
			||||||
 | 
					  dispatch_async(dispatch_get_main_queue(), ^(void) {
 | 
				
			||||||
 | 
					    [NSApp stop:nil];
 | 
				
			||||||
 | 
					    [NSApp abortModal];
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -457,5 +457,22 @@ 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();
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -26,9 +26,8 @@
 | 
				
			||||||
#include "mac.h"
 | 
					#include "mac.h"
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void setFrameIcon(const char * iconPath, wxFrame * frame) {
 | 
					void setFrameIcon(wxString iconPath, wxFrame * frame) {
 | 
				
			||||||
    if (iconPath) {
 | 
					    if (!iconPath.IsEmpty()) {
 | 
				
			||||||
        wxString iconPath(iconPath);
 | 
					 | 
				
			||||||
        wxBitmapType imgType = wxICON_DEFAULT_TYPE;
 | 
					        wxBitmapType imgType = wxICON_DEFAULT_TYPE;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        #ifdef __WXMSW__
 | 
					        #ifdef __WXMSW__
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -27,7 +27,7 @@
 | 
				
			||||||
    #include <wx/wx.h>
 | 
					    #include <wx/wx.h>
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void setFrameIcon(const char * iconPath, wxFrame * frame);
 | 
					void setFrameIcon(wxString iconPath, wxFrame * frame);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void Activate(wxFrame * frame);
 | 
					void Activate(wxFrame * frame);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -102,8 +102,8 @@ enum
 | 
				
			||||||
 | 
					
 | 
				
			||||||
bool FormApp::OnInit()
 | 
					bool FormApp::OnInit()
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    FormFrame *frame = new FormFrame(formMetadata->windowTitle, wxPoint(50, 50), wxSize(450, 340) );
 | 
					    FormFrame *frame = new FormFrame(wxString::FromUTF8(formMetadata->windowTitle), wxPoint(50, 50), wxSize(450, 340) );
 | 
				
			||||||
    setFrameIcon(formMetadata->iconPath, frame);
 | 
					    setFrameIcon(wxString::FromUTF8(formMetadata->iconPath), frame);
 | 
				
			||||||
    frame->Show( true );
 | 
					    frame->Show( true );
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    Activate(frame);
 | 
					    Activate(frame);
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -166,7 +166,7 @@ private:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
bool SearchApp::OnInit()
 | 
					bool SearchApp::OnInit()
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    SearchFrame *frame = new SearchFrame(searchMetadata->windowTitle, wxPoint(50, 50), wxSize(450, 340));
 | 
					    SearchFrame *frame = new SearchFrame(wxString::FromUTF8(searchMetadata->windowTitle), wxPoint(50, 50), wxSize(450, 340));
 | 
				
			||||||
    frame->Show(true);
 | 
					    frame->Show(true);
 | 
				
			||||||
    SetupWindowStyle(frame);
 | 
					    SetupWindowStyle(frame);
 | 
				
			||||||
    Activate(frame);
 | 
					    Activate(frame);
 | 
				
			||||||
| 
						 | 
					@ -198,7 +198,7 @@ SearchFrame::SearchFrame(const wxString &title, const wxPoint &pos, const wxSize
 | 
				
			||||||
    iconPanel = nullptr;
 | 
					    iconPanel = nullptr;
 | 
				
			||||||
    if (searchMetadata->iconPath)
 | 
					    if (searchMetadata->iconPath)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        wxString iconPath = wxString(searchMetadata->iconPath);
 | 
					        wxString iconPath = wxString::FromUTF8(searchMetadata->iconPath);
 | 
				
			||||||
        if (wxFileExists(iconPath))
 | 
					        if (wxFileExists(iconPath))
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            wxBitmap bitmap = wxBitmap(iconPath, wxBITMAP_TYPE_PNG);
 | 
					            wxBitmap bitmap = wxBitmap(iconPath, wxBITMAP_TYPE_PNG);
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -81,7 +81,7 @@ public:
 | 
				
			||||||
      if (error_set_metadata->errors[i].level == ERROR_METADATA_LEVEL_WARNING) {
 | 
					      if (error_set_metadata->errors[i].level == ERROR_METADATA_LEVEL_WARNING) {
 | 
				
			||||||
        level = wxT("WARNING");
 | 
					        level = wxT("WARNING");
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      wxString error_text = wxString::Format(wxT("[%s] %s\n"), level, error_set_metadata->errors[i].message);
 | 
					      wxString error_text = wxString::Format(wxT("[%s] %s\n"), level, wxString::FromUTF8(error_set_metadata->errors[i].message));
 | 
				
			||||||
      errors_text.Append(error_text);
 | 
					      errors_text.Append(error_text);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -171,7 +171,7 @@ bool TroubleshootingApp::OnInit()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  if (troubleshooting_metadata->window_icon_path)
 | 
					  if (troubleshooting_metadata->window_icon_path)
 | 
				
			||||||
  {
 | 
					  {
 | 
				
			||||||
    setFrameIcon(troubleshooting_metadata->window_icon_path, frame);
 | 
					    setFrameIcon(wxString::FromUTF8(troubleshooting_metadata->window_icon_path), frame);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  frame->Show(true);
 | 
					  frame->Show(true);
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -54,7 +54,7 @@ DerivedWelcomeFrame::DerivedWelcomeFrame(wxWindow *parent)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  if (welcome_metadata->tray_image_path)
 | 
					  if (welcome_metadata->tray_image_path)
 | 
				
			||||||
  {
 | 
					  {
 | 
				
			||||||
    wxBitmap trayBitmap = wxBitmap(welcome_metadata->tray_image_path, wxBITMAP_TYPE_PNG);
 | 
					    wxBitmap trayBitmap = wxBitmap(wxString::FromUTF8(welcome_metadata->tray_image_path), wxBITMAP_TYPE_PNG);
 | 
				
			||||||
    this->tray_bitmap->SetBitmap(trayBitmap);
 | 
					    this->tray_bitmap->SetBitmap(trayBitmap);
 | 
				
			||||||
    #ifdef __WXOSX__
 | 
					    #ifdef __WXOSX__
 | 
				
			||||||
      this->tray_info_label->SetLabel("You should see the espanso icon on the status bar:");
 | 
					      this->tray_info_label->SetLabel("You should see the espanso icon on the status bar:");
 | 
				
			||||||
| 
						 | 
					@ -91,7 +91,7 @@ bool WelcomeApp::OnInit()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  if (welcome_metadata->window_icon_path)
 | 
					  if (welcome_metadata->window_icon_path)
 | 
				
			||||||
  {
 | 
					  {
 | 
				
			||||||
    setFrameIcon(welcome_metadata->window_icon_path, frame);
 | 
					    setFrameIcon(wxString::FromUTF8(welcome_metadata->window_icon_path), frame);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  frame->Show(true);
 | 
					  frame->Show(true);
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -45,7 +45,7 @@
 | 
				
			||||||
            <property name="minimum_size"></property>
 | 
					            <property name="minimum_size"></property>
 | 
				
			||||||
            <property name="name">WelcomeFrame</property>
 | 
					            <property name="name">WelcomeFrame</property>
 | 
				
			||||||
            <property name="pos"></property>
 | 
					            <property name="pos"></property>
 | 
				
			||||||
            <property name="size">521,544</property>
 | 
					            <property name="size">521,597</property>
 | 
				
			||||||
            <property name="style">wxCAPTION|wxCLOSE_BOX|wxSYSTEM_MENU</property>
 | 
					            <property name="style">wxCAPTION|wxCLOSE_BOX|wxSYSTEM_MENU</property>
 | 
				
			||||||
            <property name="subclass">; ; forward_declare</property>
 | 
					            <property name="subclass">; ; forward_declare</property>
 | 
				
			||||||
            <property name="title">Espanso is running!</property>
 | 
					            <property name="title">Espanso is running!</property>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -54,7 +54,7 @@ class WelcomeFrame : public wxFrame
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	public:
 | 
						public:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		WelcomeFrame( wxWindow* parent, wxWindowID id = wxID_ANY, const wxString& title = wxT("Espanso is running!"), const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxSize( 521,544 ), long style = wxCAPTION|wxCLOSE_BOX|wxSYSTEM_MENU|wxTAB_TRAVERSAL );
 | 
							WelcomeFrame( wxWindow* parent, wxWindowID id = wxID_ANY, const wxString& title = wxT("Espanso is running!"), const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxSize( 521,597 ), long style = wxCAPTION|wxCLOSE_BOX|wxSYSTEM_MENU|wxTAB_TRAVERSAL );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		~WelcomeFrame();
 | 
							~WelcomeFrame();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -130,7 +130,7 @@ DerivedFrame::DerivedFrame(wxWindow *parent)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  if (wizard_metadata->welcome_image_path)
 | 
					  if (wizard_metadata->welcome_image_path)
 | 
				
			||||||
  {
 | 
					  {
 | 
				
			||||||
    wxBitmap welcomeBitmap = wxBitmap(wizard_metadata->welcome_image_path, wxBITMAP_TYPE_PNG);
 | 
					    wxBitmap welcomeBitmap = wxBitmap(wxString::FromUTF8(wizard_metadata->welcome_image_path), wxBITMAP_TYPE_PNG);
 | 
				
			||||||
    this->welcome_image->SetBitmap(welcomeBitmap);
 | 
					    this->welcome_image->SetBitmap(welcomeBitmap);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -140,12 +140,12 @@ DerivedFrame::DerivedFrame(wxWindow *parent)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  if (wizard_metadata->accessibility_image_1_path)
 | 
					  if (wizard_metadata->accessibility_image_1_path)
 | 
				
			||||||
  {
 | 
					  {
 | 
				
			||||||
    wxBitmap accessiblityImage1 = wxBitmap(wizard_metadata->accessibility_image_1_path, wxBITMAP_TYPE_PNG);
 | 
					    wxBitmap accessiblityImage1 = wxBitmap(wxString::FromUTF8(wizard_metadata->accessibility_image_1_path), wxBITMAP_TYPE_PNG);
 | 
				
			||||||
    this->accessibility_image1->SetBitmap(accessiblityImage1);
 | 
					    this->accessibility_image1->SetBitmap(accessiblityImage1);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  if (wizard_metadata->accessibility_image_2_path)
 | 
					  if (wizard_metadata->accessibility_image_2_path)
 | 
				
			||||||
  {
 | 
					  {
 | 
				
			||||||
    wxBitmap accessiblityImage2 = wxBitmap(wizard_metadata->accessibility_image_2_path, wxBITMAP_TYPE_PNG);
 | 
					    wxBitmap accessiblityImage2 = wxBitmap(wxString::FromUTF8(wizard_metadata->accessibility_image_2_path), wxBITMAP_TYPE_PNG);
 | 
				
			||||||
    this->accessibility_image2->SetBitmap(accessiblityImage2);
 | 
					    this->accessibility_image2->SetBitmap(accessiblityImage2);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -380,7 +380,7 @@ bool WizardApp::OnInit()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  if (wizard_metadata->window_icon_path)
 | 
					  if (wizard_metadata->window_icon_path)
 | 
				
			||||||
  {
 | 
					  {
 | 
				
			||||||
    setFrameIcon(wizard_metadata->window_icon_path, frame);
 | 
					    setFrameIcon(wxString::FromUTF8(wizard_metadata->window_icon_path), frame);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  frame->Show(true);
 | 
					  frame->Show(true);
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -24,7 +24,7 @@ use std::{
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use crate::{Extension, ExtensionOutput, ExtensionResult, Params, Value};
 | 
					use crate::{Extension, ExtensionOutput, ExtensionResult, Params, Value};
 | 
				
			||||||
use log::{info, warn};
 | 
					use log::{error, info};
 | 
				
			||||||
use thiserror::Error;
 | 
					use thiserror::Error;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[allow(clippy::upper_case_acronyms)]
 | 
					#[allow(clippy::upper_case_acronyms)]
 | 
				
			||||||
| 
						 | 
					@ -191,24 +191,16 @@ impl Extension for ShellExtension {
 | 
				
			||||||
            info!("this debug information was shown because the 'debug' option is true.");
 | 
					            info!("this debug information was shown because the 'debug' option is true.");
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
          let ignore_error = params
 | 
					          if !output.status.success() {
 | 
				
			||||||
            .get("ignore_error")
 | 
					            error!(
 | 
				
			||||||
            .and_then(|v| v.as_bool())
 | 
					 | 
				
			||||||
            .copied()
 | 
					 | 
				
			||||||
            .unwrap_or(false);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
          if !output.status.success() || !error_str.trim().is_empty() {
 | 
					 | 
				
			||||||
            warn!(
 | 
					 | 
				
			||||||
              "shell command exited with code: {} and error: {}",
 | 
					              "shell command exited with code: {} and error: {}",
 | 
				
			||||||
              output.status, error_str
 | 
					              output.status, error_str
 | 
				
			||||||
            );
 | 
					            );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if !ignore_error {
 | 
					 | 
				
			||||||
            return ExtensionResult::Error(
 | 
					            return ExtensionResult::Error(
 | 
				
			||||||
              ShellExtensionError::ExecutionError(error_str.to_string()).into(),
 | 
					              ShellExtensionError::ExecutionError(error_str.to_string()).into(),
 | 
				
			||||||
            );
 | 
					            );
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
          }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
          let trim = params
 | 
					          let trim = params
 | 
				
			||||||
            .get("trim")
 | 
					            .get("trim")
 | 
				
			||||||
| 
						 | 
					@ -388,24 +380,4 @@ mod tests {
 | 
				
			||||||
      ExtensionResult::Error(_)
 | 
					      ExtensionResult::Error(_)
 | 
				
			||||||
    ));
 | 
					    ));
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					 | 
				
			||||||
  #[test]
 | 
					 | 
				
			||||||
  #[cfg(not(target_os = "windows"))]
 | 
					 | 
				
			||||||
  fn ignore_error() {
 | 
					 | 
				
			||||||
    let extension = ShellExtension::new(&PathBuf::new());
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    let param = vec![
 | 
					 | 
				
			||||||
      ("cmd".to_string(), Value::String("exit 1".to_string())),
 | 
					 | 
				
			||||||
      ("ignore_error".to_string(), Value::Bool(true)),
 | 
					 | 
				
			||||||
    ]
 | 
					 | 
				
			||||||
    .into_iter()
 | 
					 | 
				
			||||||
    .collect::<Params>();
 | 
					 | 
				
			||||||
    assert_eq!(
 | 
					 | 
				
			||||||
      extension
 | 
					 | 
				
			||||||
        .calculate(&Default::default(), &Default::default(), ¶m)
 | 
					 | 
				
			||||||
        .into_success()
 | 
					 | 
				
			||||||
        .unwrap(),
 | 
					 | 
				
			||||||
      ExtensionOutput::Single("".to_string())
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,6 +1,6 @@
 | 
				
			||||||
[package]
 | 
					[package]
 | 
				
			||||||
name = "espanso"
 | 
					name = "espanso"
 | 
				
			||||||
version = "2.0.2-alpha"
 | 
					version = "2.0.3-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"
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -32,8 +32,8 @@ use crate::{
 | 
				
			||||||
  common_flags::*,
 | 
					  common_flags::*,
 | 
				
			||||||
  exit_code::{
 | 
					  exit_code::{
 | 
				
			||||||
    DAEMON_ALREADY_RUNNING, DAEMON_FATAL_CONFIG_ERROR, DAEMON_GENERAL_ERROR,
 | 
					    DAEMON_ALREADY_RUNNING, DAEMON_FATAL_CONFIG_ERROR, DAEMON_GENERAL_ERROR,
 | 
				
			||||||
    DAEMON_LEGACY_ALREADY_RUNNING, DAEMON_SUCCESS, WORKER_EXIT_ALL_PROCESSES, WORKER_RESTART,
 | 
					    DAEMON_LEGACY_ALREADY_RUNNING, DAEMON_SUCCESS, WORKER_ERROR_EXIT_NO_CODE,
 | 
				
			||||||
    WORKER_SUCCESS,
 | 
					    WORKER_EXIT_ALL_PROCESSES, WORKER_RESTART, WORKER_SUCCESS,
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  ipc::{create_ipc_client_to_worker, IPCEvent},
 | 
					  ipc::{create_ipc_client_to_worker, IPCEvent},
 | 
				
			||||||
  lock::{acquire_daemon_lock, acquire_legacy_lock, acquire_worker_lock},
 | 
					  lock::{acquire_daemon_lock, acquire_legacy_lock, acquire_worker_lock},
 | 
				
			||||||
| 
						 | 
					@ -271,6 +271,10 @@ fn spawn_worker(
 | 
				
			||||||
              .send(code)
 | 
					              .send(code)
 | 
				
			||||||
              .expect("unable to forward worker exit code");
 | 
					              .expect("unable to forward worker exit code");
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					          exit_notify
 | 
				
			||||||
 | 
					            .send(WORKER_ERROR_EXIT_NO_CODE)
 | 
				
			||||||
 | 
					            .expect("unable to forward worker exit code");
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    })
 | 
					    })
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -20,6 +20,7 @@
 | 
				
			||||||
use std::process::Command;
 | 
					use std::process::Command;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use anyhow::Result;
 | 
					use anyhow::Result;
 | 
				
			||||||
 | 
					use std::process::ExitStatus;
 | 
				
			||||||
use thiserror::Error;
 | 
					use thiserror::Error;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use crate::cli::util::CommandExt;
 | 
					use crate::cli::util::CommandExt;
 | 
				
			||||||
| 
						 | 
					@ -31,8 +32,7 @@ pub fn launch_daemon(paths_overrides: &PathsOverrides) -> Result<()> {
 | 
				
			||||||
  command.args(&["daemon"]);
 | 
					  command.args(&["daemon"]);
 | 
				
			||||||
  command.with_paths_overrides(paths_overrides);
 | 
					  command.with_paths_overrides(paths_overrides);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  let mut child = command.spawn()?;
 | 
					  let result = spawn_and_wait(command)?;
 | 
				
			||||||
  let result = child.wait()?;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  if result.success() {
 | 
					  if result.success() {
 | 
				
			||||||
    Ok(())
 | 
					    Ok(())
 | 
				
			||||||
| 
						 | 
					@ -46,3 +46,33 @@ pub enum DaemonError {
 | 
				
			||||||
  #[error("unexpected error, 'espanso daemon' returned a non-zero exit code.")]
 | 
					  #[error("unexpected error, 'espanso daemon' returned a non-zero exit code.")]
 | 
				
			||||||
  NonZeroExitCode,
 | 
					  NonZeroExitCode,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[cfg(not(target_os = "macos"))]
 | 
				
			||||||
 | 
					fn spawn_and_wait(mut command: Command) -> Result<ExitStatus> {
 | 
				
			||||||
 | 
					  let mut child = command.spawn()?;
 | 
				
			||||||
 | 
					  Ok(child.wait()?)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// On macOS, if we simply wait for the daemon process to terminate, the application will
 | 
				
			||||||
 | 
					// appear as "Not Responding" after a few seconds, even though it's working correctly.
 | 
				
			||||||
 | 
					// To avoid this undesirable behavior, we spawn an headless eventloop so that the
 | 
				
			||||||
 | 
					// launcher looks "alive", while waiting for the daemon
 | 
				
			||||||
 | 
					#[cfg(target_os = "macos")]
 | 
				
			||||||
 | 
					fn spawn_and_wait(mut command: Command) -> Result<ExitStatus> {
 | 
				
			||||||
 | 
					  let mut child = command.spawn()?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  let result = std::thread::Builder::new()
 | 
				
			||||||
 | 
					    .name("daemon-monitor-thread".to_owned())
 | 
				
			||||||
 | 
					    .spawn(move || {
 | 
				
			||||||
 | 
					      let results = child.wait();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      espanso_mac_utils::exit_headless_eventloop();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      results
 | 
				
			||||||
 | 
					    })?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  espanso_mac_utils::start_headless_eventloop();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  let thread_result = result.join().expect("unable to join daemon-monitor-thread");
 | 
				
			||||||
 | 
					  Ok(thread_result?)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -168,6 +168,13 @@ impl<'a> espanso_engine::process::UndoEnabledProvider for ConfigManager<'a> {
 | 
				
			||||||
      return false;
 | 
					      return false;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Because we cannot filter out espanso-generated events when using the X11 record injection
 | 
				
			||||||
 | 
					    // method, we need to disable undo_backspace to avoid looping (espanso picks up its own
 | 
				
			||||||
 | 
					    // injections, causing the program to misbehave)
 | 
				
			||||||
 | 
					    if cfg!(target_os = "linux") && self.active().disable_x11_fast_inject() {
 | 
				
			||||||
 | 
					      return false;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    self.active().undo_backspace()
 | 
					    self.active().undo_backspace()
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -37,12 +37,12 @@ impl<'a> funnel::Source<'a> for DetectSource {
 | 
				
			||||||
    select.recv(&self.receiver)
 | 
					    select.recv(&self.receiver)
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  fn receive(&self, op: SelectedOperation) -> Event {
 | 
					  fn receive(&self, op: SelectedOperation) -> Option<Event> {
 | 
				
			||||||
    let (input_event, source_id) = op
 | 
					    let (input_event, source_id) = op
 | 
				
			||||||
      .recv(&self.receiver)
 | 
					      .recv(&self.receiver)
 | 
				
			||||||
      .expect("unable to select data from DetectSource receiver");
 | 
					      .expect("unable to select data from DetectSource receiver");
 | 
				
			||||||
    match input_event {
 | 
					    match input_event {
 | 
				
			||||||
      InputEvent::Keyboard(keyboard_event) => Event {
 | 
					      InputEvent::Keyboard(keyboard_event) => Some(Event {
 | 
				
			||||||
        source_id,
 | 
					        source_id,
 | 
				
			||||||
        etype: EventType::Keyboard(KeyboardEvent {
 | 
					        etype: EventType::Keyboard(KeyboardEvent {
 | 
				
			||||||
          key: convert_to_engine_key(keyboard_event.key),
 | 
					          key: convert_to_engine_key(keyboard_event.key),
 | 
				
			||||||
| 
						 | 
					@ -50,20 +50,21 @@ impl<'a> funnel::Source<'a> for DetectSource {
 | 
				
			||||||
          status: convert_to_engine_status(keyboard_event.status),
 | 
					          status: convert_to_engine_status(keyboard_event.status),
 | 
				
			||||||
          variant: keyboard_event.variant.map(convert_to_engine_variant),
 | 
					          variant: keyboard_event.variant.map(convert_to_engine_variant),
 | 
				
			||||||
        }),
 | 
					        }),
 | 
				
			||||||
      },
 | 
					      }),
 | 
				
			||||||
      InputEvent::Mouse(mouse_event) => Event {
 | 
					      InputEvent::Mouse(mouse_event) => Some(Event {
 | 
				
			||||||
        source_id,
 | 
					        source_id,
 | 
				
			||||||
        etype: EventType::Mouse(MouseEvent {
 | 
					        etype: EventType::Mouse(MouseEvent {
 | 
				
			||||||
          status: convert_to_engine_status(mouse_event.status),
 | 
					          status: convert_to_engine_status(mouse_event.status),
 | 
				
			||||||
          button: convert_to_engine_mouse_button(mouse_event.button),
 | 
					          button: convert_to_engine_mouse_button(mouse_event.button),
 | 
				
			||||||
        }),
 | 
					        }),
 | 
				
			||||||
      },
 | 
					      }),
 | 
				
			||||||
      InputEvent::HotKey(hotkey_event) => Event {
 | 
					      InputEvent::HotKey(hotkey_event) => Some(Event {
 | 
				
			||||||
        source_id,
 | 
					        source_id,
 | 
				
			||||||
        etype: EventType::HotKey(HotKeyEvent {
 | 
					        etype: EventType::HotKey(HotKeyEvent {
 | 
				
			||||||
          hotkey_id: hotkey_event.hotkey_id,
 | 
					          hotkey_id: hotkey_event.hotkey_id,
 | 
				
			||||||
        }),
 | 
					        }),
 | 
				
			||||||
      },
 | 
					      }),
 | 
				
			||||||
 | 
					      InputEvent::AllModifiersReleased => None,
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -45,13 +45,13 @@ impl<'a> funnel::Source<'a> for ExitSource<'a> {
 | 
				
			||||||
    select.recv(&self.exit_signal)
 | 
					    select.recv(&self.exit_signal)
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  fn receive(&self, op: SelectedOperation) -> Event {
 | 
					  fn receive(&self, op: SelectedOperation) -> Option<Event> {
 | 
				
			||||||
    let mode = op
 | 
					    let mode = op
 | 
				
			||||||
      .recv(&self.exit_signal)
 | 
					      .recv(&self.exit_signal)
 | 
				
			||||||
      .expect("unable to select data from ExitSource receiver");
 | 
					      .expect("unable to select data from ExitSource receiver");
 | 
				
			||||||
    Event {
 | 
					    Some(Event {
 | 
				
			||||||
      source_id: self.sequencer.next_id(),
 | 
					      source_id: self.sequencer.next_id(),
 | 
				
			||||||
      etype: EventType::ExitRequested(mode),
 | 
					      etype: EventType::ExitRequested(mode),
 | 
				
			||||||
    }
 | 
					    })
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -81,6 +81,8 @@ pub fn init_and_spawn(
 | 
				
			||||||
              // Update the modifiers state
 | 
					              // Update the modifiers state
 | 
				
			||||||
              if let Some((modifier, is_pressed)) = get_modifier_status(&event) {
 | 
					              if let Some((modifier, is_pressed)) = get_modifier_status(&event) {
 | 
				
			||||||
                modifier_state_store_clone.update_state(modifier, is_pressed);
 | 
					                modifier_state_store_clone.update_state(modifier, is_pressed);
 | 
				
			||||||
 | 
					              } else if let InputEvent::AllModifiersReleased = &event {
 | 
				
			||||||
 | 
					                modifier_state_store_clone.clear_state();
 | 
				
			||||||
              }
 | 
					              }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
              // Update the key state (if needed)
 | 
					              // Update the key state (if needed)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -91,6 +91,13 @@ impl ModifierStateStore {
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  pub fn clear_state(&self) {
 | 
				
			||||||
 | 
					    let mut state = self.state.lock().expect("unable to obtain modifier state");
 | 
				
			||||||
 | 
					    for (_, status) in &mut state.modifiers {
 | 
				
			||||||
 | 
					      status.release();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
struct ModifiersState {
 | 
					struct ModifiersState {
 | 
				
			||||||
| 
						 | 
					@ -142,3 +149,44 @@ impl ModifierStatusProvider for ModifierStateStore {
 | 
				
			||||||
    self.is_any_conflicting_modifier_pressed()
 | 
					    self.is_any_conflicting_modifier_pressed()
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl espanso_engine::process::ModifierStateProvider for ModifierStateStore {
 | 
				
			||||||
 | 
					  fn get_modifier_state(&self) -> espanso_engine::process::ModifierState {
 | 
				
			||||||
 | 
					    let mut state = self.state.lock().expect("unable to obtain modifier state");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let mut is_ctrl_down = false;
 | 
				
			||||||
 | 
					    let mut is_alt_down = false;
 | 
				
			||||||
 | 
					    let mut is_meta_down = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    for (modifier, status) in &mut state.modifiers {
 | 
				
			||||||
 | 
					      if status.is_outdated() {
 | 
				
			||||||
 | 
					        warn!(
 | 
				
			||||||
 | 
					          "detected outdated modifier records for {:?}, releasing the state",
 | 
				
			||||||
 | 
					          modifier
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					        status.release();
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      if status.is_pressed() {
 | 
				
			||||||
 | 
					        match modifier {
 | 
				
			||||||
 | 
					          Modifier::Ctrl => {
 | 
				
			||||||
 | 
					            is_ctrl_down = true;
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					          Modifier::Alt => {
 | 
				
			||||||
 | 
					            is_alt_down = true;
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					          Modifier::Meta => {
 | 
				
			||||||
 | 
					            is_meta_down = true;
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					          _ => {}
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    espanso_engine::process::ModifierState {
 | 
				
			||||||
 | 
					      is_ctrl_down,
 | 
				
			||||||
 | 
					      is_alt_down,
 | 
				
			||||||
 | 
					      is_meta_down,
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -50,13 +50,13 @@ impl<'a> funnel::Source<'a> for SecureInputSource<'a> {
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  fn receive(&self, op: SelectedOperation) -> Event {
 | 
					  fn receive(&self, op: SelectedOperation) -> Option<Event> {
 | 
				
			||||||
    if cfg!(target_os = "macos") {
 | 
					    if cfg!(target_os = "macos") {
 | 
				
			||||||
      let si_event = op
 | 
					      let si_event = op
 | 
				
			||||||
        .recv(&self.receiver)
 | 
					        .recv(&self.receiver)
 | 
				
			||||||
        .expect("unable to select data from SecureInputSource receiver");
 | 
					        .expect("unable to select data from SecureInputSource receiver");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      Event {
 | 
					      Some(Event {
 | 
				
			||||||
        source_id: self.sequencer.next_id(),
 | 
					        source_id: self.sequencer.next_id(),
 | 
				
			||||||
        etype: match si_event {
 | 
					        etype: match si_event {
 | 
				
			||||||
          SecureInputEvent::Disabled => EventType::SecureInputDisabled,
 | 
					          SecureInputEvent::Disabled => EventType::SecureInputDisabled,
 | 
				
			||||||
| 
						 | 
					@ -64,13 +64,12 @@ impl<'a> funnel::Source<'a> for SecureInputSource<'a> {
 | 
				
			||||||
            EventType::SecureInputEnabled(SecureInputEnabledEvent { app_name, app_path })
 | 
					            EventType::SecureInputEnabled(SecureInputEnabledEvent { app_name, app_path })
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
      }
 | 
					      })
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
      println!("noop");
 | 
					      Some(Event {
 | 
				
			||||||
      Event {
 | 
					 | 
				
			||||||
        source_id: self.sequencer.next_id(),
 | 
					        source_id: self.sequencer.next_id(),
 | 
				
			||||||
        etype: EventType::NOOP,
 | 
					        etype: EventType::NOOP,
 | 
				
			||||||
      }
 | 
					      })
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -46,12 +46,12 @@ impl<'a> funnel::Source<'a> for UISource<'a> {
 | 
				
			||||||
    select.recv(&self.ui_receiver)
 | 
					    select.recv(&self.ui_receiver)
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  fn receive(&self, op: SelectedOperation) -> Event {
 | 
					  fn receive(&self, op: SelectedOperation) -> Option<Event> {
 | 
				
			||||||
    let ui_event = op
 | 
					    let ui_event = op
 | 
				
			||||||
      .recv(&self.ui_receiver)
 | 
					      .recv(&self.ui_receiver)
 | 
				
			||||||
      .expect("unable to select data from UISource receiver");
 | 
					      .expect("unable to select data from UISource receiver");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    Event {
 | 
					    Some(Event {
 | 
				
			||||||
      source_id: self.sequencer.next_id(),
 | 
					      source_id: self.sequencer.next_id(),
 | 
				
			||||||
      etype: match ui_event {
 | 
					      etype: match ui_event {
 | 
				
			||||||
        UIEvent::TrayIconClick => EventType::TrayIconClicked,
 | 
					        UIEvent::TrayIconClick => EventType::TrayIconClicked,
 | 
				
			||||||
| 
						 | 
					@ -60,6 +60,6 @@ impl<'a> funnel::Source<'a> for UISource<'a> {
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        UIEvent::Heartbeat => EventType::Heartbeat,
 | 
					        UIEvent::Heartbeat => EventType::Heartbeat,
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
    }
 | 
					    })
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -223,6 +223,7 @@ pub fn initialize_and_spawn(
 | 
				
			||||||
        &combined_match_cache,
 | 
					        &combined_match_cache,
 | 
				
			||||||
        &config_manager,
 | 
					        &config_manager,
 | 
				
			||||||
        &config_manager,
 | 
					        &config_manager,
 | 
				
			||||||
 | 
					        &modifier_state_store,
 | 
				
			||||||
      );
 | 
					      );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      let event_injector = EventInjectorAdapter::new(&*injector, &config_manager);
 | 
					      let event_injector = EventInjectorAdapter::new(&*injector, &config_manager);
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -23,6 +23,7 @@ pub const WORKER_GENERAL_ERROR: i32 = 2;
 | 
				
			||||||
pub const WORKER_LEGACY_ALREADY_RUNNING: i32 = 3;
 | 
					pub const WORKER_LEGACY_ALREADY_RUNNING: i32 = 3;
 | 
				
			||||||
pub const WORKER_EXIT_ALL_PROCESSES: i32 = 50;
 | 
					pub const WORKER_EXIT_ALL_PROCESSES: i32 = 50;
 | 
				
			||||||
pub const WORKER_RESTART: i32 = 51;
 | 
					pub const WORKER_RESTART: i32 = 51;
 | 
				
			||||||
 | 
					pub const WORKER_ERROR_EXIT_NO_CODE: i32 = 90;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub const DAEMON_SUCCESS: i32 = 0;
 | 
					pub const DAEMON_SUCCESS: i32 = 0;
 | 
				
			||||||
pub const DAEMON_ALREADY_RUNNING: i32 = 1;
 | 
					pub const DAEMON_ALREADY_RUNNING: i32 = 1;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -69,25 +69,25 @@ pub fn load_icon_paths(runtime_dir: &Path) -> Result<IconPaths> {
 | 
				
			||||||
  Ok(IconPaths {
 | 
					  Ok(IconPaths {
 | 
				
			||||||
    form_icon: Some(extract_icon(
 | 
					    form_icon: Some(extract_icon(
 | 
				
			||||||
      WINDOWS_LOGO_ICO_BINARY,
 | 
					      WINDOWS_LOGO_ICO_BINARY,
 | 
				
			||||||
      &runtime_dir.join("form.ico"),
 | 
					      &runtime_dir.join("formv2.ico"),
 | 
				
			||||||
    )?),
 | 
					    )?),
 | 
				
			||||||
    search_icon: Some(extract_icon(ICON_BINARY, &runtime_dir.join("search.png"))?),
 | 
					    search_icon: Some(extract_icon(ICON_BINARY, &runtime_dir.join("search.png"))?),
 | 
				
			||||||
    wizard_icon: Some(extract_icon(
 | 
					    wizard_icon: Some(extract_icon(
 | 
				
			||||||
      WINDOWS_LOGO_ICO_BINARY,
 | 
					      WINDOWS_LOGO_ICO_BINARY,
 | 
				
			||||||
      &runtime_dir.join("wizard.ico"),
 | 
					      &runtime_dir.join("wizardv2.ico"),
 | 
				
			||||||
    )?),
 | 
					    )?),
 | 
				
			||||||
    tray_icon_normal: Some(extract_icon(
 | 
					    tray_icon_normal: Some(extract_icon(
 | 
				
			||||||
      WINDOWS_NORMAL_DARK_ICO_BINARY,
 | 
					      WINDOWS_NORMAL_DARK_ICO_BINARY,
 | 
				
			||||||
      &runtime_dir.join("normal.ico"),
 | 
					      &runtime_dir.join("normalv2.ico"),
 | 
				
			||||||
    )?),
 | 
					    )?),
 | 
				
			||||||
    tray_icon_disabled: Some(extract_icon(
 | 
					    tray_icon_disabled: Some(extract_icon(
 | 
				
			||||||
      WINDOWS_DISABLED_DARK_ICO_BINARY,
 | 
					      WINDOWS_DISABLED_DARK_ICO_BINARY,
 | 
				
			||||||
      &runtime_dir.join("disabled.ico"),
 | 
					      &runtime_dir.join("disabledv2.ico"),
 | 
				
			||||||
    )?),
 | 
					    )?),
 | 
				
			||||||
    logo: Some(extract_icon(ICON_BINARY, &runtime_dir.join("icon.png"))?),
 | 
					    logo: Some(extract_icon(ICON_BINARY, &runtime_dir.join("iconv2.png"))?),
 | 
				
			||||||
    logo_no_background: Some(extract_icon(
 | 
					    logo_no_background: Some(extract_icon(
 | 
				
			||||||
      LOGO_NO_BACKGROUND_BINARY,
 | 
					      LOGO_NO_BACKGROUND_BINARY,
 | 
				
			||||||
      &runtime_dir.join("icon_no_background.png"),
 | 
					      &runtime_dir.join("icon_no_backgroundv2.png"),
 | 
				
			||||||
    )?),
 | 
					    )?),
 | 
				
			||||||
    tray_explain_image: Some(extract_icon(
 | 
					    tray_explain_image: Some(extract_icon(
 | 
				
			||||||
      WINDOWS_TRAY_EXPLAIN_IMAGE,
 | 
					      WINDOWS_TRAY_EXPLAIN_IMAGE,
 | 
				
			||||||
| 
						 | 
					@ -100,17 +100,20 @@ pub fn load_icon_paths(runtime_dir: &Path) -> Result<IconPaths> {
 | 
				
			||||||
#[cfg(target_os = "macos")]
 | 
					#[cfg(target_os = "macos")]
 | 
				
			||||||
pub fn load_icon_paths(runtime_dir: &Path) -> Result<IconPaths> {
 | 
					pub fn load_icon_paths(runtime_dir: &Path) -> Result<IconPaths> {
 | 
				
			||||||
  Ok(IconPaths {
 | 
					  Ok(IconPaths {
 | 
				
			||||||
    search_icon: Some(extract_icon(ICON_BINARY, &runtime_dir.join("search.png"))?),
 | 
					    search_icon: Some(extract_icon(
 | 
				
			||||||
    tray_icon_normal: Some(extract_icon(MAC_BINARY, &runtime_dir.join("normal.png"))?),
 | 
					      ICON_BINARY,
 | 
				
			||||||
 | 
					      &runtime_dir.join("searchv2.png"),
 | 
				
			||||||
 | 
					    )?),
 | 
				
			||||||
 | 
					    tray_icon_normal: Some(extract_icon(MAC_BINARY, &runtime_dir.join("normalv2.png"))?),
 | 
				
			||||||
    tray_icon_disabled: Some(extract_icon(
 | 
					    tray_icon_disabled: Some(extract_icon(
 | 
				
			||||||
      MAC_DISABLED_BINARY,
 | 
					      MAC_DISABLED_BINARY,
 | 
				
			||||||
      &runtime_dir.join("disabled.png"),
 | 
					      &runtime_dir.join("disabledv2.png"),
 | 
				
			||||||
    )?),
 | 
					    )?),
 | 
				
			||||||
    tray_icon_system_disabled: Some(extract_icon(
 | 
					    tray_icon_system_disabled: Some(extract_icon(
 | 
				
			||||||
      MAC_SYSTEM_DISABLED_BINARY,
 | 
					      MAC_SYSTEM_DISABLED_BINARY,
 | 
				
			||||||
      &runtime_dir.join("systemdisabled.png"),
 | 
					      &runtime_dir.join("systemdisabledv2.png"),
 | 
				
			||||||
    )?),
 | 
					    )?),
 | 
				
			||||||
    logo: Some(extract_icon(ICON_BINARY, &runtime_dir.join("icon.png"))?),
 | 
					    logo: Some(extract_icon(ICON_BINARY, &runtime_dir.join("iconv2.png"))?),
 | 
				
			||||||
    logo_no_background: Some(extract_icon(
 | 
					    logo_no_background: Some(extract_icon(
 | 
				
			||||||
      LOGO_NO_BACKGROUND_BINARY,
 | 
					      LOGO_NO_BACKGROUND_BINARY,
 | 
				
			||||||
      &runtime_dir.join("icon_no_background.png"),
 | 
					      &runtime_dir.join("icon_no_background.png"),
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -67,6 +67,17 @@ fn main() {
 | 
				
			||||||
  let mut cmd = Command::new("cargo");
 | 
					  let mut cmd = Command::new("cargo");
 | 
				
			||||||
  cmd.args(&args);
 | 
					  cmd.args(&args);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // If compiling for macOS x86-64, set the minimum supported version
 | 
				
			||||||
 | 
					  // to 10.13
 | 
				
			||||||
 | 
					  let is_macos = cfg!(target_os = "macos");
 | 
				
			||||||
 | 
					  let is_x86_arch = cfg!(target_arch = "x86_64");
 | 
				
			||||||
 | 
					  if is_macos
 | 
				
			||||||
 | 
					    && (override_target_arch == "current" && is_x86_arch
 | 
				
			||||||
 | 
					      || override_target_arch == "x86_64-apple-darwin")
 | 
				
			||||||
 | 
					  {
 | 
				
			||||||
 | 
					    cmd.env("MACOSX_DEPLOYMENT_TARGET", "10.13");
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // Remove cargo/rust-specific env variables, as otherwise they mess up the
 | 
					  // Remove cargo/rust-specific env variables, as otherwise they mess up the
 | 
				
			||||||
  // nested cargo build call.
 | 
					  // nested cargo build call.
 | 
				
			||||||
  let all_vars = envmnt::vars();
 | 
					  let all_vars = envmnt::vars();
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,5 +1,5 @@
 | 
				
			||||||
name: espanso
 | 
					name: espanso
 | 
				
			||||||
version: 0.7.3
 | 
					version: 2.0.3-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.
 | 
				
			||||||
| 
						 | 
					@ -21,13 +21,17 @@ description: |
 | 
				
			||||||
  * Works with almost any program
 | 
					  * Works with almost any program
 | 
				
			||||||
  * Works with Emojis ¯\_(ツ)_/¯
 | 
					  * Works with Emojis ¯\_(ツ)_/¯
 | 
				
			||||||
  * Works with Images
 | 
					  * Works with Images
 | 
				
			||||||
 | 
					  * Includes a powerful Search Bar
 | 
				
			||||||
  * Date expansion support
 | 
					  * Date expansion support
 | 
				
			||||||
  * Custom scripts support
 | 
					  * Custom scripts support
 | 
				
			||||||
  * Shell commands support
 | 
					  * Shell commands support
 | 
				
			||||||
 | 
					  * Support Forms
 | 
				
			||||||
  * App-specific configurations
 | 
					  * App-specific configurations
 | 
				
			||||||
  * Expandable with packages
 | 
					  * Expandable with packages
 | 
				
			||||||
  * Built-in package manager for espanso hub: https://hub.espanso.org/
 | 
					  * Built-in package manager for espanso hub: https://hub.espanso.org/
 | 
				
			||||||
  * File based configuration
 | 
					  * File based configuration
 | 
				
			||||||
 | 
					  * Support Regex triggers
 | 
				
			||||||
 | 
					  * Experimental Wayland support (currently not available through Snap, visit the website for more info).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  ## Get Started
 | 
					  ## Get Started
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -47,23 +51,35 @@ parts:
 | 
				
			||||||
    source: .
 | 
					    source: .
 | 
				
			||||||
    build-packages:
 | 
					    build-packages:
 | 
				
			||||||
     - libssl-dev
 | 
					     - libssl-dev
 | 
				
			||||||
 | 
					     - libdbus-1-dev
 | 
				
			||||||
 | 
					     - libwxgtk3.0-gtk3-dev
 | 
				
			||||||
     - pkg-config
 | 
					     - pkg-config
 | 
				
			||||||
     - cmake
 | 
					     - libxkbcommon-dev
 | 
				
			||||||
     - libxtst-dev
 | 
					     - libxtst-dev
 | 
				
			||||||
     - libx11-dev
 | 
					     - libx11-dev
 | 
				
			||||||
     - libxdo-dev
 | 
					 | 
				
			||||||
    stage-packages:
 | 
					    stage-packages:
 | 
				
			||||||
     - libx11-6
 | 
					     - libx11-6
 | 
				
			||||||
     - libxau6
 | 
					     - libxau6
 | 
				
			||||||
     - libxcb1
 | 
					     - libxcb1
 | 
				
			||||||
     - libxdmcp6
 | 
					     - libxdmcp6
 | 
				
			||||||
     - libxdo3
 | 
					 | 
				
			||||||
     - libxext6
 | 
					     - libxext6
 | 
				
			||||||
     - libxinerama1
 | 
					     - libxinerama1
 | 
				
			||||||
     - libxkbcommon0
 | 
					     - libxkbcommon0
 | 
				
			||||||
     - libxtst6
 | 
					     - libxtst6
 | 
				
			||||||
     - libnotify-bin
 | 
					     - libnotify-bin
 | 
				
			||||||
     - xclip
 | 
					     - libdbus-1-3
 | 
				
			||||||
 | 
					     - libssl1.1
 | 
				
			||||||
 | 
					     - libwxbase3.0-0v5
 | 
				
			||||||
 | 
					     - libwxgtk3.0-0v5
 | 
				
			||||||
 | 
					     - libatk-bridge2.0-0
 | 
				
			||||||
 | 
					     - libatspi2.0-0
 | 
				
			||||||
 | 
					     - libcairo-gobject2
 | 
				
			||||||
 | 
					     - libepoxy0
 | 
				
			||||||
 | 
					     - libgtk-3-0
 | 
				
			||||||
 | 
					     - libwayland-client0
 | 
				
			||||||
 | 
					     - libwayland-cursor0
 | 
				
			||||||
 | 
					     - libwayland-egl1
 | 
				
			||||||
 | 
					     - libwxgtk3.0-gtk3-0v5
 | 
				
			||||||
 | 
					
 | 
				
			||||||
apps:
 | 
					apps:
 | 
				
			||||||
  espanso:
 | 
					  espanso:
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue
	
	Block a user