use super::KeyParams;
use crate::keyboard::send_input;
#[cfg(feature = "script_process_context")]
use crate::keyboard::post_message;
use crate::script_engine::instruction::{
InstructionData, InstructionHandler, InstructionMetadata, ScriptError,
};
use crate::script_engine::VMContext;
use crate::scripts_builtin::keyboard::{parse_key_args, KeyMode};
use crate::utils::sleep_ms;
pub struct KeyClickHandler;
impl InstructionHandler for KeyClickHandler {
fn name(&self) -> &str {
"key"
}
fn parse(&self, args: &[&str]) -> Result<InstructionData, ScriptError> {
let mut params = parse_key_args(args)?;
if params.mode == KeyMode::Send {
params.send_inputs =
send_input::build_key_click_inputs(params.vk_code, params.extended).to_vec();
}
Ok(InstructionData::Custom(Box::new(params)))
}
fn execute(
&self,
vm: &mut VMContext,
data: &InstructionData,
_metadata: Option<&InstructionMetadata>,
) -> Result<(), ScriptError> {
let params = data.extract_custom::<KeyParams>("Invalid key parameters")?;
let effective_mode = if params.mode_specified {
params.mode
} else {
match super::get_input_mode(vm).as_str() {
"post" => KeyMode::Post,
_ => KeyMode::Send, }
};
match effective_mode {
KeyMode::Send => {
if params.delay_ms > 0 {
if params.send_inputs.len() >= 2 {
send_input::execute_single_input(¶ms.send_inputs[0]).map_err(|e| {
ScriptError::ExecutionError(format!("SendInput press failed: {:?}", e))
})?;
sleep_ms(params.delay_ms);
send_input::execute_single_input(¶ms.send_inputs[1]).map_err(|e| {
ScriptError::ExecutionError(format!(
"SendInput release failed: {:?}",
e
))
})?;
} else {
return Err(ScriptError::ExecutionError(
"Invalid pre-built inputs: expected 2 inputs for delayed click".into(),
));
}
} else {
send_input::execute_inputs(¶ms.send_inputs).map_err(|e| {
ScriptError::ExecutionError(format!("SendInput click failed: {:?}", e))
})?;
}
}
KeyMode::Post => {
#[cfg(feature = "script_process_context")]
{
let hwnd = vm.process.get_hwnd_or_err()?;
if params.delay_ms > 0 {
post_message::post_key_down_atomic(hwnd, params.vk_code, params.scan_code);
sleep_ms(params.delay_ms);
post_message::post_key_up_atomic(hwnd, params.vk_code, params.scan_code);
} else {
post_message::post_key_click_atomic(hwnd, params.vk_code, params.scan_code);
}
}
#[cfg(not(feature = "script_process_context"))]
{
return Err(ScriptError::ExecutionError(
"PostMessage mode requires 'script_process_context' feature. \
Enable it in Cargo.toml: features = [\"scripts_keyboard_with_post\"] \
or use SendInput mode (default).".into()
));
}
}
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_key_with_delay_and_mode() {
let handler = KeyClickHandler;
let result = handler.parse(&["A", "50", "post"]).unwrap();
match result {
InstructionData::Custom(boxed) => {
let params = boxed.downcast_ref::<KeyParams>().unwrap();
assert_eq!(params.vk_code, 0x41); assert!(params.scan_code > 0); assert_eq!(params.mode, KeyMode::Post);
assert_eq!(params.delay_ms, 50);
}
_ => panic!("Expected Custom data"),
}
}
#[test]
fn test_parse_key_default_values() {
let handler = KeyClickHandler;
let result = handler.parse(&["ENTER"]).unwrap();
match result {
InstructionData::Custom(boxed) => {
let params = boxed.downcast_ref::<KeyParams>().unwrap();
assert_eq!(params.vk_code, 0x0D); assert!(params.scan_code > 0); assert_eq!(params.mode, KeyMode::Send); assert_eq!(params.delay_ms, 0); }
_ => panic!("Expected Custom data"),
}
}
#[test]
fn test_parse_key_only_delay() {
let handler = KeyClickHandler;
let result = handler.parse(&["SPACE", "100"]).unwrap();
match result {
InstructionData::Custom(boxed) => {
let params = boxed.downcast_ref::<KeyParams>().unwrap();
assert_eq!(params.vk_code, 0x20); assert_eq!(params.mode, KeyMode::Send); assert_eq!(params.delay_ms, 100);
}
_ => panic!("Expected Custom data"),
}
}
#[test]
fn test_parse_key_only_mode() {
let handler = KeyClickHandler;
let result = handler.parse(&["TAB", "post"]).unwrap();
match result {
InstructionData::Custom(boxed) => {
let params = boxed.downcast_ref::<KeyParams>().unwrap();
assert_eq!(params.vk_code, 0x09); assert_eq!(params.mode, KeyMode::Post);
assert_eq!(params.delay_ms, 0);
}
_ => panic!("Expected Custom data"),
}
}
#[test]
fn test_parse_key_explicit_send_mode() {
let handler = KeyClickHandler;
let result = handler.parse(&["A", "send"]).unwrap();
match result {
InstructionData::Custom(boxed) => {
let params = boxed.downcast_ref::<KeyParams>().unwrap();
assert_eq!(params.vk_code, 0x41);
assert_eq!(params.mode, KeyMode::Send);
assert_eq!(params.delay_ms, 0);
}
_ => panic!("Expected Custom data"),
}
}
#[test]
fn test_prebuilt_inputs_are_complete() {
let handler = KeyClickHandler;
let result = handler.parse(&["A"]).unwrap();
match result {
InstructionData::Custom(boxed) => {
let params = boxed.downcast_ref::<KeyParams>().unwrap();
assert_eq!(
params.send_inputs.len(),
2,
"Should pre-build both down and up inputs"
);
}
_ => panic!("Expected Custom data"),
}
let result = handler.parse(&["A", "50"]).unwrap();
match result {
InstructionData::Custom(boxed) => {
let params = boxed.downcast_ref::<KeyParams>().unwrap();
assert_eq!(
params.send_inputs.len(),
2,
"Should pre-build both down and up inputs even with delay"
);
assert_eq!(params.delay_ms, 50);
}
_ => panic!("Expected Custom data"),
}
let result = handler.parse(&["A", "post"]).unwrap();
match result {
InstructionData::Custom(boxed) => {
let params = boxed.downcast_ref::<KeyParams>().unwrap();
assert_eq!(
params.send_inputs.len(),
0,
"PostMessage mode should not pre-build INPUT structures"
);
}
_ => panic!("Expected Custom data"),
}
}
}