QUOTE: Be the change, make a difference.

libterm

An easy-to-use terminal library

libterm.h (8113B)


      1 #ifndef _LIB_TERM_H
      2 #define _LIB_TERM_H
      3 
      4 #include <stddef.h>
      5 #include <stdbool.h>
      6 #include <termios.h>
      7 
      8 typedef enum {
      9   TERM_CODE_RAWMODE_ENABLE,
     10   TERM_CODE_RAWMODE_DISABLE,
     11   TERM_CODE_CURSOR_READ,
     12   TERM_CODE_WINDOW_READ,
     13   TERM_CODE_KEY_POLL,
     14   TERM_CODE_KEY_READ,
     15   TERM_CODE_WRITE,
     16   TERM_CODE_FLUSH,
     17 } term_code_t;
     18 
     19 typedef enum {
     20   KEY_ENTER = 13,
     21   KEY_ESC = 27,
     22   KEY_BACKSPACE = 127,
     23   KEY_ARROW_LEFT = 1000,
     24   KEY_ARROW_RIGHT,
     25   KEY_ARROW_UP,
     26   KEY_ARROW_DOWN,
     27   KEY_DEL,
     28   KEY_HOME,
     29   KEY_END,
     30   KEY_PAGE_UP,
     31   KEY_PAGE_DOWN,
     32 } term_keycode_t;
     33 
     34 typedef void (*term_dispatch_fn_t)(int fd, term_code_t code, void *data, size_t len);
     35 typedef void *(*term_fetch_fn_t)(int fd, term_code_t code, void *data, size_t len, size_t ret_len);
     36 
     37 typedef struct {
     38   struct termios termios;
     39   char *buffer;
     40   size_t len;
     41   bool teleterm_mode;
     42   term_dispatch_fn_t dispatch;
     43   term_fetch_fn_t fetch;
     44   int fd;
     45 } term_t;
     46 
     47 term_t term_init(void);
     48 void term_cleanup(term_t *self);
     49 void term_fd_set(term_t *self, int fd);
     50 void term_teleterm_enable(term_t *self, term_dispatch_fn_t dispatch, term_fetch_fn_t fetch);
     51 void term_rawmode_enable(term_t *self);
     52 void term_rawmode_disable(term_t *self);
     53 void term_write(term_t *self,  char *str);
     54 void term_writef(term_t *self, const char *format, ...);
     55 void term_flush(term_t *self);
     56 int term_read_cursor(term_t *self, size_t *rows, size_t *cols);
     57 int term_read_window(term_t *self, size_t *rows, size_t *cols);
     58 int term_read_key(term_t *self);
     59 int term_poll_key(term_t *self, int timeout_ms);
     60 
     61 #ifdef LIB_TERM_IMPL
     62 
     63 #include <stdio.h>
     64 #include <stdlib.h>
     65 #include <string.h>
     66 #include <unistd.h>
     67 #include <sys/ioctl.h>
     68 #include <errno.h>
     69 #include <stdarg.h>
     70 #include <poll.h>
     71 
     72 void _term_panic(term_t *self, const char *str) {
     73   write(self->fd, "\x1b[2J", 4);
     74   write(self->fd, "\x1b[H", 3);
     75   write(self->fd, "\x1b[?25h", 6);
     76   perror(str);
     77   exit(EXIT_FAILURE);
     78 }
     79 
     80 term_t term_init(void) {
     81   return (term_t) {
     82     .buffer = NULL,
     83     .len = 0,
     84     .teleterm_mode = false,
     85     .dispatch = NULL,
     86     .fetch = NULL,
     87     .fd = STDOUT_FILENO,
     88   };
     89 }
     90 
     91 void term_cleanup(term_t *self) {
     92   if (self) {
     93     if (self->buffer) {
     94       free(self->buffer);
     95       self->buffer = NULL;
     96       self->len = 0;
     97     }
     98   }
     99 }
    100 
    101 void term_fd_set(term_t *self, int fd) {
    102   self->fd = fd;
    103 }
    104 
    105 void term_teleterm_enable(term_t *self, term_dispatch_fn_t dispatch, term_fetch_fn_t fetch) {
    106   self->teleterm_mode = true;
    107   self->dispatch = dispatch;
    108   self->fetch = fetch;
    109 }
    110 
    111 void term_rawmode_enable(term_t *self) {
    112   if (self->teleterm_mode) {
    113     self->dispatch(self->fd, TERM_CODE_RAWMODE_ENABLE, NULL, 0);
    114     return;
    115   }
    116   if (tcgetattr(STDIN_FILENO, &self->termios) == -1) {
    117     _term_panic(self, "tcgetattr");
    118   }
    119   struct termios raw = self->termios;
    120   raw.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON);
    121   raw.c_oflag &= ~(OPOST);
    122   raw.c_cflag |= (CS8);
    123   raw.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG);
    124   raw.c_cc[VMIN] = 0;
    125   raw.c_cc[VTIME] = 1;
    126   if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw) == -1) {
    127     _term_panic(self, "tcsetattr");
    128   }
    129 }
    130 
    131 void term_rawmode_disable(term_t *self) {
    132   if (self->teleterm_mode) {
    133     self->dispatch(self->fd, TERM_CODE_RAWMODE_DISABLE, NULL, 0);
    134     return;
    135   }
    136   if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &self->termios) == -1) {
    137     _term_panic(self, "tcsetattr");
    138   }
    139 }
    140 
    141 void term_write(term_t *self,  char *str) {
    142   if (self->teleterm_mode) {
    143     self->dispatch(self->fd, TERM_CODE_WRITE, NULL, 0);
    144   }
    145   size_t str_len = strlen(str);
    146   char *new_buffer = (char *) realloc(self->buffer, sizeof(char) * (self->len + str_len + 1));
    147   if (new_buffer == NULL) {
    148     fprintf(stderr, "Cannot realloc memory!\n");
    149     return;
    150   }
    151   self->buffer = new_buffer;
    152   memcpy(self->buffer + self->len, str, str_len);
    153   self->len += str_len;
    154   self->buffer[self->len] = '\0';
    155 }
    156 
    157 void term_writef(term_t *self, const char *format, ...) {
    158   va_list args;
    159   va_start(args, format);
    160   char str[1000];
    161   vsnprintf(str, sizeof(str), format, args);
    162   term_write(self, str);
    163   va_end(args);
    164 }
    165 
    166 void term_flush(term_t *self) {
    167   if (self->len > 0) {
    168     if (self->teleterm_mode) {
    169       self->dispatch(self->fd, TERM_CODE_FLUSH, self->buffer, self->len);
    170     }
    171     else {
    172       write(self->fd, self->buffer, self->len);
    173     }
    174     free(self->buffer);
    175     self->buffer = NULL;
    176     self->len = 0;
    177   }
    178 }
    179 
    180 int term_read_cursor(term_t *self, size_t *rows, size_t *cols) {
    181   if (self->teleterm_mode) {
    182     void *data = self->fetch(self->fd, TERM_CODE_CURSOR_READ, NULL, 0, 2 * sizeof(size_t));
    183     if (data) {
    184       size_t *tuple = (size_t *) data;
    185       *rows = tuple[0];
    186       *cols = tuple[1];
    187       free(data);
    188       return 0;
    189     }
    190     return -1;
    191   }
    192   char buf[32];
    193   unsigned int i = 0;
    194   if (write(self->fd, "\x1b[6n", 4) != 4) {
    195     return -1;
    196   }
    197   while (i < sizeof(buf) - 1) {
    198     if (read(STDIN_FILENO, &buf[i], 1) != 1) {
    199       break;
    200     }
    201     if (buf[i] == 'R') {
    202       break;
    203     }
    204     i++;
    205   }
    206   buf[i] = '\0';
    207   if (buf[0] != '\x1b' || buf[1] != '[') {
    208     return -1;
    209   }
    210   if (sscanf(&buf[2], "%zu;%zu", rows, cols) != 2) {
    211     return -1;
    212   }
    213   return 0;
    214 }
    215 
    216 int term_read_window(term_t *self, size_t *rows, size_t *cols) {
    217   if (self->teleterm_mode) {
    218     void *data = self->fetch(self->fd, TERM_CODE_WINDOW_READ, NULL, 0, 2 * sizeof(size_t));
    219     if (data) {
    220       size_t *tuple = (size_t *) data;
    221       *rows = tuple[0];
    222       *cols = tuple[1];
    223       free(data);
    224       return 0;
    225     }
    226     return -1;
    227   }
    228   struct winsize ws;
    229   if (ioctl(self->fd, TIOCGWINSZ, &ws) == -1 || ws.ws_col == 0) {
    230     if (write(self->fd, "\x1b[999C\x1b[999B", 12) != 12) {
    231       return -1;
    232     }
    233     return term_read_cursor(self, rows, cols);
    234   }
    235   else {
    236     *cols = ws.ws_col;
    237     *rows = ws.ws_row;
    238     return 0;
    239   }
    240 }
    241 
    242 int term_read_key(term_t *self) {
    243   if (self->teleterm_mode) {
    244     void *data = self->fetch(self->fd, TERM_CODE_KEY_READ, NULL, 0, sizeof(int));
    245     if (data) {
    246       int *single = (int *) data;
    247       int key = *single;
    248       free(data);
    249       return key;
    250     }
    251     return -1;
    252   }
    253   int len;
    254   char c;
    255   while ((len = read(STDIN_FILENO, &c, 1)) != 1) {
    256     if (len == -1 && errno != EAGAIN) {
    257       _term_panic(self, "read");
    258     }
    259   }
    260   if (c == '\x1b') {
    261     char seq[3];
    262     if (read(STDIN_FILENO, &seq[0], 1) != 1) {
    263       return '\x1b';
    264     }
    265     if (read(STDIN_FILENO, &seq[1], 1) != 1) {
    266       return '\x1b';
    267     }
    268     if (seq[0] == '[') {
    269       if (seq[1] >= '0' && seq[1] <= '9') {
    270         if (read(STDIN_FILENO, &seq[2], 1) != 1) {
    271           return '\x1b';
    272         }
    273         if (seq[2] == '~') {
    274           switch (seq[1]) {
    275             case '1':
    276               return KEY_HOME;
    277             case '3':
    278               return KEY_DEL;
    279             case '4':
    280               return KEY_END;
    281             case '5':
    282               return KEY_PAGE_UP;
    283             case '6':
    284               return KEY_PAGE_DOWN;
    285             case '7':
    286               return KEY_HOME;
    287             case '8':
    288               return KEY_END;
    289           }
    290         }
    291       }
    292       else {
    293         switch (seq[1]) {
    294           case 'A':
    295             return KEY_ARROW_UP;
    296           case 'B':
    297             return KEY_ARROW_DOWN;
    298           case 'C':
    299             return KEY_ARROW_RIGHT;
    300           case 'D':
    301             return KEY_ARROW_LEFT;
    302           case 'H':
    303             return KEY_HOME;
    304           case 'F':
    305             return KEY_END;
    306         }
    307         return '\x1b';
    308       }
    309     }
    310     else if (seq[0] == 'O') {
    311       switch (seq[1]) {
    312         case 'H':
    313           return KEY_HOME;
    314         case 'F':
    315           return KEY_END;
    316       }
    317     }
    318   }
    319   return c;
    320 }
    321 
    322 int term_poll_key(term_t *self, int timeout_ms) {
    323   if (self->teleterm_mode) {
    324     void *data = self->fetch(self->fd, TERM_CODE_KEY_POLL, &timeout_ms, sizeof(timeout_ms), sizeof(int));
    325     if (data) {
    326       int *single = (int *) data;
    327       int key = *single;
    328       free(data);
    329       return key;
    330     }
    331     return -1;
    332   }
    333   struct pollfd fds[1];
    334   fds[0].fd = STDIN_FILENO;
    335   fds[0].events = POLLIN | POLLPRI;
    336   if (poll(fds, 1, timeout_ms)) {
    337     return term_read_key(self);
    338   }
    339   return 0;
    340 }
    341 
    342 #endif // LIB_TERM_IMPL
    343 #endif // _LIB_TERM_H