pub mod keyboard;
pub mod mouse;
pub mod screen;
pub mod window;
pub use keyboard::KeyboardController;
pub use mouse::MouseController;
pub use screen::ScreenCapture;
pub use window::WindowManager;
use serde::{Deserialize, Serialize};
use std::sync::atomic::{AtomicU64, Ordering};
#[derive(Debug)]
pub struct ActionRateLimiter {
max_actions_per_sec: u32,
last_action_ms: AtomicU64,
actions_in_window: AtomicU64,
}
impl ActionRateLimiter {
pub fn new(max_actions_per_sec: u32) -> Self {
Self {
max_actions_per_sec,
last_action_ms: AtomicU64::new(0),
actions_in_window: AtomicU64::new(0),
}
}
pub fn check(&self) -> bool {
let now_ms = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_millis() as u64;
let window_ms = 1000;
loop {
let last = self.last_action_ms.load(Ordering::Acquire);
if now_ms.saturating_sub(last) > window_ms {
if self
.last_action_ms
.compare_exchange(last, now_ms, Ordering::AcqRel, Ordering::Acquire)
.is_ok()
{
self.actions_in_window.store(1, Ordering::Release);
return true;
}
continue;
} else {
let count = self.actions_in_window.fetch_add(1, Ordering::AcqRel) + 1;
return count <= self.max_actions_per_sec as u64;
}
}
}
}
impl Default for ActionRateLimiter {
fn default() -> Self {
Self::new(10) }
}
#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum MovementProfile {
#[default]
Linear,
EaseInOut,
Bezier,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct TypingProfile {
#[serde(default)]
pub base_delay_ms: u64,
#[serde(default)]
pub variation_ms: u64,
}
const BLOCKED_COMBOS: &[&str] = &[
"ctrl+alt+delete",
"cmd+q", "alt+f4", "ctrl+alt+f1", "ctrl+alt+f2",
"ctrl+alt+f3",
];
pub fn is_blocked_combo(combo: &str) -> bool {
let normalized = combo.to_lowercase().replace(' ', "");
BLOCKED_COMBOS
.iter()
.any(|blocked| normalized == blocked.replace(' ', ""))
}
#[cfg(test)]
#[path = "../../tests/unit/computer/mod_test.rs"]
mod tests;