TL; DR:
I need to retrieve the INPUT_RECORDs* from the console input buffer in real time without preventing a parallel/the main thread from reading them and printing the characters to the console screen buffer.
All I manage to achieve is to either retrieve the INPUT_RECORDs using PeekConsoleInput (or ReadConsoleInput) OR have the main thread handle reading/printing the typed characters to the screen.
How can I reliably peek at the console input buffer and retrieve the INPUT_RECORDs while letting the main thread handle printing the typed characters to the screen?
* preferably only KEY_EVENT_RECORDs
I can use the following two lines to have my console application print to the screen whatever the user types on the keyboard (including the deletion of characters in case of backspace
or delete
as well as inserting characters by use of the arrow keys):
let mut line = String::new();
std::io::stdin().read_line(&mut line).unwrap();
I would like to keep that behavior while retrieving the INPUT_RECORDs from the console input buffer.**
I tried using PeekConsoleInput in a background thread and keep the above code in the main thread but the main thread seems to be faster in taking the INPUT_RECORDs out of the input buffer leaving no records for the background thread to peek at.
The attached code shows how running PeekConsoleInput in a background thread does successfully peek the INPUT_RECORDs as long as no other thread removes them (flush = true
vs flush = false
).
How can I reliably peek at the console input buffer and retrieve the INPUT_RECORDs while letting the main thread handle printing the typed characters to the screen?
** I can then send those INPUT_RECORDs to child processes and using WriteConsoleInput replicate the users input on multiple consoles simultaneously.
Cargo.toml
[dependencies]
tokio = {version = "1.27.0", features = ["macros", "rt-multi-thread", "time"]}
[dependencies.windows]
version = "0.44.0"
features = [
"Win32_Foundation",
"Win32_System_Console",
]
main.rs
use std::time::Duration;
use windows::Win32::System::Console::{
FlushConsoleInputBuffer, GetStdHandle, PeekConsoleInputW, INPUT_RECORD, STD_INPUT_HANDLE,
};
async fn peek_console_input(nb_of_records_to_read: u32, flush: bool) {
const NB_EVENTS: usize = 2;
let mut nb_of_records_read = 0;
let console_handle = unsafe { GetStdHandle(STD_INPUT_HANDLE).unwrap() };
loop {
let mut input_buffer: [INPUT_RECORD; NB_EVENTS] = [INPUT_RECORD::default(); NB_EVENTS];
let mut number_of_events_read = 0;
unsafe {
PeekConsoleInputW(
console_handle,
&mut input_buffer,
&mut number_of_events_read,
)
.expect("Failed to read console input");
}
if number_of_events_read > 0 as u32 {
if flush {
unsafe {
FlushConsoleInputBuffer(console_handle);
};
}
nb_of_records_read += number_of_events_read;
for record in input_buffer {
let event = unsafe { record.Event.KeyEvent };
println!(
"key_down: {}, unicode_char: {}",
event.bKeyDown.as_bool(),
unsafe { event.uChar.UnicodeChar }
);
}
}
if nb_of_records_read >= nb_of_records_to_read {
break;
}
}
}
#[tokio::main]
async fn main() {
let nb_of_records_to_read = 8;
for flush in [true, false] {
println!(
"Spawning a thread peeking at the console input buffer \
for the next {nb_of_records_to_read} input records \
(key down and up count as 1 record each)"
);
let join_handle = tokio::spawn(peek_console_input(nb_of_records_to_read, flush));
if flush {
println!(
"Putting main thread to sleep for 2 milliseconds at a time \
while waiting for the background thread to finish."
);
println!("Please press any combination of keys and behold the output");
while !join_handle.is_finished() {
tokio::time::sleep(Duration::from_millis(2)).await;
}
} else {
println!("Main thread is reading a line from stdin, please start pressing keys");
let mut line = String::new();
std::io::stdin().read_line(&mut line).unwrap();
println!("read line: {}", line);
}
join_handle.abort();
join_handle.await.unwrap();
}
}