Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions src-tauri/src/engine/keyboard.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,44 @@ fn send_key_up(vk: u16, use_shift: bool) {
}
}

pub struct KeyboardHold {
vk: u16,
use_shift: bool,
released: bool,
}

impl KeyboardHold {
pub fn repeat(&self) {
send_key_event(self.vk, 0);
}

pub fn release(&mut self) {
if self.released {
return;
}

send_key_up(self.vk, self.use_shift);
self.released = true;
}
}

impl Drop for KeyboardHold {
fn drop(&mut self) {
self.release();
}
}

pub fn hold_key(vk: u16, uppercase: bool) -> KeyboardHold {
let use_shift = should_hold_shift_for_case(vk, uppercase);
send_key_down(vk, use_shift);

KeyboardHold {
vk,
use_shift,
released: false,
}
}

pub fn send_key_batch(vk: u16, n: usize, uppercase: bool) {
let use_shift = should_hold_shift_for_case(vk, uppercase);
let inputs_per_press = if use_shift { 4 } else { 2 };
Expand Down
89 changes: 54 additions & 35 deletions src-tauri/src/engine/worker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ use windows_sys::Win32::UI::Input::KeyboardAndMouse::GetDoubleClickTime;

use super::cycle::ClickCyclePlan;
use super::failsafe::should_stop_for_failsafe;
use super::keyboard::{is_alphabetic_vk, send_key_presses};
use super::keyboard::{hold_key, is_alphabetic_vk, send_key_presses};
use super::mouse::{
get_button_flags, get_cursor_pos, move_mouse, send_clicks, smooth_move, VirtualScreenRect,
};
Expand Down Expand Up @@ -396,6 +396,21 @@ fn plan_cycle_batch(
}
}

fn should_hold_keyboard_across_cycles(config: &ClickerConfig) -> bool {
config.input_type == 1
&& config.key_code > 0
&& !config.double_click_enabled
&& config.duty >= 100.0
}

fn keyboard_hold_repeat_count(cycle_batch: CycleBatchPlan, key_already_held: bool) -> usize {
if key_already_held {
cycle_batch.physical_clicks
} else {
cycle_batch.physical_clicks.saturating_sub(1)
}
}

// -- Engine loop --

pub fn start_clicker(config: ClickerConfig, control: RunControl) -> RunOutcome {
Expand Down Expand Up @@ -438,6 +453,8 @@ pub fn start_clicker(config: ClickerConfig, control: RunControl) -> RunOutcome {

let has_position = config.use_sequence();
let use_smoothing = config.smoothing == 1 && cps < 50.0;
let hold_keyboard_across_cycles = should_hold_keyboard_across_cycles(&config);
let mut held_keyboard_key = None;

let mut sequence_index = 0usize;
let mut cycle_target = current_cycle_target(&config, sequence_index);
Expand Down Expand Up @@ -559,23 +576,38 @@ pub fn start_clicker(config: ClickerConfig, control: RunControl) -> RunOutcome {
ClickCyclePlan::double(hold_ms, cycle_ms, config.double_click_gap_ms);

if is_keyboard {
if cycle_batch.double_cycles > 0 {
send_key_presses(
config.key_code,
cycle_batch.double_cycles,
config.keyboard_uppercase,
double_cycle_plan,
&control,
);
}
if cycle_batch.single_cycles > 0 {
send_key_presses(
config.key_code,
cycle_batch.single_cycles,
config.keyboard_uppercase,
single_cycle_plan,
&control,
);
if hold_keyboard_across_cycles {
let key_already_held = held_keyboard_key.is_some();
let repeat_count = keyboard_hold_repeat_count(cycle_batch, key_already_held);

if !key_already_held {
held_keyboard_key = Some(hold_key(config.key_code, config.keyboard_uppercase));
}

if let Some(held_key) = held_keyboard_key.as_ref() {
for _ in 0..repeat_count {
held_key.repeat();
}
}
} else {
if cycle_batch.double_cycles > 0 {
send_key_presses(
config.key_code,
cycle_batch.double_cycles,
config.keyboard_uppercase,
double_cycle_plan,
&control,
);
}
if cycle_batch.single_cycles > 0 {
send_key_presses(
config.key_code,
cycle_batch.single_cycles,
config.keyboard_uppercase,
single_cycle_plan,
&control,
);
}
}
} else {
if cycle_batch.double_cycles > 0 {
Expand Down Expand Up @@ -626,6 +658,10 @@ pub fn start_clicker(config: ClickerConfig, control: RunControl) -> RunOutcome {
}
}

if let Some(mut held_key) = held_keyboard_key {
held_key.release();
}

unsafe { NtSetTimerResolution(10000, 0, &mut current) };

let elapsed_secs = start_time.elapsed().as_secs_f64();
Expand Down Expand Up @@ -802,21 +838,4 @@ mod tests {
}
);
}

#[test]
fn keyboard_uppercase_is_enabled_only_for_letter_keys() {
let mut settings = sample_settings();
settings.input_type = "keyboard".to_string();
settings.keyboard_key = "a".to_string();
settings.keyboard_key_case = "upper".to_string();

let config = build_config(&settings).expect("letter key should parse");
assert_eq!(config.key_code, b'A' as u16);
assert!(config.keyboard_uppercase);

settings.keyboard_key = "1".to_string();
let config = build_config(&settings).expect("digit key should parse");
assert_eq!(config.key_code, b'1' as u16);
assert!(!config.keyboard_uppercase);
}
}
Loading