use self::winapi::ctypes::c_int; use self::winapi::shared::{basetsd::ULONG_PTR, minwindef::*, windef::*}; use self::winapi::um::winbase::*; use self::winapi::um::winuser::*; use winapi; use crate::win::keycodes::*; use crate::{Key, KeyboardControllable, MouseButton, MouseControllable}; use std::mem::*; extern "system" { pub fn GetLastError() -> DWORD; } /// The main struct for handling the event emitting #[derive(Default)] pub struct Enigo; static mut LAYOUT: HKL = std::ptr::null_mut(); /// The dwExtraInfo value in keyboard and mouse structure that used in SendInput() pub const ENIGO_INPUT_EXTRA_VALUE: ULONG_PTR = 100; fn mouse_event(flags: u32, data: u32, dx: i32, dy: i32) -> DWORD { let mut input: INPUT = unsafe { std::mem::MaybeUninit::zeroed().assume_init() }; input.type_ = INPUT_MOUSE; unsafe { let dst_ptr = (&mut input.u as *mut _) as *mut u8; let m = MOUSEINPUT { dx, dy, mouseData: data, dwFlags: flags, time: 0, dwExtraInfo: ENIGO_INPUT_EXTRA_VALUE, }; let src_ptr = (&m as *const _) as *const u8; std::ptr::copy_nonoverlapping(src_ptr, dst_ptr, size_of::()); } unsafe { SendInput(1, &mut input as LPINPUT, size_of::() as c_int) } } fn keybd_event(flags: u32, vk: u16, scan: u16) -> DWORD { let mut scan = scan; unsafe { // https://github.com/rustdesk/rustdesk/issues/366 if scan == 0 { if LAYOUT.is_null() { let current_window_thread_id = GetWindowThreadProcessId(GetForegroundWindow(), std::ptr::null_mut()); LAYOUT = GetKeyboardLayout(current_window_thread_id); } scan = MapVirtualKeyExW(vk as _, 0, LAYOUT) as _; } } let mut input: INPUT = unsafe { std::mem::MaybeUninit::zeroed().assume_init() }; input.type_ = INPUT_KEYBOARD; unsafe { let dst_ptr = (&mut input.u as *mut _) as *mut u8; let k = KEYBDINPUT { wVk: vk, wScan: scan, dwFlags: flags, time: 0, dwExtraInfo: ENIGO_INPUT_EXTRA_VALUE, }; let src_ptr = (&k as *const _) as *const u8; std::ptr::copy_nonoverlapping(src_ptr, dst_ptr, size_of::()); } unsafe { SendInput(1, &mut input as LPINPUT, size_of::() as c_int) } } fn get_error() -> String { unsafe { let buff_size = 256; let mut buff: Vec = Vec::with_capacity(buff_size); buff.resize(buff_size, 0); let errno = GetLastError(); let chars_copied = FormatMessageW( FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ARGUMENT_ARRAY, std::ptr::null(), errno, 0, buff.as_mut_ptr(), (buff_size + 1) as u32, std::ptr::null_mut(), ); if chars_copied == 0 { return "".to_owned(); } let mut curr_char: usize = chars_copied as usize; while curr_char > 0 { let ch = buff[curr_char]; if ch >= ' ' as u16 { break; } curr_char -= 1; } let sl = std::slice::from_raw_parts(buff.as_ptr(), curr_char); let err_msg = String::from_utf16(sl); return err_msg.unwrap_or("".to_owned()); } } impl MouseControllable for Enigo { fn as_any(&self) -> &dyn std::any::Any { self } fn as_mut_any(&mut self) -> &mut dyn std::any::Any { self } fn mouse_move_to(&mut self, x: i32, y: i32) { mouse_event( MOUSEEVENTF_MOVE | MOUSEEVENTF_ABSOLUTE | MOUSEEVENTF_VIRTUALDESK, 0, (x - unsafe { GetSystemMetrics(SM_XVIRTUALSCREEN) }) * 65535 / unsafe { GetSystemMetrics(SM_CXVIRTUALSCREEN) }, (y - unsafe { GetSystemMetrics(SM_YVIRTUALSCREEN) }) * 65535 / unsafe { GetSystemMetrics(SM_CYVIRTUALSCREEN) }, ); } fn mouse_move_relative(&mut self, x: i32, y: i32) { mouse_event(MOUSEEVENTF_MOVE, 0, x, y); } fn mouse_down(&mut self, button: MouseButton) -> crate::ResultType { let res = mouse_event( match button { MouseButton::Left => MOUSEEVENTF_LEFTDOWN, MouseButton::Middle => MOUSEEVENTF_MIDDLEDOWN, MouseButton::Right => MOUSEEVENTF_RIGHTDOWN, _ => unimplemented!(), }, 0, 0, 0, ); if res == 0 { let err = get_error(); if !err.is_empty() { return Err(err.into()); } } Ok(()) } fn mouse_up(&mut self, button: MouseButton) { mouse_event( match button { MouseButton::Left => MOUSEEVENTF_LEFTUP, MouseButton::Middle => MOUSEEVENTF_MIDDLEUP, MouseButton::Right => MOUSEEVENTF_RIGHTUP, _ => unimplemented!(), }, 0, 0, 0, ); } fn mouse_click(&mut self, button: MouseButton) { self.mouse_down(button).ok(); self.mouse_up(button); } fn mouse_scroll_x(&mut self, length: i32) { mouse_event(MOUSEEVENTF_HWHEEL, unsafe { transmute(length * 120) }, 0, 0); } fn mouse_scroll_y(&mut self, length: i32) { mouse_event(MOUSEEVENTF_WHEEL, unsafe { transmute(length * 120) }, 0, 0); } } impl KeyboardControllable for Enigo { fn as_any(&self) -> &dyn std::any::Any { self } fn as_mut_any(&mut self) -> &mut dyn std::any::Any { self } fn key_sequence(&mut self, sequence: &str) { let mut buffer = [0; 2]; for c in sequence.chars() { // Windows uses uft-16 encoding. We need to check // for variable length characters. As such some // characters can be 32 bit long and those are // encoded in such called hight and low surrogates // each 16 bit wide that needs to be send after // another to the SendInput function without // being interrupted by "keyup" let result = c.encode_utf16(&mut buffer); if result.len() == 1 { self.unicode_key_click(result[0]); } else { for utf16_surrogate in result { self.unicode_key_down(utf16_surrogate.clone()); } // do i need to produce a keyup? // self.unicode_key_up(0); } } } fn key_click(&mut self, key: Key) { let vk = self.key_to_keycode(key); keybd_event(0, vk, 0); keybd_event(KEYEVENTF_KEYUP, vk, 0); } fn key_down(&mut self, key: Key) -> crate::ResultType { let code = self.key_to_keycode(key); if code == 0 || code == 65535 { return Err("".into()); } let res = keybd_event(0, code, 0); if res == 0 { let err = get_error(); if !err.is_empty() { return Err(err.into()); } } Ok(()) } fn key_up(&mut self, key: Key) { keybd_event(KEYEVENTF_KEYUP, self.key_to_keycode(key), 0); } fn get_key_state(&mut self, key: Key) -> bool { let keycode = self.key_to_keycode(key); let x = unsafe { GetKeyState(keycode as _) }; if key == Key::CapsLock || key == Key::NumLock || key == Key::Scroll { return (x & 0x1) == 0x1; } return (x as u16 & 0x8000) == 0x8000; } } impl Enigo { /// Gets the (width, height) of the main display in screen coordinates /// (pixels). /// /// # Example /// /// ```no_run /// use enigo::*; /// let mut size = Enigo::main_display_size(); /// ``` pub fn main_display_size() -> (usize, usize) { let w = unsafe { GetSystemMetrics(SM_CXSCREEN) as usize }; let h = unsafe { GetSystemMetrics(SM_CYSCREEN) as usize }; (w, h) } /// Gets the location of mouse in screen coordinates (pixels). /// /// # Example /// /// ```no_run /// use enigo::*; /// let mut location = Enigo::mouse_location(); /// ``` pub fn mouse_location() -> (i32, i32) { let mut point = POINT { x: 0, y: 0 }; let result = unsafe { GetCursorPos(&mut point) }; if result != 0 { (point.x, point.y) } else { (0, 0) } } fn unicode_key_click(&self, unicode_char: u16) { self.unicode_key_down(unicode_char); self.unicode_key_up(unicode_char); } fn unicode_key_down(&self, unicode_char: u16) { keybd_event(KEYEVENTF_UNICODE, 0, unicode_char); } fn unicode_key_up(&self, unicode_char: u16) { keybd_event(KEYEVENTF_UNICODE | KEYEVENTF_KEYUP, 0, unicode_char); } fn key_to_keycode(&self, key: Key) -> u16 { unsafe { LAYOUT = std::ptr::null_mut(); } // do not use the codes from crate winapi they're // wrongly typed with i32 instead of i16 use the // ones provided by win/keycodes.rs that are prefixed // with an 'E' infront of the original name #[allow(deprecated)] // I mean duh, we still need to support deprecated keys until they're removed match key { Key::Alt => EVK_MENU, Key::Backspace => EVK_BACK, Key::CapsLock => EVK_CAPITAL, Key::Control => EVK_LCONTROL, Key::Delete => EVK_DELETE, Key::DownArrow => EVK_DOWN, Key::End => EVK_END, Key::Escape => EVK_ESCAPE, Key::F1 => EVK_F1, Key::F10 => EVK_F10, Key::F11 => EVK_F11, Key::F12 => EVK_F12, Key::F2 => EVK_F2, Key::F3 => EVK_F3, Key::F4 => EVK_F4, Key::F5 => EVK_F5, Key::F6 => EVK_F6, Key::F7 => EVK_F7, Key::F8 => EVK_F8, Key::F9 => EVK_F9, Key::Home => EVK_HOME, Key::LeftArrow => EVK_LEFT, Key::Option => EVK_MENU, Key::PageDown => EVK_NEXT, Key::PageUp => EVK_PRIOR, Key::Return => EVK_RETURN, Key::RightArrow => EVK_RIGHT, Key::Shift => EVK_SHIFT, Key::Space => EVK_SPACE, Key::Tab => EVK_TAB, Key::UpArrow => EVK_UP, Key::Numpad0 => EVK_NUMPAD0, Key::Numpad1 => EVK_NUMPAD1, Key::Numpad2 => EVK_NUMPAD2, Key::Numpad3 => EVK_NUMPAD3, Key::Numpad4 => EVK_NUMPAD4, Key::Numpad5 => EVK_NUMPAD5, Key::Numpad6 => EVK_NUMPAD6, Key::Numpad7 => EVK_NUMPAD7, Key::Numpad8 => EVK_NUMPAD8, Key::Numpad9 => EVK_NUMPAD9, Key::Cancel => EVK_CANCEL, Key::Clear => EVK_CLEAR, Key::Pause => EVK_PAUSE, Key::Kana => EVK_KANA, Key::Hangul => EVK_HANGUL, Key::Junja => EVK_JUNJA, Key::Final => EVK_FINAL, Key::Hanja => EVK_HANJA, Key::Kanji => EVK_KANJI, Key::Convert => EVK_CONVERT, Key::Select => EVK_SELECT, Key::Print => EVK_PRINT, Key::Execute => EVK_EXECUTE, Key::Snapshot => EVK_SNAPSHOT, Key::Insert => EVK_INSERT, Key::Help => EVK_HELP, Key::Sleep => EVK_SLEEP, Key::Separator => EVK_SEPARATOR, Key::Mute => EVK_VOLUME_MUTE, Key::VolumeDown => EVK_VOLUME_DOWN, Key::VolumeUp => EVK_VOLUME_UP, Key::Scroll => EVK_SCROLL, Key::NumLock => EVK_NUMLOCK, Key::RWin => EVK_RWIN, Key::Apps => EVK_APPS, Key::Add => EVK_ADD, Key::Multiply => EVK_MULTIPLY, Key::Decimal => EVK_DECIMAL, Key::Subtract => EVK_SUBTRACT, Key::Divide => EVK_DIVIDE, Key::NumpadEnter => EVK_RETURN, Key::Equals => '=' as _, Key::RightShift => EVK_RSHIFT, Key::RightControl => EVK_RCONTROL, Key::RightAlt => EVK_RMENU, Key::Raw(raw_keycode) => raw_keycode, Key::Layout(c) => self.get_layoutdependent_keycode(c.to_string()), Key::Super | Key::Command | Key::Windows | Key::Meta => EVK_LWIN, } } fn get_layoutdependent_keycode(&self, string: String) -> u16 { // get the first char from the string ignore the rest // ensure its not a multybyte char if let Some(chr) = string.chars().nth(0) { // NOTE VkKeyScanW uses the current keyboard LAYOUT // to specify a LAYOUT use VkKeyScanExW and GetKeyboardLayout // or load one with LoadKeyboardLayoutW let current_window_thread_id = unsafe { GetWindowThreadProcessId(GetForegroundWindow(), std::ptr::null_mut()) }; unsafe { LAYOUT = GetKeyboardLayout(current_window_thread_id) }; let keycode_and_shiftstate = unsafe { VkKeyScanExW(chr as _, LAYOUT) }; if keycode_and_shiftstate == (EVK_DECIMAL as i16) && chr == '.' { // a workaround of italian keyboard shift + '.' issue EVK_PERIOD as _ } else { keycode_and_shiftstate as _ } } else { 0 } } }