An easy-to-use terminal library
git clone git://soophie.de/so-term
1
#ifndef LIB_TERM_H2
#define LIB_TERM_H1
#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,
29
KEY_END,30
KEY_PAGE_UP,31
KEY_PAGE_DOWN,17
} keycode_t;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, size_t len);37
typedef struct {38
struct termios termios;39
char *buffer;21
int len;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;24
void term_panic(const char *s);25
void term_disable_raw_mode(void);26
void term_enable_raw_mode(void);27
int term_read_cursor_pos(int *rows, int *cols);28
int term_read_window_size(int *rows, int *cols);29
int term_poll_key(int timeout);30
int term_read_key(void);31
void term_write(char *str);32
void term_writef(const char *format, ...);33
void term_flush(void);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_IMPL
64
#include <stdlib.h>65
#include <string.h>66
#include <unistd.h>41
#include <termios.h>67
#include <sys/ioctl.h>68
#include <errno.h>69
#include <stdarg.h>70
#include <poll.h>47
struct termios termios;48
term_t term = { NULL, 0 };50
void term_panic(const char *str) {51
write(STDOUT_FILENO, "\x1b[2J", 4);52
write(STDOUT_FILENO, "\x1b[H", 3);53
write(STDOUT_FILENO, "\x1b[?25h", 6);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
}58
void term_disable_raw_mode(void) {59
if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &termios) == -1) {60
term_panic("tcsetattr");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
}64
void term_enable_raw_mode(void) {65
if (tcgetattr(STDIN_FILENO, &termios) == -1) {66
term_panic("tcgetattr");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
}68
atexit(term_disable_raw_mode);69
struct termios raw = termios;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);
124
raw.c_cc[VMIN] = 0;125
raw.c_cc[VTIME] = 1;126
if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw) == -1) {77
term_panic("tcsetattr");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
}81
int term_read_cursor_pos(int *rows, int *cols) {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, 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;84
if (write(STDOUT_FILENO, "\x1b[6n", 4) != 4) {194
if (write(self->fd, "\x1b[6n", 4) != 4) {195
return -1;196
}197
while (i < sizeof(buf) - 1) {
207
if (buf[0] != '\x1b' || buf[1] != '[') {208
return -1;209
}100
if (sscanf(&buf[2], "%d;%d", rows, cols) != 2) {210
if (sscanf(&buf[2], "%zu;%zu", rows, cols) != 2) {211
return -1;212
}103
return -0;213
return 0;214
}106
int term_read_window_size(int *rows, int *cols) {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, 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;108
if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) == -1 || ws.ws_col == 0) {109
if (write(STDOUT_FILENO, "\x1b[999C\x1b[999B", 12) != 12) {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
}112
return term_read_cursor_pos(rows, cols);233
return term_read_cursor(self, rows, cols);234
}235
else {236
*cols = ws.ws_col;
239
}240
}121
int term_poll_key(int timeout) {122
struct pollfd fds[1];123
fds[0].fd = STDIN_FILENO;124
fds[0].events = POLLIN | POLLPRI;125
if (poll(fds, 1, timeout)) {126
return term_read_key();242
int term_read_key(term_t *self) {243
if (self->teleterm_mode) {244
void *data = self->fetch(self->fd, TERM_CODE_KEY_READ, 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
}128
return 0;129
}131
int term_read_key(void) {253
int len;254
char c;255
while ((len = read(STDIN_FILENO, &c, 1)) != 1) {256
if (len == -1 && errno != EAGAIN) {136
term_panic("read");257
_term_panic(self, "read");258
}259
}260
if (c == '\x1b') {
319
return c;320
}201
void term_write(char *str) {202
int str_len = strlen(str);203
char *new_buffer = realloc(term.buffer, sizeof(char) * (term.len + str_len + 1));204
if (new_buffer == NULL) {205
fprintf(stderr, "Cannot realloc memory!\n");206
return;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, 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
}208
term.buffer = new_buffer;209
memcpy(term.buffer + term.len, str, str_len);210
term.len += str_len;211
term.buffer[term.len] = '\0';212
}214
void term_writef(const char *format, ...) {215
va_list args;216
va_start(args, format);217
char str[1000];218
vsnprintf(str, sizeof(str), format, args);219
term_write(str);220
va_end(args);221
}223
void term_flush(void) {224
if (term.len > 0) {225
write(STDOUT_FILENO, term.buffer, term.len);226
free(term.buffer);227
term.buffer = NULL;228
term.len = 0;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_IMPL233
#endif // LIB_TERM_H343
#endif // _LIB_TERM_H