1
#![doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/README.md"))]
2
// Prevent console window in addition to Slint window in Windows release builds when, e.g., starting the app via file manager. Ignored on other platforms.
3
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
4

            
5
use state::State;
6
use std::error::Error;
7
use std::sync::{Arc, atomic::AtomicU64, atomic::Ordering};
8

            
9
mod autoclicker;
10
mod hotkey;
11
mod state;
12

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

            
16
const VERSION: &str = env!("CARGO_PKG_VERSION");
17
const APP_ID: &str = concat!("io.github.heathcliff26.", env!("CARGO_PKG_NAME"));
18

            
19
slint::include_modules!();
20

            
21
// Need 2 threads here, one will be blocked by the Slint event loop.
22
#[tokio::main(flavor = "multi_thread", worker_threads = 2)]
23
async fn main() -> Result<(), Box<dyn Error>> {
24
    let autoclicker = match autoclicker::Autoclicker::new() {
25
        Ok(ac) => ac,
26
        Err(e) => {
27
            eprintln!("Failed to initialize autoclicker: {e}");
28
            std::process::exit(1);
29
        }
30
    };
31

            
32
    let app = AppWindow::new()?;
33
    app.global::<GlobalState>().set_version(VERSION.into());
34

            
35
    slint::set_xdg_app_id(APP_ID).expect("Failed to set XDG app ID");
36

            
37
    init_global_state(&app);
38

            
39
    let autoclicker_delay: u64 = app.global::<GlobalState>().get_delay().try_into().unwrap();
40
    let autoclicker_delay = Arc::new(AtomicU64::new(autoclicker_delay));
41

            
42
    let global_hotkey = hotkey::HotkeyPortal::register().await?;
43
    autoclicker.trigger_on_hotkey(global_hotkey.clone(), Arc::clone(&autoclicker_delay));
44

            
45
    register_start_auto_click(&app, autoclicker, autoclicker_delay.clone());
46
    register_settings_changed(&app, autoclicker_delay);
47
    register_configure_hotkey(&app, global_hotkey);
48

            
49
    app.run()?;
50

            
51
    save_global_state(&app);
52

            
53
    Ok(())
54
}
55

            
56
/// Initialize the global state from the saved state file.
57
1
fn init_global_state(app: &AppWindow) {
58
1
    let state = match State::from_file() {
59
1
        Ok(state) => state,
60
        Err(e) => {
61
            eprintln!("Failed to load state: {e}");
62
            None
63
        }
64
    };
65
1
    if let Some(state) = state {
66
1
        state.update_app(app);
67
1
    }
68
1
}
69

            
70
/// Save the global state to file.
71
1
fn save_global_state(app: &AppWindow) {
72
1
    let state = State::from_app(app);
73
1
    if let Err(e) = state.save_to_file() {
74
        eprintln!("Failed to save state: {e}");
75
1
    }
76
1
}
77

            
78
/// Register the callback for clicking the "Start Auto-click" button.
79
1
fn register_start_auto_click(
80
1
    app: &AppWindow,
81
1
    autoclicker: autoclicker::Autoclicker,
82
1
    autoclicker_delay: Arc<AtomicU64>,
83
1
) {
84
1
    let app_weak = app.as_weak();
85

            
86
1
    app.global::<GlobalState>().on_start_auto_click({
87
1
        move || {
88
1
            let app = app_weak.unwrap();
89
1
            let global_state = app.global::<GlobalState>();
90

            
91
1
            let start_delay: Option<u64> = match global_state.get_use_start_delay() {
92
1
                true => Some(global_state.get_start_delay().try_into().unwrap()),
93
                false => None,
94
            };
95
1
            let duration: Option<u64> = match global_state.get_use_duration() {
96
1
                true => Some(global_state.get_duration().try_into().unwrap()),
97
                false => None,
98
            };
99

            
100
1
            let delay = Arc::clone(&autoclicker_delay);
101

            
102
1
            let mut autoclicker = autoclicker.clone();
103
1
            tokio::spawn(async move {
104
1
                autoclicker.autoclick(delay, start_delay, duration).await;
105
1
            });
106
1
        }
107
    });
108
1
}
109

            
110
/// Register the callback for setting changes.
111
1
fn register_settings_changed(app: &AppWindow, autoclicker_delay: Arc<AtomicU64>) {
112
1
    let app_weak = app.as_weak();
113

            
114
1
    app.global::<GlobalState>().on_settings_changed({
115
1
        move || {
116
1
            let app = app_weak.unwrap();
117
1
            let global_state = app.global::<GlobalState>();
118

            
119
1
            autoclicker_delay.store(
120
1
                global_state.get_delay().try_into().unwrap(),
121
1
                Ordering::Release,
122
1
            );
123

            
124
1
            save_global_state(&app);
125
1
        }
126
    });
127
1
}
128

            
129
/// Register the callback for configuring the hotkey.
130
fn register_configure_hotkey(app: &AppWindow, global_hotkey: hotkey::HotkeyPortal) {
131
    app.global::<GlobalState>().on_configure_hotkey({
132
        move || {
133
            let global_hotkey = global_hotkey.clone();
134
            tokio::spawn(async move {
135
                global_hotkey.configure_hotkey().await;
136
            });
137
        }
138
    });
139
}