win_text_inject/
sendinput.rs1use windows::Win32::UI::Input::KeyboardAndMouse::{
4 SendInput, INPUT, INPUT_0, INPUT_KEYBOARD, KEYBDINPUT, KEYBD_EVENT_FLAGS, KEYEVENTF_KEYUP,
5 KEYEVENTF_UNICODE, VIRTUAL_KEY,
6};
7
8use crate::Error;
9
10pub const INJECT_TAG: usize = 0x57_54_49_4A; pub(crate) fn tagged_keyboard_input(vk: VIRTUAL_KEY, flags: KEYBD_EVENT_FLAGS) -> INPUT {
18 INPUT {
19 r#type: INPUT_KEYBOARD,
20 Anonymous: INPUT_0 {
21 ki: KEYBDINPUT {
22 wVk: vk,
23 wScan: 0,
24 dwFlags: flags,
25 time: 0,
26 dwExtraInfo: INJECT_TAG,
27 },
28 },
29 }
30}
31
32fn unicode_unit(unit: u16, key_up: bool) -> INPUT {
33 let flags = if key_up {
34 KEYEVENTF_UNICODE | KEYEVENTF_KEYUP
35 } else {
36 KEYEVENTF_UNICODE
37 };
38 INPUT {
39 r#type: INPUT_KEYBOARD,
40 Anonymous: INPUT_0 {
41 ki: KEYBDINPUT {
42 wVk: VIRTUAL_KEY(0),
44 wScan: unit,
45 dwFlags: flags,
46 time: 0,
47 dwExtraInfo: INJECT_TAG,
48 },
49 },
50 }
51}
52
53pub(crate) fn unicode_inputs(text: &str) -> Vec<INPUT> {
59 let mut inputs = Vec::with_capacity(text.len() * 2);
60 for unit in text.encode_utf16() {
61 inputs.push(unicode_unit(unit, false));
62 inputs.push(unicode_unit(unit, true));
63 }
64 inputs
65}
66
67const CHUNK: usize = 256;
69
70pub(crate) fn send(inputs: &[INPUT]) -> Result<(), Error> {
71 if inputs.is_empty() {
72 return Ok(());
73 }
74 for chunk in inputs.chunks(CHUNK) {
75 let sent = unsafe { SendInput(chunk, std::mem::size_of::<INPUT>() as i32) };
76 if sent as usize != chunk.len() {
78 return Err(Error::SendInputBlocked);
79 }
80 }
81 Ok(())
82}
83
84pub fn type_text(text: &str) -> Result<(), Error> {
90 send(&unicode_inputs(text))
91}
92
93#[cfg(test)]
94mod tests {
95 use super::*;
96
97 fn scans(text: &str) -> Vec<u16> {
98 unicode_inputs(text)
99 .iter()
100 .map(|i| unsafe { i.Anonymous.ki.wScan })
101 .collect()
102 }
103
104 #[test]
105 fn each_code_unit_produces_a_down_and_an_up() {
106 assert_eq!(unicode_inputs("abc").len(), 6);
107 }
108
109 #[test]
110 fn non_bmp_characters_produce_four_events() {
111 assert_eq!(unicode_inputs("\u{1F600}").len(), 4);
113 assert_eq!(scans("\u{1F600}"), vec![0xD83D, 0xD83D, 0xDE00, 0xDE00]);
114 }
115
116 #[test]
117 fn unicode_events_must_not_carry_a_virtual_key() {
118 for input in unicode_inputs("hi\u{1F600}") {
119 assert_eq!(unsafe { input.Anonymous.ki.wVk }, VIRTUAL_KEY(0));
120 }
121 }
122
123 #[test]
124 fn every_event_is_tagged_for_hook_filtering() {
125 for input in unicode_inputs("hi") {
126 assert_eq!(unsafe { input.Anonymous.ki.dwExtraInfo }, INJECT_TAG);
127 }
128 }
129
130 #[test]
131 fn empty_text_produces_no_events() {
132 assert!(unicode_inputs("").is_empty());
133 assert!(send(&[]).is_ok());
134 }
135
136 #[test]
137 fn alternating_events_are_down_then_up() {
138 let inputs = unicode_inputs("ab");
139 let ups: Vec<bool> = inputs
140 .iter()
141 .map(|i| unsafe { i.Anonymous.ki.dwFlags }.contains(KEYEVENTF_KEYUP))
142 .collect();
143 assert_eq!(ups, vec![false, true, false, true]);
144 }
145}