1
use std::sync::Arc;
2
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
3
use std::time::Duration;
4

            
5
use enigo::{Button, Coordinate, Direction, Enigo, Mouse, Settings};
6
use tokio::sync::Mutex;
7
use tokio::time::sleep;
8

            
9
use futures_util::StreamExt;
10

            
11
use crate::hotkey::HotkeyPortal;
12

            
13
#[cfg(test)]
14
mod test;
15

            
16
/// Implement the autoclicker functionality
17
#[derive(Clone)]
18
pub struct Autoclicker {
19
    enigo: Arc<Mutex<Enigo>>,
20
    running: Arc<AtomicBool>,
21
    stopped: Arc<AtomicBool>,
22
}
23

            
24
impl Autoclicker {
25
    /// Create a new Autoclicker instance.
26
    /// This will initialize the enigo instance for virtual input.
27
    /// Returns an error if the enigo instance cannot be created.
28
5
    pub fn new() -> Result<Self, Box<dyn std::error::Error>> {
29
5
        let mut enigo = Enigo::new(&Settings::default())?;
30

            
31
        // Move the mouse slightly to ensure the permission prompt is triggered.
32
5
        enigo.move_mouse(0, 0, Coordinate::Rel)?;
33

            
34
5
        Ok(Autoclicker {
35
5
            enigo: Arc::new(Mutex::new(enigo)),
36
5
            running: Arc::new(AtomicBool::new(false)),
37
5
            stopped: Arc::new(AtomicBool::new(true)),
38
5
        })
39
5
    }
40

            
41
    /// Start the autoclicker with the given delay in milliseconds between clicks.
42
    /// If a start delay (in seconds) is provided, it will wait for it before starting.
43
    /// If a duration (in seconds) is provided, it will stop the autoclicker after that duration.
44
    /// Returns true if the autoclicker was started, false if it was already running.
45
4
    pub async fn autoclick(
46
4
        &mut self,
47
4
        delay_ms: Arc<AtomicU64>,
48
4
        start_delay: Option<u64>,
49
4
        duration: Option<u64>,
50
4
    ) -> bool {
51
4
        let running = Arc::clone(&self.running);
52
4
        let stopped = Arc::clone(&self.stopped);
53
4
        if self.is_running() || !self.is_stopped() {
54
2
            return false;
55
2
        }
56
2
        running.store(true, Ordering::SeqCst);
57
2
        stopped.store(false, Ordering::SeqCst);
58

            
59
2
        if let Some(start_delay) = start_delay {
60
1
            println!("Waiting for {start_delay} s before starting autoclicker");
61
1
            sleep(Duration::from_secs(start_delay)).await;
62
1
        }
63

            
64
2
        let enigo = Arc::clone(&self.enigo);
65

            
66
2
        tokio::spawn(async move {
67
2
            println!(
68
                "Autoclicker started with delay: {} ms",
69
2
                delay_ms.load(Ordering::Relaxed)
70
            );
71
4
            while running.load(Ordering::Relaxed) {
72
2
                if let Err(e) = enigo.lock().await.button(Button::Left, Direction::Click) {
73
                    eprintln!("Failed to click mouse button: {e}");
74
2
                };
75

            
76
2
                let mut elapsed_time_ms = 0;
77
93
                while running.load(Ordering::Relaxed)
78
91
                    && elapsed_time_ms < delay_ms.load(Ordering::Acquire)
79
                {
80
91
                    sleep(Duration::from_millis(10)).await;
81
91
                    elapsed_time_ms += 10;
82
                }
83
            }
84
2
            stopped.store(true, Ordering::Release);
85
2
            println!("Autoclicker stopped");
86
2
        });
87

            
88
2
        if let Some(duration) = duration {
89
1
            let running = Arc::clone(&self.running);
90
1
            tokio::spawn(async move {
91
1
                println!("Autoclicker will stop after {duration} s");
92
1
                sleep(Duration::from_secs(duration)).await;
93
1
                running.store(false, Ordering::Release);
94
1
            });
95
1
        }
96

            
97
2
        true
98
4
    }
99

            
100
    /// Listen to the event stream and trigger the autoclicker on each event.
101
    pub fn trigger_on_hotkey(&self, portal: HotkeyPortal, delay_ms: Arc<AtomicU64>) {
102
        let portal = portal.clone();
103
        let mut autoclicker = self.clone();
104
        tokio::spawn(async move {
105
            let mut stream = match portal.activated_stream().await {
106
                Ok(s) => s,
107
                Err(e) => {
108
                    eprintln!("Failed to receive hotkey activation events: {e}");
109
                    return;
110
                }
111
            };
112
            while stream.next().await.is_some() {
113
                println!("Hotkey activated");
114
                let started = autoclicker
115
                    .autoclick(Arc::clone(&delay_ms), None, None)
116
                    .await;
117
                if !started {
118
                    autoclicker.running.store(false, Ordering::Release);
119
                }
120
            }
121
        });
122
    }
123

            
124
    /// Check if the autoclicker is currently running.
125
7
    pub fn is_running(&self) -> bool {
126
7
        self.running.load(Ordering::SeqCst)
127
7
    }
128
    /// Check if the autoclicker is currently stopped.
129
3
    pub fn is_stopped(&self) -> bool {
130
3
        self.stopped.load(Ordering::SeqCst)
131
3
    }
132
}