mirror of
https://github.com/rustdesk/rustdesk.git
synced 2024-12-12 18:29:10 +08:00
185 lines
5.7 KiB
Rust
185 lines
5.7 KiB
Rust
use crate::{Key, KeyboardControllable};
|
|
use std::error::Error;
|
|
use std::fmt;
|
|
|
|
/// An error that can occur when parsing DSL
|
|
#[derive(Debug, PartialEq, Eq)]
|
|
pub enum ParseError {
|
|
/// When a tag doesn't exist.
|
|
/// Example: {+TEST}{-TEST}
|
|
/// ^^^^ ^^^^
|
|
UnknownTag(String),
|
|
|
|
/// When a { is encountered inside a {TAG}.
|
|
/// Example: {+HELLO{WORLD}
|
|
/// ^
|
|
UnexpectedOpen,
|
|
|
|
/// When a { is never matched with a }.
|
|
/// Example: {+SHIFT}Hello{-SHIFT
|
|
/// ^
|
|
UnmatchedOpen,
|
|
|
|
/// Opposite of UnmatchedOpen.
|
|
/// Example: +SHIFT}Hello{-SHIFT}
|
|
/// ^
|
|
UnmatchedClose,
|
|
}
|
|
impl Error for ParseError {
|
|
fn description(&self) -> &str {
|
|
match *self {
|
|
ParseError::UnknownTag(_) => "Unknown tag",
|
|
ParseError::UnexpectedOpen => "Unescaped open bracket ({) found inside tag name",
|
|
ParseError::UnmatchedOpen => "Unmatched open bracket ({). No matching close (})",
|
|
ParseError::UnmatchedClose => "Unmatched close bracket (}). No previous open ({)",
|
|
}
|
|
}
|
|
}
|
|
impl fmt::Display for ParseError {
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
f.write_str(&self.to_string())
|
|
}
|
|
}
|
|
|
|
/// Evaluate the DSL. This tokenizes the input and presses the keys.
|
|
pub fn eval<K>(enigo: &mut K, input: &str) -> Result<(), ParseError>
|
|
where
|
|
K: KeyboardControllable,
|
|
{
|
|
for token in tokenize(input)? {
|
|
match token {
|
|
Token::Sequence(buffer) => {
|
|
for key in buffer.chars() {
|
|
enigo.key_click(Key::Layout(key));
|
|
}
|
|
}
|
|
Token::Unicode(buffer) => enigo.key_sequence(&buffer),
|
|
Token::KeyUp(key) => enigo.key_up(key),
|
|
Token::KeyDown(key) => enigo.key_down(key).unwrap_or(()),
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
#[derive(Debug, PartialEq, Eq)]
|
|
enum Token {
|
|
Sequence(String),
|
|
Unicode(String),
|
|
KeyUp(Key),
|
|
KeyDown(Key),
|
|
}
|
|
|
|
fn tokenize(input: &str) -> Result<Vec<Token>, ParseError> {
|
|
let mut unicode = false;
|
|
|
|
let mut tokens = Vec::new();
|
|
let mut buffer = String::new();
|
|
let mut iter = input.chars().peekable();
|
|
|
|
fn flush(tokens: &mut Vec<Token>, buffer: String, unicode: bool) {
|
|
if !buffer.is_empty() {
|
|
if unicode {
|
|
tokens.push(Token::Unicode(buffer));
|
|
} else {
|
|
tokens.push(Token::Sequence(buffer));
|
|
}
|
|
}
|
|
}
|
|
|
|
while let Some(c) = iter.next() {
|
|
if c == '{' {
|
|
match iter.next() {
|
|
Some('{') => buffer.push('{'),
|
|
Some(mut c) => {
|
|
flush(&mut tokens, buffer, unicode);
|
|
buffer = String::new();
|
|
|
|
let mut tag = String::new();
|
|
loop {
|
|
tag.push(c);
|
|
match iter.next() {
|
|
Some('{') => match iter.peek() {
|
|
Some(&'{') => {
|
|
iter.next();
|
|
c = '{'
|
|
}
|
|
_ => return Err(ParseError::UnexpectedOpen),
|
|
},
|
|
Some('}') => match iter.peek() {
|
|
Some(&'}') => {
|
|
iter.next();
|
|
c = '}'
|
|
}
|
|
_ => break,
|
|
},
|
|
Some(new) => c = new,
|
|
None => return Err(ParseError::UnmatchedOpen),
|
|
}
|
|
}
|
|
match &*tag {
|
|
"+UNICODE" => unicode = true,
|
|
"-UNICODE" => unicode = false,
|
|
"+SHIFT" => tokens.push(Token::KeyDown(Key::Shift)),
|
|
"-SHIFT" => tokens.push(Token::KeyUp(Key::Shift)),
|
|
"+CTRL" => tokens.push(Token::KeyDown(Key::Control)),
|
|
"-CTRL" => tokens.push(Token::KeyUp(Key::Control)),
|
|
"+META" => tokens.push(Token::KeyDown(Key::Meta)),
|
|
"-META" => tokens.push(Token::KeyUp(Key::Meta)),
|
|
"+ALT" => tokens.push(Token::KeyDown(Key::Alt)),
|
|
"-ALT" => tokens.push(Token::KeyUp(Key::Alt)),
|
|
_ => return Err(ParseError::UnknownTag(tag)),
|
|
}
|
|
}
|
|
None => return Err(ParseError::UnmatchedOpen),
|
|
}
|
|
} else if c == '}' {
|
|
match iter.next() {
|
|
Some('}') => buffer.push('}'),
|
|
_ => return Err(ParseError::UnmatchedClose),
|
|
}
|
|
} else {
|
|
buffer.push(c);
|
|
}
|
|
}
|
|
|
|
flush(&mut tokens, buffer, unicode);
|
|
|
|
Ok(tokens)
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn success() {
|
|
assert_eq!(
|
|
tokenize("{{Hello World!}} {+CTRL}hi{-CTRL}"),
|
|
Ok(vec![
|
|
Token::Sequence("{Hello World!} ".into()),
|
|
Token::KeyDown(Key::Control),
|
|
Token::Sequence("hi".into()),
|
|
Token::KeyUp(Key::Control)
|
|
])
|
|
);
|
|
}
|
|
#[test]
|
|
fn unexpected_open() {
|
|
assert_eq!(tokenize("{hello{}world}"), Err(ParseError::UnexpectedOpen));
|
|
}
|
|
#[test]
|
|
fn unmatched_open() {
|
|
assert_eq!(
|
|
tokenize("{this is going to fail"),
|
|
Err(ParseError::UnmatchedOpen)
|
|
);
|
|
}
|
|
#[test]
|
|
fn unmatched_close() {
|
|
assert_eq!(
|
|
tokenize("{+CTRL}{{this}} is going to fail}"),
|
|
Err(ParseError::UnmatchedClose)
|
|
);
|
|
}
|
|
}
|