keyflow 0.1.0

Cross-platform input simulation library for keyboard, mouse and hotkeys.
Documentation
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 {
    /// Create epoll file descriptor for the specified device and listen to it
    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) {
                    // skip keyflows virtual device
                    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);
    }
}