keyflow 0.1.1

Cross-platform input simulation library for keyboard, mouse and hotkeys.
Documentation
use crate::error::Result;
use crate::hotkey::HotkeyRegistry;
use crate::platform::Simulation;
use crate::types::{InputEvent, InternalMessage, Movement};
use crossbeam_channel::{Receiver, RecvTimeoutError};
use std::sync::{Arc, RwLock};
use std::time::{Duration, Instant};

pub(crate) struct EventHandler {
    backend: Box<dyn Simulation>,
    hotkey_registry: Arc<RwLock<HotkeyRegistry>>,
    message_rx: Receiver<InternalMessage>,

    pending_relative_dx: i32,
    pending_relative_dy: i32,
}

impl EventHandler {
    pub(crate) fn new(
        backend: Box<dyn Simulation>,
        hotkey_registry: Arc<RwLock<HotkeyRegistry>>,
        message_rx: Receiver<InternalMessage>,
    ) -> Self {
        Self {
            backend,
            message_rx,
            hotkey_registry,
            pending_relative_dx: 0,
            pending_relative_dy: 0,
        }
    }

    pub(crate) fn run(&mut self) -> Result<()> {
        const FLUSH_TIMEOUT: Duration = Duration::from_micros(500);

        loop {
            // block receive
            let first_msg = match self.message_rx.recv_timeout(FLUSH_TIMEOUT) {
                Ok(msg) => msg,
                Err(RecvTimeoutError::Timeout) => {
                    self.flush_pending_relative()?;
                    continue;
                }
                Err(RecvTimeoutError::Disconnected) => {
                    break;
                }
            };

            // start batch timer after first blocking received
            let batch_timer = Instant::now();
            let mut batch = vec![];

            match first_msg {
                // Relative motion receives special treatment
                InternalMessage::SimulateEvent(InputEvent::MouseMove(Movement::Relative {
                    dx,
                    dy,
                })) => {
                    self.pending_relative_dx += dx;
                    self.pending_relative_dy += dy;
                }

                msg => batch.push(msg),
            }

            // receive all or until batch timer elapsed timeout to reduce lags
            while batch_timer.elapsed() < FLUSH_TIMEOUT {
                match self.message_rx.try_recv() {
                    Ok(InternalMessage::SimulateEvent(InputEvent::MouseMove(
                        Movement::Relative { dx, dy },
                    ))) => {
                        self.pending_relative_dx += dx;
                        self.pending_relative_dy += dy;
                    }
                    Ok(msg) => batch.push(msg),
                    Err(_) => break,
                }
            }

            if let Err(e) = self.process_messages(batch) {
                eprintln!("Error processing batch: {}", e);
            }

            // need to flush when only relative events are incoming
            self.flush_pending_relative()?;
        }
        Ok(())
    }

    fn process_messages(&mut self, batch: Vec<InternalMessage>) -> Result<()> {
        for msg in batch {
            self.flush_pending_relative()?;

            match msg {
                InternalMessage::SimulateEvent(event) => {
                    self.process_input_event(event)?;
                }

                InternalMessage::RegisterHotkey {
                    id,
                    combo,
                    callback,
                } => {
                    self.hotkey_registry
                        .write()
                        .unwrap()
                        .register(combo, id, callback)?;
                }

                InternalMessage::UnregisterHotkey { id } => {
                    self.hotkey_registry.write().unwrap().unregister(&id)?;
                }

                InternalMessage::BatchEvents(batch) => {
                    // send batch directly to backend to collect events
                    self.backend.send_batch(batch)?;
                }
            }
        }

        Ok(())
    }

    fn process_input_event(&mut self, event: InputEvent) -> Result<()> {
        match event {
            InputEvent::KeyEvent { key, action } => {
                self.backend.send_key(key, action)?;
            }

            InputEvent::ButtonEvent { button, action } => {
                self.backend.send_button(button, action)?;
            }

            InputEvent::MouseMove(mv) => {
                self.backend.send_movement(mv)?;
            }

            InputEvent::MouseScroll(scroll) => {
                self.backend.send_scroll(scroll)?;
            }

            InputEvent::Delay(duration) => {
                std::thread::sleep(duration);
            }
        }

        Ok(())
    }

    /// send the pending to the backend, so there is only one syscall
    #[inline]
    fn flush_pending_relative(&mut self) -> Result<()> {
        if self.pending_relative_dx != 0 || self.pending_relative_dy != 0 {
            self.backend.send_movement(Movement::Relative {
                dx: self.pending_relative_dx,
                dy: self.pending_relative_dy,
            })?;

            // reset
            self.pending_relative_dx = 0;
            self.pending_relative_dy = 0;
        }
        Ok(())
    }
}