use std::collections::{HashMap, HashSet};
use std::path::{Path, PathBuf};
use anyhow::{Context, Result};
use evdev::{Device, EventStream, Key};
use tokio::sync::broadcast;
use tokio::task;
use tracing::debug;
use crate::device;
use crate::device::util;
pub struct DeviceHandle {
pub handle: task::JoinHandle<()>,
}
pub trait DeviceHandler: Send + 'static {
fn handle_device_stream(
&mut self,
events: EventStream,
grab_rx: Option<broadcast::Receiver<device::GrabEvent>>,
device_info: util::DeviceInfo,
) -> Result<DeviceHandle>;
}
pub struct DeviceHandles<H: DeviceHandler> {
always_grabbed_devices: HashMap<PathBuf, DeviceHandle>,
toggled_devices: HashMap<PathBuf, DeviceHandle>,
handler: H,
grab_tx: broadcast::Sender<device::GrabEvent>,
all_combo_keys: HashSet<Key>,
}
impl<H: DeviceHandler> DeviceHandles<H> {
pub fn new(
handler: H,
grab_tx: broadcast::Sender<device::GrabEvent>,
all_combo_keys: HashSet<Key>,
) -> DeviceHandles<H> {
DeviceHandles {
always_grabbed_devices: HashMap::<PathBuf, DeviceHandle>::new(),
toggled_devices: HashMap::<PathBuf, DeviceHandle>::new(),
handler,
grab_tx,
all_combo_keys,
}
}
pub(crate) fn add(&mut self, path: &PathBuf, device: Device) -> Result<()> {
let device_info = util::DeviceInfo::new(&device, false);
util::log_device_info(&device, &path, &device_info, "Listening to device", true);
let supports_any_keys = supports_any_keys(&device, &self.all_combo_keys);
if supports_any_keys {
debug!(
"Device supports one or more configured combo keys: {}",
device.name().unwrap_or("(Unnamed device)")
);
}
if supports_any_keys {
let join_handle = self.handler.handle_device_stream(
start_device_stream(device, path)?,
None,
device_info,
)?;
self.always_grabbed_devices
.insert(path.clone(), join_handle);
} else {
let join_handle = self.handler.handle_device_stream(
start_device_stream(device, path)?,
Some(self.grab_tx.subscribe()),
device_info,
)?;
self.toggled_devices.insert(path.clone(), join_handle);
}
Ok(())
}
pub(crate) fn remove(&mut self, path: &PathBuf) -> Option<DeviceHandle> {
if let Some(handle) = self.always_grabbed_devices.remove(path) {
return Some(handle);
}
self.toggled_devices.remove(path)
}
pub(crate) fn is_empty(&self) -> bool {
self.always_grabbed_devices.is_empty() && self.toggled_devices.is_empty()
}
}
fn supports_any_keys(d: &Device, all_combo_keys: &HashSet<Key>) -> bool {
if let Some(device_keys) = d.supported_keys() {
for key in all_combo_keys.iter() {
if device_keys.contains(*key) {
return true;
}
}
}
false
}
fn start_device_stream(device: Device, path: &Path) -> Result<EventStream> {
device.into_event_stream().with_context(|| {
format!(
"Failed to initialize async fd for device: {}",
path.to_string_lossy()
)
})
}