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