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,69 +18,99 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use espanso_detect::{SourceCreationOptions, event::{InputEvent, KeyboardEvent, Status}};
|
use espanso_detect::{
|
||||||
use log::{error};
|
event::{InputEvent, KeyboardEvent, Status},
|
||||||
|
SourceCreationOptions,
|
||||||
|
};
|
||||||
|
use log::error;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
use detect::DetectSource;
|
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 detect;
|
||||||
pub mod exit;
|
pub mod exit;
|
||||||
|
pub mod key_state;
|
||||||
pub mod modifier;
|
pub mod modifier;
|
||||||
pub mod secure_input;
|
pub mod secure_input;
|
||||||
pub mod sequencer;
|
pub mod sequencer;
|
||||||
pub mod ui;
|
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 (sender, receiver) = crossbeam::channel::unbounded();
|
||||||
let (init_tx, init_rx) = crossbeam::channel::unbounded();
|
let (init_tx, init_rx) = crossbeam::channel::unbounded();
|
||||||
|
|
||||||
let modifier_state_store = ModifierStateStore::new();
|
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 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 sequencer_clone = sequencer.clone();
|
||||||
|
let key_state_store_clone = key_state_store.clone();
|
||||||
if let Err(error) = std::thread::Builder::new()
|
if let Err(error) = std::thread::Builder::new()
|
||||||
.name("detect thread".to_string())
|
.name("detect thread".to_string())
|
||||||
.spawn(
|
.spawn(move || match espanso_detect::get_source(source_options) {
|
||||||
move || match espanso_detect::get_source(source_options) {
|
Ok(mut source) => {
|
||||||
Ok(mut source) => {
|
if source.initialize().is_err() {
|
||||||
if source.initialize().is_err() {
|
|
||||||
init_tx
|
|
||||||
.send(false)
|
|
||||||
.expect("unable to send to the init_tx channel");
|
|
||||||
} else {
|
|
||||||
init_tx
|
|
||||||
.send(true)
|
|
||||||
.expect("unable to send to the init_tx channel");
|
|
||||||
|
|
||||||
source
|
|
||||||
.eventloop(Box::new(move |event| {
|
|
||||||
// Update the modifiers state
|
|
||||||
if let Some((modifier, is_pressed)) = get_modifier_status(&event) {
|
|
||||||
state_store_clone.update_state(modifier, is_pressed);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generate a monotonically increasing id for the current event
|
|
||||||
let source_id = sequencer_clone.next_id();
|
|
||||||
|
|
||||||
sender
|
|
||||||
.send((event, source_id))
|
|
||||||
.expect("unable to send to the source channel");
|
|
||||||
}))
|
|
||||||
.expect("detect eventloop crashed");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(error) => {
|
|
||||||
error!("cannot initialize event source: {:?}", error);
|
|
||||||
init_tx
|
init_tx
|
||||||
.send(false)
|
.send(false)
|
||||||
.expect("unable to send to the init_tx channel");
|
.expect("unable to send to the init_tx channel");
|
||||||
|
} else {
|
||||||
|
init_tx
|
||||||
|
.send(true)
|
||||||
|
.expect("unable to send to the init_tx channel");
|
||||||
|
|
||||||
|
source
|
||||||
|
.eventloop(Box::new(move |event| {
|
||||||
|
println!("key -> {:?}", event);
|
||||||
|
// Update the modifiers state
|
||||||
|
if let Some((modifier, is_pressed)) = get_modifier_status(&event) {
|
||||||
|
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
|
||||||
|
let source_id = sequencer_clone.next_id();
|
||||||
|
|
||||||
|
sender
|
||||||
|
.send((event, source_id))
|
||||||
|
.expect("unable to send to the source channel");
|
||||||
|
}))
|
||||||
|
.expect("detect eventloop crashed");
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
)
|
Err(error) => {
|
||||||
|
error!("cannot initialize event source: {:?}", error);
|
||||||
|
init_tx
|
||||||
|
.send(false)
|
||||||
|
.expect("unable to send to the init_tx channel");
|
||||||
|
}
|
||||||
|
})
|
||||||
{
|
{
|
||||||
error!("detection thread initialization failed: {:?}", error);
|
error!("detection thread initialization failed: {:?}", error);
|
||||||
return Err(DetectSourceError::ThreadInitFailed.into());
|
return Err(DetectSourceError::ThreadInitFailed.into());
|
||||||
|
@ -94,7 +124,12 @@ pub fn init_and_spawn(source_options: SourceCreationOptions) -> Result<(DetectSo
|
||||||
return Err(DetectSourceError::InitFailed.into());
|
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)]
|
#[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::Control => Some((Modifier::Ctrl, is_pressed)),
|
||||||
espanso_detect::event::Key::Meta => Some((Modifier::Meta, is_pressed)),
|
espanso_detect::event::Key::Meta => Some((Modifier::Meta, is_pressed)),
|
||||||
espanso_detect::event::Key::Shift => Some((Modifier::Shift, is_pressed)),
|
espanso_detect::event::Key::Shift => Some((Modifier::Shift, is_pressed)),
|
||||||
_ => None
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => None,
|
_ => None,
|
||||||
|
|
|
@ -23,7 +23,7 @@ use anyhow::Result;
|
||||||
use crossbeam::channel::Receiver;
|
use crossbeam::channel::Receiver;
|
||||||
use espanso_config::{config::ConfigStore, matches::store::MatchStore};
|
use espanso_config::{config::ConfigStore, matches::store::MatchStore};
|
||||||
use espanso_detect::SourceCreationOptions;
|
use espanso_detect::SourceCreationOptions;
|
||||||
use espanso_inject::InjectorCreationOptions;
|
use espanso_inject::{InjectorCreationOptions, KeyboardStateProvider};
|
||||||
use espanso_path::Paths;
|
use espanso_path::Paths;
|
||||||
use espanso_ui::{event::UIEvent, UIRemote};
|
use espanso_ui::{event::UIEvent, UIRemote};
|
||||||
use log::{debug, error, info, warn};
|
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);
|
let has_granted_capabilities = grant_linux_capabilities(use_evdev_backend);
|
||||||
|
|
||||||
// TODO: pass all the options
|
// 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 {
|
super::engine::funnel::init_and_spawn(SourceCreationOptions {
|
||||||
use_evdev: use_evdev_backend,
|
use_evdev: use_evdev_backend,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
@ -134,6 +134,7 @@ pub fn initialize_and_spawn(
|
||||||
|
|
||||||
let injector = espanso_inject::get_injector(InjectorCreationOptions {
|
let injector = espanso_inject::get_injector(InjectorCreationOptions {
|
||||||
use_evdev: use_evdev_backend,
|
use_evdev: use_evdev_backend,
|
||||||
|
keyboard_state_provider: key_state_store.map(|store| Box::new(store) as Box<dyn KeyboardStateProvider>),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
.expect("failed to initialize injector module"); // TODO: handle the options
|
.expect("failed to initialize injector module"); // TODO: handle the options
|
||||||
|
|
Loading…
Reference in New Issue
Block a user