From 5e2c5d0afa785b15c654b08409f0500ef9b96365 Mon Sep 17 00:00:00 2001 From: David Sherret Date: Tue, 14 Sep 2021 08:37:27 -0400 Subject: [PATCH] fix: permission prompt stuffing on Windows (#11969) --- runtime/permissions.rs | 98 ++++++++++++++++++++++++++++++++++++++---- 1 file changed, 89 insertions(+), 9 deletions(-) diff --git a/runtime/permissions.rs b/runtime/permissions.rs index d1ee7f9993..c6fcfc58cb 100644 --- a/runtime/permissions.rs +++ b/runtime/permissions.rs @@ -16,8 +16,6 @@ use log::debug; use std::collections::HashSet; use std::fmt; use std::hash::Hash; -#[cfg(not(test))] -use std::io; use std::path::{Path, PathBuf}; #[cfg(test)] use std::sync::atomic::AtomicBool; @@ -1203,13 +1201,95 @@ fn permission_prompt(message: &str) -> bool { #[cfg(not(unix))] fn clear_stdin() { + use winapi::shared::minwindef::TRUE; + use winapi::shared::minwindef::UINT; + use winapi::shared::minwindef::WORD; + use winapi::shared::ntdef::WCHAR; + use winapi::um::processenv::GetStdHandle; + use winapi::um::winbase::STD_INPUT_HANDLE; + use winapi::um::wincon::FlushConsoleInputBuffer; + use winapi::um::wincon::PeekConsoleInputW; + use winapi::um::wincon::WriteConsoleInputW; + use winapi::um::wincontypes::INPUT_RECORD; + use winapi::um::wincontypes::KEY_EVENT; + use winapi::um::winnt::HANDLE; + use winapi::um::winuser::MapVirtualKeyW; + use winapi::um::winuser::MAPVK_VK_TO_VSC; + use winapi::um::winuser::VK_RETURN; + unsafe { - let stdin = winapi::um::processenv::GetStdHandle( - winapi::um::winbase::STD_INPUT_HANDLE, - ); - let flags = - winapi::um::winbase::PURGE_TXCLEAR | winapi::um::winbase::PURGE_RXCLEAR; - winapi::um::commapi::PurgeComm(stdin, flags); + let stdin = GetStdHandle(STD_INPUT_HANDLE); + // emulate an enter key press to clear any line buffered console characters + emulate_enter_key_press(stdin); + // read the buffered line or enter key press + read_stdin_line(); + // check if our emulated key press was executed + if is_input_buffer_empty(stdin) { + // if so, move the cursor up to prevent a blank line + move_cursor_up(); + } else { + // the emulated key press is still pending, so a buffered line was read + // and we can flush the emulated key press + flush_input_buffer(stdin); + } + } + + unsafe fn flush_input_buffer(stdin: HANDLE) { + let success = FlushConsoleInputBuffer(stdin); + if success != TRUE { + panic!( + "Error flushing console input buffer: {}", + std::io::Error::last_os_error().to_string() + ) + } + } + + unsafe fn emulate_enter_key_press(stdin: HANDLE) { + // https://github.com/libuv/libuv/blob/a39009a5a9252a566ca0704d02df8dabc4ce328f/src/win/tty.c#L1121-L1131 + let mut input_record: INPUT_RECORD = std::mem::zeroed(); + input_record.EventType = KEY_EVENT; + input_record.Event.KeyEvent_mut().bKeyDown = TRUE; + input_record.Event.KeyEvent_mut().wRepeatCount = 1; + input_record.Event.KeyEvent_mut().wVirtualKeyCode = VK_RETURN as WORD; + input_record.Event.KeyEvent_mut().wVirtualScanCode = + MapVirtualKeyW(VK_RETURN as UINT, MAPVK_VK_TO_VSC) as WORD; + *input_record.Event.KeyEvent_mut().uChar.UnicodeChar_mut() = + '\r' as WCHAR; + + let mut record_written = 0; + let success = + WriteConsoleInputW(stdin, &input_record, 1, &mut record_written); + if success != TRUE { + panic!( + "Error emulating enter key press: {}", + std::io::Error::last_os_error().to_string() + ) + } + } + + unsafe fn is_input_buffer_empty(stdin: HANDLE) -> bool { + let mut buffer = Vec::with_capacity(1); + let mut events_read = 0; + let success = + PeekConsoleInputW(stdin, buffer.as_mut_ptr(), 1, &mut events_read); + if success != TRUE { + panic!( + "Error peeking console input buffer: {}", + std::io::Error::last_os_error().to_string() + ) + } + events_read == 0 + } + + fn move_cursor_up() { + use std::io::Write; + write!(std::io::stderr(), "\x1B[1A").expect("expected to move cursor up"); + } + + fn read_stdin_line() { + let mut input = String::new(); + let stdin = std::io::stdin(); + stdin.read_line(&mut input).expect("expected to read line"); } } @@ -1226,7 +1306,7 @@ fn permission_prompt(message: &str) -> bool { eprint!("{}", colors::bold(&msg)); loop { let mut input = String::new(); - let stdin = io::stdin(); + let stdin = std::io::stdin(); let result = stdin.read_line(&mut input); if result.is_err() { return false;