summaryrefslogtreecommitdiff
path: root/runtime/ops/tty.rs
diff options
context:
space:
mode:
authorDivy Srivastava <dj.srivastava23@gmail.com>2024-05-29 09:53:04 -0700
committerGitHub <noreply@github.com>2024-05-29 22:23:04 +0530
commita947c6fbf7c71544687c79716eadbffe4bdedc82 (patch)
tree50807b95951b2aaf6b2126f66cd02e8990fd7bcd /runtime/ops/tty.rs
parent4f9b23b3664578c2bf48415db246fb21e49abddb (diff)
fix(ext/node): windows cancel stdin read in line mode (#23969)
This patch fixes stdin read hanging on user input when switching tty mode on Windows Fixes #21111 On Windows, when switching from line to raw mode: - Cancel ongoing console read by writing a return keypress to its input buffer. This blocks the main thread until any ongoing read has been cancelled to prevent interference with the screen state. - On the read thread, restore the cursor position to where it was before writing the enter, undoing its effect on the screen state. - Restart reading and notify the main thread.
Diffstat (limited to 'runtime/ops/tty.rs')
-rw-r--r--runtime/ops/tty.rs81
1 files changed, 81 insertions, 0 deletions
diff --git a/runtime/ops/tty.rs b/runtime/ops/tty.rs
index 3d721734c..be22bdd2a 100644
--- a/runtime/ops/tty.rs
+++ b/runtime/ops/tty.rs
@@ -13,6 +13,13 @@ use rustyline::KeyCode;
use rustyline::KeyEvent;
use rustyline::Modifiers;
+#[cfg(windows)]
+use deno_core::parking_lot::Mutex;
+#[cfg(windows)]
+use deno_io::WinTtyState;
+#[cfg(windows)]
+use std::sync::Arc;
+
#[cfg(unix)]
use deno_core::ResourceId;
#[cfg(unix)]
@@ -94,6 +101,7 @@ fn op_set_raw(
#[cfg(windows)]
{
use winapi::shared::minwindef::FALSE;
+
use winapi::um::consoleapi;
let handle = handle_or_fd;
@@ -116,6 +124,79 @@ fn op_set_raw(
mode_raw_input_off(original_mode)
};
+ let stdin_state = state.borrow::<Arc<Mutex<WinTtyState>>>();
+ let mut stdin_state = stdin_state.lock();
+
+ if stdin_state.reading {
+ let cvar = stdin_state.cvar.clone();
+
+ /* Trick to unblock an ongoing line-buffered read operation if not already pending.
+ See https://github.com/libuv/libuv/pull/866 for prior art */
+ if original_mode & COOKED_MODE != 0 && !stdin_state.cancelled {
+ // SAFETY: Write enter key event to force the console wait to return.
+ let record = unsafe {
+ let mut record: wincon::INPUT_RECORD = std::mem::zeroed();
+ record.EventType = wincon::KEY_EVENT;
+ record.Event.KeyEvent_mut().wVirtualKeyCode =
+ winapi::um::winuser::VK_RETURN as u16;
+ record.Event.KeyEvent_mut().bKeyDown = 1;
+ record.Event.KeyEvent_mut().wRepeatCount = 1;
+ *record.Event.KeyEvent_mut().uChar.UnicodeChar_mut() = '\r' as u16;
+ record.Event.KeyEvent_mut().dwControlKeyState = 0;
+ record.Event.KeyEvent_mut().wVirtualScanCode =
+ winapi::um::winuser::MapVirtualKeyW(
+ winapi::um::winuser::VK_RETURN as u32,
+ winapi::um::winuser::MAPVK_VK_TO_VSC,
+ ) as u16;
+ record
+ };
+ stdin_state.cancelled = true;
+
+ // SAFETY: winapi call to open conout$ and save screen state.
+ let active_screen_buffer = unsafe {
+ /* Save screen state before sending the VK_RETURN event */
+ let handle = winapi::um::fileapi::CreateFileW(
+ "conout$"
+ .encode_utf16()
+ .chain(Some(0))
+ .collect::<Vec<_>>()
+ .as_ptr(),
+ winapi::um::winnt::GENERIC_READ | winapi::um::winnt::GENERIC_WRITE,
+ winapi::um::winnt::FILE_SHARE_READ
+ | winapi::um::winnt::FILE_SHARE_WRITE,
+ std::ptr::null_mut(),
+ winapi::um::fileapi::OPEN_EXISTING,
+ 0,
+ std::ptr::null_mut(),
+ );
+
+ let mut active_screen_buffer = std::mem::zeroed();
+ winapi::um::wincon::GetConsoleScreenBufferInfo(
+ handle,
+ &mut active_screen_buffer,
+ );
+ winapi::um::handleapi::CloseHandle(handle);
+ active_screen_buffer
+ };
+ stdin_state.screen_buffer_info = Some(active_screen_buffer);
+
+ // SAFETY: winapi call to write the VK_RETURN event.
+ if unsafe {
+ winapi::um::wincon::WriteConsoleInputW(handle, &record, 1, &mut 0)
+ } == FALSE
+ {
+ return Err(Error::last_os_error().into());
+ }
+
+ /* Wait for read thread to acknowledge the cancellation to ensure that nothing
+ interferes with the screen state.
+ NOTE: `wait_while` automatically unlocks stdin_state */
+ cvar.wait_while(&mut stdin_state, |state: &mut WinTtyState| {
+ state.cancelled
+ });
+ }
+ }
+
// SAFETY: winapi call
if unsafe { consoleapi::SetConsoleMode(handle, new_mode) } == FALSE {
return Err(Error::last_os_error().into());