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