tty-test.c (4711B)
1 #include <stdbool.h> 2 #include <stdio.h> 3 #include <stdlib.h> 4 #include <uv.h> 5 #ifdef MSWIN 6 # include <windows.h> 7 #else 8 # include <unistd.h> 9 #endif 10 11 #define is_terminal(stream) (uv_guess_handle(fileno(stream)) == UV_TTY) 12 #define BUF_SIZE 0xfff 13 #define CTRL_C 0x03 14 15 uv_tty_t tty; 16 uv_tty_t tty_out; 17 18 bool owns_tty(void); // silence -Wmissing-prototypes 19 bool owns_tty(void) 20 { 21 #ifdef MSWIN 22 // XXX: We need to make proper detect owns tty 23 // HWND consoleWnd = GetConsoleWindow(); 24 // DWORD dwProcessId; 25 // GetWindowThreadProcessId(consoleWnd, &dwProcessId); 26 // return GetCurrentProcessId() == dwProcessId; 27 return true; 28 #else 29 return getsid(0) == getpid(); 30 #endif 31 } 32 33 static void walk_cb(uv_handle_t *handle, void *arg) 34 { 35 if (!uv_is_closing(handle)) { 36 #ifdef MSWIN 37 uv_tty_set_mode(&tty, UV_TTY_MODE_NORMAL); 38 #endif 39 uv_close(handle, NULL); 40 } 41 } 42 43 #ifndef MSWIN 44 static void sig_handler(int signum) 45 { 46 switch (signum) { 47 case SIGWINCH: { 48 int width, height; 49 uv_tty_get_winsize(&tty, &width, &height); 50 fprintf(stderr, "rows: %d, cols: %d\n", height, width); 51 return; 52 } 53 case SIGHUP: 54 exit(42); // arbitrary exit code to test against 55 return; 56 default: 57 return; 58 } 59 } 60 #endif 61 62 #ifdef MSWIN 63 static void sigwinch_cb(uv_signal_t *handle, int signum) 64 { 65 int width, height; 66 uv_tty_get_winsize(&tty_out, &width, &height); 67 fprintf(stderr, "rows: %d, cols: %d\n", height, width); 68 } 69 #endif 70 71 static void alloc_cb(uv_handle_t *handle, size_t suggested, uv_buf_t *buf) 72 { 73 buf->len = BUF_SIZE; 74 buf->base = malloc(BUF_SIZE); 75 } 76 77 static void read_cb(uv_stream_t *stream, ssize_t cnt, const uv_buf_t *buf) 78 { 79 if (cnt <= 0) { 80 uv_read_stop(stream); 81 return; 82 } 83 84 int *interrupted = stream->data; 85 86 for (int i = 0; i < cnt; i++) { 87 if (buf->base[i] == CTRL_C) { 88 (*interrupted)++; 89 } 90 } 91 92 uv_loop_t write_loop; 93 uv_loop_init(&write_loop); 94 uv_tty_t out; 95 uv_tty_init(&write_loop, &out, fileno(stdout), 0); 96 97 uv_write_t req; 98 uv_buf_t b = { 99 .base = buf->base, 100 #ifdef MSWIN 101 .len = (ULONG)cnt 102 #else 103 .len = (size_t)cnt 104 #endif 105 }; 106 uv_write(&req, (uv_stream_t *)&out, &b, 1, NULL); 107 uv_run(&write_loop, UV_RUN_DEFAULT); 108 109 uv_close((uv_handle_t *)&out, NULL); 110 uv_run(&write_loop, UV_RUN_DEFAULT); 111 if (uv_loop_close(&write_loop)) { 112 abort(); 113 } 114 free(buf->base); 115 116 if (*interrupted >= 2) { 117 uv_walk(uv_default_loop(), walk_cb, NULL); 118 } else if (*interrupted == 1) { 119 fprintf(stderr, "interrupt received, press again to exit\n"); 120 } 121 } 122 123 static void prepare_cb(uv_prepare_t *handle) 124 { 125 fprintf(stderr, "tty ready\n"); 126 uv_prepare_stop(handle); 127 } 128 129 int main(int argc, char **argv) 130 { 131 if (!owns_tty()) { 132 fprintf(stderr, "process does not own the terminal\n"); 133 exit(2); 134 } 135 136 if (!is_terminal(stdin)) { 137 fprintf(stderr, "stdin is not a terminal\n"); 138 exit(2); 139 } 140 141 if (!is_terminal(stdout)) { 142 fprintf(stderr, "stdout is not a terminal\n"); 143 exit(2); 144 } 145 146 if (!is_terminal(stderr)) { 147 fprintf(stderr, "stderr is not a terminal\n"); 148 exit(2); 149 } 150 151 if (argc > 1) { 152 errno = 0; 153 int count = (int)strtol(argv[1], NULL, 10); 154 if (errno != 0) { 155 abort(); 156 } 157 count = (count < 0 || count > 99999) ? 0 : count; 158 for (int i = 0; i < count; i++) { 159 printf("line%d\n", i); 160 } 161 fflush(stdout); 162 return 0; 163 } 164 165 int interrupted = 0; 166 uv_prepare_t prepare; 167 uv_prepare_init(uv_default_loop(), &prepare); 168 uv_prepare_start(&prepare, prepare_cb); 169 #ifndef MSWIN 170 uv_tty_init(uv_default_loop(), &tty, fileno(stderr), 1); 171 #else 172 uv_tty_init(uv_default_loop(), &tty, fileno(stdin), 1); 173 uv_tty_init(uv_default_loop(), &tty_out, fileno(stdout), 0); 174 int width, height; 175 uv_tty_get_winsize(&tty_out, &width, &height); 176 #endif 177 uv_tty_set_mode(&tty, UV_TTY_MODE_RAW); 178 #ifdef MSWIN 179 DWORD dwMode; 180 if (GetConsoleMode(tty.handle, &dwMode)) { 181 dwMode |= ENABLE_VIRTUAL_TERMINAL_INPUT; 182 SetConsoleMode(tty.handle, dwMode); 183 } 184 if (GetConsoleMode(tty_out.handle, &dwMode)) { 185 dwMode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING; 186 SetConsoleMode(tty_out.handle, dwMode); 187 } 188 #endif 189 tty.data = &interrupted; 190 uv_read_start((uv_stream_t *)&tty, alloc_cb, read_cb); 191 #ifndef MSWIN 192 struct sigaction sa; 193 sigemptyset(&sa.sa_mask); 194 sa.sa_flags = 0; 195 sa.sa_handler = sig_handler; 196 sigaction(SIGHUP, &sa, NULL); 197 sigaction(SIGWINCH, &sa, NULL); 198 #else 199 uv_signal_t sigwinch_watcher; 200 uv_signal_init(uv_default_loop(), &sigwinch_watcher); 201 uv_signal_start(&sigwinch_watcher, sigwinch_cb, SIGWINCH); 202 #endif 203 uv_run(uv_default_loop(), UV_RUN_DEFAULT); 204 205 #ifndef MSWIN 206 // XXX: Without this the SIGHUP handler is skipped on some systems. 207 sleep(100); 208 #endif 209 210 return 0; 211 }