← Back to davo.co
summaryrefslogtreecommitdiffstats
path: root/termin.c
blob: 5de89da82df269bc5202ecef2e272d3b0bd61e5f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
#include "termin.h"
#include "c23-fallback.h"
#include <termios.h>
#include <stdio.h>
#include <stdbool.h>
#ifndef __STDC_NO_THREADS__
# include <threads.h>
#endif
#include <ctype.h>
#include <string.h>

/* Store the flags as they were set before termin_unbuffered. */
static tcflag_t termin_echo;

/* The flags that we manipulate. */
#define TERMIN_FLAGS (ECHO | ECHONL | ICANON)

void termin_reset(void) {
  struct termios term;
  tcgetattr(0, &term);
  term.c_lflag |= termin_echo;
  tcsetattr(0, TCSAFLUSH, &term);
}

void termin_unbuffered(void) {
  // have stdin unbuffered on stdio level
  setvbuf(stdin, 0, _IONBF, 0);
  // store the terminal settings with respect to the flags
  struct termios term;
  tcgetattr(0, &term);
  termin_echo = term.c_lflag & TERMIN_FLAGS;
  // install exit handlers
  atexit(termin_reset);
  at_quick_exit(termin_reset);
  // change the terminal settings to raw mode
  term.c_lflag &= ~TERMIN_FLAGS;
  tcsetattr(0, TCSAFLUSH, &term);
}

static unsigned minc = 0;
static unsigned maxc = UCHAR_MAX;

static
void term_init(void) {
  while (!termin_trans[minc]) ++minc;
  while (!termin_trans[maxc]) --maxc;
}

#ifndef __STDC_NO_THREADS__
static once_flag term_once = ONCE_FLAG_INIT;
#endif

static
bool termin_read_csi(size_t len, char command[len]) {
  for (char *p = command, *stop = command+len-1; p < stop; ++p) {
    int c = getchar();
    // catch all: EOF, 0, control characters
    if (c < ' ') return false;
    p[0] = c;
    if ('@' <= c && c <= '~') {
      p[1] = 0;
      return true;
    }
  }
  return false;
}

char const* termin_read_esc(size_t len, char command[len]) {
  command[1] = getchar();
  switch (command[1]) {
  case '[':
    return
      termin_read_csi(len-2, command+2)
      ? command
      : 0;
  case 'N':
  case 'O':
  case 'Z':
  case '%':
    command[2] = getchar();
    command[3] = 0;
    return command;
  default:
    ungetc(command[1], stdin);
    return nullptr;
  }
}

char termin_translate(char const* command) {
  call_once(&term_once, term_init);
  for (unsigned c = minc; c <= maxc; ++c) {
    if (termin_trans[c] && !strcmp(command, termin_trans[c])) {
      return c;
    }
  }
  return 0;
}