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 {
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;
}
};
let batch_timer = Instant::now();
let mut batch = vec![];
match first_msg {
InternalMessage::SimulateEvent(InputEvent::MouseMove(Movement::Relative {
dx,
dy,
})) => {
self.pending_relative_dx += dx;
self.pending_relative_dy += dy;
}
msg => batch.push(msg),
}
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);
}
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) => {
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(())
}
#[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,
})?;
self.pending_relative_dx = 0;
self.pending_relative_dy = 0;
}
Ok(())
}
}