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};
pub struct KeyDownHandler;
impl InstructionHandler for KeyDownHandler {
fn name(&self) -> &str {
"key_down"
}
fn parse(&self, args: &[&str]) -> Result<InstructionData, ScriptError> {
if args.is_empty() {
return Err(ScriptError::ParseError(
"Missing key name. Usage: key_down <name> [mode:send|post]".into(),
));
}
let mut params = parse_key_args(args)?;
params.delay_ms = 0;
if params.mode == KeyMode::Send {
params.send_inputs = vec![send_input::build_key_down_input(
params.vk_code,
params.extended,
)];
}
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.send_inputs.len() >= 1 {
send_input::execute_single_input(¶ms.send_inputs[0]).map_err(|e| {
ScriptError::ExecutionError(format!("SendInput press failed: {:?}", e))
})?;
} else {
return Err(ScriptError::ExecutionError(
"Invalid pre-built inputs: expected 1 input for key_down".into(),
));
}
}
KeyMode::Post => {
#[cfg(feature = "script_process_context")]
{
post_message::post_key_down_atomic(
vm.process.get_hwnd_or_err()?,
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_down_with_mode() {
let handler = KeyDownHandler;
let result = handler.parse(&["SHIFT", "post"]).unwrap();
match result {
InstructionData::Custom(boxed) => {
let params = boxed.downcast_ref::<KeyParams>().unwrap();
assert_eq!(params.vk_code, 0x10); assert!(params.scan_code > 0); assert_eq!(params.mode, KeyMode::Post);
assert_eq!(params.delay_ms, 0); }
_ => panic!("Expected Custom data"),
}
}
#[test]
fn test_parse_key_down_missing_name() {
let handler = KeyDownHandler;
assert!(handler.parse(&[]).is_err());
}
#[test]
fn test_prebuilt_down_input() {
let handler = KeyDownHandler;
let result = handler.parse(&["SHIFT"]).unwrap();
match result {
InstructionData::Custom(boxed) => {
let params = boxed.downcast_ref::<KeyParams>().unwrap();
assert_eq!(
params.send_inputs.len(),
1,
"Should pre-build KEYDOWN input"
);
assert_eq!(params.delay_ms, 0);
}
_ => panic!("Expected Custom data"),
}
let result = handler.parse(&["SHIFT", "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"
);
}
_ => panic!("Expected Custom data"),
}
}
}