feat(core): implement KeyStateProvider for wayland injector

This commit is contained in:
Federico Terzi 2021-07-28 22:37:46 +02:00
parent 31b93ebdb0
commit 3aea65de72
3 changed files with 212 additions and 44 deletions

View 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)
}
}

View File

@ -18,69 +18,99 @@
*/
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) {
Ok(mut source) => {
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);
.spawn(move || match espanso_detect::get_source(source_options) {
Ok(mut source) => {
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| {
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);
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,

View File

@ -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