use std::collections::HashMap;
use anyhow::{bail, Result};
use evdev::{EventStream, EventType, Key};
use tokio::sync::{broadcast, mpsc};
use tokio::task;
use tracing::{debug, info, trace, warn};
use crate::device::handles::{DeviceHandle, DeviceHandler};
use crate::device::{shortcut, util, Event, GrabEvent, InputBatch};
use crate::msgs::event;
pub struct InputHandler {
config: HandlerConfig,
}
#[derive(Clone)]
struct HandlerConfig {
combo_states: Vec<shortcut::ComboState>,
event_tx: mpsc::Sender<Event>,
}
impl InputHandler {
pub fn new(
key_combos: &shortcut::KeyCombos,
event_tx: mpsc::Sender<Event>,
) -> Result<InputHandler> {
let mut keymap = HashMap::new();
for entry in key_combos.combos.iter() {
add_key_combo(&mut keymap, entry)?;
}
if keymap.is_empty() {
bail!(
"At least one keyboard shortcut must be configured for switching between devices"
);
}
Ok(InputHandler {
config: HandlerConfig {
combo_states: keymap
.into_iter()
.filter_map(|(keys, action)| {
if keys.is_empty() {
None
} else {
Some(shortcut::ComboState::new(keys, action))
}
})
.collect(),
event_tx,
},
})
}
}
fn add_key_combo(
keymap: &mut HashMap<Vec<Key>, Event>,
keysaction: &shortcut::KeyCombo,
) -> Result<()> {
if !keysaction.keys.is_empty() {
if let Some(existing_action) =
keymap.insert(keysaction.keys.clone(), keysaction.action.clone())
{
bail!(
"Key combination '{:?}' for {:?} collides with existing combination for {:?}",
keysaction.keys,
keysaction.action,
existing_action
)
}
}
Ok(())
}
impl DeviceHandler for InputHandler {
fn handle_device_stream(
&mut self,
mut stream: EventStream,
grab_rx: Option<broadcast::Receiver<GrabEvent>>,
mut device_info: util::DeviceInfo,
) -> Result<DeviceHandle> {
let config = self.config.clone();
let handle = if let Some(grab_rx) = grab_rx {
task::spawn(async move {
read_device_or_grab_events(&mut stream, config, grab_rx, device_info).await
})
} else {
handle_grab_event(&mut stream, &mut device_info, GrabEvent::Grab);
task::spawn(async move { read_device_events(&mut stream, config, device_info).await })
};
Ok(DeviceHandle { handle })
}
}
async fn read_device_events(
stream: &mut EventStream,
mut handler_config: HandlerConfig,
device_info: util::DeviceInfo,
) {
let mut input_events_batch = Vec::new();
let mut combo_events_batch = Vec::new();
loop {
match stream.next_event().await {
Ok(event) => {
handle_input_event(
stream,
&mut handler_config,
event,
&device_info,
&mut input_events_batch,
&mut combo_events_batch,
)
.await
}
Err(e) => {
info!(
"Got an error event for {:?}, removing device (might be unplugged?): {}",
stream.device().name().unwrap_or("(Unnamed device)"),
e
);
}
}
}
}
async fn read_device_or_grab_events(
stream: &mut EventStream,
mut handler_config: HandlerConfig,
mut grab_rx: broadcast::Receiver<GrabEvent>,
mut device_info: util::DeviceInfo,
) {
let mut input_events_batch = Vec::new();
let mut combo_events_batch = Vec::new();
loop {
tokio::select! {
event = stream.next_event() => {
match event {
Ok(event) => {
handle_input_event(stream, &mut handler_config, event, &device_info, &mut input_events_batch, &mut combo_events_batch).await
}
Err(e) => {
info!(
"Got an error event for {:?}, removing device (might be unplugged?): {}",
stream.device().name().unwrap_or("(Unnamed device)"),
e
);
}
}
}
grab = grab_rx.recv() => {
match grab {
Ok(grab) => {
if !handle_grab_event(stream, &mut device_info, grab) {
return
}
}
Err(e) => {
warn!(
"Error on grab broadcast for {:?}, removing device: {}",
stream.device().name(),
e
);
return
}
}
}
}
}
}
async fn handle_input_event(
stream: &mut EventStream,
c: &mut HandlerConfig,
event: evdev::InputEvent,
device_info: &util::DeviceInfo,
input_events_batch: &mut Vec<event::InputEvent>,
combo_events_batch: &mut Vec<Event>,
) {
if event.event_type() == EventType::SYNCHRONIZATION
|| (input_events_batch.len() + combo_events_batch.len()) >= 100
{
if !input_events_batch.is_empty() {
let event = Event::Input(InputBatch {
events: std::mem::replace(input_events_batch, Vec::new()),
is_grabbed: device_info.is_grabbed,
});
if let Err(e) = c.event_tx.send(event).await {
warn!("Error sending input events for routing: {:?}", e);
}
}
if !combo_events_batch.is_empty() {
let batch = std::mem::replace(combo_events_batch, Vec::new());
for combo_event in batch {
if let Err(e) = c.event_tx.send(combo_event).await {
warn!("Error sending combo events for routing: {:?}", e);
}
}
}
} else {
let mut any_consume = false;
for cs in c.combo_states.iter_mut() {
match cs.check_combo(&event) {
shortcut::ComboAction::ConsumeEvent => {
any_consume = true;
}
shortcut::ComboAction::PassEvent => {}
shortcut::ComboAction::ConsumeEventAndEmitAction(action) => {
any_consume = true;
combo_events_batch.push(action);
}
shortcut::ComboAction::PassEventAndEmitAction(action) => {
combo_events_batch.push(action);
}
}
}
if any_consume {
debug!(
"Dropping key event as it's the last key completing one or more combos: {:?}",
event
);
} else {
input_events_batch.push(convert_device_event(event, stream.device(), device_info))
}
}
}
fn handle_grab_event(
stream: &mut EventStream,
device_info: &mut util::DeviceInfo,
grab: GrabEvent,
) -> bool {
match grab {
GrabEvent::Grab => {
debug!(
"Grabbing device: {:?}",
stream.device().name().unwrap_or("(Unnamed device)")
);
if let Err(e) = stream.device_mut().grab() {
warn!(
"Failed to grab device {:?}, removing device: {}",
stream.device().name(),
e
);
return false;
}
device_info.is_grabbed = true;
return true;
}
GrabEvent::Ungrab => {
debug!(
"Ungrabbing device: {:?}",
stream.device().name().unwrap_or("(Unnamed device)")
);
if let Err(e) = stream.device_mut().ungrab() {
warn!(
"Failed to ungrab device {:?}, : {}",
stream.device().name(),
e
);
return false;
}
device_info.is_grabbed = false;
return true;
}
}
}
fn convert_device_event(
event: evdev::InputEvent,
device: &evdev::Device,
device_info: &util::DeviceInfo,
) -> event::InputEvent {
let net_event = if let evdev::InputEventKind::AbsAxis(axis) = event.kind() {
if let Some((axis_min, axis_max)) = device_info.dims.get(&axis.0) {
event::InputEvent {
inputi32: None,
inputf64: Some(event::InputF64::from_evdev(event, *axis_min, *axis_max)),
}
} else {
event::InputEvent {
inputi32: Some(event::InputI32::from_evdev(event)),
inputf64: None,
}
}
} else {
event::InputEvent {
inputi32: Some(event::InputI32::from_evdev(event)),
inputf64: None,
}
};
trace!(
"Input event @ {}: {} -> {:?}",
device.name().unwrap_or("(Unnamed device)"),
util::log_event(&event),
net_event
);
net_event
}