feat(core): implement KeyStateProvider for wayland injector
This commit is contained in:
parent
31b93ebdb0
commit
3aea65de72
132
espanso/src/cli/worker/engine/funnel/key_state.rs
Normal file
132
espanso/src/cli/worker/engine/funnel/key_state.rs
Normal file
|
@ -0,0 +1,132 @@
|
|||
/*
|
||||
* 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::{
|
||||
collections::HashMap,
|
||||
sync::{Arc, Mutex},
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
|
||||
use espanso_inject::KeyboardStateProvider;
|
||||
use log::warn;
|
||||
|
||||
/// This duration represents the maximum length for which a pressed key
|
||||
/// event is considered valid. This is useful when the "release" event is
|
||||
/// lost for whatever reason, so that espanso becomes eventually consistent
|
||||
/// after a while.
|
||||
const KEY_PRESS_EVENT_INVALIDATION_TIMEOUT: Duration = Duration::from_secs(3);
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct KeyStateStore {
|
||||
state: Arc<Mutex<KeyState>>,
|
||||
}
|
||||
|
||||
impl KeyStateStore {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
state: Arc::new(Mutex::new(KeyState::default())),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_key_pressed(&self, key_code: u32) -> bool {
|
||||
let mut state = self.state.lock().expect("unable to obtain modifier state");
|
||||
|
||||
if let Some(status) = state.keys.get_mut(&key_code) {
|
||||
if status.is_outdated() {
|
||||
warn!(
|
||||
"detected outdated key records for {:?}, releasing the state",
|
||||
key_code
|
||||
);
|
||||
status.release();
|
||||
}
|
||||
|
||||
status.is_pressed()
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update_state(&self, key_code: u32, is_pressed: bool) {
|
||||
let mut state = self.state.lock().expect("unable to obtain key state");
|
||||
if let Some(status) = state.keys.get_mut(&key_code) {
|
||||
if is_pressed {
|
||||
status.press();
|
||||
} else {
|
||||
status.release();
|
||||
}
|
||||
} else {
|
||||
state.keys.insert(key_code, KeyStatus::new(is_pressed));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct KeyState {
|
||||
keys: HashMap<u32, KeyStatus>,
|
||||
}
|
||||
|
||||
impl Default for KeyState {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
keys: HashMap::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct KeyStatus {
|
||||
pressed_at: Option<Instant>,
|
||||
}
|
||||
|
||||
impl KeyStatus {
|
||||
fn new(is_pressed: bool) -> Self {
|
||||
Self {
|
||||
pressed_at: if is_pressed {
|
||||
Some(Instant::now())
|
||||
} else {
|
||||
None
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn is_pressed(&self) -> bool {
|
||||
self.pressed_at.is_some()
|
||||
}
|
||||
|
||||
fn is_outdated(&self) -> bool {
|
||||
let now = Instant::now();
|
||||
if let Some(pressed_at) = self.pressed_at {
|
||||
now.duration_since(pressed_at) > KEY_PRESS_EVENT_INVALIDATION_TIMEOUT
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
fn release(&mut self) {
|
||||
self.pressed_at = None
|
||||
}
|
||||
|
||||
fn press(&mut self) {
|
||||
self.pressed_at = Some(Instant::now());
|
||||
}
|
||||
}
|
||||
|
||||
impl KeyboardStateProvider for KeyStateStore {
|
||||
fn is_key_pressed(&self, code: u32) -> bool {
|
||||
self.is_key_pressed(code)
|
||||
}
|
||||
}
|
|
@ -18,34 +18,54 @@
|
|||
*/
|
||||
|
||||
use anyhow::Result;
|
||||
use espanso_detect::{SourceCreationOptions, event::{InputEvent, KeyboardEvent, Status}};
|
||||
use log::{error};
|
||||
use espanso_detect::{
|
||||
event::{InputEvent, KeyboardEvent, Status},
|
||||
SourceCreationOptions,
|
||||
};
|
||||
use log::error;
|
||||
use thiserror::Error;
|
||||
|
||||
use detect::DetectSource;
|
||||
|
||||
use self::{modifier::{Modifier, ModifierStateStore}, sequencer::Sequencer};
|
||||
use self::{
|
||||
key_state::KeyStateStore,
|
||||
modifier::{Modifier, ModifierStateStore},
|
||||
sequencer::Sequencer,
|
||||
};
|
||||
|
||||
pub mod detect;
|
||||
pub mod exit;
|
||||
pub mod key_state;
|
||||
pub mod modifier;
|
||||
pub mod secure_input;
|
||||
pub mod sequencer;
|
||||
pub mod ui;
|
||||
|
||||
pub fn init_and_spawn(source_options: SourceCreationOptions) -> Result<(DetectSource, ModifierStateStore, Sequencer)> {
|
||||
pub fn init_and_spawn(
|
||||
source_options: SourceCreationOptions,
|
||||
) -> Result<(
|
||||
DetectSource,
|
||||
ModifierStateStore,
|
||||
Sequencer,
|
||||
Option<KeyStateStore>,
|
||||
)> {
|
||||
let (sender, receiver) = crossbeam::channel::unbounded();
|
||||
let (init_tx, init_rx) = crossbeam::channel::unbounded();
|
||||
|
||||
let modifier_state_store = ModifierStateStore::new();
|
||||
let key_state_store = if source_options.use_evdev {
|
||||
Some(KeyStateStore::new())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let sequencer = Sequencer::new();
|
||||
|
||||
let state_store_clone = modifier_state_store.clone();
|
||||
let modifier_state_store_clone = modifier_state_store.clone();
|
||||
let sequencer_clone = sequencer.clone();
|
||||
let key_state_store_clone = key_state_store.clone();
|
||||
if let Err(error) = std::thread::Builder::new()
|
||||
.name("detect thread".to_string())
|
||||
.spawn(
|
||||
move || match espanso_detect::get_source(source_options) {
|
||||
.spawn(move || match espanso_detect::get_source(source_options) {
|
||||
Ok(mut source) => {
|
||||
if source.initialize().is_err() {
|
||||
init_tx
|
||||
|
@ -58,9 +78,20 @@ pub fn init_and_spawn(source_options: SourceCreationOptions) -> Result<(DetectSo
|
|||
|
||||
source
|
||||
.eventloop(Box::new(move |event| {
|
||||
println!("key -> {:?}", event);
|
||||
// Update the modifiers state
|
||||
if let Some((modifier, is_pressed)) = get_modifier_status(&event) {
|
||||
state_store_clone.update_state(modifier, is_pressed);
|
||||
modifier_state_store_clone.update_state(modifier, is_pressed);
|
||||
}
|
||||
|
||||
// Update the key state (if needed)
|
||||
if let Some(key_state_store) = &key_state_store_clone {
|
||||
if let InputEvent::Keyboard(keyboard_event) = &event {
|
||||
key_state_store.update_state(
|
||||
keyboard_event.code,
|
||||
keyboard_event.status == Status::Pressed,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Generate a monotonically increasing id for the current event
|
||||
|
@ -79,8 +110,7 @@ pub fn init_and_spawn(source_options: SourceCreationOptions) -> Result<(DetectSo
|
|||
.send(false)
|
||||
.expect("unable to send to the init_tx channel");
|
||||
}
|
||||
},
|
||||
)
|
||||
})
|
||||
{
|
||||
error!("detection thread initialization failed: {:?}", error);
|
||||
return Err(DetectSourceError::ThreadInitFailed.into());
|
||||
|
@ -94,7 +124,12 @@ pub fn init_and_spawn(source_options: SourceCreationOptions) -> Result<(DetectSo
|
|||
return Err(DetectSourceError::InitFailed.into());
|
||||
}
|
||||
|
||||
Ok((DetectSource { receiver }, modifier_state_store, sequencer))
|
||||
Ok((
|
||||
DetectSource { receiver },
|
||||
modifier_state_store,
|
||||
sequencer,
|
||||
key_state_store,
|
||||
))
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
|
@ -121,7 +156,7 @@ fn get_modifier_status(event: &InputEvent) -> Option<(Modifier, bool)> {
|
|||
espanso_detect::event::Key::Control => Some((Modifier::Ctrl, is_pressed)),
|
||||
espanso_detect::event::Key::Meta => Some((Modifier::Meta, is_pressed)),
|
||||
espanso_detect::event::Key::Shift => Some((Modifier::Shift, is_pressed)),
|
||||
_ => None
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
_ => None,
|
||||
|
|
|
@ -23,7 +23,7 @@ use anyhow::Result;
|
|||
use crossbeam::channel::Receiver;
|
||||
use espanso_config::{config::ConfigStore, matches::store::MatchStore};
|
||||
use espanso_detect::SourceCreationOptions;
|
||||
use espanso_inject::InjectorCreationOptions;
|
||||
use espanso_inject::{InjectorCreationOptions, KeyboardStateProvider};
|
||||
use espanso_path::Paths;
|
||||
use espanso_ui::{event::UIEvent, UIRemote};
|
||||
use log::{debug, error, info, warn};
|
||||
|
@ -92,7 +92,7 @@ pub fn initialize_and_spawn(
|
|||
let has_granted_capabilities = grant_linux_capabilities(use_evdev_backend);
|
||||
|
||||
// TODO: pass all the options
|
||||
let (detect_source, modifier_state_store, sequencer) =
|
||||
let (detect_source, modifier_state_store, sequencer, key_state_store) =
|
||||
super::engine::funnel::init_and_spawn(SourceCreationOptions {
|
||||
use_evdev: use_evdev_backend,
|
||||
..Default::default()
|
||||
|
@ -134,6 +134,7 @@ pub fn initialize_and_spawn(
|
|||
|
||||
let injector = espanso_inject::get_injector(InjectorCreationOptions {
|
||||
use_evdev: use_evdev_backend,
|
||||
keyboard_state_provider: key_state_store.map(|store| Box::new(store) as Box<dyn KeyboardStateProvider>),
|
||||
..Default::default()
|
||||
})
|
||||
.expect("failed to initialize injector module"); // TODO: handle the options
|
||||
|
|
Loading…
Reference in New Issue
Block a user