keyflow 0.1.0

Cross-platform input simulation library for keyboard, mouse and hotkeys.
Documentation
use crate::error::Result;
use crate::platform::Simulation;
use crate::platform::linux::mapping::{button_to_platform, key_to_platform};
use crate::types::*;
use evdev::uinput::VirtualDevice;
use evdev::{AbsInfo, AbsoluteAxisCode, AttributeSet, KeyCode, RelativeAxisCode, UinputAbsSetup};
use smallvec::SmallVec;
use std::time::Duration;
use strum::IntoEnumIterator;

pub struct Backend {
    device: VirtualDevice,
    events: SmallVec<[evdev::InputEvent; 32]>,
    pos_x: i32,
    pos_y: i32,
    screen: Screen,
    screen_max_x: i32,
    screen_max_y: i32,
}

impl Backend {
    /// Creates a [`Backend`] with:
    /// * all keys defined in [`Key`],
    /// * all mouse buttons defined in [`Button`]
    /// * absolute X-axis which gets mapped from 0...screen_width
    /// * absolute Y-axis which gets mapped from 0...screen_height
    /// * relative X/Y-axis & H/V-wheel
    ///
    /// # Arguments
    /// * `screen` - Screen limits
    pub(crate) fn new(screen: Screen) -> Result<Self> {
        let mut keys = AttributeSet::new();

        for key in Key::iter() {
            if let Some(k) = key_to_platform(key) {
                keys.insert(k);
            }
        }

        for button in Button::iter() {
            if let Some(b) = button_to_platform(button) {
                keys.insert(b);
            }
        }

        let max_x = screen.left + screen.width;
        let max_y = screen.top + screen.height;

        let abs_setup_x = UinputAbsSetup::new(
            AbsoluteAxisCode::ABS_X,
            AbsInfo::new(0, screen.left, max_x, 0, 0, 0),
        );
        let abs_setup_y = UinputAbsSetup::new(
            AbsoluteAxisCode::ABS_Y,
            AbsInfo::new(0, screen.top, max_y, 0, 0, 0),
        );

        let relative_axes = AttributeSet::<RelativeAxisCode>::from_iter([
            RelativeAxisCode::REL_X,
            RelativeAxisCode::REL_Y,
            RelativeAxisCode::REL_WHEEL,
            RelativeAxisCode::REL_HWHEEL,
        ]);

        let device = VirtualDevice::builder()?
            .name("keyflow_device")
            .with_keys(&keys)?
            .with_absolute_axis(&abs_setup_x)?
            .with_absolute_axis(&abs_setup_y)?
            .with_relative_axes(&relative_axes)?
            .build()?;

        Ok(Self {
            device,
            events: SmallVec::new(),
            pos_x: 0,
            pos_y: 0,
            screen,
            screen_max_x: max_x,
            screen_max_y: max_y,
        })
    }

    fn emit(&mut self) -> Result<()> {
        self.device.emit(&self.events)?;
        self.events.clear();
        Ok(())
    }

    fn add_key_or_button(&mut self, kc: KeyCode, action: Action) -> Result<()> {
        match action {
            Action::Press => {
                self.events.push(*evdev::KeyEvent::new(kc, 1));
            }
            Action::Release => {
                self.events.push(*evdev::KeyEvent::new(kc, 0));
            }
            Action::Click => {
                self.events.push(*evdev::KeyEvent::new(kc, 1));

                self.emit()?;
                std::thread::sleep(Duration::from_millis(1));

                self.events.push(*evdev::KeyEvent::new(kc, 0));
            }
        }
        Ok(())
    }

    fn add_movement(&mut self, movement: Movement) {
        match movement {
            Movement::Relative { dx, dy } => {
                self.events
                    .push(*evdev::RelativeAxisEvent::new(RelativeAxisCode::REL_X, dx));
                self.events
                    .push(*evdev::RelativeAxisEvent::new(RelativeAxisCode::REL_Y, dy));

                // Calculate the cursor position and clamps to the screen limits
                self.pos_x += dx;
                self.pos_y += dy;

                self.pos_x = self.pos_x.clamp(self.screen.left, self.screen_max_x);
                self.pos_y = self.pos_y.clamp(self.screen.top, self.screen_max_y);
            }
            Movement::Absolute { x, y } => {
                let x = x.clamp(self.screen.left, self.screen_max_x);
                let y = y.clamp(self.screen.top, self.screen_max_y);

                // force movement of cursor if the last known position is equal to the specified coordinates,
                // because it seems the movement gets suppressed by the OS otherwise
                if self.pos_x == x && self.pos_y == y {
                    let nx = check_boundaries(x, self.screen_max_x);
                    let ny = check_boundaries(y, self.screen_max_y);

                    self.events
                        .push(*evdev::AbsoluteAxisEvent::new(AbsoluteAxisCode::ABS_X, nx));
                    self.events
                        .push(*evdev::AbsoluteAxisEvent::new(AbsoluteAxisCode::ABS_Y, ny));
                }

                self.events
                    .push(*evdev::AbsoluteAxisEvent::new(AbsoluteAxisCode::ABS_X, x));
                self.events
                    .push(*evdev::AbsoluteAxisEvent::new(AbsoluteAxisCode::ABS_Y, y));

                self.pos_x = x;
                self.pos_y = x;
            }
        }
    }

    fn add_scroll(&mut self, scroll: Scroll) {
        match scroll {
            Scroll::Vertical(delta) => {
                self.events.push(*evdev::RelativeAxisEvent::new(
                    RelativeAxisCode::REL_WHEEL,
                    delta,
                ));
            }
            Scroll::Horizontal(delta) => {
                self.events.push(*evdev::RelativeAxisEvent::new(
                    RelativeAxisCode::REL_HWHEEL,
                    delta,
                ));
            }
        }
    }
}

impl Simulation for Backend {
    fn send_key(&mut self, key: Key, action: Action) -> Result<()> {
        if let Some(evdev_key) = key_to_platform(key) {
            self.add_key_or_button(evdev_key, action)?;
        }
        self.emit()
    }

    fn send_button(&mut self, button: Button, action: Action) -> Result<()> {
        if let Some(evdev_button) = button_to_platform(button) {
            self.add_key_or_button(evdev_button, action)?;
        }
        self.emit()
    }

    fn send_movement(&mut self, movement: Movement) -> Result<()> {
        self.add_movement(movement);
        self.emit()
    }

    fn send_scroll(&mut self, scroll: Scroll) -> Result<()> {
        self.add_scroll(scroll);
        self.emit()
    }

    fn send_batch(&mut self, batch: Vec<InputEvent>) -> Result<()> {
        for event in batch {
            match event {
                InputEvent::KeyEvent { key, action } => {
                    if let Some(evdev_key) = key_to_platform(key) {
                        self.add_key_or_button(evdev_key, action)?;
                    }
                }

                InputEvent::ButtonEvent { button, action } => {
                    if let Some(evdev_button) = button_to_platform(button) {
                        self.add_key_or_button(evdev_button, action)?;
                    }
                }

                InputEvent::MouseMove(movement) => {
                    self.add_movement(movement);
                }

                InputEvent::MouseScroll(scroll) => {
                    self.add_scroll(scroll);
                }

                InputEvent::Delay(duration) => {
                    if !self.events.is_empty() {
                        self.emit()?;
                    }

                    std::thread::sleep(duration);
                }
            }
        }

        if !self.events.is_empty() {
            self.emit()?;
        }

        Ok(())
    }
}

fn check_boundaries(pos: i32, max: i32) -> i32 {
    if pos <= max { pos + 1 } else { pos - 1 }
}