use crate::Hook;
use crate::error::{Error, Result};
use crate::event::{Event, EventType};
use crate::keycode::Key;
use std::collections::HashMap;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, Mutex};
use std::time::{Duration, Instant};
#[derive(Debug, Clone, Default)]
pub struct EventStatistics {
pub total_event_count: u64,
pub key_press_count: u64,
pub key_release_count: u64,
pub mouse_press_count: u64,
pub mouse_release_count: u64,
pub mouse_click_count: u64,
pub mouse_move_count: u64,
pub mouse_drag_count: u64,
pub mouse_wheel_count: u64,
pub key_frequency: HashMap<Key, u64>,
pub total_mouse_distance: f64,
pub current_mouse_position: (f64, f64),
pub start_time: Option<Instant>,
pub end_time: Option<Instant>,
pub first_key_time: Option<Instant>,
pub last_key_time: Option<Instant>,
pub active_typing_duration: Duration,
pub first_mouse_time: Option<Instant>,
pub last_mouse_time: Option<Instant>,
pub avg_click_interval: Option<Duration>,
last_click_time: Option<Instant>,
click_interval_sum: Duration,
click_interval_count: u64,
pub button_clicks: HashMap<crate::event::Button, u64>,
pub total_vertical_scroll: f64,
pub total_horizontal_scroll: f64,
}
impl EventStatistics {
pub fn new() -> Self {
Self {
key_frequency: HashMap::new(),
button_clicks: HashMap::new(),
current_mouse_position: (0.0, 0.0),
..Default::default()
}
}
pub fn record_event(&mut self, event: &Event) {
self.total_event_count += 1;
match event.event_type {
EventType::KeyPressed => {
self.key_press_count += 1;
let now = Instant::now();
if self.first_key_time.is_none() {
self.first_key_time = Some(now);
}
if let Some(last) = self.last_key_time {
let interval = now.duration_since(last);
if interval < Duration::from_secs(5) {
self.active_typing_duration += interval;
}
}
self.last_key_time = Some(now);
if let Some(ref kb) = event.keyboard {
*self.key_frequency.entry(kb.key).or_insert(0) += 1;
}
}
EventType::KeyReleased => {
self.key_release_count += 1;
}
EventType::MousePressed => {
self.mouse_press_count += 1;
let now = Instant::now();
if let Some(last) = self.last_click_time {
let interval = now.duration_since(last);
self.click_interval_sum += interval;
self.click_interval_count += 1;
self.avg_click_interval =
Some(self.click_interval_sum / self.click_interval_count as u32);
}
self.last_click_time = Some(now);
if let Some(ref mouse) = event.mouse
&& let Some(button) = mouse.button
{
*self.button_clicks.entry(button).or_insert(0) += 1;
}
}
EventType::MouseReleased => {
self.mouse_release_count += 1;
}
EventType::MouseClicked => {
self.mouse_click_count += 1;
}
EventType::MouseMoved | EventType::MouseDragged => {
if event.event_type == EventType::MouseMoved {
self.mouse_move_count += 1;
} else {
self.mouse_drag_count += 1;
}
let now = Instant::now();
if self.first_mouse_time.is_none() {
self.first_mouse_time = Some(now);
}
self.last_mouse_time = Some(now);
if let Some(ref mouse) = event.mouse {
let dx = mouse.x - self.current_mouse_position.0;
let dy = mouse.y - self.current_mouse_position.1;
self.total_mouse_distance += (dx * dx + dy * dy).sqrt();
self.current_mouse_position = (mouse.x, mouse.y);
}
}
EventType::MouseWheel => {
self.mouse_wheel_count += 1;
if let Some(ref wheel) = event.wheel {
match wheel.direction {
crate::event::ScrollDirection::Up => {
self.total_vertical_scroll += wheel.delta
}
crate::event::ScrollDirection::Down => {
self.total_vertical_scroll -= wheel.delta
}
crate::event::ScrollDirection::Left => {
self.total_horizontal_scroll -= wheel.delta
}
crate::event::ScrollDirection::Right => {
self.total_horizontal_scroll += wheel.delta
}
}
}
}
_ => {}
}
}
pub fn total_events(&self) -> u64 {
self.total_event_count
}
pub fn most_frequent_key(&self) -> Option<(Key, u64)> {
self.key_frequency
.iter()
.max_by_key(|(_, count)| *count)
.map(|(key, count)| (*key, *count))
}
pub fn most_frequent_button(&self) -> Option<(crate::event::Button, u64)> {
self.button_clicks
.iter()
.max_by_key(|(_, count)| *count)
.map(|(btn, count)| (*btn, *count))
}
pub fn collection_duration(&self) -> Duration {
match (self.start_time, self.end_time) {
(Some(start), Some(end)) => end.duration_since(start),
(Some(start), None) => start.elapsed(),
_ => Duration::ZERO,
}
}
pub fn events_per_minute(&self) -> f64 {
let duration = self.collection_duration();
if duration.as_secs() == 0 {
return 0.0;
}
self.total_event_count as f64 / duration.as_secs_f64() * 60.0
}
pub fn keys_per_minute(&self) -> f64 {
let duration = self.collection_duration();
if duration.as_secs() == 0 {
return 0.0;
}
self.key_press_count as f64 / duration.as_secs_f64() * 60.0
}
pub fn mouse_activity_ratio(&self) -> f64 {
let total_input = self.key_press_count + self.mouse_press_count + self.mouse_move_count;
if total_input == 0 {
return 0.0;
}
(self.mouse_move_count + self.mouse_press_count) as f64 / total_input as f64
}
pub fn is_active_recently(&self, duration: Duration) -> bool {
let now = Instant::now();
let key_active = self
.last_key_time
.map(|t| now.duration_since(t) < duration)
.unwrap_or(false);
let mouse_active = self
.last_mouse_time
.map(|t| now.duration_since(t) < duration)
.unwrap_or(false);
key_active || mouse_active
}
pub fn needs_break(&self, threshold: Duration) -> bool {
if self.active_typing_duration > threshold {
if let Some(last) = self.last_key_time {
let since_last = Instant::now().duration_since(last);
if since_last > Duration::from_secs(60) {
return false; }
}
true
} else {
false
}
}
pub fn summary(&self) -> String {
let duration = self.collection_duration();
let minutes = duration.as_secs() / 60;
let seconds = duration.as_secs() % 60;
let mut summary = format!(
"=== Input Statistics ===\n\
Duration: {:02}:{:02}\n\
Total Events: {}\n\
Events/min: {:.1}\n\n",
minutes,
seconds,
self.total_event_count,
self.events_per_minute()
);
summary.push_str(&format!(
"Keyboard:\n\
- Presses: {}\n\
- Releases: {}\n\
- Keys/min: {:.1}\n",
self.key_press_count,
self.key_release_count,
self.keys_per_minute()
));
if let Some((key, count)) = self.most_frequent_key() {
summary.push_str(&format!("- Most pressed: {:?} ({} times)\n", key, count));
}
summary.push('\n');
summary.push_str(&format!(
"Mouse:\n\
- Clicks: {}\n\
- Moves: {}\n\
- Drags: {}\n\
- Distance: {:.0} pixels\n",
self.mouse_click_count,
self.mouse_move_count,
self.mouse_drag_count,
self.total_mouse_distance
));
if let Some(interval) = self.avg_click_interval {
summary.push_str(&format!("- Avg click interval: {:?}\n", interval));
}
if let Some((btn, count)) = self.most_frequent_button() {
summary.push_str(&format!("- Most clicked: {:?} ({} times)\n", btn, count));
}
summary
}
pub fn merge(&mut self, other: &EventStatistics) {
self.total_event_count += other.total_event_count;
self.key_press_count += other.key_press_count;
self.key_release_count += other.key_release_count;
self.mouse_press_count += other.mouse_press_count;
self.mouse_release_count += other.mouse_release_count;
self.mouse_click_count += other.mouse_click_count;
self.mouse_move_count += other.mouse_move_count;
self.mouse_drag_count += other.mouse_drag_count;
self.mouse_wheel_count += other.mouse_wheel_count;
for (key, count) in &other.key_frequency {
*self.key_frequency.entry(*key).or_insert(0) += count;
}
for (btn, count) in &other.button_clicks {
*self.button_clicks.entry(*btn).or_insert(0) += count;
}
self.total_mouse_distance += other.total_mouse_distance;
self.total_vertical_scroll += other.total_vertical_scroll;
self.total_horizontal_scroll += other.total_horizontal_scroll;
self.active_typing_duration += other.active_typing_duration;
}
}
pub struct StatisticsCollector {
stats: Arc<Mutex<EventStatistics>>,
hook: Option<Hook>,
running: Arc<AtomicBool>,
}
impl StatisticsCollector {
pub fn new() -> Self {
let mut stats = EventStatistics::new();
stats.start_time = Some(Instant::now());
Self {
stats: Arc::new(Mutex::new(stats)),
hook: None,
running: Arc::new(AtomicBool::new(false)),
}
}
pub fn start(&mut self) -> Result<()> {
if self.running.load(Ordering::SeqCst) {
return Err(Error::AlreadyRunning);
}
let stats = self.stats.clone();
let running = self.running.clone();
let hook = Hook::new();
hook.run_async(move |event: &Event| {
if !running.load(Ordering::SeqCst) {
return;
}
if let Ok(mut s) = stats.lock() {
s.record_event(event);
}
})?;
self.running.store(true, Ordering::SeqCst);
self.hook = Some(hook);
Ok(())
}
pub fn stop(&mut self) -> Result<EventStatistics> {
if !self.running.swap(false, Ordering::SeqCst) {
return Err(Error::NotRunning);
}
if let Some(hook) = self.hook.take() {
hook.stop()?;
}
let mut stats = self
.stats
.lock()
.map_err(|_| Error::ThreadError("statistics mutex poisoned".into()))?;
stats.end_time = Some(Instant::now());
Ok(stats.clone())
}
pub fn snapshot(&self) -> EventStatistics {
match self.stats.lock() {
Ok(s) => s.clone(),
Err(_) => EventStatistics::new(), }
}
pub fn is_collecting(&self) -> bool {
self.running.load(Ordering::SeqCst)
}
pub fn collect_for(duration: Duration) -> Result<EventStatistics> {
let mut collector = Self::new();
collector.start()?;
std::thread::sleep(duration);
collector.stop()
}
}
impl Default for StatisticsCollector {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_statistics_new() {
let stats = EventStatistics::new();
assert_eq!(stats.total_events(), 0);
assert!(stats.most_frequent_key().is_none());
assert_eq!(stats.events_per_minute(), 0.0);
}
#[test]
fn test_record_key_press() {
let mut stats = EventStatistics::new();
let event = Event::key_pressed(Key::KeyA, 30);
stats.record_event(&event);
assert_eq!(stats.key_press_count, 1);
assert_eq!(stats.key_frequency.get(&Key::KeyA), Some(&1));
}
#[test]
fn test_most_frequent_key() {
let mut stats = EventStatistics::new();
stats.record_event(&Event::key_pressed(Key::KeyA, 30));
stats.record_event(&Event::key_pressed(Key::KeyA, 30));
stats.record_event(&Event::key_pressed(Key::KeyB, 48));
let (key, count) = stats.most_frequent_key().unwrap();
assert_eq!(key, Key::KeyA);
assert_eq!(count, 2);
}
#[test]
fn test_mouse_distance() {
let mut stats = EventStatistics::new();
stats.current_mouse_position = (0.0, 0.0);
stats.record_event(&Event::mouse_moved(3.0, 4.0)); assert!((stats.total_mouse_distance - 5.0).abs() < 0.001);
stats.record_event(&Event::mouse_moved(6.0, 8.0)); assert!((stats.total_mouse_distance - 10.0).abs() < 0.001);
}
#[test]
fn test_merge() {
let mut stats1 = EventStatistics::new();
stats1.record_event(&Event::key_pressed(Key::KeyA, 30));
let mut stats2 = EventStatistics::new();
stats2.record_event(&Event::key_pressed(Key::KeyB, 48));
stats1.merge(&stats2);
assert_eq!(stats1.key_press_count, 2);
assert_eq!(stats1.key_frequency.get(&Key::KeyA), Some(&1));
assert_eq!(stats1.key_frequency.get(&Key::KeyB), Some(&1));
}
}