An easy-to-use terminal library
git clone git://soophie.de/so-term
blob: c2455bc0725eddbdd872fc0aaaff20aa08f97a2a (8113 bytes)[doc][blame][raw]
1
#ifndef _LIB_TERM_H2
#define _LIB_TERM_H4
#include <stddef.h>5
#include <stdbool.h>6
#include <termios.h>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;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;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);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;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);61
#ifdef LIB_TERM_IMPL63
#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>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
}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
}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
}101
void term_fd_set(term_t *self, int fd) {102
self->fd = fd;103
}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
}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
}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
}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
}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
}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
}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
}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
}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
}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
}342
#endif // LIB_TERM_IMPL343
#endif // _LIB_TERM_H