use std::collections::HashSet;
use std::str::FromStr;
use anyhow::{anyhow, bail, Result};
use evdev::{EventType, Key};
use crate::device::Event;
pub struct KeyCombos {
pub combos: Vec<KeyCombo>,
pub all_keys: HashSet<Key>,
}
pub struct KeyCombo {
pub keys: Vec<Key>,
pub action: Event,
}
pub fn parse_key_combos(
keys_next: &str,
keys_prev: Option<&str>,
keys_goto: Vec<String>,
) -> Result<KeyCombos> {
let mut combos = vec![];
combos.push(parse_action(keys_next, Event::SwitchNext)?);
if let Some(kp) = keys_prev {
combos.push(parse_action(kp, Event::SwitchPrev)?);
}
for kg in keys_goto.into_iter() {
combos.push(parse_goto(&kg)?);
}
let all_keys = combos.iter().flat_map(|combo| combo.keys.clone()).collect();
Ok(KeyCombos { combos, all_keys })
}
fn parse_goto(keys_goto: &str) -> Result<KeyCombo> {
let split: Vec<&str> = keys_goto.split('=').collect();
if split.len() != 2 {
bail!(
"Invalid --shortcut-goto: Expected 'key1,key2,key3=[fingerprint-prefix]', but was '{}'",
keys_goto
);
}
let keys = split.get(0).expect("entry_split has len=2");
let fingerprint_prefix = split.get(1).expect("entry_split has len=2").to_string();
parse_action(keys, Event::SwitchTo(fingerprint_prefix))
}
fn parse_action(keys: &str, action: Event) -> Result<KeyCombo> {
let keys_iter = if keys.contains(",") {
keys.split(',')
} else {
keys.split('+')
};
let mut keys = vec![];
for keyname_orig in keys_iter {
let keyname = keyname_orig.trim().to_uppercase();
if let Ok(key) = Key::from_str(format!("KEY_{}", keyname).as_str()) {
keys.push(key);
} else {
keys.push(
Key::from_str(format!("{}", keyname).as_str())
.map_err(|e| anyhow!("Unsupported key '{}': Tried KEY_{} and {}, see list of available keys at https://docs.rs/evdev/latest/evdev/struct.KeyCode.html (error: {:?})", keyname_orig, keyname, keyname, e))?,
);
}
}
keys.sort();
Ok(KeyCombo { keys, action })
}
pub(crate) enum ComboAction {
ConsumeEvent,
PassEvent,
ConsumeEventAndEmitAction(Event),
PassEventAndEmitAction(Event),
}
#[derive(Clone)]
pub(crate) struct ComboState {
action: Event,
combo_key_codes: Vec<u16>,
pressed_keys: bit_vec::BitVec,
last_keypress: Option<evdev::InputEvent>,
}
impl ComboState {
pub(crate) fn new(combo_keys: Vec<Key>, action: Event) -> ComboState {
let len = combo_keys.len();
ComboState {
action,
combo_key_codes: combo_keys.into_iter().map(|k| k.code()).collect(),
pressed_keys: bit_vec::BitVec::from_elem(len, false),
last_keypress: None,
}
}
pub(crate) fn check_combo(&mut self, event: &evdev::InputEvent) -> ComboAction {
if event.event_type() != EventType::KEY {
return ComboAction::PassEvent;
}
if let Some(idx) = self.key_idx(event.code()) {
self.pressed_keys.set(idx, event.value() >= 1);
if let Some(last_keypress) = &self.last_keypress {
let matching = last_keypress.event_type() == event.event_type()
&& last_keypress.code() == event.code();
if self.pressed_keys.none() {
self.last_keypress = None;
if matching {
ComboAction::ConsumeEventAndEmitAction(self.action.clone())
} else {
ComboAction::PassEventAndEmitAction(self.action.clone())
}
} else {
if matching {
ComboAction::ConsumeEvent
} else {
ComboAction::PassEvent
}
}
} else {
if self.pressed_keys.all() {
self.last_keypress = Some(event.clone());
ComboAction::ConsumeEvent
} else {
ComboAction::PassEvent
}
}
} else {
ComboAction::PassEvent
}
}
fn key_idx(&self, key_code: u16) -> Option<usize> {
for (idx, combo_key_code) in self.combo_key_codes.iter().enumerate() {
if key_code == *combo_key_code {
return Some(idx);
}
}
return None;
}
}