tty-test.c 4.3 KB

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