libuv/src/win/tty.c
Javier Blazquez dabc737d78 win: prevent tty event explosion machine hang
The tty subsystem on Windows was listening for console events from all
processes to detect when our console window was being resized. This
could cause an explosion in the number of events queued by the system
when running many console applications in parallel that all wrote to
their console quickly. The end result was a complete machine hang.

Now we determine, whenever possible, what our corresponding conhost.exe
process is and listen for console events from that process only. This
detection does not work in 32-bit applications running on 64-bit
Windows so those default to the old behavior of listening to all
processes.

PR-URL: https://github.com/libuv/libuv/pull/2308
Reviewed-By: Saúl Ibarra Corretgé <saghul@gmail.com>
Reviewed-By: Bartosz Sosnowski <bartosz@janeasystems.com>
2019-07-12 10:14:26 +02:00

2349 lines
74 KiB
C

/* Copyright Joyent, Inc. and other Node contributors. All rights reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*/
#include <assert.h>
#include <io.h>
#include <string.h>
#include <stdlib.h>
#if defined(_MSC_VER) && _MSC_VER < 1600
# include "uv/stdint-msvc2008.h"
#else
# include <stdint.h>
#endif
#ifndef COMMON_LVB_REVERSE_VIDEO
# define COMMON_LVB_REVERSE_VIDEO 0x4000
#endif
#include "uv.h"
#include "internal.h"
#include "handle-inl.h"
#include "stream-inl.h"
#include "req-inl.h"
#ifndef InterlockedOr
# define InterlockedOr _InterlockedOr
#endif
#define UNICODE_REPLACEMENT_CHARACTER (0xfffd)
#define ANSI_NORMAL 0x00
#define ANSI_ESCAPE_SEEN 0x02
#define ANSI_CSI 0x04
#define ANSI_ST_CONTROL 0x08
#define ANSI_IGNORE 0x10
#define ANSI_IN_ARG 0x20
#define ANSI_IN_STRING 0x40
#define ANSI_BACKSLASH_SEEN 0x80
#define MAX_INPUT_BUFFER_LENGTH 8192
#define MAX_CONSOLE_CHAR 8192
#ifndef ENABLE_VIRTUAL_TERMINAL_PROCESSING
#define ENABLE_VIRTUAL_TERMINAL_PROCESSING 0x0004
#endif
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.
*
* Normally cursor movement in windows is relative to the console screen buffer,
* e.g. the application is allowed to overwrite the 'history'. This is very
* inconvenient, it makes absolute cursor movement pretty useless. There is
* also the concept of 'client rect' which is defined by the actual size of
* the console window and the scroll position of the screen buffer, but it's
* very volatile because it changes when the user scrolls.
*
* To make cursor movement behave sensibly we define a virtual window to which
* cursor movement is confined. The virtual window is always as wide as the
* console screen buffer, but it's height is defined by the size of the
* console window. The top of the virtual window aligns with the position
* of the caret when the first stdout/err handle is created, unless that would
* mean that it would extend beyond the bottom of the screen buffer - in that
* that case it's located as far down as possible.
*
* When the user writes a long text or many newlines, such that the output
* reaches beyond the bottom of the virtual window, the virtual window is
* shifted downwards, but not resized.
*
* Since all tty i/o happens on the same console, this window is shared
* between all stdout/stderr handles.
*/
static int uv_tty_virtual_offset = -1;
static int uv_tty_virtual_height = -1;
static int uv_tty_virtual_width = -1;
/* The console window size
* We keep this separate from uv_tty_virtual_*. We use those values to only
* handle signalling SIGWINCH
*/
static HANDLE uv__tty_console_handle = INVALID_HANDLE_VALUE;
static int uv__tty_console_height = -1;
static int uv__tty_console_width = -1;
static DWORD WINAPI uv__tty_console_resize_message_loop_thread(void* param);
static void CALLBACK uv__tty_console_resize_event(HWINEVENTHOOK hWinEventHook,
DWORD event,
HWND hwnd,
LONG idObject,
LONG idChild,
DWORD dwEventThread,
DWORD dwmsEventTime);
/* We use a semaphore rather than a mutex or critical section because in some
cases (uv__cancel_read_console) we need take the lock in the main thread and
release it in another thread. Using a semaphore ensures that in such
scenario the main thread will still block when trying to acquire the lock. */
static uv_sem_t uv_tty_output_lock;
static WORD uv_tty_default_text_attributes =
FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE;
static char uv_tty_default_fg_color = 7;
static char uv_tty_default_bg_color = 0;
static char uv_tty_default_fg_bright = 0;
static char uv_tty_default_bg_bright = 0;
static char uv_tty_default_inverse = 0;
typedef enum {
UV_SUPPORTED,
UV_UNCHECKED,
UV_UNSUPPORTED
} uv_vtermstate_t;
/* Determine whether or not ANSI support is enabled. */
static uv_vtermstate_t uv__vterm_state = UV_UNCHECKED;
static void uv__determine_vterm_state(HANDLE handle);
void uv_console_init(void) {
if (uv_sem_init(&uv_tty_output_lock, 1))
abort();
uv__tty_console_handle = CreateFileW(L"CONOUT$",
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_WRITE,
0,
OPEN_EXISTING,
0,
0);
if (uv__tty_console_handle != INVALID_HANDLE_VALUE) {
QueueUserWorkItem(uv__tty_console_resize_message_loop_thread,
NULL,
WT_EXECUTELONGFUNCTION);
}
}
int uv_tty_init(uv_loop_t* loop, uv_tty_t* tty, uv_file fd, int unused) {
BOOL readable;
DWORD NumberOfEvents;
HANDLE handle;
CONSOLE_SCREEN_BUFFER_INFO screen_buffer_info;
(void)unused;
uv__once_init();
handle = (HANDLE) uv__get_osfhandle(fd);
if (handle == INVALID_HANDLE_VALUE)
return UV_EBADF;
if (fd <= 2) {
/* In order to avoid closing a stdio file descriptor 0-2, duplicate the
* underlying OS handle and forget about the original fd.
* We could also opt to use the original OS handle and just never close it,
* but then there would be no reliable way to cancel pending read operations
* upon close.
*/
if (!DuplicateHandle(INVALID_HANDLE_VALUE,
handle,
INVALID_HANDLE_VALUE,
&handle,
0,
FALSE,
DUPLICATE_SAME_ACCESS))
return uv_translate_sys_error(GetLastError());
fd = -1;
}
readable = GetNumberOfConsoleInputEvents(handle, &NumberOfEvents);
if (!readable) {
/* Obtain the screen buffer info with the output handle. */
if (!GetConsoleScreenBufferInfo(handle, &screen_buffer_info)) {
return uv_translate_sys_error(GetLastError());
}
/* Obtain the tty_output_lock because the virtual window state is shared
* between all uv_tty_t handles. */
uv_sem_wait(&uv_tty_output_lock);
if (uv__vterm_state == UV_UNCHECKED)
uv__determine_vterm_state(handle);
/* Remember the original console text attributes. */
uv_tty_capture_initial_style(&screen_buffer_info);
uv_tty_update_virtual_window(&screen_buffer_info);
uv_sem_post(&uv_tty_output_lock);
}
uv_stream_init(loop, (uv_stream_t*) tty, UV_TTY);
uv_connection_init((uv_stream_t*) tty);
tty->handle = handle;
tty->u.fd = fd;
tty->reqs_pending = 0;
tty->flags |= UV_HANDLE_BOUND;
if (readable) {
/* Initialize TTY input specific fields. */
tty->flags |= UV_HANDLE_TTY_READABLE | UV_HANDLE_READABLE;
/* TODO: remove me in v2.x. */
tty->tty.rd.unused_ = NULL;
tty->tty.rd.read_line_buffer = uv_null_buf_;
tty->tty.rd.read_raw_wait = NULL;
/* Init keycode-to-vt100 mapper state. */
tty->tty.rd.last_key_len = 0;
tty->tty.rd.last_key_offset = 0;
tty->tty.rd.last_utf16_high_surrogate = 0;
memset(&tty->tty.rd.last_input_record, 0, sizeof tty->tty.rd.last_input_record);
} else {
/* TTY output specific fields. */
tty->flags |= UV_HANDLE_WRITABLE;
/* Init utf8-to-utf16 conversion state. */
tty->tty.wr.utf8_bytes_left = 0;
tty->tty.wr.utf8_codepoint = 0;
/* Initialize eol conversion state */
tty->tty.wr.previous_eol = 0;
/* Init ANSI parser state. */
tty->tty.wr.ansi_parser_state = ANSI_NORMAL;
}
return 0;
}
/* Set the default console text attributes based on how the console was
* configured when libuv started.
*/
static void uv_tty_capture_initial_style(CONSOLE_SCREEN_BUFFER_INFO* info) {
static int style_captured = 0;
/* Only do this once.
Assumption: Caller has acquired uv_tty_output_lock. */
if (style_captured)
return;
/* Save raw win32 attributes. */
uv_tty_default_text_attributes = info->wAttributes;
/* Convert black text on black background to use white text. */
if (uv_tty_default_text_attributes == 0)
uv_tty_default_text_attributes = 7;
/* Convert Win32 attributes to ANSI colors. */
uv_tty_default_fg_color = 0;
uv_tty_default_bg_color = 0;
uv_tty_default_fg_bright = 0;
uv_tty_default_bg_bright = 0;
uv_tty_default_inverse = 0;
if (uv_tty_default_text_attributes & FOREGROUND_RED)
uv_tty_default_fg_color |= 1;
if (uv_tty_default_text_attributes & FOREGROUND_GREEN)
uv_tty_default_fg_color |= 2;
if (uv_tty_default_text_attributes & FOREGROUND_BLUE)
uv_tty_default_fg_color |= 4;
if (uv_tty_default_text_attributes & BACKGROUND_RED)
uv_tty_default_bg_color |= 1;
if (uv_tty_default_text_attributes & BACKGROUND_GREEN)
uv_tty_default_bg_color |= 2;
if (uv_tty_default_text_attributes & BACKGROUND_BLUE)
uv_tty_default_bg_color |= 4;
if (uv_tty_default_text_attributes & FOREGROUND_INTENSITY)
uv_tty_default_fg_bright = 1;
if (uv_tty_default_text_attributes & BACKGROUND_INTENSITY)
uv_tty_default_bg_bright = 1;
if (uv_tty_default_text_attributes & COMMON_LVB_REVERSE_VIDEO)
uv_tty_default_inverse = 1;
style_captured = 1;
}
int uv_tty_set_mode(uv_tty_t* tty, uv_tty_mode_t mode) {
DWORD flags;
unsigned char was_reading;
uv_alloc_cb alloc_cb;
uv_read_cb read_cb;
int err;
if (!(tty->flags & UV_HANDLE_TTY_READABLE)) {
return UV_EINVAL;
}
if (!!mode == !!(tty->flags & UV_HANDLE_TTY_RAW)) {
return 0;
}
switch (mode) {
case UV_TTY_MODE_NORMAL:
flags = ENABLE_ECHO_INPUT | ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT;
break;
case UV_TTY_MODE_RAW:
flags = ENABLE_WINDOW_INPUT;
break;
case UV_TTY_MODE_IO:
return UV_ENOTSUP;
default:
return UV_EINVAL;
}
/* If currently reading, stop, and restart reading. */
if (tty->flags & UV_HANDLE_READING) {
was_reading = 1;
alloc_cb = tty->alloc_cb;
read_cb = tty->read_cb;
err = uv_tty_read_stop(tty);
if (err) {
return uv_translate_sys_error(err);
}
} else {
was_reading = 0;
alloc_cb = NULL;
read_cb = NULL;
}
uv_sem_wait(&uv_tty_output_lock);
if (!SetConsoleMode(tty->handle, flags)) {
err = uv_translate_sys_error(GetLastError());
uv_sem_post(&uv_tty_output_lock);
return err;
}
uv_sem_post(&uv_tty_output_lock);
/* Update flag. */
tty->flags &= ~UV_HANDLE_TTY_RAW;
tty->flags |= mode ? UV_HANDLE_TTY_RAW : 0;
/* If we just stopped reading, restart. */
if (was_reading) {
err = uv_tty_read_start(tty, alloc_cb, read_cb);
if (err) {
return uv_translate_sys_error(err);
}
}
return 0;
}
int uv_tty_get_winsize(uv_tty_t* tty, int* width, int* height) {
CONSOLE_SCREEN_BUFFER_INFO info;
if (!GetConsoleScreenBufferInfo(tty->handle, &info)) {
return uv_translate_sys_error(GetLastError());
}
uv_sem_wait(&uv_tty_output_lock);
uv_tty_update_virtual_window(&info);
uv_sem_post(&uv_tty_output_lock);
*width = uv_tty_virtual_width;
*height = uv_tty_virtual_height;
return 0;
}
static void CALLBACK uv_tty_post_raw_read(void* data, BOOLEAN didTimeout) {
uv_loop_t* loop;
uv_tty_t* handle;
uv_req_t* req;
assert(data);
assert(!didTimeout);
req = (uv_req_t*) data;
handle = (uv_tty_t*) req->data;
loop = handle->loop;
UnregisterWait(handle->tty.rd.read_raw_wait);
handle->tty.rd.read_raw_wait = NULL;
SET_REQ_SUCCESS(req);
POST_COMPLETION_FOR_REQ(loop, req);
}
static void uv_tty_queue_read_raw(uv_loop_t* loop, uv_tty_t* handle) {
uv_read_t* req;
BOOL r;
assert(handle->flags & UV_HANDLE_READING);
assert(!(handle->flags & UV_HANDLE_READ_PENDING));
assert(handle->handle && handle->handle != INVALID_HANDLE_VALUE);
handle->tty.rd.read_line_buffer = uv_null_buf_;
req = &handle->read_req;
memset(&req->u.io.overlapped, 0, sizeof(req->u.io.overlapped));
r = RegisterWaitForSingleObject(&handle->tty.rd.read_raw_wait,
handle->handle,
uv_tty_post_raw_read,
(void*) req,
INFINITE,
WT_EXECUTEINWAITTHREAD | WT_EXECUTEONLYONCE);
if (!r) {
handle->tty.rd.read_raw_wait = NULL;
SET_REQ_ERROR(req, GetLastError());
uv_insert_pending_req(loop, (uv_req_t*)req);
}
handle->flags |= UV_HANDLE_READ_PENDING;
handle->reqs_pending++;
}
static DWORD CALLBACK uv_tty_line_read_thread(void* data) {
uv_loop_t* loop;
uv_tty_t* handle;
uv_req_t* req;
DWORD bytes, read_bytes;
WCHAR utf16[MAX_INPUT_BUFFER_LENGTH / 3];
DWORD chars, read_chars;
LONG status;
COORD pos;
BOOL read_console_success;
assert(data);
req = (uv_req_t*) data;
handle = (uv_tty_t*) req->data;
loop = handle->loop;
assert(handle->tty.rd.read_line_buffer.base != NULL);
assert(handle->tty.rd.read_line_buffer.len > 0);
/* ReadConsole can't handle big buffers. */
if (handle->tty.rd.read_line_buffer.len < MAX_INPUT_BUFFER_LENGTH) {
bytes = handle->tty.rd.read_line_buffer.len;
} else {
bytes = MAX_INPUT_BUFFER_LENGTH;
}
/* At last, unicode! 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;
}
read_console_success = ReadConsoleW(handle->handle,
(void*) utf16,
chars,
&read_chars,
NULL);
if (read_console_success) {
read_bytes = WideCharToMultiByte(CP_UTF8,
0,
utf16,
read_chars,
handle->tty.rd.read_line_buffer.base,
bytes,
NULL,
NULL);
SET_REQ_SUCCESS(req);
req->u.io.overlapped.InternalHigh = read_bytes;
} else {
SET_REQ_ERROR(req, GetLastError());
}
status = InterlockedExchange(&uv__read_console_status, COMPLETED);
if (status == TRAP_REQUESTED) {
/* 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 (read_console_success && InterlockedOr(&uv__restore_screen_state, 0)) {
HANDLE active_screen_buffer;
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);
}
}
uv_sem_post(&uv_tty_output_lock);
}
POST_COMPLETION_FOR_REQ(loop, req);
return 0;
}
static void uv_tty_queue_read_line(uv_loop_t* loop, uv_tty_t* handle) {
uv_read_t* req;
BOOL r;
assert(handle->flags & UV_HANDLE_READING);
assert(!(handle->flags & UV_HANDLE_READ_PENDING));
assert(handle->handle && handle->handle != INVALID_HANDLE_VALUE);
req = &handle->read_req;
memset(&req->u.io.overlapped, 0, sizeof(req->u.io.overlapped));
handle->tty.rd.read_line_buffer = uv_buf_init(NULL, 0);
handle->alloc_cb((uv_handle_t*) handle, 8192, &handle->tty.rd.read_line_buffer);
if (handle->tty.rd.read_line_buffer.base == NULL ||
handle->tty.rd.read_line_buffer.len == 0) {
handle->read_cb((uv_stream_t*) handle,
UV_ENOBUFS,
&handle->tty.rd.read_line_buffer);
return;
}
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);
if (!r) {
SET_REQ_ERROR(req, GetLastError());
uv_insert_pending_req(loop, (uv_req_t*)req);
}
handle->flags |= UV_HANDLE_READ_PENDING;
handle->reqs_pending++;
}
static void uv_tty_queue_read(uv_loop_t* loop, uv_tty_t* handle) {
if (handle->flags & UV_HANDLE_TTY_RAW) {
uv_tty_queue_read_raw(loop, handle);
} else {
uv_tty_queue_read_line(loop, handle);
}
}
static const char* get_vt100_fn_key(DWORD code, char shift, char ctrl,
size_t* len) {
#define VK_CASE(vk, normal_str, shift_str, ctrl_str, shift_ctrl_str) \
case (vk): \
if (shift && ctrl) { \
*len = sizeof shift_ctrl_str; \
return "\033" shift_ctrl_str; \
} else if (shift) { \
*len = sizeof shift_str ; \
return "\033" shift_str; \
} else if (ctrl) { \
*len = sizeof ctrl_str; \
return "\033" ctrl_str; \
} else { \
*len = sizeof normal_str; \
return "\033" normal_str; \
}
switch (code) {
/* These mappings are the same as Cygwin's. Unmodified and alt-modified
* keypad keys comply with linux console, modifiers comply with xterm
* modifier usage. F1. f12 and shift-f1. f10 comply with linux console, f6.
* f12 with and without modifiers comply with rxvt. */
VK_CASE(VK_INSERT, "[2~", "[2;2~", "[2;5~", "[2;6~")
VK_CASE(VK_END, "[4~", "[4;2~", "[4;5~", "[4;6~")
VK_CASE(VK_DOWN, "[B", "[1;2B", "[1;5B", "[1;6B")
VK_CASE(VK_NEXT, "[6~", "[6;2~", "[6;5~", "[6;6~")
VK_CASE(VK_LEFT, "[D", "[1;2D", "[1;5D", "[1;6D")
VK_CASE(VK_CLEAR, "[G", "[1;2G", "[1;5G", "[1;6G")
VK_CASE(VK_RIGHT, "[C", "[1;2C", "[1;5C", "[1;6C")
VK_CASE(VK_UP, "[A", "[1;2A", "[1;5A", "[1;6A")
VK_CASE(VK_HOME, "[1~", "[1;2~", "[1;5~", "[1;6~")
VK_CASE(VK_PRIOR, "[5~", "[5;2~", "[5;5~", "[5;6~")
VK_CASE(VK_DELETE, "[3~", "[3;2~", "[3;5~", "[3;6~")
VK_CASE(VK_NUMPAD0, "[2~", "[2;2~", "[2;5~", "[2;6~")
VK_CASE(VK_NUMPAD1, "[4~", "[4;2~", "[4;5~", "[4;6~")
VK_CASE(VK_NUMPAD2, "[B", "[1;2B", "[1;5B", "[1;6B")
VK_CASE(VK_NUMPAD3, "[6~", "[6;2~", "[6;5~", "[6;6~")
VK_CASE(VK_NUMPAD4, "[D", "[1;2D", "[1;5D", "[1;6D")
VK_CASE(VK_NUMPAD5, "[G", "[1;2G", "[1;5G", "[1;6G")
VK_CASE(VK_NUMPAD6, "[C", "[1;2C", "[1;5C", "[1;6C")
VK_CASE(VK_NUMPAD7, "[A", "[1;2A", "[1;5A", "[1;6A")
VK_CASE(VK_NUMPAD8, "[1~", "[1;2~", "[1;5~", "[1;6~")
VK_CASE(VK_NUMPAD9, "[5~", "[5;2~", "[5;5~", "[5;6~")
VK_CASE(VK_DECIMAL, "[3~", "[3;2~", "[3;5~", "[3;6~")
VK_CASE(VK_F1, "[[A", "[23~", "[11^", "[23^" )
VK_CASE(VK_F2, "[[B", "[24~", "[12^", "[24^" )
VK_CASE(VK_F3, "[[C", "[25~", "[13^", "[25^" )
VK_CASE(VK_F4, "[[D", "[26~", "[14^", "[26^" )
VK_CASE(VK_F5, "[[E", "[28~", "[15^", "[28^" )
VK_CASE(VK_F6, "[17~", "[29~", "[17^", "[29^" )
VK_CASE(VK_F7, "[18~", "[31~", "[18^", "[31^" )
VK_CASE(VK_F8, "[19~", "[32~", "[19^", "[32^" )
VK_CASE(VK_F9, "[20~", "[33~", "[20^", "[33^" )
VK_CASE(VK_F10, "[21~", "[34~", "[21^", "[34^" )
VK_CASE(VK_F11, "[23~", "[23$", "[23^", "[23@" )
VK_CASE(VK_F12, "[24~", "[24$", "[24^", "[24@" )
default:
*len = 0;
return NULL;
}
#undef VK_CASE
}
void uv_process_tty_read_raw_req(uv_loop_t* loop, uv_tty_t* handle,
uv_req_t* req) {
/* Shortcut for handle->tty.rd.last_input_record.Event.KeyEvent. */
#define KEV handle->tty.rd.last_input_record.Event.KeyEvent
DWORD records_left, records_read;
uv_buf_t buf;
off_t buf_used;
assert(handle->type == UV_TTY);
assert(handle->flags & UV_HANDLE_TTY_READABLE);
handle->flags &= ~UV_HANDLE_READ_PENDING;
if (!(handle->flags & UV_HANDLE_READING) ||
!(handle->flags & UV_HANDLE_TTY_RAW)) {
goto out;
}
if (!REQ_SUCCESS(req)) {
/* An error occurred while waiting for the event. */
if ((handle->flags & UV_HANDLE_READING)) {
handle->flags &= ~UV_HANDLE_READING;
handle->read_cb((uv_stream_t*)handle,
uv_translate_sys_error(GET_REQ_ERROR(req)),
&uv_null_buf_);
}
goto out;
}
/* Fetch the number of events */
if (!GetNumberOfConsoleInputEvents(handle->handle, &records_left)) {
handle->flags &= ~UV_HANDLE_READING;
DECREASE_ACTIVE_COUNT(loop, handle);
handle->read_cb((uv_stream_t*)handle,
uv_translate_sys_error(GetLastError()),
&uv_null_buf_);
goto out;
}
/* Windows sends a lot of events that we're not interested in, so buf will be
* allocated on demand, when there's actually something to emit. */
buf = uv_null_buf_;
buf_used = 0;
while ((records_left > 0 || handle->tty.rd.last_key_len > 0) &&
(handle->flags & UV_HANDLE_READING)) {
if (handle->tty.rd.last_key_len == 0) {
/* Read the next input record */
if (!ReadConsoleInputW(handle->handle,
&handle->tty.rd.last_input_record,
1,
&records_read)) {
handle->flags &= ~UV_HANDLE_READING;
DECREASE_ACTIVE_COUNT(loop, handle);
handle->read_cb((uv_stream_t*) handle,
uv_translate_sys_error(GetLastError()),
&buf);
goto out;
}
records_left--;
/* Ignore other events that are not key events. */
if (handle->tty.rd.last_input_record.EventType != KEY_EVENT) {
continue;
}
/* Ignore keyup events, unless the left alt key was held and a valid
* unicode character was emitted. */
if (!KEV.bKeyDown &&
(KEV.wVirtualKeyCode != VK_MENU ||
KEV.uChar.UnicodeChar == 0)) {
continue;
}
/* Ignore keypresses to numpad number keys if the left alt is held
* because the user is composing a character, or windows simulating this.
*/
if ((KEV.dwControlKeyState & LEFT_ALT_PRESSED) &&
!(KEV.dwControlKeyState & ENHANCED_KEY) &&
(KEV.wVirtualKeyCode == VK_INSERT ||
KEV.wVirtualKeyCode == VK_END ||
KEV.wVirtualKeyCode == VK_DOWN ||
KEV.wVirtualKeyCode == VK_NEXT ||
KEV.wVirtualKeyCode == VK_LEFT ||
KEV.wVirtualKeyCode == VK_CLEAR ||
KEV.wVirtualKeyCode == VK_RIGHT ||
KEV.wVirtualKeyCode == VK_HOME ||
KEV.wVirtualKeyCode == VK_UP ||
KEV.wVirtualKeyCode == VK_PRIOR ||
KEV.wVirtualKeyCode == VK_NUMPAD0 ||
KEV.wVirtualKeyCode == VK_NUMPAD1 ||
KEV.wVirtualKeyCode == VK_NUMPAD2 ||
KEV.wVirtualKeyCode == VK_NUMPAD3 ||
KEV.wVirtualKeyCode == VK_NUMPAD4 ||
KEV.wVirtualKeyCode == VK_NUMPAD5 ||
KEV.wVirtualKeyCode == VK_NUMPAD6 ||
KEV.wVirtualKeyCode == VK_NUMPAD7 ||
KEV.wVirtualKeyCode == VK_NUMPAD8 ||
KEV.wVirtualKeyCode == VK_NUMPAD9)) {
continue;
}
if (KEV.uChar.UnicodeChar != 0) {
int prefix_len, char_len;
/* Character key pressed */
if (KEV.uChar.UnicodeChar >= 0xD800 &&
KEV.uChar.UnicodeChar < 0xDC00) {
/* UTF-16 high surrogate */
handle->tty.rd.last_utf16_high_surrogate = KEV.uChar.UnicodeChar;
continue;
}
/* Prefix with \u033 if alt was held, but alt was not used as part a
* compose sequence. */
if ((KEV.dwControlKeyState & (LEFT_ALT_PRESSED | RIGHT_ALT_PRESSED))
&& !(KEV.dwControlKeyState & (LEFT_CTRL_PRESSED |
RIGHT_CTRL_PRESSED)) && KEV.bKeyDown) {
handle->tty.rd.last_key[0] = '\033';
prefix_len = 1;
} else {
prefix_len = 0;
}
if (KEV.uChar.UnicodeChar >= 0xDC00 &&
KEV.uChar.UnicodeChar < 0xE000) {
/* UTF-16 surrogate pair */
WCHAR utf16_buffer[2];
utf16_buffer[0] = handle->tty.rd.last_utf16_high_surrogate;
utf16_buffer[1] = KEV.uChar.UnicodeChar;
char_len = WideCharToMultiByte(CP_UTF8,
0,
utf16_buffer,
2,
&handle->tty.rd.last_key[prefix_len],
sizeof handle->tty.rd.last_key,
NULL,
NULL);
} else {
/* Single UTF-16 character */
char_len = WideCharToMultiByte(CP_UTF8,
0,
&KEV.uChar.UnicodeChar,
1,
&handle->tty.rd.last_key[prefix_len],
sizeof handle->tty.rd.last_key,
NULL,
NULL);
}
/* Whatever happened, the last character wasn't a high surrogate. */
handle->tty.rd.last_utf16_high_surrogate = 0;
/* If the utf16 character(s) couldn't be converted something must be
* wrong. */
if (!char_len) {
handle->flags &= ~UV_HANDLE_READING;
DECREASE_ACTIVE_COUNT(loop, handle);
handle->read_cb((uv_stream_t*) handle,
uv_translate_sys_error(GetLastError()),
&buf);
goto out;
}
handle->tty.rd.last_key_len = (unsigned char) (prefix_len + char_len);
handle->tty.rd.last_key_offset = 0;
continue;
} else {
/* Function key pressed */
const char* vt100;
size_t prefix_len, vt100_len;
vt100 = get_vt100_fn_key(KEV.wVirtualKeyCode,
!!(KEV.dwControlKeyState & SHIFT_PRESSED),
!!(KEV.dwControlKeyState & (
LEFT_CTRL_PRESSED |
RIGHT_CTRL_PRESSED)),
&vt100_len);
/* If we were unable to map to a vt100 sequence, just ignore. */
if (!vt100) {
continue;
}
/* Prefix with \x033 when the alt key was held. */
if (KEV.dwControlKeyState & (LEFT_ALT_PRESSED | RIGHT_ALT_PRESSED)) {
handle->tty.rd.last_key[0] = '\033';
prefix_len = 1;
} else {
prefix_len = 0;
}
/* Copy the vt100 sequence to the handle buffer. */
assert(prefix_len + vt100_len < sizeof handle->tty.rd.last_key);
memcpy(&handle->tty.rd.last_key[prefix_len], vt100, vt100_len);
handle->tty.rd.last_key_len = (unsigned char) (prefix_len + vt100_len);
handle->tty.rd.last_key_offset = 0;
continue;
}
} else {
/* Copy any bytes left from the last keypress to the user buffer. */
if (handle->tty.rd.last_key_offset < handle->tty.rd.last_key_len) {
/* Allocate a buffer if needed */
if (buf_used == 0) {
buf = uv_buf_init(NULL, 0);
handle->alloc_cb((uv_handle_t*) handle, 1024, &buf);
if (buf.base == NULL || buf.len == 0) {
handle->read_cb((uv_stream_t*) handle, UV_ENOBUFS, &buf);
goto out;
}
assert(buf.base != NULL);
}
buf.base[buf_used++] = handle->tty.rd.last_key[handle->tty.rd.last_key_offset++];
/* If the buffer is full, emit it */
if ((size_t) buf_used == buf.len) {
handle->read_cb((uv_stream_t*) handle, buf_used, &buf);
buf = uv_null_buf_;
buf_used = 0;
}
continue;
}
/* Apply dwRepeat from the last input record. */
if (--KEV.wRepeatCount > 0) {
handle->tty.rd.last_key_offset = 0;
continue;
}
handle->tty.rd.last_key_len = 0;
continue;
}
}
/* Send the buffer back to the user */
if (buf_used > 0) {
handle->read_cb((uv_stream_t*) handle, buf_used, &buf);
}
out:
/* Wait for more input events. */
if ((handle->flags & UV_HANDLE_READING) &&
!(handle->flags & UV_HANDLE_READ_PENDING)) {
uv_tty_queue_read(loop, handle);
}
DECREASE_PENDING_REQ_COUNT(handle);
#undef KEV
}
void uv_process_tty_read_line_req(uv_loop_t* loop, uv_tty_t* handle,
uv_req_t* req) {
uv_buf_t buf;
assert(handle->type == UV_TTY);
assert(handle->flags & UV_HANDLE_TTY_READABLE);
buf = handle->tty.rd.read_line_buffer;
handle->flags &= ~UV_HANDLE_READ_PENDING;
handle->tty.rd.read_line_buffer = uv_null_buf_;
if (!REQ_SUCCESS(req)) {
/* Read was not successful */
if (handle->flags & UV_HANDLE_READING) {
/* Real error */
handle->flags &= ~UV_HANDLE_READING;
DECREASE_ACTIVE_COUNT(loop, handle);
handle->read_cb((uv_stream_t*) handle,
uv_translate_sys_error(GET_REQ_ERROR(req)),
&buf);
}
} else {
if (!(handle->flags & UV_HANDLE_CANCELLATION_PENDING) &&
req->u.io.overlapped.InternalHigh != 0) {
/* Read successful. TODO: read unicode, convert to utf-8 */
DWORD bytes = req->u.io.overlapped.InternalHigh;
handle->read_cb((uv_stream_t*) handle, bytes, &buf);
}
handle->flags &= ~UV_HANDLE_CANCELLATION_PENDING;
}
/* Wait for more input events. */
if ((handle->flags & UV_HANDLE_READING) &&
!(handle->flags & UV_HANDLE_READ_PENDING)) {
uv_tty_queue_read(loop, handle);
}
DECREASE_PENDING_REQ_COUNT(handle);
}
void uv_process_tty_read_req(uv_loop_t* loop, uv_tty_t* handle,
uv_req_t* req) {
assert(handle->type == UV_TTY);
assert(handle->flags & UV_HANDLE_TTY_READABLE);
/* If the read_line_buffer member is zero, it must have been an raw read.
* Otherwise it was a line-buffered read. FIXME: This is quite obscure. Use a
* flag or something. */
if (handle->tty.rd.read_line_buffer.len == 0) {
uv_process_tty_read_raw_req(loop, handle, req);
} else {
uv_process_tty_read_line_req(loop, handle, req);
}
}
int uv_tty_read_start(uv_tty_t* handle, uv_alloc_cb alloc_cb,
uv_read_cb read_cb) {
uv_loop_t* loop = handle->loop;
if (!(handle->flags & UV_HANDLE_TTY_READABLE)) {
return ERROR_INVALID_PARAMETER;
}
handle->flags |= UV_HANDLE_READING;
INCREASE_ACTIVE_COUNT(loop, handle);
handle->read_cb = read_cb;
handle->alloc_cb = alloc_cb;
/* If reading was stopped and then started again, there could still be a read
* request pending. */
if (handle->flags & UV_HANDLE_READ_PENDING) {
return 0;
}
/* Maybe the user stopped reading half-way while processing key events.
* Short-circuit if this could be the case. */
if (handle->tty.rd.last_key_len > 0) {
SET_REQ_SUCCESS(&handle->read_req);
uv_insert_pending_req(handle->loop, (uv_req_t*) &handle->read_req);
/* Make sure no attempt is made to insert it again until it's handled. */
handle->flags |= UV_HANDLE_READ_PENDING;
handle->reqs_pending++;
return 0;
}
uv_tty_queue_read(loop, handle);
return 0;
}
int uv_tty_read_stop(uv_tty_t* handle) {
INPUT_RECORD record;
DWORD written, err;
handle->flags &= ~UV_HANDLE_READING;
DECREASE_ACTIVE_COUNT(handle->loop, handle);
if (!(handle->flags & UV_HANDLE_READ_PENDING))
return 0;
if (handle->flags & UV_HANDLE_TTY_RAW) {
/* Cancel raw read. Write some bullshit event to force the console wait to
* return. */
memset(&record, 0, sizeof record);
record.EventType = FOCUS_EVENT;
if (!WriteConsoleInputW(handle->handle, &record, 1, &written)) {
return GetLastError();
}
} else if (!(handle->flags & UV_HANDLE_CANCELLATION_PENDING)) {
/* Cancel line-buffered read if not already pending */
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));
/* Hold the output lock during the cancellation, to ensure that further
writes don't interfere with the screen state. It will be the ReadConsole
thread's responsibility to release the lock. */
uv_sem_wait(&uv_tty_output_lock);
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. */
uv_sem_post(&uv_tty_output_lock);
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) {
uv_tty_virtual_width = info->dwSize.X;
uv_tty_virtual_height = info->srWindow.Bottom - info->srWindow.Top + 1;
/* Recompute virtual window offset row. */
if (uv_tty_virtual_offset == -1) {
uv_tty_virtual_offset = info->dwCursorPosition.Y;
} else if (uv_tty_virtual_offset < info->dwCursorPosition.Y -
uv_tty_virtual_height + 1) {
/* If suddenly find the cursor outside of the virtual window, it must have
* somehow scrolled. Update the virtual window offset. */
uv_tty_virtual_offset = info->dwCursorPosition.Y -
uv_tty_virtual_height + 1;
}
if (uv_tty_virtual_offset + uv_tty_virtual_height > info->dwSize.Y) {
uv_tty_virtual_offset = info->dwSize.Y - uv_tty_virtual_height;
}
if (uv_tty_virtual_offset < 0) {
uv_tty_virtual_offset = 0;
}
}
static COORD uv_tty_make_real_coord(uv_tty_t* handle,
CONSOLE_SCREEN_BUFFER_INFO* info, int x, unsigned char x_relative, int y,
unsigned char y_relative) {
COORD result;
uv_tty_update_virtual_window(info);
/* Adjust y position */
if (y_relative) {
y = info->dwCursorPosition.Y + y;
} else {
y = uv_tty_virtual_offset + y;
}
/* Clip y to virtual client rectangle */
if (y < uv_tty_virtual_offset) {
y = uv_tty_virtual_offset;
} else if (y >= uv_tty_virtual_offset + uv_tty_virtual_height) {
y = uv_tty_virtual_offset + uv_tty_virtual_height - 1;
}
/* Adjust x */
if (x_relative) {
x = info->dwCursorPosition.X + x;
}
/* Clip x */
if (x < 0) {
x = 0;
} else if (x >= uv_tty_virtual_width) {
x = uv_tty_virtual_width - 1;
}
result.X = (unsigned short) x;
result.Y = (unsigned short) y;
return result;
}
static int uv_tty_emit_text(uv_tty_t* handle, WCHAR buffer[], DWORD length,
DWORD* error) {
DWORD written;
if (*error != ERROR_SUCCESS) {
return -1;
}
if (!WriteConsoleW(handle->handle,
(void*) buffer,
length,
&written,
NULL)) {
*error = GetLastError();
return -1;
}
return 0;
}
static int uv_tty_move_caret(uv_tty_t* handle, int x, unsigned char x_relative,
int y, unsigned char y_relative, DWORD* error) {
CONSOLE_SCREEN_BUFFER_INFO info;
COORD pos;
if (*error != ERROR_SUCCESS) {
return -1;
}
retry:
if (!GetConsoleScreenBufferInfo(handle->handle, &info)) {
*error = GetLastError();
}
pos = uv_tty_make_real_coord(handle, &info, x, x_relative, y, y_relative);
if (!SetConsoleCursorPosition(handle->handle, pos)) {
if (GetLastError() == ERROR_INVALID_PARAMETER) {
/* The console may be resized - retry */
goto retry;
} else {
*error = GetLastError();
return -1;
}
}
return 0;
}
static int uv_tty_reset(uv_tty_t* handle, DWORD* error) {
const COORD origin = {0, 0};
const WORD char_attrs = uv_tty_default_text_attributes;
CONSOLE_SCREEN_BUFFER_INFO info;
DWORD count, written;
if (*error != ERROR_SUCCESS) {
return -1;
}
/* Reset original text attributes. */
if (!SetConsoleTextAttribute(handle->handle, char_attrs)) {
*error = GetLastError();
return -1;
}
/* Move the cursor position to (0, 0). */
if (!SetConsoleCursorPosition(handle->handle, origin)) {
*error = GetLastError();
return -1;
}
/* Clear the screen buffer. */
retry:
if (!GetConsoleScreenBufferInfo(handle->handle, &info)) {
*error = GetLastError();
return -1;
}
count = info.dwSize.X * info.dwSize.Y;
if (!(FillConsoleOutputCharacterW(handle->handle,
L'\x20',
count,
origin,
&written) &&
FillConsoleOutputAttribute(handle->handle,
char_attrs,
written,
origin,
&written))) {
if (GetLastError() == ERROR_INVALID_PARAMETER) {
/* The console may be resized - retry */
goto retry;
} else {
*error = GetLastError();
return -1;
}
}
/* Move the virtual window up to the top. */
uv_tty_virtual_offset = 0;
uv_tty_update_virtual_window(&info);
return 0;
}
static int uv_tty_clear(uv_tty_t* handle, int dir, char entire_screen,
DWORD* error) {
CONSOLE_SCREEN_BUFFER_INFO info;
COORD start, end;
DWORD count, written;
int x1, x2, y1, y2;
int x1r, x2r, y1r, y2r;
if (*error != ERROR_SUCCESS) {
return -1;
}
if (dir == 0) {
/* Clear from current position */
x1 = 0;
x1r = 1;
} else {
/* Clear from column 0 */
x1 = 0;
x1r = 0;
}
if (dir == 1) {
/* Clear to current position */
x2 = 0;
x2r = 1;
} else {
/* Clear to end of row. We pretend the console is 65536 characters wide,
* uv_tty_make_real_coord will clip it to the actual console width. */
x2 = 0xffff;
x2r = 0;
}
if (!entire_screen) {
/* Stay on our own row */
y1 = y2 = 0;
y1r = y2r = 1;
} else {
/* Apply columns direction to row */
y1 = x1;
y1r = x1r;
y2 = x2;
y2r = x2r;
}
retry:
if (!GetConsoleScreenBufferInfo(handle->handle, &info)) {
*error = GetLastError();
return -1;
}
start = uv_tty_make_real_coord(handle, &info, x1, x1r, y1, y1r);
end = uv_tty_make_real_coord(handle, &info, x2, x2r, y2, y2r);
count = (end.Y * info.dwSize.X + end.X) -
(start.Y * info.dwSize.X + start.X) + 1;
if (!(FillConsoleOutputCharacterW(handle->handle,
L'\x20',
count,
start,
&written) &&
FillConsoleOutputAttribute(handle->handle,
info.wAttributes,
written,
start,
&written))) {
if (GetLastError() == ERROR_INVALID_PARAMETER) {
/* The console may be resized - retry */
goto retry;
} else {
*error = GetLastError();
return -1;
}
}
return 0;
}
#define FLIP_FGBG \
do { \
WORD fg = info.wAttributes & 0xF; \
WORD bg = info.wAttributes & 0xF0; \
info.wAttributes &= 0xFF00; \
info.wAttributes |= fg << 4; \
info.wAttributes |= bg >> 4; \
} while (0)
static int uv_tty_set_style(uv_tty_t* handle, DWORD* error) {
unsigned short argc = handle->tty.wr.ansi_csi_argc;
unsigned short* argv = handle->tty.wr.ansi_csi_argv;
int i;
CONSOLE_SCREEN_BUFFER_INFO info;
char fg_color = -1, bg_color = -1;
char fg_bright = -1, bg_bright = -1;
char inverse = -1;
if (argc == 0) {
/* Reset mode */
fg_color = uv_tty_default_fg_color;
bg_color = uv_tty_default_bg_color;
fg_bright = uv_tty_default_fg_bright;
bg_bright = uv_tty_default_bg_bright;
inverse = uv_tty_default_inverse;
}
for (i = 0; i < argc; i++) {
short arg = argv[i];
if (arg == 0) {
/* Reset mode */
fg_color = uv_tty_default_fg_color;
bg_color = uv_tty_default_bg_color;
fg_bright = uv_tty_default_fg_bright;
bg_bright = uv_tty_default_bg_bright;
inverse = uv_tty_default_inverse;
} else if (arg == 1) {
/* Foreground bright on */
fg_bright = 1;
} else if (arg == 2) {
/* Both bright off */
fg_bright = 0;
bg_bright = 0;
} else if (arg == 5) {
/* Background bright on */
bg_bright = 1;
} else if (arg == 7) {
/* Inverse: on */
inverse = 1;
} else if (arg == 21 || arg == 22) {
/* Foreground bright off */
fg_bright = 0;
} else if (arg == 25) {
/* Background bright off */
bg_bright = 0;
} else if (arg == 27) {
/* Inverse: off */
inverse = 0;
} else if (arg >= 30 && arg <= 37) {
/* Set foreground color */
fg_color = arg - 30;
} else if (arg == 39) {
/* Default text color */
fg_color = uv_tty_default_fg_color;
fg_bright = uv_tty_default_fg_bright;
} else if (arg >= 40 && arg <= 47) {
/* Set background color */
bg_color = arg - 40;
} else if (arg == 49) {
/* Default background color */
bg_color = uv_tty_default_bg_color;
bg_bright = uv_tty_default_bg_bright;
} else if (arg >= 90 && arg <= 97) {
/* Set bold foreground color */
fg_bright = 1;
fg_color = arg - 90;
} else if (arg >= 100 && arg <= 107) {
/* Set bold background color */
bg_bright = 1;
bg_color = arg - 100;
}
}
if (fg_color == -1 && bg_color == -1 && fg_bright == -1 &&
bg_bright == -1 && inverse == -1) {
/* Nothing changed */
return 0;
}
if (!GetConsoleScreenBufferInfo(handle->handle, &info)) {
*error = GetLastError();
return -1;
}
if ((info.wAttributes & COMMON_LVB_REVERSE_VIDEO) > 0) {
FLIP_FGBG;
}
if (fg_color != -1) {
info.wAttributes &= ~(FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE);
if (fg_color & 1) info.wAttributes |= FOREGROUND_RED;
if (fg_color & 2) info.wAttributes |= FOREGROUND_GREEN;
if (fg_color & 4) info.wAttributes |= FOREGROUND_BLUE;
}
if (fg_bright != -1) {
if (fg_bright) {
info.wAttributes |= FOREGROUND_INTENSITY;
} else {
info.wAttributes &= ~FOREGROUND_INTENSITY;
}
}
if (bg_color != -1) {
info.wAttributes &= ~(BACKGROUND_RED | BACKGROUND_GREEN | BACKGROUND_BLUE);
if (bg_color & 1) info.wAttributes |= BACKGROUND_RED;
if (bg_color & 2) info.wAttributes |= BACKGROUND_GREEN;
if (bg_color & 4) info.wAttributes |= BACKGROUND_BLUE;
}
if (bg_bright != -1) {
if (bg_bright) {
info.wAttributes |= BACKGROUND_INTENSITY;
} else {
info.wAttributes &= ~BACKGROUND_INTENSITY;
}
}
if (inverse != -1) {
if (inverse) {
info.wAttributes |= COMMON_LVB_REVERSE_VIDEO;
} else {
info.wAttributes &= ~COMMON_LVB_REVERSE_VIDEO;
}
}
if ((info.wAttributes & COMMON_LVB_REVERSE_VIDEO) > 0) {
FLIP_FGBG;
}
if (!SetConsoleTextAttribute(handle->handle, info.wAttributes)) {
*error = GetLastError();
return -1;
}
return 0;
}
static int uv_tty_save_state(uv_tty_t* handle, unsigned char save_attributes,
DWORD* error) {
CONSOLE_SCREEN_BUFFER_INFO info;
if (*error != ERROR_SUCCESS) {
return -1;
}
if (!GetConsoleScreenBufferInfo(handle->handle, &info)) {
*error = GetLastError();
return -1;
}
uv_tty_update_virtual_window(&info);
handle->tty.wr.saved_position.X = info.dwCursorPosition.X;
handle->tty.wr.saved_position.Y = info.dwCursorPosition.Y - uv_tty_virtual_offset;
handle->flags |= UV_HANDLE_TTY_SAVED_POSITION;
if (save_attributes) {
handle->tty.wr.saved_attributes = info.wAttributes &
(FOREGROUND_INTENSITY | BACKGROUND_INTENSITY);
handle->flags |= UV_HANDLE_TTY_SAVED_ATTRIBUTES;
}
return 0;
}
static int uv_tty_restore_state(uv_tty_t* handle,
unsigned char restore_attributes, DWORD* error) {
CONSOLE_SCREEN_BUFFER_INFO info;
WORD new_attributes;
if (*error != ERROR_SUCCESS) {
return -1;
}
if (handle->flags & UV_HANDLE_TTY_SAVED_POSITION) {
if (uv_tty_move_caret(handle,
handle->tty.wr.saved_position.X,
0,
handle->tty.wr.saved_position.Y,
0,
error) != 0) {
return -1;
}
}
if (restore_attributes &&
(handle->flags & UV_HANDLE_TTY_SAVED_ATTRIBUTES)) {
if (!GetConsoleScreenBufferInfo(handle->handle, &info)) {
*error = GetLastError();
return -1;
}
new_attributes = info.wAttributes;
new_attributes &= ~(FOREGROUND_INTENSITY | BACKGROUND_INTENSITY);
new_attributes |= handle->tty.wr.saved_attributes;
if (!SetConsoleTextAttribute(handle->handle, new_attributes)) {
*error = GetLastError();
return -1;
}
}
return 0;
}
static int uv_tty_set_cursor_visibility(uv_tty_t* handle,
BOOL visible,
DWORD* error) {
CONSOLE_CURSOR_INFO cursor_info;
if (!GetConsoleCursorInfo(handle->handle, &cursor_info)) {
*error = GetLastError();
return -1;
}
cursor_info.bVisible = visible;
if (!SetConsoleCursorInfo(handle->handle, &cursor_info)) {
*error = GetLastError();
return -1;
}
return 0;
}
static int uv_tty_write_bufs(uv_tty_t* handle,
const uv_buf_t bufs[],
unsigned int nbufs,
DWORD* error) {
/* We can only write 8k characters at a time. Windows can't handle much more
* characters in a single console write anyway. */
WCHAR utf16_buf[MAX_CONSOLE_CHAR];
WCHAR* utf16_buffer;
DWORD utf16_buf_used = 0;
unsigned int i, len, max_len, pos;
int allocate = 0;
#define FLUSH_TEXT() \
do { \
pos = 0; \
do { \
len = utf16_buf_used - pos; \
if (len > MAX_CONSOLE_CHAR) \
len = MAX_CONSOLE_CHAR; \
uv_tty_emit_text(handle, &utf16_buffer[pos], len, error); \
pos += len; \
} while (pos < utf16_buf_used); \
if (allocate) { \
uv__free(utf16_buffer); \
allocate = 0; \
utf16_buffer = utf16_buf; \
} \
utf16_buf_used = 0; \
} while (0)
#define ENSURE_BUFFER_SPACE(wchars_needed) \
if (wchars_needed > ARRAY_SIZE(utf16_buf) - utf16_buf_used) { \
FLUSH_TEXT(); \
}
/* Cache for fast access */
unsigned char utf8_bytes_left = handle->tty.wr.utf8_bytes_left;
unsigned int utf8_codepoint = handle->tty.wr.utf8_codepoint;
unsigned char previous_eol = handle->tty.wr.previous_eol;
unsigned char ansi_parser_state = handle->tty.wr.ansi_parser_state;
/* Store the error here. If we encounter an error, stop trying to do i/o but
* keep parsing the buffer so we leave the parser in a consistent state. */
*error = ERROR_SUCCESS;
utf16_buffer = utf16_buf;
uv_sem_wait(&uv_tty_output_lock);
for (i = 0; i < nbufs; i++) {
uv_buf_t buf = bufs[i];
unsigned int j;
if (uv__vterm_state == UV_SUPPORTED && buf.len > 0) {
utf16_buf_used = MultiByteToWideChar(CP_UTF8,
0,
buf.base,
buf.len,
NULL,
0);
if (utf16_buf_used == 0) {
*error = GetLastError();
break;
}
max_len = (utf16_buf_used + 1) * sizeof(WCHAR);
allocate = max_len > MAX_CONSOLE_CHAR;
if (allocate)
utf16_buffer = uv__malloc(max_len);
if (!MultiByteToWideChar(CP_UTF8,
0,
buf.base,
buf.len,
utf16_buffer,
utf16_buf_used)) {
if (allocate)
uv__free(utf16_buffer);
*error = GetLastError();
break;
}
FLUSH_TEXT();
continue;
}
for (j = 0; j < buf.len; j++) {
unsigned char c = buf.base[j];
/* Run the character through the utf8 decoder We happily accept non
* shortest form encodings and invalid code points - there's no real harm
* that can be done. */
if (utf8_bytes_left == 0) {
/* Read utf-8 start byte */
DWORD first_zero_bit;
unsigned char not_c = ~c;
#ifdef _MSC_VER /* msvc */
if (_BitScanReverse(&first_zero_bit, not_c)) {
#else /* assume gcc */
if (c != 0) {
first_zero_bit = (sizeof(int) * 8) - 1 - __builtin_clz(not_c);
#endif
if (first_zero_bit == 7) {
/* Ascii - pass right through */
utf8_codepoint = (unsigned int) c;
} else if (first_zero_bit <= 5) {
/* Multibyte sequence */
utf8_codepoint = (0xff >> (8 - first_zero_bit)) & c;
utf8_bytes_left = (char) (6 - first_zero_bit);
} else {
/* Invalid continuation */
utf8_codepoint = UNICODE_REPLACEMENT_CHARACTER;
}
} else {
/* 0xff -- invalid */
utf8_codepoint = UNICODE_REPLACEMENT_CHARACTER;
}
} else if ((c & 0xc0) == 0x80) {
/* Valid continuation of utf-8 multibyte sequence */
utf8_bytes_left--;
utf8_codepoint <<= 6;
utf8_codepoint |= ((unsigned int) c & 0x3f);
} else {
/* Start byte where continuation was expected. */
utf8_bytes_left = 0;
utf8_codepoint = UNICODE_REPLACEMENT_CHARACTER;
/* Patch buf offset so this character will be parsed again as a start
* byte. */
j--;
}
/* Maybe we need to parse more bytes to find a character. */
if (utf8_bytes_left != 0) {
continue;
}
/* Parse vt100/ansi escape codes */
if (ansi_parser_state == ANSI_NORMAL) {
switch (utf8_codepoint) {
case '\033':
ansi_parser_state = ANSI_ESCAPE_SEEN;
continue;
case 0233:
ansi_parser_state = ANSI_CSI;
handle->tty.wr.ansi_csi_argc = 0;
continue;
}
} else if (ansi_parser_state == ANSI_ESCAPE_SEEN) {
switch (utf8_codepoint) {
case '[':
ansi_parser_state = ANSI_CSI;
handle->tty.wr.ansi_csi_argc = 0;
continue;
case '^':
case '_':
case 'P':
case ']':
/* Not supported, but we'll have to parse until we see a stop code,
* e. g. ESC \ or BEL. */
ansi_parser_state = ANSI_ST_CONTROL;
continue;
case '\033':
/* Ignore double escape. */
continue;
case 'c':
/* Full console reset. */
FLUSH_TEXT();
uv_tty_reset(handle, error);
ansi_parser_state = ANSI_NORMAL;
continue;
case '7':
/* Save the cursor position and text attributes. */
FLUSH_TEXT();
uv_tty_save_state(handle, 1, error);
ansi_parser_state = ANSI_NORMAL;
continue;
case '8':
/* Restore the cursor position and text attributes */
FLUSH_TEXT();
uv_tty_restore_state(handle, 1, error);
ansi_parser_state = ANSI_NORMAL;
continue;
default:
if (utf8_codepoint >= '@' && utf8_codepoint <= '_') {
/* Single-char control. */
ansi_parser_state = ANSI_NORMAL;
continue;
} else {
/* Invalid - proceed as normal, */
ansi_parser_state = ANSI_NORMAL;
}
}
} else if (ansi_parser_state & ANSI_CSI) {
if (!(ansi_parser_state & ANSI_IGNORE)) {
if (utf8_codepoint >= '0' && utf8_codepoint <= '9') {
/* Parsing a numerical argument */
if (!(ansi_parser_state & ANSI_IN_ARG)) {
/* We were not currently parsing a number */
/* Check for too many arguments */
if (handle->tty.wr.ansi_csi_argc >= ARRAY_SIZE(handle->tty.wr.ansi_csi_argv)) {
ansi_parser_state |= ANSI_IGNORE;
continue;
}
ansi_parser_state |= ANSI_IN_ARG;
handle->tty.wr.ansi_csi_argc++;
handle->tty.wr.ansi_csi_argv[handle->tty.wr.ansi_csi_argc - 1] =
(unsigned short) utf8_codepoint - '0';
continue;
} else {
/* We were already parsing a number. Parse next digit. */
uint32_t value = 10 *
handle->tty.wr.ansi_csi_argv[handle->tty.wr.ansi_csi_argc - 1];
/* Check for overflow. */
if (value > UINT16_MAX) {
ansi_parser_state |= ANSI_IGNORE;
continue;
}
handle->tty.wr.ansi_csi_argv[handle->tty.wr.ansi_csi_argc - 1] =
(unsigned short) value + (utf8_codepoint - '0');
continue;
}
} else if (utf8_codepoint == ';') {
/* Denotes the end of an argument. */
if (ansi_parser_state & ANSI_IN_ARG) {
ansi_parser_state &= ~ANSI_IN_ARG;
continue;
} else {
/* If ANSI_IN_ARG is not set, add another argument and default it
* to 0. */
/* Check for too many arguments */
if (handle->tty.wr.ansi_csi_argc >= ARRAY_SIZE(handle->tty.wr.ansi_csi_argv)) {
ansi_parser_state |= ANSI_IGNORE;
continue;
}
handle->tty.wr.ansi_csi_argc++;
handle->tty.wr.ansi_csi_argv[handle->tty.wr.ansi_csi_argc - 1] = 0;
continue;
}
} else if (utf8_codepoint == '?' && !(ansi_parser_state & ANSI_IN_ARG) &&
handle->tty.wr.ansi_csi_argc == 0) {
/* Ignores '?' if it is the first character after CSI[. This is an
* extension character from the VT100 codeset that is supported and
* used by most ANSI terminals today. */
continue;
} else if (utf8_codepoint >= '@' && utf8_codepoint <= '~' &&
(handle->tty.wr.ansi_csi_argc > 0 || utf8_codepoint != '[')) {
int x, y, d;
/* Command byte */
switch (utf8_codepoint) {
case 'A':
/* cursor up */
FLUSH_TEXT();
y = -(handle->tty.wr.ansi_csi_argc ? handle->tty.wr.ansi_csi_argv[0] : 1);
uv_tty_move_caret(handle, 0, 1, y, 1, error);
break;
case 'B':
/* cursor down */
FLUSH_TEXT();
y = handle->tty.wr.ansi_csi_argc ? handle->tty.wr.ansi_csi_argv[0] : 1;
uv_tty_move_caret(handle, 0, 1, y, 1, error);
break;
case 'C':
/* cursor forward */
FLUSH_TEXT();
x = handle->tty.wr.ansi_csi_argc ? handle->tty.wr.ansi_csi_argv[0] : 1;
uv_tty_move_caret(handle, x, 1, 0, 1, error);
break;
case 'D':
/* cursor back */
FLUSH_TEXT();
x = -(handle->tty.wr.ansi_csi_argc ? handle->tty.wr.ansi_csi_argv[0] : 1);
uv_tty_move_caret(handle, x, 1, 0, 1, error);
break;
case 'E':
/* cursor next line */
FLUSH_TEXT();
y = handle->tty.wr.ansi_csi_argc ? handle->tty.wr.ansi_csi_argv[0] : 1;
uv_tty_move_caret(handle, 0, 0, y, 1, error);
break;
case 'F':
/* cursor previous line */
FLUSH_TEXT();
y = -(handle->tty.wr.ansi_csi_argc ? handle->tty.wr.ansi_csi_argv[0] : 1);
uv_tty_move_caret(handle, 0, 0, y, 1, error);
break;
case 'G':
/* cursor horizontal move absolute */
FLUSH_TEXT();
x = (handle->tty.wr.ansi_csi_argc >= 1 && handle->tty.wr.ansi_csi_argv[0])
? handle->tty.wr.ansi_csi_argv[0] - 1 : 0;
uv_tty_move_caret(handle, x, 0, 0, 1, error);
break;
case 'H':
case 'f':
/* cursor move absolute */
FLUSH_TEXT();
y = (handle->tty.wr.ansi_csi_argc >= 1 && handle->tty.wr.ansi_csi_argv[0])
? handle->tty.wr.ansi_csi_argv[0] - 1 : 0;
x = (handle->tty.wr.ansi_csi_argc >= 2 && handle->tty.wr.ansi_csi_argv[1])
? handle->tty.wr.ansi_csi_argv[1] - 1 : 0;
uv_tty_move_caret(handle, x, 0, y, 0, error);
break;
case 'J':
/* Erase screen */
FLUSH_TEXT();
d = handle->tty.wr.ansi_csi_argc ? handle->tty.wr.ansi_csi_argv[0] : 0;
if (d >= 0 && d <= 2) {
uv_tty_clear(handle, d, 1, error);
}
break;
case 'K':
/* Erase line */
FLUSH_TEXT();
d = handle->tty.wr.ansi_csi_argc ? handle->tty.wr.ansi_csi_argv[0] : 0;
if (d >= 0 && d <= 2) {
uv_tty_clear(handle, d, 0, error);
}
break;
case 'm':
/* Set style */
FLUSH_TEXT();
uv_tty_set_style(handle, error);
break;
case 's':
/* Save the cursor position. */
FLUSH_TEXT();
uv_tty_save_state(handle, 0, error);
break;
case 'u':
/* Restore the cursor position */
FLUSH_TEXT();
uv_tty_restore_state(handle, 0, error);
break;
case 'l':
/* Hide the cursor */
if (handle->tty.wr.ansi_csi_argc == 1 &&
handle->tty.wr.ansi_csi_argv[0] == 25) {
FLUSH_TEXT();
uv_tty_set_cursor_visibility(handle, 0, error);
}
break;
case 'h':
/* Show the cursor */
if (handle->tty.wr.ansi_csi_argc == 1 &&
handle->tty.wr.ansi_csi_argv[0] == 25) {
FLUSH_TEXT();
uv_tty_set_cursor_visibility(handle, 1, error);
}
break;
}
/* Sequence ended - go back to normal state. */
ansi_parser_state = ANSI_NORMAL;
continue;
} else {
/* We don't support commands that use private mode characters or
* intermediaries. Ignore the rest of the sequence. */
ansi_parser_state |= ANSI_IGNORE;
continue;
}
} else {
/* We're ignoring this command. Stop only on command character. */
if (utf8_codepoint >= '@' && utf8_codepoint <= '~') {
ansi_parser_state = ANSI_NORMAL;
}
continue;
}
} else if (ansi_parser_state & ANSI_ST_CONTROL) {
/* Unsupported control code.
* Ignore everything until we see `BEL` or `ESC \`. */
if (ansi_parser_state & ANSI_IN_STRING) {
if (!(ansi_parser_state & ANSI_BACKSLASH_SEEN)) {
if (utf8_codepoint == '"') {
ansi_parser_state &= ~ANSI_IN_STRING;
} else if (utf8_codepoint == '\\') {
ansi_parser_state |= ANSI_BACKSLASH_SEEN;
}
} else {
ansi_parser_state &= ~ANSI_BACKSLASH_SEEN;
}
} else {
if (utf8_codepoint == '\007' || (utf8_codepoint == '\\' &&
(ansi_parser_state & ANSI_ESCAPE_SEEN))) {
/* End of sequence */
ansi_parser_state = ANSI_NORMAL;
} else if (utf8_codepoint == '\033') {
/* Escape character */
ansi_parser_state |= ANSI_ESCAPE_SEEN;
} else if (utf8_codepoint == '"') {
/* String starting */
ansi_parser_state |= ANSI_IN_STRING;
ansi_parser_state &= ~ANSI_ESCAPE_SEEN;
ansi_parser_state &= ~ANSI_BACKSLASH_SEEN;
} else {
ansi_parser_state &= ~ANSI_ESCAPE_SEEN;
}
}
continue;
} else {
/* Inconsistent state */
abort();
}
/* We wouldn't mind emitting utf-16 surrogate pairs. Too bad, the windows
* console doesn't really support UTF-16, so just emit the replacement
* character. */
if (utf8_codepoint > 0xffff) {
utf8_codepoint = UNICODE_REPLACEMENT_CHARACTER;
}
if (utf8_codepoint == 0x0a || utf8_codepoint == 0x0d) {
/* EOL conversion - emit \r\n when we see \n. */
if (utf8_codepoint == 0x0a && previous_eol != 0x0d) {
/* \n was not preceded by \r; print \r\n. */
ENSURE_BUFFER_SPACE(2);
utf16_buf[utf16_buf_used++] = L'\r';
utf16_buf[utf16_buf_used++] = L'\n';
} else if (utf8_codepoint == 0x0d && previous_eol == 0x0a) {
/* \n was followed by \r; do not print the \r, since the source was
* either \r\n\r (so the second \r is redundant) or was \n\r (so the
* \n was processed by the last case and an \r automatically
* inserted). */
} else {
/* \r without \n; print \r as-is. */
ENSURE_BUFFER_SPACE(1);
utf16_buf[utf16_buf_used++] = (WCHAR) utf8_codepoint;
}
previous_eol = (char) utf8_codepoint;
} else if (utf8_codepoint <= 0xffff) {
/* Encode character into utf-16 buffer. */
ENSURE_BUFFER_SPACE(1);
utf16_buf[utf16_buf_used++] = (WCHAR) utf8_codepoint;
previous_eol = 0;
}
}
}
/* Flush remaining characters */
FLUSH_TEXT();
/* Copy cached values back to struct. */
handle->tty.wr.utf8_bytes_left = utf8_bytes_left;
handle->tty.wr.utf8_codepoint = utf8_codepoint;
handle->tty.wr.previous_eol = previous_eol;
handle->tty.wr.ansi_parser_state = ansi_parser_state;
uv_sem_post(&uv_tty_output_lock);
if (*error == STATUS_SUCCESS) {
return 0;
} else {
return -1;
}
#undef FLUSH_TEXT
}
int uv_tty_write(uv_loop_t* loop,
uv_write_t* req,
uv_tty_t* handle,
const uv_buf_t bufs[],
unsigned int nbufs,
uv_write_cb cb) {
DWORD error;
UV_REQ_INIT(req, UV_WRITE);
req->handle = (uv_stream_t*) handle;
req->cb = cb;
handle->reqs_pending++;
handle->stream.conn.write_reqs_pending++;
REGISTER_HANDLE_REQ(loop, handle, req);
req->u.io.queued_bytes = 0;
if (!uv_tty_write_bufs(handle, bufs, nbufs, &error)) {
SET_REQ_SUCCESS(req);
} else {
SET_REQ_ERROR(req, error);
}
uv_insert_pending_req(loop, (uv_req_t*) req);
return 0;
}
int uv__tty_try_write(uv_tty_t* handle,
const uv_buf_t bufs[],
unsigned int nbufs) {
DWORD error;
if (handle->stream.conn.write_reqs_pending > 0)
return UV_EAGAIN;
if (uv_tty_write_bufs(handle, bufs, nbufs, &error))
return uv_translate_sys_error(error);
return uv__count_bufs(bufs, nbufs);
}
void uv_process_tty_write_req(uv_loop_t* loop, uv_tty_t* handle,
uv_write_t* req) {
int err;
handle->write_queue_size -= req->u.io.queued_bytes;
UNREGISTER_HANDLE_REQ(loop, handle, req);
if (req->cb) {
err = GET_REQ_ERROR(req);
req->cb(req, uv_translate_sys_error(err));
}
handle->stream.conn.write_reqs_pending--;
if (handle->stream.conn.shutdown_req != NULL &&
handle->stream.conn.write_reqs_pending == 0) {
uv_want_endgame(loop, (uv_handle_t*)handle);
}
DECREASE_PENDING_REQ_COUNT(handle);
}
void uv_tty_close(uv_tty_t* handle) {
assert(handle->u.fd == -1 || handle->u.fd > 2);
if (handle->flags & UV_HANDLE_READING)
uv_tty_read_stop(handle);
if (handle->u.fd == -1)
CloseHandle(handle->handle);
else
close(handle->u.fd);
handle->u.fd = -1;
handle->handle = INVALID_HANDLE_VALUE;
handle->flags &= ~(UV_HANDLE_READABLE | UV_HANDLE_WRITABLE);
uv__handle_closing(handle);
if (handle->reqs_pending == 0) {
uv_want_endgame(handle->loop, (uv_handle_t*) handle);
}
}
void uv_tty_endgame(uv_loop_t* loop, uv_tty_t* handle) {
if (!(handle->flags & UV_HANDLE_TTY_READABLE) &&
handle->stream.conn.shutdown_req != NULL &&
handle->stream.conn.write_reqs_pending == 0) {
UNREGISTER_HANDLE_REQ(loop, handle, handle->stream.conn.shutdown_req);
/* TTY shutdown is really just a no-op */
if (handle->stream.conn.shutdown_req->cb) {
if (handle->flags & UV_HANDLE_CLOSING) {
handle->stream.conn.shutdown_req->cb(handle->stream.conn.shutdown_req, UV_ECANCELED);
} else {
handle->stream.conn.shutdown_req->cb(handle->stream.conn.shutdown_req, 0);
}
}
handle->stream.conn.shutdown_req = NULL;
DECREASE_PENDING_REQ_COUNT(handle);
return;
}
if (handle->flags & UV_HANDLE_CLOSING &&
handle->reqs_pending == 0) {
/* The wait handle used for raw reading should be unregistered when the
* wait callback runs. */
assert(!(handle->flags & UV_HANDLE_TTY_READABLE) ||
handle->tty.rd.read_raw_wait == NULL);
assert(!(handle->flags & UV_HANDLE_CLOSED));
uv__handle_close(handle);
}
}
/*
* uv_process_tty_accept_req() is a stub to keep DELEGATE_STREAM_REQ working
* TODO: find a way to remove it
*/
void uv_process_tty_accept_req(uv_loop_t* loop, uv_tty_t* handle,
uv_req_t* raw_req) {
abort();
}
/*
* uv_process_tty_connect_req() is a stub to keep DELEGATE_STREAM_REQ working
* TODO: find a way to remove it
*/
void uv_process_tty_connect_req(uv_loop_t* loop, uv_tty_t* handle,
uv_connect_t* req) {
abort();
}
int uv_tty_reset_mode(void) {
/* Not necessary to do anything. */
return 0;
}
/* Determine whether or not this version of windows supports
* proper ANSI color codes. Should be supported as of windows
* 10 version 1511, build number 10.0.10586.
*/
static void uv__determine_vterm_state(HANDLE handle) {
DWORD dwMode = 0;
if (!GetConsoleMode(handle, &dwMode)) {
uv__vterm_state = UV_UNSUPPORTED;
return;
}
dwMode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING;
if (!SetConsoleMode(handle, dwMode)) {
uv__vterm_state = UV_UNSUPPORTED;
return;
}
uv__vterm_state = UV_SUPPORTED;
}
static DWORD WINAPI uv__tty_console_resize_message_loop_thread(void* param) {
CONSOLE_SCREEN_BUFFER_INFO sb_info;
NTSTATUS status;
ULONG_PTR conhost_pid;
MSG msg;
if (!GetConsoleScreenBufferInfo(uv__tty_console_handle, &sb_info))
return 0;
uv__tty_console_width = sb_info.dwSize.X;
uv__tty_console_height = sb_info.srWindow.Bottom - sb_info.srWindow.Top + 1;
if (pSetWinEventHook == NULL || pNtQueryInformationProcess == NULL)
return 0;
status = pNtQueryInformationProcess(GetCurrentProcess(),
ProcessConsoleHostProcess,
&conhost_pid,
sizeof(conhost_pid),
NULL);
if (!NT_SUCCESS(status))
/* We couldn't retrieve our console host process, probably because this
* is a 32-bit process running on 64-bit Windows. Fall back to receiving
* console events from all processes. */
conhost_pid = 0;
/* Ensure the PID is a multiple of 4, which is required by SetWinEventHook */
conhost_pid &= ~(ULONG_PTR)0x3;
if (!pSetWinEventHook(EVENT_CONSOLE_LAYOUT,
EVENT_CONSOLE_LAYOUT,
NULL,
uv__tty_console_resize_event,
(DWORD)conhost_pid,
0,
WINEVENT_OUTOFCONTEXT))
return 0;
while (GetMessage(&msg, NULL, 0, 0)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return 0;
}
static void CALLBACK uv__tty_console_resize_event(HWINEVENTHOOK hWinEventHook,
DWORD event,
HWND hwnd,
LONG idObject,
LONG idChild,
DWORD dwEventThread,
DWORD dwmsEventTime) {
CONSOLE_SCREEN_BUFFER_INFO sb_info;
int width, height;
if (!GetConsoleScreenBufferInfo(uv__tty_console_handle, &sb_info))
return;
width = sb_info.dwSize.X;
height = sb_info.srWindow.Bottom - sb_info.srWindow.Top + 1;
if (width != uv__tty_console_width || height != uv__tty_console_height) {
uv__tty_console_width = width;
uv__tty_console_height = height;
uv__signal_dispatch(SIGWINCH);
}
}