#![allow(dead_code)]
use anyhow::Result;
use std::collections::VecDeque;
use std::time::{Duration, Instant};
use crate::monitor::MonitorConfig;
#[derive(Debug, Clone)]
pub struct UserActivity {
pub active_window: Option<String>,
pub keyboard_events: u64,
pub mouse_clicks: u64,
pub mouse_moves: u64,
pub is_idle: bool,
pub timestamp: Instant,
}
impl Default for UserActivity {
fn default() -> Self {
Self {
active_window: None,
keyboard_events: 0,
mouse_clicks: 0,
mouse_moves: 0,
is_idle: false,
timestamp: Instant::now(),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ActivityType {
Keyboard,
MouseMove,
MouseClick,
WindowChange,
Idle,
}
pub struct InputTracker {
config: MonitorConfig,
keyboard_count: u64,
mouse_moves: u64,
mouse_clicks: u64,
active_window: Option<String>,
last_activity: Instant,
history: VecDeque<UserActivity>,
history_duration: Duration,
}
impl InputTracker {
pub fn new(config: MonitorConfig) -> Result<Self> {
Ok(Self {
config,
keyboard_count: 0,
mouse_moves: 0,
mouse_clicks: 0,
active_window: None,
last_activity: Instant::now(),
history: VecDeque::new(),
history_duration: Duration::from_secs(60),
})
}
pub fn record_keyboard(&mut self) {
self.keyboard_count += 1;
self.last_activity = Instant::now();
}
pub fn record_mouse_move(&mut self) {
self.mouse_moves += 1;
self.last_activity = Instant::now();
}
pub fn record_mouse_click(&mut self) {
self.mouse_clicks += 1;
self.last_activity = Instant::now();
}
pub fn record_window_change(&mut self, window_title: String) {
self.active_window = Some(window_title);
self.last_activity = Instant::now();
}
pub fn get_current_activity(&self) -> UserActivity {
let is_idle = self.last_activity.elapsed().as_secs() > self.config.idle_threshold_secs;
UserActivity {
active_window: self.active_window.clone(),
keyboard_events: self.keyboard_count,
mouse_clicks: self.mouse_clicks,
mouse_moves: self.mouse_moves,
is_idle,
timestamp: Instant::now(),
}
}
pub fn reset_counters(&mut self) {
self.keyboard_count = 0;
self.mouse_moves = 0;
self.mouse_clicks = 0;
}
pub fn get_active_window() -> Option<String> {
#[cfg(target_os = "macos")]
{
std::process::Command::new("osascript")
.args(["-e", "tell application \"System Events\" to get name of first process whose frontmost is true"])
.output()
.ok()
.and_then(|o| String::from_utf8(o.stdout).ok())
.map(|s| s.trim().to_string())
}
#[cfg(target_os = "linux")]
{
std::process::Command::new("xdotool")
.arg("getactivewindow")
.arg("getwindowname")
.output()
.ok()
.and_then(|o| String::from_utf8(o.stdout).ok())
.map(|s| s.trim().to_string())
}
#[cfg(target_os = "windows")]
{
use std::process::Command;
let script = r#"
Add-Type @"
using System;
using System.Runtime.InteropServices;
using System.Text;
public class Win32 {
[DllImport("user32.dll")]
public static extern IntPtr GetForegroundWindow();
[DllImport("user32.dll")]
public static extern int GetWindowText(IntPtr hWnd, StringBuilder text, int count);
}
"@
$hwnd = [Win32]::GetForegroundWindow()
$title = New-Object System.Text.StringBuilder 256
[Win32]::GetWindowText($hwnd, $title, 256) | Out-Null
$title.ToString()
"#;
Command::new("powershell")
.args(["-Command", script])
.output()
.ok()
.and_then(|o| String::from_utf8(o.stdout).ok())
.map(|s| s.trim().to_string())
}
#[cfg(not(any(target_os = "macos", target_os = "linux", target_os = "windows")))]
{
None
}
}
}