Skip to content

Commit faf92ef

Browse files
committed
feat(config): add regex dependency and update app data persistence
refactor: modify key behavior and remove unused serialization code chore: update README and LICENSE files
1 parent b43b971 commit faf92ef

18 files changed

+858
-914
lines changed

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ serde_json = "1.0.134"
1515
lazy_static = "1.5.0"
1616
thiserror = "2.0.9"
1717
log = "0.4.22"
18+
regex = "1.11.1"
1819

1920
[dependencies.libcosmic]
2021
git = "https://github.com/pop-os/libcosmic.git"

README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
![image](https://github.com/user-attachments/assets/b6aba3b6-d9ee-47ba-a204-8d3c7e01f572)
1+
![image](https://github.com/user-attachments/assets/4d5facec-9f27-4457-8890-9412347ee145)
22

33
# AutoClicker for Keyboard and Mouse
44

@@ -21,6 +21,7 @@ This application simulates repeated key presses and mouse clicks at a specified
2121

2222
- Global hotkeys **do not work** with pure Wayland applications on the COSMIC Desktop, but they work fine with Xwayland applications (e.g., Steam and games running through Proton).
2323
- The application starts with a phantom winit window. This is a minor issue that may be addressed in the future.
24+
- Modifier Behavior set to "Click" is currently not working. Got busy, and decided to commit the current changes. Will finish modifier behavior at a later date.
2425

2526
## Building from Source
2627

@@ -48,6 +49,7 @@ To build the application from source, you need to have Rust and Cargo installed.
4849
- [lazy_static](https://crates.io/crates/lazy_static)
4950
- [thiserror](https://crates.io/crates/thiserror)
5051
- [log](https://crates.io/crates/log)
52+
- [regex](https://crates.io/crates/regex)
5153

5254
## Contributing
5355

THIRD_PARTY_LICENSES.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ thiserror: Apache-2.0 or MIT
1313

1414
log: Apache-2.0 or MIT
1515

16+
regex: Apache-2.0 or MIT
17+
1618
For full license texts, please see:
1719
- MPL-2.0: https://www.mozilla.org/en-US/MPL/2.0/
1820
- Apache-2.0: https://www.apache.org/licenses/LICENSE-2.0

src/app.rs

Lines changed: 250 additions & 223 deletions
Large diffs are not rendered by default.

src/config.rs

Lines changed: 31 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,6 @@
1-
use cosmic::{
2-
cosmic_config::{Config as CosmicConfig, CosmicConfigEntry, Error as CosmicError},
3-
iced::keyboard::Key,
4-
};
51
use serde::{Serialize, Deserialize};
62
use std::str::FromStr;
73

8-
use crate::utils::{KeyWrapper, serialize_keys, deserialize_keys};
9-
104
const KEY_BEHAVIOR_MODES: [(&str, KeyBehaviorMode); 2] = [
115
("Click", KeyBehaviorMode::Click),
126
("Hold", KeyBehaviorMode::Hold),
@@ -44,34 +38,40 @@ impl Default for KeyBehaviorMode {
4438
}
4539
}
4640

47-
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
48-
pub struct Config {
49-
pub captured_keys: Vec<KeyWrapper>,
50-
pub current_key_bind: Vec<KeyWrapper>,
51-
pub interval_ms: u64,
52-
pub modifier_mode: KeyBehaviorMode,
41+
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
42+
pub enum ModifierBehaviorMode {
43+
Hold,
44+
Click,
5345
}
5446

55-
impl CosmicConfigEntry for Config {
56-
const VERSION: u64 = 1;
57-
58-
fn write_entry(&self, _config: &CosmicConfig) -> std::result::Result<(), CosmicError> {
59-
serde_json::to_string(self)
60-
.map_err(|e| CosmicError::Io(std::io::Error::new(std::io::ErrorKind::Other, format!("JSON error: {}", e))))?;
61-
Ok(())
47+
impl std::fmt::Display for ModifierBehaviorMode {
48+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
49+
match self {
50+
ModifierBehaviorMode::Hold => write!(f, "Hold"),
51+
ModifierBehaviorMode::Click => write!(f, "Click"),
52+
}
6253
}
54+
}
6355

64-
fn get_entry(_config: &CosmicConfig) -> std::result::Result<Self, (Vec<CosmicError>, Self)> {
65-
// Try to read from config, fallback to default if fails
66-
let default = Self::default();
67-
Ok(default)
56+
impl FromStr for ModifierBehaviorMode {
57+
type Err = ();
58+
59+
fn from_str(input: &str) -> Result<ModifierBehaviorMode, Self::Err> {
60+
match input {
61+
"Hold" => Ok(ModifierBehaviorMode::Hold),
62+
"Click" => Ok(ModifierBehaviorMode::Click),
63+
_ => Err(()),
64+
}
6865
}
66+
}
6967

70-
fn update_keys<T: AsRef<str>>(&mut self, _: &CosmicConfig, _: &[T]) -> (Vec<CosmicError>, Vec<&'static str>) {
71-
(vec![], vec![])
68+
impl Default for ModifierBehaviorMode {
69+
fn default() -> Self {
70+
ModifierBehaviorMode::Click
7271
}
7372
}
7473

74+
7575
#[derive(Debug, Clone, Copy, Serialize, Deserialize, Default)]
7676
pub struct HotkeyModifiers {
7777
pub ctrl: bool,
@@ -111,20 +111,19 @@ impl Default for TempHotkeyState {
111111
}
112112
}
113113

114-
#[derive(Default, Serialize, Deserialize)]
114+
#[derive(Debug, Default, Serialize, Deserialize)]
115115
pub struct AppData {
116116
#[serde(skip)]
117-
pub captured_keys: Vec<Key>,
118-
#[serde(serialize_with = "serialize_keys", deserialize_with = "deserialize_keys")]
119-
pub selected_keys: Vec<Key>,
117+
pub captured_keys: Vec<String>,
118+
#[serde(default)]
119+
pub selected_keys: Vec<String>,
120120
#[serde(default)]
121121
pub global_keybind: GlobalHotkey,
122122
pub interval_ms: u64,
123-
pub modifier_mode: KeyBehaviorMode,
123+
pub key_behavior: KeyBehaviorMode,
124+
pub modifier_behavior: ModifierBehaviorMode,
124125
#[serde(skip)]
125126
pub capturing_global_hotkey: bool,
126127
#[serde(skip)]
127-
pub captured_global_hotkey: Option<Key>,
128-
#[serde(skip)]
129128
pub temp_hotkey: TempHotkeyState,
130129
}

src/constants.rs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,11 @@
11
pub const MIN_INTERVAL_MS: u64 = 10;
2-
pub const MAX_INTERVAL_MS: u64 = 1000;
2+
pub const MAX_INTERVAL_MS: u64 = 5000;
33
pub const DEFAULT_INTERVAL_MS: u64 = 100;
4-
pub const HOTKEY_TOGGLE_DELAY_MS: u64 = 500;
54
pub const SIMULATION_HOLD_DELAY_MS: u64 = 50;
6-
pub const SIMULATION_HOLD_INTERVAL_MS: u64 = 10;
75

86
pub const MAX_RETRIES: u32 = 3;
97
pub const RETRY_DELAY_MS: u64 = 5;
108
pub const MAX_DEVICE_INIT_RETRIES: u32 = 3;
119
pub const DEVICE_INIT_RETRY_DELAY_MS: u64 = 100;
1210

13-
pub const LISTENER_SLEEP_MS: u64 = 10;
11+
pub const LISTENER_SLEEP_MS: u64 = 10;

src/simulator.rs

Lines changed: 103 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,9 @@ use evdev_rs::{
1414
};
1515

1616
use crate::{
17-
config::KeyBehaviorMode,
17+
config::{KeyBehaviorMode, ModifierBehaviorMode},
1818
constants::{
1919
SIMULATION_HOLD_DELAY_MS,
20-
SIMULATION_HOLD_INTERVAL_MS,
2120
MAX_RETRIES,
2221
RETRY_DELAY_MS,
2322
MAX_DEVICE_INIT_RETRIES,
@@ -26,25 +25,39 @@ use crate::{
2625
error::{SimulatorError, Result},
2726
};
2827

29-
fn write_event_with_retry(device: &UInputDevice, event: &InputEvent) -> Result<()> {
28+
fn retry<T, F>(mut operation: F, max_retries: u32, delay_ms: u64, log_fn: impl Fn(usize)) -> Result<T>
29+
where
30+
F: FnMut() -> Result<T>,
31+
{
3032
let mut last_error = None;
31-
for attempt in 0..MAX_RETRIES {
32-
match device.write_event(event) {
33-
Ok(_) => return Ok(()),
33+
for attempt in 0..max_retries {
34+
match operation() {
35+
Ok(result) => return Ok(result),
3436
Err(e) => {
3537
last_error = Some(e);
36-
if attempt < MAX_RETRIES - 1 {
37-
log::debug!("Write event attempt {} failed, retrying...", attempt + 1);
38-
thread::sleep(Duration::from_millis(RETRY_DELAY_MS));
38+
if attempt < max_retries - 1 {
39+
log_fn((attempt + 1) as usize);
40+
thread::sleep(Duration::from_millis(delay_ms));
3941
}
4042
}
4143
}
4244
}
43-
Err(SimulatorError::KeySimulation(format!(
44-
"Failed after {} retries: {:?}",
45-
MAX_RETRIES,
46-
last_error.unwrap()
47-
)).into())
45+
Err(last_error.unwrap())
46+
}
47+
48+
fn write_event_with_retry(device: &UInputDevice, event: &InputEvent) -> Result<()> {
49+
retry(
50+
|| {
51+
device.write_event(event)
52+
.map_err(|e| SimulatorError::KeySimulation(format!("Failed event: {:?}", e)).into())
53+
},
54+
MAX_RETRIES,
55+
RETRY_DELAY_MS,
56+
|attempt| {
57+
log::debug!("Write event attempt {} failed, retrying...", attempt);
58+
},
59+
)
60+
.map_err(|e| e)
4861
}
4962

5063
fn write_key_events(device: &UInputDevice, keys: &[EventCode], value: i32, timeval: &TimeVal) -> Result<()> {
@@ -81,37 +94,68 @@ fn setup_device(selected_keys: &Arc<Mutex<Vec<EventCode>>>) -> Result<UInputDevi
8194
}
8295

8396
fn setup_device_with_retry(selected_keys: &Arc<Mutex<Vec<EventCode>>>) -> Result<UInputDevice> {
84-
let mut last_error = None;
85-
for attempt in 0..MAX_DEVICE_INIT_RETRIES {
86-
match setup_device(selected_keys) {
87-
Ok(device) => return Ok(device),
88-
Err(e) => {
89-
last_error = Some(e);
90-
if attempt < MAX_DEVICE_INIT_RETRIES - 1 {
91-
log::warn!("Device initialization attempt {} failed, retrying...", attempt + 1);
92-
thread::sleep(Duration::from_millis(DEVICE_INIT_RETRY_DELAY_MS));
97+
retry(
98+
|| setup_device(selected_keys),
99+
MAX_DEVICE_INIT_RETRIES,
100+
DEVICE_INIT_RETRY_DELAY_MS,
101+
|attempt| {
102+
log::warn!("Device initialization attempt {} failed, retrying...", attempt);
103+
},
104+
)
105+
.map_err(|e| SimulatorError::DeviceInitialization(format!("Failed after {} retries: {:?}", MAX_DEVICE_INIT_RETRIES, e)).into())
106+
}
107+
108+
// New function to initialize simulation keys
109+
pub fn initialize_simulation_keys(
110+
app_data: &crate::config::AppData,
111+
selected_keys: &mut Vec<evdev_rs::enums::EventCode>,
112+
key_behavior: &mut crate::config::KeyBehaviorMode,
113+
) {
114+
selected_keys.clear();
115+
*key_behavior = app_data.key_behavior;
116+
117+
log::debug!("Initializing simulation with keys: {:?}", app_data.selected_keys);
118+
119+
for raw in &app_data.selected_keys {
120+
if let Some(device_key) = crate::utils::key_utils::raw_key_to_device_keycode(raw) {
121+
if let Some(ev_key) = crate::utils::key_utils::keycode_to_evkey(device_key) {
122+
// Handle modifier keys based on modifier behavior setting
123+
if crate::utils::key_utils::is_modifier_key(raw) &&
124+
app_data.modifier_behavior == crate::config::ModifierBehaviorMode::Click {
125+
selected_keys.push(evdev_rs::enums::EventCode::EV_KEY(ev_key));
126+
} else {
127+
selected_keys.push(evdev_rs::enums::EventCode::EV_KEY(ev_key));
93128
}
129+
log::debug!("Added key: {:?}", ev_key);
94130
}
131+
} else {
132+
log::warn!("Failed to map key: {}", raw);
95133
}
96134
}
97-
Err(SimulatorError::DeviceInitialization(format!(
98-
"Failed after {} retries: {:?}",
99-
MAX_DEVICE_INIT_RETRIES,
100-
last_error.unwrap()
101-
)).into())
135+
136+
if selected_keys.is_empty() {
137+
log::warn!("No valid keys initialized for simulation");
138+
} else {
139+
log::info!("Simulation initialized with {} keys", selected_keys.len());
140+
}
102141
}
103142

104143
// Main simulation loop that handles both click and hold modes
105144
pub fn simulate_keys(
106145
running: Arc<Mutex<bool>>,
107146
interval_ms: Arc<Mutex<u64>>,
108147
selected_keys: Arc<Mutex<Vec<EventCode>>>,
109-
modifier_mode: Arc<Mutex<KeyBehaviorMode>>,
148+
key_behavior: Arc<Mutex<KeyBehaviorMode>>,
149+
modifier_behavior: ModifierBehaviorMode,
110150
) -> Result<()> {
111151
let uinput_device = setup_device_with_retry(&selected_keys)?;
112152
let timeval = TimeVal::new(0, 0);
113-
let keys = selected_keys.lock().unwrap().clone();
114-
let mode = *modifier_mode.lock().unwrap();
153+
// Combine acquisitions for keys and mode.
154+
let (keys, mode) = {
155+
let keys = selected_keys.lock().unwrap().clone();
156+
let mode = *key_behavior.lock().unwrap();
157+
(keys, mode)
158+
};
115159

116160
log::info!("Device initialized with keys: {:?}", keys);
117161
log::info!("Key behavior mode set to: {:?}", mode);
@@ -127,24 +171,42 @@ pub fn simulate_keys(
127171
write_key_events(&uinput_device, &keys, 1, &timeval)?;
128172

129173
while *running.lock().unwrap() {
130-
write_key_events(&uinput_device, &[], 0, &timeval)?; // Just sync
131-
thread::sleep(Duration::from_millis(SIMULATION_HOLD_INTERVAL_MS));
174+
write_key_events(&uinput_device, &[], 0, &timeval)?;
132175
}
133176

134177
// Release keys
135178
write_key_events(&uinput_device, &keys, 0, &timeval)?;
136179
}
137180
KeyBehaviorMode::Click => {
138-
while *running.lock().unwrap() {
139-
let interval = *interval_ms.lock().unwrap();
181+
if modifier_behavior == ModifierBehaviorMode::Click {
182+
let (mod_keys, non_mod_keys): (Vec<EventCode>, Vec<EventCode>) =
183+
keys.iter().cloned().partition(|k| crate::utils::key_utils::is_modifier_evcode(k));
184+
185+
while *running.lock().unwrap() {
186+
let interval = *interval_ms.lock().unwrap();
187+
for nm in &non_mod_keys {
188+
if !mod_keys.is_empty() {
189+
write_key_events(&uinput_device, &mod_keys, 1, &timeval)?;
190+
}
191+
write_key_events(&uinput_device, &[*nm], 1, &timeval)?;
192+
write_key_events(&uinput_device, &[*nm], 0, &timeval)?;
193+
if !mod_keys.is_empty() {
194+
write_key_events(&uinput_device, &mod_keys, 0, &timeval)?;
195+
}
196+
thread::sleep(Duration::from_millis(interval));
197+
}
198+
}
199+
} else {
200+
while *running.lock().unwrap() {
201+
let interval = *interval_ms.lock().unwrap();
140202

141-
// Press keys
142-
write_key_events(&uinput_device, &keys, 1, &timeval)?;
143-
thread::sleep(Duration::from_millis(SIMULATION_HOLD_DELAY_MS));
203+
// Press keys
204+
write_key_events(&uinput_device, &keys, 1, &timeval)?;
144205

145-
// Release keys
146-
write_key_events(&uinput_device, &keys, 0, &timeval)?;
147-
thread::sleep(Duration::from_millis(interval));
206+
// Release keys
207+
write_key_events(&uinput_device, &keys, 0, &timeval)?;
208+
thread::sleep(Duration::from_millis(interval));
209+
}
148210
}
149211
}
150212
}

0 commit comments

Comments
 (0)