win,tty: restore cursor after canceling line read
When we send VK_RETURN to make ReadConsole return, a spurious new line is echoed to the screen. This is pretty visible in Node.js, since it calls uv_tty_read_start() and uv_tty_read_stop() in rapid succession during startup. With this change, we save the screen state just before sending VK_RETURN, and restore the cursor position as soon as ReadConsole returns. PR-URL: https://github.com/libuv/libuv/pull/866 Reviewed-by: Bert Belder <bertbelder@gmail.com> Reviewed-By: Saúl Ibarra Corretgé <saghul@gmail.com>
This commit is contained in:
parent
349aa6c0dd
commit
9eb1311971
120
src/win/tty.c
120
src/win/tty.c
@ -57,11 +57,23 @@
|
||||
|
||||
static void uv_tty_capture_initial_style(CONSOLE_SCREEN_BUFFER_INFO* info);
|
||||
static void uv_tty_update_virtual_window(CONSOLE_SCREEN_BUFFER_INFO* info);
|
||||
static int uv__cancel_read_console(uv_tty_t* handle);
|
||||
|
||||
|
||||
/* Null uv_buf_t */
|
||||
static const uv_buf_t uv_null_buf_ = { 0, NULL };
|
||||
|
||||
enum uv__read_console_status_e {
|
||||
NOT_STARTED,
|
||||
IN_PROGRESS,
|
||||
TRAP_REQUESTED,
|
||||
COMPLETED
|
||||
};
|
||||
|
||||
static volatile LONG uv__read_console_status = NOT_STARTED;
|
||||
static volatile LONG uv__restore_screen_state;
|
||||
static CONSOLE_SCREEN_BUFFER_INFO uv__saved_screen_state;
|
||||
|
||||
|
||||
/*
|
||||
* The console virtual window.
|
||||
@ -399,6 +411,8 @@ static DWORD CALLBACK uv_tty_line_read_thread(void* data) {
|
||||
DWORD bytes, read_bytes;
|
||||
WCHAR utf16[MAX_INPUT_BUFFER_LENGTH / 3];
|
||||
DWORD chars, read_chars;
|
||||
LONG status;
|
||||
COORD pos;
|
||||
|
||||
assert(data);
|
||||
|
||||
@ -420,6 +434,14 @@ static DWORD CALLBACK uv_tty_line_read_thread(void* data) {
|
||||
/* One utf-16 codeunit never takes more than 3 utf-8 codeunits to encode */
|
||||
chars = bytes / 3;
|
||||
|
||||
status = InterlockedExchange(&uv__read_console_status, IN_PROGRESS);
|
||||
if (status == TRAP_REQUESTED) {
|
||||
SET_REQ_SUCCESS(req);
|
||||
req->u.io.overlapped.InternalHigh = 0;
|
||||
POST_COMPLETION_FOR_REQ(loop, req);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (ReadConsoleW(handle->handle,
|
||||
(void*) utf16,
|
||||
chars,
|
||||
@ -439,6 +461,33 @@ static DWORD CALLBACK uv_tty_line_read_thread(void* data) {
|
||||
SET_REQ_ERROR(req, GetLastError());
|
||||
}
|
||||
|
||||
InterlockedExchange(&uv__read_console_status, COMPLETED);
|
||||
|
||||
/* If we canceled the read by sending a VK_RETURN event, restore the screen
|
||||
state to undo the visual effect of the VK_RETURN*/
|
||||
if (InterlockedOr(&uv__restore_screen_state, 0)) {
|
||||
HANDLE active_screen_buffer = CreateFileA("conout$",
|
||||
GENERIC_READ | GENERIC_WRITE,
|
||||
FILE_SHARE_READ | FILE_SHARE_WRITE,
|
||||
NULL,
|
||||
OPEN_EXISTING,
|
||||
FILE_ATTRIBUTE_NORMAL,
|
||||
NULL);
|
||||
if (active_screen_buffer != INVALID_HANDLE_VALUE) {
|
||||
pos = uv__saved_screen_state.dwCursorPosition;
|
||||
|
||||
/* If the cursor was at the bottom line of the screen buffer, the
|
||||
VK_RETURN would have caused the buffer contents to scroll up by
|
||||
one line. The right position to reset the cursor to is therefore one
|
||||
line higher */
|
||||
if (pos.Y == uv__saved_screen_state.dwSize.Y - 1)
|
||||
pos.Y--;
|
||||
|
||||
SetConsoleCursorPosition(active_screen_buffer, pos);
|
||||
CloseHandle(active_screen_buffer);
|
||||
}
|
||||
}
|
||||
|
||||
POST_COMPLETION_FOR_REQ(loop, req);
|
||||
return 0;
|
||||
}
|
||||
@ -464,6 +513,11 @@ static void uv_tty_queue_read_line(uv_loop_t* loop, uv_tty_t* handle) {
|
||||
}
|
||||
assert(handle->tty.rd.read_line_buffer.base != NULL);
|
||||
|
||||
/* Reset flags No locking is required since there cannot be a line read
|
||||
in progress. We are also relying on the memory barrier provided by
|
||||
QueueUserWorkItem*/
|
||||
uv__restore_screen_state = FALSE;
|
||||
uv__read_console_status = NOT_STARTED;
|
||||
r = QueueUserWorkItem(uv_tty_line_read_thread,
|
||||
(void*) req,
|
||||
WT_EXECUTELONGFUNCTION);
|
||||
@ -923,7 +977,7 @@ int uv_tty_read_start(uv_tty_t* handle, uv_alloc_cb alloc_cb,
|
||||
|
||||
int uv_tty_read_stop(uv_tty_t* handle) {
|
||||
INPUT_RECORD record;
|
||||
DWORD written;
|
||||
DWORD written, err;
|
||||
|
||||
handle->flags &= ~UV_HANDLE_READING;
|
||||
DECREASE_ACTIVE_COUNT(handle->loop, handle);
|
||||
@ -940,22 +994,66 @@ int uv_tty_read_stop(uv_tty_t* handle) {
|
||||
}
|
||||
} else if (!(handle->flags & UV_HANDLE_CANCELLATION_PENDING)) {
|
||||
/* Cancel line-buffered read if not already pending */
|
||||
/* Write enter key event to force the console wait to return. */
|
||||
record.EventType = KEY_EVENT;
|
||||
record.Event.KeyEvent.bKeyDown = TRUE;
|
||||
record.Event.KeyEvent.wRepeatCount = 1;
|
||||
record.Event.KeyEvent.wVirtualKeyCode = VK_RETURN;
|
||||
record.Event.KeyEvent.wVirtualScanCode =
|
||||
MapVirtualKeyW(VK_RETURN, MAPVK_VK_TO_VSC);
|
||||
record.Event.KeyEvent.uChar.UnicodeChar = L'\r';
|
||||
record.Event.KeyEvent.dwControlKeyState = 0;
|
||||
WriteConsoleInputW(handle->handle, &record, 1, &written);
|
||||
err = uv__cancel_read_console(handle);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
handle->flags |= UV_HANDLE_CANCELLATION_PENDING;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int uv__cancel_read_console(uv_tty_t* handle) {
|
||||
HANDLE active_screen_buffer = INVALID_HANDLE_VALUE;
|
||||
INPUT_RECORD record;
|
||||
DWORD written;
|
||||
DWORD err = 0;
|
||||
LONG status;
|
||||
|
||||
assert(!(handle->flags & UV_HANDLE_CANCELLATION_PENDING));
|
||||
|
||||
status = InterlockedExchange(&uv__read_console_status, TRAP_REQUESTED);
|
||||
if (status != IN_PROGRESS) {
|
||||
/* Either we have managed to set a trap for the other thread before
|
||||
ReadConsole is called, or ReadConsole has returned because the user
|
||||
has pressed ENTER. In either case, there is nothing else to do. */
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Save screen state before sending the VK_RETURN event */
|
||||
active_screen_buffer = CreateFileA("conout$",
|
||||
GENERIC_READ | GENERIC_WRITE,
|
||||
FILE_SHARE_READ | FILE_SHARE_WRITE,
|
||||
NULL,
|
||||
OPEN_EXISTING,
|
||||
FILE_ATTRIBUTE_NORMAL,
|
||||
NULL);
|
||||
|
||||
if (active_screen_buffer != INVALID_HANDLE_VALUE &&
|
||||
GetConsoleScreenBufferInfo(active_screen_buffer,
|
||||
&uv__saved_screen_state)) {
|
||||
InterlockedOr(&uv__restore_screen_state, 1);
|
||||
}
|
||||
|
||||
/* Write enter key event to force the console wait to return. */
|
||||
record.EventType = KEY_EVENT;
|
||||
record.Event.KeyEvent.bKeyDown = TRUE;
|
||||
record.Event.KeyEvent.wRepeatCount = 1;
|
||||
record.Event.KeyEvent.wVirtualKeyCode = VK_RETURN;
|
||||
record.Event.KeyEvent.wVirtualScanCode =
|
||||
MapVirtualKeyW(VK_RETURN, MAPVK_VK_TO_VSC);
|
||||
record.Event.KeyEvent.uChar.UnicodeChar = L'\r';
|
||||
record.Event.KeyEvent.dwControlKeyState = 0;
|
||||
if (!WriteConsoleInputW(handle->handle, &record, 1, &written))
|
||||
err = GetLastError();
|
||||
|
||||
if (active_screen_buffer != INVALID_HANDLE_VALUE)
|
||||
CloseHandle(active_screen_buffer);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
|
||||
static void uv_tty_update_virtual_window(CONSOLE_SCREEN_BUFFER_INFO* info) {
|
||||
int old_virtual_width = uv_tty_virtual_width;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user