use windows::Win32::UI::Input::KeyboardAndMouse::{
GetAsyncKeyState, SendInput, INPUT, INPUT_0, INPUT_KEYBOARD, KEYBDINPUT, KEYEVENTF_KEYUP,
VIRTUAL_KEY, VK_LCONTROL, VK_LMENU, VK_LSHIFT, VK_LWIN, VK_RCONTROL, VK_RMENU, VK_RSHIFT,
VK_RWIN,
};
use crate::sendinput::{tagged_keyboard_input, INJECT_TAG};
use crate::Error;
const MODIFIERS: [VIRTUAL_KEY; 8] = [
VK_LSHIFT,
VK_RSHIFT,
VK_LCONTROL,
VK_RCONTROL,
VK_LMENU,
VK_RMENU,
VK_LWIN,
VK_RWIN,
];
const KEY_DOWN_MASK: u16 = 0x8000;
fn is_down(vk: VIRTUAL_KEY) -> bool {
(unsafe { GetAsyncKeyState(vk.0 as i32) } as u16 & KEY_DOWN_MASK) != 0
}
pub fn sanitize() -> Result<Vec<VIRTUAL_KEY>, Error> {
let held: Vec<VIRTUAL_KEY> = MODIFIERS.into_iter().filter(|vk| is_down(*vk)).collect();
if held.is_empty() {
return Ok(held);
}
let inputs: Vec<INPUT> = held
.iter()
.map(|vk| INPUT {
r#type: INPUT_KEYBOARD,
Anonymous: INPUT_0 {
ki: KEYBDINPUT {
wVk: *vk,
wScan: 0,
dwFlags: KEYEVENTF_KEYUP,
time: 0,
dwExtraInfo: INJECT_TAG,
},
},
})
.collect();
let sent = unsafe { SendInput(&inputs, std::mem::size_of::<INPUT>() as i32) };
if sent as usize != inputs.len() {
return Err(Error::SendInputBlocked);
}
Ok(held)
}
pub fn any_held() -> bool {
MODIFIERS.into_iter().any(is_down)
}
pub(crate) fn key_up(vk: VIRTUAL_KEY) -> INPUT {
tagged_keyboard_input(vk, KEYEVENTF_KEYUP)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn modifier_set_covers_both_sides_of_every_modifier() {
assert_eq!(MODIFIERS.len(), 8);
for pair in [
(VK_LSHIFT, VK_RSHIFT),
(VK_LCONTROL, VK_RCONTROL),
(VK_LMENU, VK_RMENU),
(VK_LWIN, VK_RWIN),
] {
assert!(MODIFIERS.contains(&pair.0));
assert!(MODIFIERS.contains(&pair.1));
}
}
#[test]
fn any_held_agrees_with_per_key_state() {
let individually = MODIFIERS.into_iter().any(is_down);
assert_eq!(any_held(), individually);
}
}