use std::{thread, time::Duration};
use std::fmt::{self, Display, Formatter};
use crate::{CommandCode, Key, MouseButton, GenericError, traits::*};
#[derive(Debug, Eq, PartialEq)]
pub enum Command {
Delay(u32),
KeyDown(Key),
KeyUp(Key),
KeyClick(Key),
MouseMoveRel(i32, i32),
MouseMoveAbs(i32, i32),
MouseScroll(i32, i32),
MouseDown(MouseButton),
MouseUp(MouseButton),
MouseClick(MouseButton),
AsciiCharDown(u8),
AsciiCharUp(u8),
AsciiChar(u8),
AsciiString(Vec<u8>),
UnicodeCharDown(char),
UnicodeCharUp(char),
UnicodeChar(char),
UnicodeString(String),
}
#[derive(Debug)]
pub enum CommandBytesError {
InvalidCommandCode(u8),
InvalidKey(u8),
InvalidMouseButton(u8),
InvalidUnicodeScalar(u32),
InvalidUTF8,
BufferTooShort(usize),
}
use CommandBytesError::*;
impl Display for CommandBytesError {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self {
InvalidCommandCode(byte) => write!(f, "Invalid command code byte ({})", byte),
InvalidKey(byte) => write!(f, "Invalid key byte ({})", byte),
InvalidMouseButton(byte) => write!(f, "Invalid mouse button byte ({})", byte),
InvalidUnicodeScalar(ucs) => write!(f, "Invalid Unicode scalar ({:#010X})", ucs),
InvalidUTF8 => write!(f, "Invalid UTF-8 string"),
BufferTooShort(len) => write!(f, "Expected buffer to be at least {} bytes in length", len),
}
}
}
impl std::error::Error for CommandBytesError {}
fn parse_int(b_0: u8, b_1: u8) -> i32 {
(((b_0 as i16) << 8) | (b_1 as i16)) as i32
}
fn parse_uint(b_0: u8, b_1: u8) -> u32 {
(((b_0 as u16) << 8) | (b_1 as u16)) as u32
}
fn parse_char(b_0: u8, b_1: u8, b_2: u8, b_3: u8) -> Result<char, CommandBytesError> {
let ch = ((b_0 as u32) << 24) | ((b_1 as u32) << 16) | ((b_2 as u32) << 8) | (b_3 as u32);
match std::char::from_u32(ch) {
Some(c) => Ok(c),
None => Err(InvalidUnicodeScalar(ch)),
}
}
fn parse_string(buf: &[u8]) -> Result<String, CommandBytesError> {
match String::from_utf8(buf.to_owned()) {
Ok(s) => Ok(s),
Err(_) => Err(InvalidUTF8),
}
}
fn parse_command_code(byte: u8) -> Result<CommandCode, CommandBytesError> {
if byte < CommandCode::COUNT {
unsafe { Ok(std::mem::transmute(byte)) }
} else {
Err(InvalidCommandCode(byte))
}
}
fn parse_key(byte: u8) -> Result<Key, CommandBytesError> {
if byte < Key::COUNT {
unsafe { Ok(std::mem::transmute(byte)) }
} else {
Err(InvalidKey(byte))
}
}
fn parse_mouse_button(byte: u8) -> Result<MouseButton, CommandBytesError> {
if byte < MouseButton::COUNT {
unsafe { Ok(std::mem::transmute(byte)) }
} else {
Err(InvalidMouseButton(byte))
}
}
fn check_buffer_length(buf: &[u8], len: usize) -> Result<(), CommandBytesError> {
if buf.len() >= len {
Ok(())
} else {
Err(BufferTooShort(len))
}
}
impl Command {
pub fn from_bytes(buf: &[u8]) -> Result<(Command, usize), CommandBytesError> {
if buf.is_empty() {
return Err(BufferTooShort(1));
}
match parse_command_code(buf[0])? {
CommandCode::Delay => {
check_buffer_length(buf, 3)?;
Ok((Command::Delay(parse_uint(buf[1], buf[2])), 3))
}
CommandCode::KeyDown => {
check_buffer_length(buf, 2)?;
Ok((Command::KeyDown(parse_key(buf[1])?), 2))
}
CommandCode::KeyUp => {
check_buffer_length(buf, 2)?;
Ok((Command::KeyUp(parse_key(buf[1])?), 2))
}
CommandCode::KeyClick => {
check_buffer_length(buf, 2)?;
Ok((Command::KeyClick(parse_key(buf[1])?), 2))
}
CommandCode::MouseMoveRel => {
check_buffer_length(buf, 5)?;
Ok((Command::MouseMoveRel(parse_int(buf[1], buf[2]), parse_int(buf[3], buf[4])), 5))
}
CommandCode::MouseMoveAbs => {
check_buffer_length(buf, 5)?;
Ok((Command::MouseMoveAbs(parse_int(buf[1], buf[2]), parse_int(buf[3], buf[4])), 5))
}
CommandCode::MouseScroll => {
check_buffer_length(buf, 5)?;
Ok((Command::MouseScroll(parse_int(buf[1], buf[2]), parse_int(buf[3], buf[4])), 5))
}
CommandCode::MouseDown => {
check_buffer_length(buf, 2)?;
Ok((Command::MouseDown(parse_mouse_button(buf[1])?), 2))
}
CommandCode::MouseUp => {
check_buffer_length(buf, 2)?;
Ok((Command::MouseUp(parse_mouse_button(buf[1])?), 2))
}
CommandCode::MouseClick => {
check_buffer_length(buf, 2)?;
Ok((Command::MouseClick(parse_mouse_button(buf[1])?), 2))
}
CommandCode::AsciiCharDown => {
check_buffer_length(buf, 2)?;
Ok((Command::AsciiCharDown(buf[1]), 2))
}
CommandCode::AsciiCharUp => {
check_buffer_length(buf, 2)?;
Ok((Command::AsciiCharUp(buf[1]), 2))
}
CommandCode::AsciiChar => {
check_buffer_length(buf, 2)?;
Ok((Command::AsciiChar(buf[1]), 2))
}
CommandCode::AsciiString => {
check_buffer_length(buf, 3)?;
let len = 3 + parse_uint(buf[1], buf[2]) as usize;
check_buffer_length(buf, len)?;
Ok((Command::AsciiString(buf[3..len].to_owned()), len))
}
CommandCode::UnicodeCharDown => {
check_buffer_length(buf, 5)?;
Ok((Command::UnicodeCharDown(parse_char(buf[1], buf[2], buf[3], buf[4])?), 5))
}
CommandCode::UnicodeCharUp => {
check_buffer_length(buf, 5)?;
Ok((Command::UnicodeCharUp(parse_char(buf[1], buf[2], buf[3], buf[4])?), 5))
}
CommandCode::UnicodeChar => {
check_buffer_length(buf, 5)?;
Ok((Command::UnicodeChar(parse_char(buf[1], buf[2], buf[3], buf[4])?), 5))
}
CommandCode::UnicodeString => {
check_buffer_length(buf, 3)?;
let len = 3 + parse_uint(buf[1], buf[2]) as usize;
check_buffer_length(buf, len)?;
Ok((Command::UnicodeString(parse_string(&buf[3..len])?), len))
}
}
}
#[cfg(not(all(
not(feature = "ascii-fallback"),
target_os = "linux",
not(x11)
)))]
pub fn execute<C>(&self, ctx: &mut C) -> Result<(), GenericError<C::PlatformError>>
where C: FallibleContext + KeyboardContext + MouseContext + AsciiKeyboardContext + UnicodeKeyboardContext
{
use Command::*;
match self {
Delay(millis) => Ok(thread::sleep(Duration::from_millis(*millis as u64))),
KeyDown(key) => ctx.key_down(*key),
KeyUp(key) => ctx.key_up(*key),
KeyClick(key) => ctx.key_click(*key),
MouseMoveRel(dx, dy) => ctx.mouse_move_rel(*dx, *dy),
MouseMoveAbs(x, y) => ctx.mouse_move_abs(*x, *y),
MouseScroll(dx, dy) => ctx.mouse_scroll(*dx, *dy),
MouseDown(button) => ctx.mouse_down(*button),
MouseUp(button) => ctx.mouse_up(*button),
MouseClick(button) => ctx.mouse_click(*button),
AsciiCharDown(ch) => ctx.ascii_char_down(*ch),
AsciiCharUp(ch) => ctx.ascii_char_up(*ch),
AsciiChar(ch) => ctx.ascii_char(*ch),
AsciiString(s) => ctx.ascii_string(s.as_slice()),
UnicodeCharDown(ch) => ctx.unicode_char_down(*ch),
UnicodeCharUp(ch) => ctx.unicode_char_up(*ch),
UnicodeChar(ch) => ctx.unicode_char(*ch),
UnicodeString(s) => ctx.unicode_string(s.as_str()),
}
}
#[cfg(all(
not(feature = "ascii-fallback"),
target_os = "linux",
not(x11)
))]
pub fn execute<C>(&self, ctx: &mut C) -> Result<(), GenericError<C::PlatformError>>
where C: FallibleContext + KeyboardContext + MouseContext + AsciiKeyboardContext
{
use Command::*;
match self {
Delay(millis) => Ok(thread::sleep(Duration::from_millis(*millis as u64))),
KeyDown(key) => ctx.key_down(*key),
KeyUp(key) => ctx.key_up(*key),
KeyClick(key) => ctx.key_click(*key),
MouseMoveRel(dx, dy) => ctx.mouse_move_rel(*dx, *dy),
MouseMoveAbs(x, y) => ctx.mouse_move_abs(*x, *y),
MouseScroll(dx, dy) => ctx.mouse_scroll(*dx, *dy),
MouseDown(button) => ctx.mouse_down(*button),
MouseUp(button) => ctx.mouse_up(*button),
MouseClick(button) => ctx.mouse_click(*button),
AsciiCharDown(ch) => ctx.ascii_char_down(*ch),
AsciiCharUp(ch) => ctx.ascii_char_up(*ch),
AsciiChar(ch) => ctx.ascii_char(*ch),
AsciiString(s) => ctx.ascii_string(s.as_slice()),
UnicodeCharDown(_) => panic!("UnicodeKeyboardContext is not implemented"),
UnicodeCharUp(_) => panic!("UnicodeKeyboardContext is not implemented"),
UnicodeChar(_) => panic!("UnicodeKeyboardContext is not implemented"),
UnicodeString(_) => panic!("UnicodeKeyboardContext is not implemented"),
}
}
}