use crate::error::*;
use crate::hotkey::HotkeyListener;
use crate::platform::linux::mapping::platform_to_key;
use crate::platform::{BackendListener, Listener};
use crate::types::KeyState;
use evdev::{Device, EventType, KeyCode};
use nix::sys::epoll;
use std::fs;
use std::sync::Arc;
use std::sync::atomic::Ordering;
impl BackendListener {
fn listen_device(path: &str, listener: Arc<HotkeyListener>) -> Result<()> {
let mut device = Device::open(path)?;
device.set_nonblocking(true)?;
let epoll_fd = epoll::Epoll::new(epoll::EpollCreateFlags::EPOLL_CLOEXEC)
.map_err(|e| KeyflowError::PlatformError(e.to_string()))?;
let event = epoll::EpollEvent::new(epoll::EpollFlags::EPOLLIN, 0);
epoll_fd
.add(&device, event)
.map_err(|e| KeyflowError::PlatformError(e.to_string()))?;
let mut events = [epoll::EpollEvent::empty(); 2];
while listener.running.load(Ordering::Relaxed) {
match device.fetch_events() {
Ok(iterator) => {
for ev in iterator {
if ev.event_type() == EventType::KEY
&& let Some(key) = platform_to_key(KeyCode::new(ev.code()))
{
let key_state = KeyState::from(ev.value());
listener.on_key_event(key, key_state);
}
}
}
Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => {
let timeout = epoll::EpollTimeout::try_from(50).expect("Timeout");
let _ = epoll_fd.wait(&mut events, timeout);
}
Err(e) => {
eprintln!("Error reading events from {}: {}", path, e);
}
}
}
Ok(())
}
fn find_keyboards(&self) -> Result<Vec<String>> {
let mut keyboards = vec![];
for entry in fs::read_dir("/dev/input")? {
let path = entry?.path();
if let Some(filename) = path.file_name() {
if !filename.to_string_lossy().starts_with("event") {
continue;
}
if let Ok(device) = Device::open(&path) {
if device.name().unwrap_or_default() == "keyflow_device" {
continue;
}
if Self::is_keyboard(&device) {
keyboards.push(path.to_string_lossy().to_string());
}
}
}
}
if keyboards.is_empty() {
return Err(KeyflowError::KeyboardsNotFound);
}
Ok(keyboards)
}
fn is_keyboard(device: &Device) -> bool {
if let Some(keys) = device.supported_keys() {
keys.contains(KeyCode::KEY_A)
&& keys.contains(KeyCode::KEY_Z)
&& keys.contains(KeyCode::KEY_SPACE)
} else {
false
}
}
}
impl Drop for BackendListener {
fn drop(&mut self) {
self.stop();
}
}
impl Listener for BackendListener {
fn start(&self) -> Result<()> {
let devices = self.find_keyboards()?;
for device_path in devices {
let state = self.listener.clone();
std::thread::spawn(move || {
if let Err(e) = Self::listen_device(&device_path, state) {
eprintln!("Error listening to keyboard {}: {}", device_path, e);
}
});
}
Ok(())
}
fn stop(&self) {
self.listener.running.store(false, Ordering::Relaxed);
}
}