keyflow 0.1.0

Cross-platform input simulation library for keyboard, mouse and hotkeys.
Documentation
use crate::builder::KeyflowBuilder;
use crate::error::*;
use crate::eventhandler::EventHandler;
use crate::hotkey::{Hotkey, HotkeyRegistry};
use crate::platform::{Backend, BackendListener, Listener, Simulation};
use crate::types::*;
use crossbeam_channel::{Sender, unbounded};
use std::sync::{Arc, OnceLock, RwLock};

static KEYFLOW: OnceLock<Keyflow> = OnceLock::new();

/// Main interface for Keyflow input simulation
///
/// All methods panic if Keyflow is not initialized. Initialize with
/// [`Keyflow::initialize()`] or [`Keyflow::builder()`].
///
/// # Thread Safety
///
/// Keyflow uses a global singleton and is thread-safe. All methods can be
/// called from any thread.
pub struct Keyflow {
    event_tx: Sender<InternalMessage>,
    hotkey_listener: Option<Box<dyn Listener>>,
}

impl Keyflow {
    /// Initialize with default settings
    ///
    /// Equivalent to [`builder().initialize()`](KeyflowBuilder::initialize). Does not enable hotkeys.
    ///
    /// # Errors
    ///
    /// Returns error if already initialized or device creation fails.
    ///
    /// # Example
    ///
    /// ```no_run
    /// use keyflow::prelude::*;
    ///
    /// Keyflow::initialize()?;
    /// Keyflow::press_key(Key::A);
    ///
    /// # Ok(())
    /// ```
    pub fn initialize() -> Result<()> {
        Self::builder().initialize()
    }

    /// Initialize with custom configuration.
    pub(crate) fn initialize_with_config(config: KeyflowBuilder) -> Result<()> {
        if KEYFLOW.get().is_some() {
            return Err(KeyflowError::AlreadyInitialized);
        }

        // creates the target_os backend, screen size is required in linux to map absolute movement correctly
        // in windows it get retrieved with `GetSystemMetrics`
        #[cfg(target_os = "linux")]
        let backend = {
            if config.screen.is_none() {
                return Err(KeyflowError::PlatformError(
                    "Screen size not set. It is required if run on linux".into(),
                ));
            }

            Box::new(Backend::new(config.screen.unwrap())?) as Box<dyn Simulation>
        };

        #[cfg(target_os = "windows")]
        let backend = { Box::new(Backend::new()?) as Box<dyn Simulation> };

        // start event handler
        let (event_tx, event_rx) = unbounded();
        let hotkey_registry = Arc::new(RwLock::new(HotkeyRegistry::new()));
        let mut handler = EventHandler::new(backend, hotkey_registry.clone(), event_rx);

        std::thread::Builder::new()
            .name("keyflow-event-handler".to_string())
            .spawn(move || {
                if let Err(e) = handler.run() {
                    eprintln!("EventHandler error: {}", e);
                }
            })
            .map_err(|e| KeyflowError::PlatformError(e.to_string()))?;

        // start hotkey listener if enabled
        let hotkey_listener = if config.enable_hotkeys {
            let listener =
                Box::new(BackendListener::new(hotkey_registry.clone())) as Box<dyn Listener>;
            listener.start()?;
            Some(listener)
        } else {
            None
        };

        KEYFLOW
            .set(Self {
                event_tx,
                hotkey_listener,
            })
            .map_err(|_| KeyflowError::AlreadyInitialized)?;

        Ok(())
    }

    /// Press a key down
    ///
    /// The key remains pressed until [`release_key`](Self::release_key) is called.
    pub fn press_key(key: Key) {
        Self::send_event(InputEvent::KeyEvent {
            key,
            action: Action::Press,
        })
        .expect("Failed to send key press event");
    }

    /// Release a key.
    pub fn release_key(key: Key) {
        Self::send_event(InputEvent::KeyEvent {
            key,
            action: Action::Release,
        })
        .expect("Failed to send key release event");
    }

    /// Click a key (press + release).
    pub fn click_key(key: Key) {
        Self::send_event(InputEvent::KeyEvent {
            key,
            action: Action::Click,
        })
        .expect("Failed to send key click event");
    }

    /// Press a mouse button.
    pub fn press_button(button: Button) {
        Self::send_event(InputEvent::ButtonEvent {
            button,
            action: Action::Press,
        })
        .expect("Failed to send button press event");
    }

    /// Release a mouse button.
    pub fn release_button(button: Button) {
        Self::send_event(InputEvent::ButtonEvent {
            button,
            action: Action::Release,
        })
        .expect("Failed to send button release event");
    }

    /// Click a mouse button (press + release).
    pub fn click_button(button: Button) {
        Self::send_event(InputEvent::ButtonEvent {
            button,
            action: Action::Click,
        })
        .expect("Failed to send button click event");
    }

    /// Move mouse relative to current position
    ///
    /// # Example
    ///
    /// ```no_run
    /// # use keyflow::prelude::*;
    /// # Keyflow::initialize().unwrap();
    /// // Move 100 pixels right, 50 pixels down
    /// Keyflow::move_relative(100, 50);
    /// ```
    pub fn move_by(dx: i32, dy: i32) {
        Self::send_event(InputEvent::MouseMove(Movement::Relative { dx, dy }))
            .expect("Failed to send relative move event");
    }

    /// Move mouse to absolute screen position
    ///
    /// # Example
    ///
    /// ```no_run
    /// # use keyflow::prelude::*;
    /// # Keyflow::initialize().unwrap();
    /// // Move to center of 1920x1080 screen
    /// Keyflow::move_to(960, 540);
    /// ```
    pub fn move_to(x: i32, y: i32) {
        Self::send_event(InputEvent::MouseMove(Movement::Absolute { x, y }))
            .expect("Failed to send absolute move event");
    }

    /// Scroll vertically
    ///
    /// Positive values scroll up, negative values scroll down.
    pub fn scroll_vertical(delta: i32) {
        Self::send_event(InputEvent::MouseScroll(Scroll::Vertical(delta)))
            .expect("Failed to send scroll event");
    }

    /// Scroll horizontally
    ///
    /// Positive values scroll right, negative values scroll left.
    pub fn scroll_horizontal(delta: i32) {
        Self::send_event(InputEvent::MouseScroll(Scroll::Horizontal(delta)))
            .expect("Failed to send scroll event");
    }

    // TODO: maybe provide macro to create batch
    /// Send a batch of events efficiently
    ///
    /// Events are processed as a single unit.
    ///
    /// See `examples/batch.rs` for usage patterns.
    ///
    /// # Example
    ///
    /// ```no_run
    /// use keyflow::prelude::*;
    /// use std::time::Duration;
    ///
    /// # Keyflow::initialize().unwrap();
    /// let events = vec![
    ///     InputEvent::KeyEvent { key: Key::A, action: Action::Press },
    ///     InputEvent::Delay(Duration::from_millis(100)),
    ///     InputEvent::KeyEvent { key: Key::A, action: Action::Release },
    /// ];
    ///
    /// Keyflow::batch(events);
    /// ```
    pub fn batch(events: Vec<InputEvent>) {
        Self::send_batch_events(events).expect("Failed to send batch events");
    }

    /// Register a global hotkey
    ///
    /// Requires initialization with [`builder().with_hotkeys()`](KeyflowBuilder::with_hotkeys).
    /// Callbacks run in dedicated threads and can safely call Keyflow methods.
    ///
    /// # Errors
    ///
    /// - Returns error if hotkeys not enabled during initialization
    /// - Returns error if `id` already exists
    ///
    /// # Example
    ///
    /// See `examples/hotkeys.rs` for complete example.
    ///
    /// ```no_run
    /// use keyflow::prelude::*;
    ///
    /// Keyflow::builder().with_hotkeys().initialize()?;
    ///
    /// Keyflow::register_hotkey(
    ///     "save",
    ///     Hotkey::new(Key::S).with_ctrl(),
    ///     || println!("Ctrl+S pressed!")
    /// )?;
    ///
    /// # Ok(())
    /// ```
    pub fn register_hotkey<F>(id: impl Into<String>, hotkey: Hotkey, callback: F) -> Result<()>
    where
        F: Fn() + Send + Sync + 'static,
    {
        let state = Self::get_state()?;

        if state.hotkey_listener.is_none() {
            return Err(KeyflowError::HotkeysNotEnabled);
        }

        state
            .event_tx
            .send(InternalMessage::RegisterHotkey {
                id: id.into(),
                combo: hotkey,
                callback: Box::new(callback),
            })
            .map_err(|_| KeyflowError::ChannelDisconnected)?;

        Ok(())
    }

    /// Unregister a hotkey by ID
    ///
    /// # Errors
    ///
    /// Returns error if hotkey with given ID does not exist.
    pub fn unregister_hotkey(id: impl Into<String>) -> Result<()> {
        let state = Self::get_state()?;

        if state.hotkey_listener.is_none() {
            return Err(KeyflowError::HotkeysNotEnabled);
        }

        state
            .event_tx
            .send(InternalMessage::UnregisterHotkey { id: id.into() })
            .map_err(|_| KeyflowError::ChannelDisconnected)?;

        Ok(())
    }

    /// Send a single event to the event handler.
    fn send_event(event: InputEvent) -> Result<()> {
        let state = Self::get_state()?;
        state
            .event_tx
            .send(InternalMessage::SimulateEvent(event))
            .map_err(|_| KeyflowError::ChannelDisconnected)
    }

    /// Send an event batch to the event handler.
    fn send_batch_events(events: Vec<InputEvent>) -> Result<()> {
        let state = Self::get_state()?;
        state
            .event_tx
            .send(InternalMessage::BatchEvents(events))
            .map_err(|_| KeyflowError::ChannelDisconnected)
    }

    fn get_state() -> Result<&'static Keyflow> {
        KEYFLOW.get().ok_or(KeyflowError::NotInitialized)
    }
}

impl Drop for Keyflow {
    fn drop(&mut self) {
        if let Some(listener) = &self.hotkey_listener {
            listener.stop()
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[cfg(target_os = "linux")]
    #[test]
    fn test_screen_required() {
        let result = Keyflow::initialize();

        assert!(result.is_err());
    }

    #[test]
    fn test_hotkeys_enabled() {
        Keyflow::builder()
            .with_hotkeys()
            .with_screen(Screen::default())
            .initialize()
            .unwrap();

        Keyflow::register_hotkey("test", Hotkey::new(Key::P).with_alt(), || {}).unwrap()
    }

    #[test]
    fn test_hotkeys_disabled() {
        Keyflow::builder()
            .with_screen(Screen::default())
            .initialize()
            .unwrap();

        let result = Keyflow::register_hotkey("test", Hotkey::new(Key::P).with_alt(), || {});
        assert!(result.is_err());
    }
}