1 /* SPDX-License-Identifier: BSD-3-Clause 2 * Copyright (c) 2020 Dmitry Kozlyuk 3 */ 4 5 #include <io.h> 6 7 #include "cmdline_private.h" 8 9 /* Missing from some MinGW-w64 distributions. */ 10 #ifndef ENABLE_VIRTUAL_TERMINAL_PROCESSING 11 #define ENABLE_VIRTUAL_TERMINAL_PROCESSING 0x0004 12 #endif 13 14 #ifndef ENABLE_VIRTUAL_TERMINAL_INPUT 15 #define ENABLE_VIRTUAL_TERMINAL_INPUT 0x0200 16 #endif 17 18 void 19 terminal_adjust(struct cmdline *cl) 20 { 21 HANDLE handle; 22 DWORD mode; 23 24 ZeroMemory(&cl->oldterm, sizeof(cl->oldterm)); 25 26 /* Detect console input, set it up and make it emulate VT100. */ 27 handle = GetStdHandle(STD_INPUT_HANDLE); 28 if (GetConsoleMode(handle, &mode)) { 29 cl->oldterm.is_console_input = 1; 30 cl->oldterm.input_mode = mode; 31 32 mode &= ~( 33 ENABLE_LINE_INPUT | /* no line buffering */ 34 ENABLE_ECHO_INPUT | /* no echo */ 35 ENABLE_PROCESSED_INPUT | /* pass Ctrl+C to program */ 36 ENABLE_MOUSE_INPUT | /* no mouse events */ 37 ENABLE_WINDOW_INPUT); /* no window resize events */ 38 mode |= ENABLE_VIRTUAL_TERMINAL_INPUT; 39 SetConsoleMode(handle, mode); 40 } 41 42 /* Detect console output and make it emulate VT100. */ 43 handle = GetStdHandle(STD_OUTPUT_HANDLE); 44 if (GetConsoleMode(handle, &mode)) { 45 cl->oldterm.is_console_output = 1; 46 cl->oldterm.output_mode = mode; 47 48 mode &= ~ENABLE_WRAP_AT_EOL_OUTPUT; 49 mode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING; 50 SetConsoleMode(handle, mode); 51 } 52 } 53 54 void 55 terminal_restore(const struct cmdline *cl) 56 { 57 if (cl->oldterm.is_console_input) { 58 HANDLE handle = GetStdHandle(STD_INPUT_HANDLE); 59 SetConsoleMode(handle, cl->oldterm.input_mode); 60 } 61 62 if (cl->oldterm.is_console_output) { 63 HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE); 64 SetConsoleMode(handle, cl->oldterm.output_mode); 65 } 66 } 67 68 static int 69 cmdline_is_key_down(const INPUT_RECORD *record) 70 { 71 return (record->EventType == KEY_EVENT) && 72 record->Event.KeyEvent.bKeyDown; 73 } 74 75 static int 76 cmdline_poll_char_console(HANDLE handle) 77 { 78 INPUT_RECORD record; 79 DWORD events; 80 81 if (!PeekConsoleInput(handle, &record, 1, &events)) { 82 /* Simulate poll(3) behavior on EOF. */ 83 return (GetLastError() == ERROR_HANDLE_EOF) ? 1 : -1; 84 } 85 86 if ((events == 0) || !cmdline_is_key_down(&record)) 87 return 0; 88 89 return 1; 90 } 91 92 static int 93 cmdline_poll_char_file(struct cmdline *cl, HANDLE handle) 94 { 95 DWORD type = GetFileType(handle); 96 97 /* Since console is handled by cmdline_poll_char_console(), 98 * this is either a serial port or input handle had been replaced. 99 */ 100 if (type == FILE_TYPE_CHAR) 101 return cmdline_poll_char_console(handle); 102 103 /* PeekNamedPipe() can handle all pipes and also sockets. */ 104 if (type == FILE_TYPE_PIPE) { 105 DWORD bytes_avail; 106 if (!PeekNamedPipe(handle, NULL, 0, NULL, &bytes_avail, NULL)) 107 return (GetLastError() == ERROR_BROKEN_PIPE) ? 1 : -1; 108 return bytes_avail ? 1 : 0; 109 } 110 111 /* There is no straightforward way to peek a file in Windows 112 * I/O model. Read the byte, if it is not the end of file, 113 * buffer it for subsequent read. This will not work with 114 * a file being appended and probably some other edge cases. 115 */ 116 if (type == FILE_TYPE_DISK) { 117 char c; 118 int ret; 119 120 ret = _read(cl->s_in, &c, sizeof(c)); 121 if (ret == 1) { 122 cl->repeat_count = 1; 123 cl->repeated_char = c; 124 } 125 return ret; 126 } 127 128 /* GetFileType() failed or file of unknown type, 129 * which we do not know how to peek anyway. 130 */ 131 return -1; 132 } 133 134 int 135 cmdline_poll_char(struct cmdline *cl) 136 { 137 HANDLE handle = (HANDLE)_get_osfhandle(cl->s_in); 138 return cl->oldterm.is_console_input ? 139 cmdline_poll_char_console(handle) : 140 cmdline_poll_char_file(cl, handle); 141 } 142 143 ssize_t 144 cmdline_read_char(struct cmdline *cl, char *c) 145 { 146 HANDLE handle; 147 INPUT_RECORD record; 148 KEY_EVENT_RECORD *key; 149 DWORD events; 150 151 if (!cl->oldterm.is_console_input) 152 return _read(cl->s_in, c, 1); 153 154 /* Return repeated strokes from previous event. */ 155 if (cl->repeat_count > 0) { 156 *c = cl->repeated_char; 157 cl->repeat_count--; 158 return 1; 159 } 160 161 handle = (HANDLE)_get_osfhandle(cl->s_in); 162 key = &record.Event.KeyEvent; 163 do { 164 if (!ReadConsoleInput(handle, &record, 1, &events)) { 165 if (GetLastError() == ERROR_HANDLE_EOF) { 166 *c = EOF; 167 return 0; 168 } 169 return -1; 170 } 171 } while (!cmdline_is_key_down(&record)); 172 173 *c = key->uChar.AsciiChar; 174 175 /* Save repeated strokes from a single event. */ 176 if (key->wRepeatCount > 1) { 177 cl->repeated_char = *c; 178 cl->repeat_count = key->wRepeatCount - 1; 179 } 180 181 return 1; 182 } 183 184 int 185 cmdline_vdprintf(int fd, const char *format, va_list op) 186 { 187 int copy, ret; 188 FILE *file; 189 190 copy = _dup(fd); 191 if (copy < 0) 192 return -1; 193 194 file = _fdopen(copy, "a"); 195 if (file == NULL) { 196 _close(copy); 197 return -1; 198 } 199 200 ret = vfprintf(file, format, op); 201 202 fclose(file); /* also closes copy */ 203 204 return ret; 205 } 206