/* SPDX-License-Identifier: BSD-3-Clause * Copyright (c) 2020 Dmitry Kozlyuk */ #include #include #include "cmdline_private.h" /* Missing from some MinGW-w64 distributions. */ #ifndef ENABLE_VIRTUAL_TERMINAL_PROCESSING #define ENABLE_VIRTUAL_TERMINAL_PROCESSING 0x0004 #endif #ifndef ENABLE_VIRTUAL_TERMINAL_INPUT #define ENABLE_VIRTUAL_TERMINAL_INPUT 0x0200 #endif void terminal_adjust(struct cmdline *cl) { HANDLE handle; DWORD mode; ZeroMemory(&cl->oldterm, sizeof(cl->oldterm)); /* Detect console input, set it up and make it emulate VT100. */ handle = GetStdHandle(STD_INPUT_HANDLE); if (GetConsoleMode(handle, &mode)) { cl->oldterm.is_console_input = 1; cl->oldterm.input_mode = mode; mode &= ~( ENABLE_LINE_INPUT | /* no line buffering */ ENABLE_ECHO_INPUT | /* no echo */ ENABLE_PROCESSED_INPUT | /* pass Ctrl+C to program */ ENABLE_MOUSE_INPUT | /* no mouse events */ ENABLE_WINDOW_INPUT); /* no window resize events */ mode |= ENABLE_VIRTUAL_TERMINAL_INPUT; SetConsoleMode(handle, mode); } /* Detect console output and make it emulate VT100. */ handle = GetStdHandle(STD_OUTPUT_HANDLE); if (GetConsoleMode(handle, &mode)) { cl->oldterm.is_console_output = 1; cl->oldterm.output_mode = mode; mode &= ~ENABLE_WRAP_AT_EOL_OUTPUT; mode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING; SetConsoleMode(handle, mode); } } void terminal_restore(const struct cmdline *cl) { if (cl->oldterm.is_console_input) { HANDLE handle = GetStdHandle(STD_INPUT_HANDLE); SetConsoleMode(handle, cl->oldterm.input_mode); } if (cl->oldterm.is_console_output) { HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE); SetConsoleMode(handle, cl->oldterm.output_mode); } } static int cmdline_is_key_down(const INPUT_RECORD *record) { return (record->EventType == KEY_EVENT) && record->Event.KeyEvent.bKeyDown; } static int cmdline_poll_char_console(HANDLE handle) { INPUT_RECORD record; DWORD events; if (!PeekConsoleInput(handle, &record, 1, &events)) { /* Simulate poll(3) behavior on EOF. */ return (GetLastError() == ERROR_HANDLE_EOF) ? 1 : -1; } if ((events == 0) || !cmdline_is_key_down(&record)) return 0; return 1; } static int cmdline_poll_char_file(struct cmdline *cl, HANDLE handle) { DWORD type = GetFileType(handle); /* Since console is handled by cmdline_poll_char_console(), * this is either a serial port or input handle had been replaced. */ if (type == FILE_TYPE_CHAR) return cmdline_poll_char_console(handle); /* PeekNamedPipe() can handle all pipes and also sockets. */ if (type == FILE_TYPE_PIPE) { DWORD bytes_avail; if (!PeekNamedPipe(handle, NULL, 0, NULL, &bytes_avail, NULL)) return (GetLastError() == ERROR_BROKEN_PIPE) ? 1 : -1; return bytes_avail ? 1 : 0; } /* There is no straightforward way to peek a file in Windows * I/O model. Read the byte, if it is not the end of file, * buffer it for subsequent read. This will not work with * a file being appended and probably some other edge cases. */ if (type == FILE_TYPE_DISK) { char c; int ret; ret = _read(cl->s_in, &c, sizeof(c)); if (ret == 1) { cl->repeat_count = 1; cl->repeated_char = c; } return ret; } /* GetFileType() failed or file of unknown type, * which we do not know how to peek anyway. */ return -1; } int cmdline_poll_char(struct cmdline *cl) { HANDLE handle = (HANDLE)_get_osfhandle(cl->s_in); return cl->oldterm.is_console_input ? cmdline_poll_char_console(handle) : cmdline_poll_char_file(cl, handle); } ssize_t cmdline_read_char(struct cmdline *cl, char *c) { HANDLE handle; INPUT_RECORD record; KEY_EVENT_RECORD *key; DWORD events; if (!cl->oldterm.is_console_input) return _read(cl->s_in, c, 1); /* Return repeated strokes from previous event. */ if (cl->repeat_count > 0) { *c = cl->repeated_char; cl->repeat_count--; return 1; } handle = (HANDLE)_get_osfhandle(cl->s_in); key = &record.Event.KeyEvent; do { if (!ReadConsoleInput(handle, &record, 1, &events)) { if (GetLastError() == ERROR_HANDLE_EOF) { *c = EOF; return 0; } return -1; } } while (!cmdline_is_key_down(&record)); *c = key->uChar.AsciiChar; /* Save repeated strokes from a single event. */ if (key->wRepeatCount > 1) { cl->repeated_char = *c; cl->repeat_count = key->wRepeatCount - 1; } return 1; } int cmdline_vdprintf(int fd, const char *format, va_list op) { int copy, ret; FILE *file; copy = _dup(fd); if (copy < 0) return -1; file = _fdopen(copy, "a"); if (file == NULL) { _close(copy); return -1; } ret = vfprintf(file, format, op); fclose(file); /* also closes copy */ return ret; }