feat(core): add support for Disable/Enable

This commit is contained in:
Federico Terzi 2021-05-23 15:47:15 +02:00
parent d193cb749b
commit 5abea19016
18 changed files with 440 additions and 3 deletions

View File

@ -61,6 +61,10 @@ impl<'a> ConfigManager<'a> {
let match_paths = config.match_paths();
(config, self.match_store.query(&match_paths))
}
pub fn default(&self) -> &'a dyn Config {
self.config_store.default()
}
}
// TODO: test

View File

@ -0,0 +1,46 @@
/*
* This file is part of espanso.
*
* Copyright (C) 2019-2021 Federico Terzi
*
* espanso is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* espanso is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with espanso. If not, see <https://www.gnu.org/licenses/>.
*/
use espanso_ui::{UIRemote, icons::TrayIcon};
use crate::engine::{dispatch::IconHandler, event::ui::IconStatus};
pub struct IconHandlerAdapter<'a> {
remote: &'a dyn UIRemote,
}
impl<'a> IconHandlerAdapter<'a> {
pub fn new(remote: &'a dyn UIRemote) -> Self {
Self { remote }
}
}
impl<'a> IconHandler for IconHandlerAdapter<'a> {
fn update_icon(&self, status: &IconStatus) -> anyhow::Result<()> {
let icon = match status {
IconStatus::Enabled => TrayIcon::Normal,
IconStatus::Disabled => TrayIcon::Disabled,
IconStatus::SecureInputDisabled => TrayIcon::SystemDisabled,
};
self.remote.update_tray_icon(icon);
Ok(())
}
}

View File

@ -20,4 +20,5 @@
pub mod clipboard_injector;
pub mod context_menu;
pub mod event_injector;
pub mod icon;
pub mod key_injector;

View File

@ -34,6 +34,7 @@ pub mod match_cache;
pub mod matcher;
pub mod multiplex;
pub mod path;
pub mod process;
pub mod render;
pub mod source;
pub mod ui;
@ -118,6 +119,8 @@ pub fn initialize_and_spawn(
super::engine::render::RendererAdapter::new(&match_cache, &config_manager, &renderer);
let path_provider = PathProviderAdapter::new(&paths);
let disable_options = process::middleware::disable::extract_disable_options(config_manager.default());
let mut processor = crate::engine::process::default(
&matchers,
&config_manager,
@ -128,6 +131,7 @@ pub fn initialize_and_spawn(
&modifier_state_store,
&sequencer,
&path_provider,
disable_options,
);
let event_injector =
@ -140,6 +144,7 @@ pub fn initialize_and_spawn(
);
let key_injector = super::engine::executor::key_injector::KeyInjectorAdapter::new(&*injector);
let context_menu_adapter = super::engine::executor::context_menu::ContextMenuHandlerAdapter::new(&*ui_remote);
let icon_adapter = super::engine::executor::icon::IconHandlerAdapter::new(&*ui_remote);
let dispatcher = crate::engine::dispatch::default(
&event_injector,
&clipboard_injector,
@ -148,6 +153,7 @@ pub fn initialize_and_spawn(
&clipboard_injector,
&clipboard_injector,
&context_menu_adapter,
&icon_adapter,
);
let mut engine = crate::engine::Engine::new(&funnel, &mut processor, &dispatcher);

View File

@ -0,0 +1,49 @@
/*
* This file is part of espanso.
*
* Copyright (C) 2019-2021 Federico Terzi
*
* espanso is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* espanso is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with espanso. If not, see <https://www.gnu.org/licenses/>.
*/
use std::time::Duration;
use crate::engine::{event::input::{Key, Variant}, process::DisableOptions};
use espanso_config::config::Config;
pub fn extract_disable_options(config: &dyn Config) -> DisableOptions {
let (toggle_key, variant) = match config.toggle_key() {
Some(key) => match key {
espanso_config::config::ToggleKey::Ctrl => (Some(Key::Control), None),
espanso_config::config::ToggleKey::Meta => (Some(Key::Meta), None),
espanso_config::config::ToggleKey::Alt => (Some(Key::Alt), None),
espanso_config::config::ToggleKey::Shift => (Some(Key::Shift), None),
espanso_config::config::ToggleKey::RightCtrl => (Some(Key::Control), Some(Variant::Right)),
espanso_config::config::ToggleKey::RightAlt => (Some(Key::Alt), Some(Variant::Right)),
espanso_config::config::ToggleKey::RightShift => (Some(Key::Shift), Some(Variant::Right)),
espanso_config::config::ToggleKey::RightMeta => (Some(Key::Meta), Some(Variant::Right)),
espanso_config::config::ToggleKey::LeftCtrl => (Some(Key::Control), Some(Variant::Left)),
espanso_config::config::ToggleKey::LeftAlt => (Some(Key::Alt), Some(Variant::Left)),
espanso_config::config::ToggleKey::LeftShift => (Some(Key::Shift), Some(Variant::Left)),
espanso_config::config::ToggleKey::LeftMeta => (Some(Key::Meta), Some(Variant::Left)),
},
None => (None, None),
};
DisableOptions {
toggle_key,
toggle_key_variant: variant,
toggle_key_maximum_window: Duration::from_millis(1000),
}
}

View File

@ -0,0 +1,20 @@
/*
* This file is part of espanso.
*
* Copyright (C) 2019-2021 Federico Terzi
*
* espanso is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* espanso is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with espanso. If not, see <https://www.gnu.org/licenses/>.
*/
pub mod disable;

View File

@ -0,0 +1,20 @@
/*
* This file is part of espanso.
*
* Copyright (C) 2019-2021 Federico Terzi
*
* espanso is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* espanso is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with espanso. If not, see <https://www.gnu.org/licenses/>.
*/
pub mod middleware;

View File

@ -17,7 +17,7 @@
* along with espanso. If not, see <https://www.gnu.org/licenses/>.
*/
use super::{ContextMenuHandler, Event, ImageInjector};
use super::{ContextMenuHandler, Event, IconHandler, ImageInjector};
use super::{ModeProvider, Dispatcher, Executor, KeyInjector, TextInjector, HtmlInjector};
pub struct DefaultDispatcher<'a> {
@ -33,6 +33,7 @@ impl<'a> DefaultDispatcher<'a> {
html_injector: &'a dyn HtmlInjector,
image_injector: &'a dyn ImageInjector,
context_menu_handler: &'a dyn ContextMenuHandler,
icon_handler: &'a dyn IconHandler,
) -> Self {
Self {
executors: vec![
@ -52,6 +53,9 @@ impl<'a> DefaultDispatcher<'a> {
)),
Box::new(super::executor::context_menu::ContextMenuExecutor::new(
context_menu_handler,
)),
Box::new(super::executor::icon_update::IconUpdateExecutor::new(
icon_handler,
))
],
}

View File

@ -0,0 +1,53 @@
/*
* This file is part of espanso.
*
* Copyright (C) 2019-2021 Federico Terzi
*
* espanso is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* espanso is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with espanso. If not, see <https://www.gnu.org/licenses/>.
*/
use super::super::{Event, Executor};
use crate::engine::event::{EventType, ui::{IconStatus}};
use anyhow::Result;
use log::error;
pub trait IconHandler {
fn update_icon(&self, status: &IconStatus) -> Result<()>;
}
pub struct IconUpdateExecutor<'a> {
handler: &'a dyn IconHandler,
}
impl<'a> IconUpdateExecutor<'a> {
pub fn new(handler: &'a dyn IconHandler) -> Self {
Self { handler }
}
}
impl<'a> Executor for IconUpdateExecutor<'a> {
fn execute(&self, event: &Event) -> bool {
if let EventType::IconStatusChange(m_event) = &event.etype {
if let Err(error) = self.handler.update_icon(&m_event.status) {
error!("icon handler reported an error: {:?}", error);
}
return true;
}
false
}
}
// TODO: test

View File

@ -18,6 +18,7 @@
*/
pub mod context_menu;
pub mod icon_update;
pub mod image_inject;
pub mod html_inject;
pub mod key_inject;

View File

@ -37,6 +37,7 @@ pub use executor::html_inject::HtmlInjector;
pub use executor::text_inject::{Mode, ModeProvider, TextInjector};
pub use executor::image_inject::{ImageInjector};
pub use executor::context_menu::{ContextMenuHandler};
pub use executor::icon_update::IconHandler;
// TODO: move into module
pub trait KeyInjector {
@ -51,6 +52,7 @@ pub fn default<'a>(
html_injector: &'a dyn HtmlInjector,
image_injector: &'a dyn ImageInjector,
context_menu_handler: &'a dyn ContextMenuHandler,
icon_handler: &'a dyn IconHandler,
) -> impl Dispatcher + 'a {
default::DefaultDispatcher::new(
event_injector,
@ -60,5 +62,6 @@ pub fn default<'a>(
html_injector,
image_injector,
context_menu_handler,
icon_handler,
)
}

View File

@ -70,6 +70,11 @@ pub enum EventType {
MatchInjected,
DiscardPrevious(internal::DiscardPreviousEvent),
Disabled,
Enabled,
SecureInputEnabled,
SecureInputDisabled,
// Effects
TriggerCompensation(effect::TriggerCompensationEvent),
CursorHintCompensation(effect::CursorHintCompensationEvent),
@ -82,6 +87,7 @@ pub enum EventType {
// UI
ShowContextMenu(ui::ShowContextMenuEvent),
IconStatusChange(ui::IconStatusChangeEvent),
}
#[derive(Debug, Clone)]

View File

@ -39,4 +39,16 @@ pub struct SimpleMenuItem {
pub struct SubMenuItem {
pub label: String,
pub items: Vec<MenuItem>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct IconStatusChangeEvent {
pub status: IconStatus
}
#[derive(Debug, Clone, PartialEq)]
pub enum IconStatus {
Enabled,
Disabled,
SecureInputDisabled,
}

View File

@ -19,13 +19,13 @@
use log::trace;
use super::{MatchFilter, MatchInfoProvider, MatchSelector, Matcher, Middleware, Multiplexer, PathProvider, Processor, Renderer, middleware::{
use super::{DisableOptions, MatchFilter, MatchInfoProvider, MatchSelector, Matcher, Middleware, Multiplexer, PathProvider, Processor, Renderer, middleware::{
match_select::MatchSelectMiddleware, matcher::MatcherMiddleware, multiplex::MultiplexMiddleware,
render::RenderMiddleware, action::{ActionMiddleware, EventSequenceProvider}, cursor_hint::CursorHintMiddleware, cause::CauseCompensateMiddleware,
delay_modifiers::{DelayForModifierReleaseMiddleware, ModifierStatusProvider}, markdown::MarkdownMiddleware,
past_discard::PastEventsDiscardMiddleware,
}};
use crate::engine::{event::{Event, EventType}, process::middleware::{context_menu::ContextMenuMiddleware, exit::ExitMiddleware, image_resolve::ImageResolverMiddleware}};
use crate::engine::{event::{Event, EventType}, process::middleware::{context_menu::ContextMenuMiddleware, disable::DisableMiddleware, exit::ExitMiddleware, icon_status::IconStatusMiddleware, image_resolve::ImageResolverMiddleware}};
use std::collections::VecDeque;
pub struct DefaultProcessor<'a> {
@ -44,11 +44,14 @@ impl<'a> DefaultProcessor<'a> {
modifier_status_provider: &'a dyn ModifierStatusProvider,
event_sequence_provider: &'a dyn EventSequenceProvider,
path_provider: &'a dyn PathProvider,
disable_options: DisableOptions,
) -> DefaultProcessor<'a> {
Self {
event_queue: VecDeque::new(),
middleware: vec![
Box::new(PastEventsDiscardMiddleware::new()),
Box::new(DisableMiddleware::new(disable_options)),
Box::new(IconStatusMiddleware::new()),
Box::new(MatcherMiddleware::new(matchers)),
Box::new(ContextMenuMiddleware::new()),
Box::new(MatchSelectMiddleware::new(match_filter, match_selector)),

View File

@ -0,0 +1,125 @@
/*
* This file is part of espanso.
*
* Copyright (C) 2019-2021 Federico Terzi
*
* espanso is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* espanso is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with espanso. If not, see <https://www.gnu.org/licenses/>.
*/
use std::{
cell::RefCell,
time::{Duration, Instant},
};
use log::{info};
use super::super::Middleware;
use crate::engine::{event::{Event, EventType, input::{Key, KeyboardEvent, Status, Variant}}};
pub struct DisableOptions {
pub toggle_key: Option<Key>,
pub toggle_key_variant: Option<Variant>,
pub toggle_key_maximum_window: Duration,
// TODO: toggle shortcut?
}
pub struct DisableMiddleware {
enabled: RefCell<bool>,
last_toggle_press: RefCell<Option<Instant>>,
options: DisableOptions,
}
impl DisableMiddleware {
pub fn new(options: DisableOptions) -> Self {
Self {
enabled: RefCell::new(true),
last_toggle_press: RefCell::new(None),
options,
}
}
}
impl Middleware for DisableMiddleware {
fn name(&self) -> &'static str {
"disable"
}
fn next(&self, event: Event, dispatch: &mut dyn FnMut(Event)) -> Event {
let mut has_status_changed = false;
let mut enabled = self.enabled.borrow_mut();
if let EventType::Keyboard(m_event) = &event.etype {
if is_toggle_key(m_event, &self.options) {
let mut last_toggle_press = self.last_toggle_press.borrow_mut();
if let Some(previous_press) = *last_toggle_press {
if previous_press.elapsed() < self.options.toggle_key_maximum_window {
*enabled = !*enabled;
*last_toggle_press = None;
has_status_changed = true;
} else {
*last_toggle_press = Some(Instant::now());
}
} else {
*last_toggle_press = Some(Instant::now());
}
}
}
if has_status_changed {
info!("toggled enabled state, is_enabled = {}", *enabled);
dispatch(Event::caused_by(
event.source_id,
if *enabled {
EventType::Enabled
} else {
EventType::Disabled
},
))
}
// Block keyboard events when disabled
if let EventType::Keyboard(_) = &event.etype {
if !*enabled {
return Event::caused_by(event.source_id, EventType::NOOP);
}
}
// TODO: also ignore hotkey and mouse events
event
}
}
fn is_toggle_key(event: &KeyboardEvent, options: &DisableOptions) -> bool {
if event.status != Status::Released {
return false;
}
if options
.toggle_key
.as_ref()
.map(|key| key == &event.key)
.unwrap_or(false)
{
if let (Some(variant), Some(e_variant)) = (&options.toggle_key_variant, &event.variant) {
variant == e_variant
} else {
true
}
} else {
false
}
}
// TODO: test

View File

@ -0,0 +1,79 @@
/*
* This file is part of espanso.
*
* Copyright (C) 2019-2021 Federico Terzi
*
* espanso is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* espanso is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with espanso. If not, see <https://www.gnu.org/licenses/>.
*/
use std::{
cell::RefCell,
};
use super::super::Middleware;
use crate::engine::event::{Event, EventType, ui::{IconStatus, IconStatusChangeEvent}};
pub struct IconStatusMiddleware {
enabled: RefCell<bool>,
secure_input_enabled: RefCell<bool>,
}
impl IconStatusMiddleware {
pub fn new() -> Self {
Self {
enabled: RefCell::new(true),
secure_input_enabled: RefCell::new(false),
}
}
}
impl Middleware for IconStatusMiddleware {
fn name(&self) -> &'static str {
"icon_status"
}
fn next(&self, event: Event, dispatch: &mut dyn FnMut(Event)) -> Event {
let mut enabled = self.enabled.borrow_mut();
let mut secure_input_enabled = self.secure_input_enabled.borrow_mut();
let mut did_update = true;
match &event.etype {
EventType::Enabled => *enabled = true,
EventType::Disabled => *enabled = false,
EventType::SecureInputEnabled => *secure_input_enabled = true,
EventType::SecureInputDisabled => *secure_input_enabled = false,
_ => did_update = false,
}
if did_update {
let status = if *enabled {
if *secure_input_enabled {
IconStatus::SecureInputDisabled
} else {
IconStatus::Enabled
}
} else {
IconStatus::Disabled
};
dispatch(Event::caused_by(event.source_id, EventType::IconStatusChange(IconStatusChangeEvent {
status,
})));
}
event
}
}
// TODO: test

View File

@ -22,7 +22,9 @@ pub mod cause;
pub mod context_menu;
pub mod cursor_hint;
pub mod delay_modifiers;
pub mod disable;
pub mod exit;
pub mod icon_status;
pub mod image_resolve;
pub mod match_select;
pub mod matcher;

View File

@ -95,6 +95,7 @@ pub enum RendererError {
pub use middleware::action::{MatchInfoProvider, EventSequenceProvider};
pub use middleware::delay_modifiers::ModifierStatusProvider;
pub use middleware::image_resolve::PathProvider;
pub use middleware::disable::DisableOptions;
pub fn default<'a, MatcherState>(
matchers: &'a [&'a dyn Matcher<'a, MatcherState>],
@ -106,6 +107,7 @@ pub fn default<'a, MatcherState>(
modifier_status_provider: &'a dyn ModifierStatusProvider,
event_sequence_provider: &'a dyn EventSequenceProvider,
path_provider: &'a dyn PathProvider,
disable_options: DisableOptions,
) -> impl Processor + 'a {
default::DefaultProcessor::new(
matchers,
@ -117,5 +119,6 @@ pub fn default<'a, MatcherState>(
modifier_status_provider,
event_sequence_provider,
path_provider,
disable_options,
)
}