neovim

Neovim text editor
git clone https://git.dasho.dev/neovim.git
Log | Files | Refs | README

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 }