use std::fmt;
use crate::error::{err_number, VBError, VBResult};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SendKey {
Char(char),
Backspace,
Break,
CapsLock,
Delete,
Down,
End,
Enter,
Esc,
Help,
Home,
Insert,
Left,
NumLock,
PageDown,
PageUp,
PrintScreen,
Right,
ScrollLock,
Tab,
Up,
Function(u8),
}
impl SendKey {
fn from_brace_name(name: &str) -> VBResult<Self> {
let upper = name.to_ascii_uppercase();
let key = match upper.as_str() {
"BACKSPACE" | "BS" | "BKSP" => Self::Backspace,
"BREAK" => Self::Break,
"CAPSLOCK" => Self::CapsLock,
"DELETE" | "DEL" => Self::Delete,
"DOWN" => Self::Down,
"END" => Self::End,
"ENTER" => Self::Enter,
"ESC" | "ESCAPE" => Self::Esc,
"HELP" => Self::Help,
"HOME" => Self::Home,
"INSERT" | "INS" => Self::Insert,
"LEFT" => Self::Left,
"NUMLOCK" => Self::NumLock,
"PGDN" => Self::PageDown,
"PGUP" => Self::PageUp,
"PRTSC" => Self::PrintScreen,
"RIGHT" => Self::Right,
"SCROLLLOCK" => Self::ScrollLock,
"TAB" => Self::Tab,
"UP" => Self::Up,
other if other.len() >= 2 && other.starts_with('F') => {
let number: u8 = other[1..]
.parse()
.map_err(|_| invalid_keys(format!("unknown key name {{{name}}}")))?;
if !(1..=16).contains(&number) {
return Err(invalid_keys(format!(
"function key {{{name}}} is out of range (F1-F16)"
)));
}
Self::Function(number)
}
other if other.chars().count() == 1 => {
Self::Char(other.chars().next().expect("non-empty"))
}
_ => return Err(invalid_keys(format!("unknown key name {{{name}}}"))),
};
Ok(key)
}
pub fn name(self) -> String {
match self {
Self::Char(c) => c.to_string(),
Self::Function(n) => format!("F{n}"),
Self::Backspace => "BACKSPACE".into(),
Self::Break => "BREAK".into(),
Self::CapsLock => "CAPSLOCK".into(),
Self::Delete => "DELETE".into(),
Self::Down => "DOWN".into(),
Self::End => "END".into(),
Self::Enter => "ENTER".into(),
Self::Esc => "ESC".into(),
Self::Help => "HELP".into(),
Self::Home => "HOME".into(),
Self::Insert => "INSERT".into(),
Self::Left => "LEFT".into(),
Self::NumLock => "NUMLOCK".into(),
Self::PageDown => "PGDN".into(),
Self::PageUp => "PGUP".into(),
Self::PrintScreen => "PRTSC".into(),
Self::Right => "RIGHT".into(),
Self::ScrollLock => "SCROLLLOCK".into(),
Self::Tab => "TAB".into(),
Self::Up => "UP".into(),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Keystroke {
pub key: SendKey,
pub shift: bool,
pub ctrl: bool,
pub alt: bool,
}
impl Keystroke {
pub fn new(key: SendKey) -> Self {
Self {
key,
shift: false,
ctrl: false,
alt: false,
}
}
}
#[derive(Debug, Clone, Copy, Default)]
struct Modifiers {
shift: bool,
ctrl: bool,
alt: bool,
}
impl Modifiers {
fn apply(self, key: SendKey) -> Keystroke {
Keystroke {
key,
shift: self.shift,
ctrl: self.ctrl,
alt: self.alt,
}
}
fn enable(mut self, marker: char) -> Self {
match marker {
'+' => self.shift = true,
'^' => self.ctrl = true,
'%' => self.alt = true,
_ => unreachable!("only + ^ % are modifiers"),
}
self
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SendKeysRequest {
pub keys: String,
pub wait: bool,
pub strokes: Vec<Keystroke>,
}
impl SendKeysRequest {
pub fn parse(keys: impl Into<String>, wait: bool) -> VBResult<Self> {
let keys = keys.into();
let strokes = parse_keys(&keys)?;
Ok(Self {
keys,
wait,
strokes,
})
}
}
fn parse_keys(keys: &str) -> VBResult<Vec<Keystroke>> {
let mut strokes = Vec::new();
let mut chars = keys.chars().peekable();
parse_items(&mut strokes, &mut chars, Modifiers::default(), false)?;
Ok(strokes)
}
fn parse_items(
strokes: &mut Vec<Keystroke>,
chars: &mut std::iter::Peekable<std::str::Chars<'_>>,
modifiers: Modifiers,
in_group: bool,
) -> VBResult<()> {
while let Some(&ch) = chars.peek() {
match ch {
')' if in_group => return Ok(()),
'+' | '^' | '%' => {
chars.next();
let inner = modifiers.enable(ch);
match chars.peek() {
Some('(') => {
chars.next();
parse_items(strokes, chars, inner, true)?;
match chars.next() {
Some(')') => {}
_ => {
return Err(invalid_keys(
"modifier group opened with \"(\" was never closed",
))
}
}
}
Some(_) => parse_single_item(strokes, chars, inner)?,
None => strokes.push(modifiers.apply(SendKey::Char(ch))),
}
}
'{' => {
chars.next();
parse_brace_group(strokes, chars, modifiers)?;
}
'~' => {
chars.next();
strokes.push(modifiers.apply(SendKey::Enter));
}
other => {
chars.next();
strokes.push(modifiers.apply(SendKey::Char(other)));
}
}
}
Ok(())
}
fn parse_single_item(
strokes: &mut Vec<Keystroke>,
chars: &mut std::iter::Peekable<std::str::Chars<'_>>,
modifiers: Modifiers,
) -> VBResult<()> {
match chars.peek().copied() {
Some('{') => {
chars.next();
parse_brace_group(strokes, chars, modifiers)
}
Some('~') => {
chars.next();
strokes.push(modifiers.apply(SendKey::Enter));
Ok(())
}
Some(marker @ ('+' | '^' | '%')) => {
chars.next();
let inner = modifiers.enable(marker);
match chars.peek().copied() {
Some('(') => {
chars.next();
parse_items(strokes, chars, inner, true)?;
match chars.next() {
Some(')') => Ok(()),
_ => Err(invalid_keys(
"modifier group opened with \"(\" was never closed",
)),
}
}
Some(_) => parse_single_item(strokes, chars, inner),
None => {
strokes.push(modifiers.apply(SendKey::Char(marker)));
Ok(())
}
}
}
Some(ch) => {
chars.next();
strokes.push(modifiers.apply(SendKey::Char(ch)));
Ok(())
}
None => Ok(()),
}
}
fn parse_brace_group(
strokes: &mut Vec<Keystroke>,
chars: &mut std::iter::Peekable<std::str::Chars<'_>>,
modifiers: Modifiers,
) -> VBResult<()> {
let mut content = String::new();
loop {
match chars.next() {
Some('}') => break,
Some(ch) => content.push(ch),
None => return Err(invalid_keys("braces must balance: missing \"}\"")),
}
}
if content.is_empty() {
if chars.peek() == Some(&'}') {
chars.next();
strokes.push(modifiers.apply(SendKey::Char('}')));
return Ok(());
}
return Err(invalid_keys("empty braces {} name no key"));
}
let (name, repeat) = match content.rsplit_once(char::is_whitespace) {
Some((name, count)) => match count.trim().parse::<u32>() {
Ok(count) => (name, count),
Err(_) => (content.as_str(), 1),
},
None => (content.as_str(), 1),
};
if !(1..=65_535).contains(&repeat) {
return Err(invalid_keys(format!(
"repeat count for {{{name} {repeat}}} is out of range"
)));
}
let key = SendKey::from_brace_name(name)?;
for _ in 0..repeat {
strokes.push(modifiers.apply(key));
}
Ok(())
}
fn invalid_keys(detail: impl fmt::Display) -> VBError {
VBError::with_description(
err_number::INVALID_PROCEDURE_CALL,
format!("Invalid procedure call or argument: invalid SendKeys string: {detail}"),
)
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SendKeysRecord {
pub keys: String,
pub wait: bool,
}
impl SendKeysRecord {
pub fn of(request: &SendKeysRequest) -> Self {
Self {
keys: request.keys.clone(),
wait: request.wait,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn parse(keys: &str) -> Vec<Keystroke> {
SendKeysRequest::parse(keys, false).unwrap().strokes
}
fn plain(key: SendKey) -> Keystroke {
Keystroke::new(key)
}
#[test]
fn plain_characters_are_typed_verbatim() {
assert_eq!(
parse("Hello, World"),
"Hello, World"
.chars()
.map(|c| plain(SendKey::Char(c)))
.collect::<Vec<_>>()
);
}
#[test]
fn tilde_is_enter() {
assert_eq!(parse("~"), vec![plain(SendKey::Enter)]);
assert_eq!(
parse("a~b"),
vec![
plain(SendKey::Char('a')),
plain(SendKey::Enter),
plain(SendKey::Char('b')),
]
);
}
#[test]
fn brace_names_decode_case_insensitively() {
assert_eq!(parse("{enter}"), vec![plain(SendKey::Enter)]);
assert_eq!(parse("{ENTER}"), vec![plain(SendKey::Enter)]);
assert_eq!(parse("{Del}"), vec![plain(SendKey::Delete)]);
assert_eq!(parse("{INS}"), vec![plain(SendKey::Insert)]);
assert_eq!(parse("{bksp}"), vec![plain(SendKey::Backspace)]);
assert_eq!(parse("{escape}"), vec![plain(SendKey::Esc)]);
}
#[test]
fn function_keys_decode() {
assert_eq!(parse("{F1}"), vec![plain(SendKey::Function(1))]);
assert_eq!(parse("{f16}"), vec![plain(SendKey::Function(16))]);
}
#[test]
fn function_key_out_of_range_is_error_5() {
assert_eq!(
SendKeysRequest::parse("{F17}", false).unwrap_err().number,
5
);
assert_eq!(SendKeysRequest::parse("{F0}", false).unwrap_err().number, 5);
}
#[test]
fn modifiers_apply_to_the_next_single_item_only() {
assert_eq!(
parse("^c"),
vec![Keystroke {
key: SendKey::Char('c'),
shift: false,
ctrl: true,
alt: false,
}]
);
assert_eq!(
parse("+abc"),
vec![
Keystroke {
key: SendKey::Char('a'),
shift: true,
ctrl: false,
alt: false
},
plain(SendKey::Char('b')),
plain(SendKey::Char('c')),
]
);
}
#[test]
fn modifier_groups_apply_to_every_member() {
assert_eq!(
parse("^(ec)"),
vec![
Keystroke {
key: SendKey::Char('e'),
shift: false,
ctrl: true,
alt: false
},
Keystroke {
key: SendKey::Char('c'),
shift: false,
ctrl: true,
alt: false
},
]
);
assert_eq!(
parse("%(FA)"),
vec![
Keystroke {
key: SendKey::Char('F'),
shift: false,
ctrl: false,
alt: true
},
Keystroke {
key: SendKey::Char('A'),
shift: false,
ctrl: false,
alt: true
},
]
);
}
#[test]
fn modifiers_combine_and_stack() {
assert_eq!(
parse("%+x"),
vec![Keystroke {
key: SendKey::Char('x'),
shift: true,
ctrl: false,
alt: true,
}]
);
assert_eq!(
parse("^%{DELETE}"),
vec![Keystroke {
key: SendKey::Delete,
shift: false,
ctrl: true,
alt: true,
}]
);
}
#[test]
fn modifiers_apply_to_modified_brace_groups() {
assert_eq!(
parse("+{F1}"),
vec![Keystroke {
key: SendKey::Function(1),
shift: true,
ctrl: false,
alt: false,
}]
);
assert_eq!(
parse("%~"),
vec![Keystroke {
key: SendKey::Enter,
shift: false,
ctrl: false,
alt: true,
}]
);
}
#[test]
fn modifier_groups_may_nest() {
assert_eq!(
parse("^(%(a))"),
vec![Keystroke {
key: SendKey::Char('a'),
shift: false,
ctrl: true,
alt: true,
}]
);
}
#[test]
fn repeats_expand_in_place() {
assert_eq!(parse("{RIGHT 10}"), vec![plain(SendKey::Right); 10]);
assert_eq!(
parse("{TAB 5}a"),
vec![
plain(SendKey::Tab),
plain(SendKey::Tab),
plain(SendKey::Tab),
plain(SendKey::Tab),
plain(SendKey::Tab),
plain(SendKey::Char('a')),
]
);
}
#[test]
fn modified_repeats_apply_to_each_copy() {
assert_eq!(
parse("+{RIGHT 3}"),
vec![
Keystroke {
key: SendKey::Right,
shift: true,
ctrl: false,
alt: false
};
3
]
);
}
#[test]
fn escaped_metacharacters_type_themselves() {
assert_eq!(parse("{+}"), vec![plain(SendKey::Char('+'))]);
assert_eq!(parse("{^}"), vec![plain(SendKey::Char('^'))]);
assert_eq!(parse("{%}"), vec![plain(SendKey::Char('%'))]);
assert_eq!(parse("{~}"), vec![plain(SendKey::Char('~'))]);
assert_eq!(parse("{{}"), vec![plain(SendKey::Char('{'))]);
assert_eq!(parse("{}}"), vec![plain(SendKey::Char('}'))]);
}
#[test]
fn bare_parentheses_are_literal_outside_modifier_groups() {
assert_eq!(
parse("f(x)"),
vec![
plain(SendKey::Char('f')),
plain(SendKey::Char('(')),
plain(SendKey::Char('x')),
plain(SendKey::Char(')')),
]
);
}
#[test]
fn stray_trailing_modifier_types_itself() {
assert_eq!(
parse("a+"),
vec![plain(SendKey::Char('a')), plain(SendKey::Char('+')),]
);
}
#[test]
fn empty_string_sends_nothing() {
assert_eq!(parse(""), Vec::new());
}
#[test]
fn unknown_names_are_error_5() {
for bad in ["{FOO}", "{}", "{ 5 }"] {
let err = SendKeysRequest::parse(bad, false).unwrap_err();
assert_eq!(err.number, err_number::INVALID_PROCEDURE_CALL, "{bad}");
}
}
#[test]
fn unbalanced_braces_are_error_5() {
let err = SendKeysRequest::parse("{ENTER", false).unwrap_err();
assert_eq!(err.number, err_number::INVALID_PROCEDURE_CALL);
assert!(err.description.contains("balance"), "{}", err.description);
}
#[test]
fn unclosed_modifier_group_is_error_5() {
let err = SendKeysRequest::parse("^(ab", false).unwrap_err();
assert_eq!(err.number, err_number::INVALID_PROCEDURE_CALL);
assert!(err.description.contains("\"(\""), "{}", err.description);
}
#[test]
fn zero_repeat_count_is_error_5() {
let err = SendKeysRequest::parse("{TAB 0}", false).unwrap_err();
assert_eq!(err.number, err_number::INVALID_PROCEDURE_CALL);
}
#[test]
fn oversized_repeat_count_is_error_5() {
assert_eq!(
SendKeysRequest::parse("{TAB 65536}", false)
.unwrap_err()
.number,
err_number::INVALID_PROCEDURE_CALL
);
}
#[test]
fn non_numeric_trailing_token_stays_part_of_the_name() {
let err = SendKeysRequest::parse("{NOPE 12x}", false).unwrap_err();
assert!(err.description.contains("NOPE 12x"), "{}", err.description);
}
#[test]
fn wait_flag_is_preserved() {
assert!(!SendKeysRequest::parse("hi", false).unwrap().wait);
assert!(SendKeysRequest::parse("hi", true).unwrap().wait);
}
#[test]
fn original_string_is_preserved_verbatim() {
let request = SendKeysRequest::parse("Username{TAB}Password{ENTER}", true).unwrap();
assert_eq!(request.keys, "Username{TAB}Password{ENTER}");
}
#[test]
fn record_captures_relevant_parts() {
let request = SendKeysRequest::parse("hi{TAB}", true).unwrap();
let record = SendKeysRecord::of(&request);
assert_eq!(record.keys, "hi{TAB}");
assert!(record.wait);
}
#[test]
fn key_names_round_trip_for_diagnostics() {
assert_eq!(SendKey::Enter.name(), "ENTER");
assert_eq!(SendKey::Function(12).name(), "F12");
assert_eq!(SendKey::Char('x').name(), "x");
}
}