An easy-to-use terminal library
git clone git://soophie.de/so-term
1
install:2
sudo cp src/libterm.h /usr/local/include/2
mkdir -p /usr/local/include/so3
sudo cp src/term.h /usr/local/include/so/5
uninstall:5
sudo rm /usr/local/include/libterm.h6
sudo rm /usr/local/include/so/term.h
1
#ifndef _LIB_TERM_H2
#define _LIB_TERM_H4
#include <stddef.h>5
#include <stdbool.h>7
typedef enum {8
TERM_CODE_RAWMODE_ENABLE,9
TERM_CODE_RAWMODE_DISABLE,10
TERM_CODE_CURSOR_READ,11
TERM_CODE_WINDOW_READ,12
TERM_CODE_KEY_POLL,13
TERM_CODE_KEY_READ,14
TERM_CODE_WRITE,15
TERM_CODE_FLUSH,16
} term_code_t;18
typedef enum {19
KEY_ENTER = 13,20
KEY_ESC = 27,21
KEY_BACKSPACE = 127,22
KEY_ARROW_LEFT = 1000,23
KEY_ARROW_RIGHT,24
KEY_ARROW_UP,25
KEY_ARROW_DOWN,26
KEY_DEL,27
KEY_HOME,28
KEY_END,29
KEY_PAGE_UP,30
KEY_PAGE_DOWN,31
} term_key_t;33
typedef struct term term_t;35
typedef void (*term_dispatch_fn_t)(term_t *self, term_code_t code, void *data, size_t len);36
typedef void *(*term_fetch_fn_t)(term_t *self, term_code_t code, void *data, size_t len, size_t ret_len);38
struct term {39
char *buffer;40
size_t len;41
term_dispatch_fn_t dispatch;42
term_fetch_fn_t fetch;43
int fd;44
};46
term_t term_init(void);47
void term_cleanup(term_t *self);48
void term_rawmode_enable(term_t *self);49
void term_rawmode_disable(term_t *self);50
void term_write(term_t *self, char *str);51
void term_writef(term_t *self, const char *format, ...);52
void term_flush(term_t *self);53
int term_read_cursor(term_t *self, size_t *rows, size_t *cols);54
int term_read_window(term_t *self, size_t *rows, size_t *cols);55
int term_read_key(term_t *self);56
int term_poll_key(term_t *self, int timeout_ms);58
#ifdef LIB_TERM_IMPL60
#include <stdio.h>61
#include <stdlib.h>62
#include <string.h>63
#include <unistd.h>64
#include <errno.h>65
#include <stdarg.h>67
void _term_panic(term_t *self, const char *str) {68
write(self->fd, "\x1b[2J", 4);69
write(self->fd, "\x1b[H", 3);70
write(self->fd, "\x1b[?25h", 6);71
perror(str);72
exit(EXIT_FAILURE);73
}75
#ifdef LIB_TERM_POSIX77
#include <termios.h>78
#include <sys/ioctl.h>79
#include <poll.h>81
static struct termios _posix_termios;83
static void posix_rawmode_enable(term_t *self) {84
if (tcgetattr(STDIN_FILENO, &_posix_termios) == -1) {85
_term_panic(self, "tcgetattr");86
}87
struct termios raw = _posix_termios;88
raw.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON);89
raw.c_oflag &= ~(OPOST);90
raw.c_cflag |= (CS8);91
raw.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG);92
raw.c_cc[VMIN] = 0;93
raw.c_cc[VTIME] = 1;94
if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw) == -1) {95
_term_panic(self, "tcsetattr");96
}97
}99
static void posix_rawmode_disable(term_t *self) {100
if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &_posix_termios) == -1) {101
_term_panic(self, "tcsetattr");102
}103
}105
static void posix_write(term_t *self, void *data, size_t len) {106
(void) self;107
(void) data;108
(void) len;109
}111
static void posix_flush(term_t *self, void *data, size_t len) {112
(void) data;113
(void) len;114
if (self->len > 0) {115
write(self->fd, self->buffer, self->len);116
free(self->buffer);117
self->buffer = NULL;118
self->len = 0;119
}120
}122
static void *posix_read_cursor(term_t *self, void *data, size_t len) {123
(void) data;124
(void) len;125
char buf[32];126
unsigned int i = 0;127
if (write(self->fd, "\x1b[6n", 4) != 4) {128
return NULL;129
}130
while (i < sizeof(buf) - 1) {131
if (read(STDIN_FILENO, &buf[i], 1) != 1) {132
break;133
}134
if (buf[i] == 'R') {135
break;136
}137
i++;138
}139
buf[i] = '\0';140
if (buf[0] != '\x1b' || buf[1] != '[') {141
return NULL;142
}143
size_t *tuple = malloc(2 * sizeof(size_t));144
if (sscanf(&buf[2], "%zu;%zu", &tuple[0], &tuple[1]) != 2) {145
free(tuple);146
return NULL;147
}148
return tuple;149
}151
static void *posix_read_window(term_t *self, void *data, size_t len) {152
struct winsize ws;153
if (ioctl(self->fd, TIOCGWINSZ, &ws) == -1 || ws.ws_col == 0) {154
if (write(self->fd, "\x1b[999C\x1b[999B", 12) == 12) {155
return posix_read_cursor(self, data, len);156
}157
}158
else {159
size_t *tuple = malloc(2 * sizeof(size_t));160
tuple[0] = ws.ws_row;161
tuple[1] = ws.ws_col;162
return tuple;163
}164
return NULL;165
}167
static void *posix_read_key(term_t *self) {168
int len;169
char c;170
int key;171
while ((len = read(STDIN_FILENO, &c, 1)) != 1) {172
if (len == -1 && errno != EAGAIN) {173
_term_panic(self, "read");174
}175
}176
if (c == '\x1b') {177
char seq[3];178
if (read(STDIN_FILENO, &seq[0], 1) != 1) {179
key = '\x1b';180
}181
if (read(STDIN_FILENO, &seq[1], 1) != 1) {182
key = '\x1b';183
}184
if (seq[0] == '[') {185
if (seq[1] >= '0' && seq[1] <= '9') {186
if (read(STDIN_FILENO, &seq[2], 1) != 1) {187
key = '\x1b';188
}189
if (seq[2] == '~') {190
switch (seq[1]) {191
case '1':192
key = KEY_HOME;193
break;194
case '3':195
key = KEY_DEL;196
break;197
case '4':198
key = KEY_END;199
break;200
case '5':201
key = KEY_PAGE_UP;202
break;203
case '6':204
key = KEY_PAGE_DOWN;205
break;206
case '7':207
key = KEY_HOME;208
break;209
case '8':210
key = KEY_END;211
break;212
default:213
break;214
}215
}216
}217
else {218
switch (seq[1]) {219
case 'A':220
key = KEY_ARROW_UP;221
break;222
case 'B':223
key = KEY_ARROW_DOWN;224
break;225
case 'C':226
key = KEY_ARROW_RIGHT;227
break;228
case 'D':229
key = KEY_ARROW_LEFT;230
break;231
case 'H':232
key = KEY_HOME;233
break;234
case 'F':235
key = KEY_END;236
break;237
default:238
key = '\x1b';239
break;240
}241
}242
}243
else if (seq[0] == 'O') {244
switch (seq[1]) {245
case 'H':246
key = KEY_HOME;247
break;248
case 'F':249
key = KEY_END;250
break;251
default:252
break;253
}254
}255
}256
else {257
key = c;258
}259
int *ret = malloc(sizeof(int));260
*ret = key;261
return ret;262
}264
static void *posix_poll_key(term_t *self, void *data, size_t len) {265
(void) len;266
struct pollfd fds[1];267
fds[0].fd = STDIN_FILENO;268
fds[0].events = POLLIN | POLLPRI;269
int timeout_ms = *(int *) data;270
if (poll(fds, 1, timeout_ms)) {271
return posix_read_key(self);272
}273
int *ret = malloc(sizeof(int));274
*ret = 0;275
return ret;276
}278
static void posix_dispatch(term_t *self, term_code_t code, void *data, size_t len) {279
switch (code) {280
case TERM_CODE_RAWMODE_ENABLE:281
posix_rawmode_enable(self);282
break;283
case TERM_CODE_RAWMODE_DISABLE:284
posix_rawmode_disable(self);285
break;286
case TERM_CODE_WRITE:287
posix_write(self, data, len);288
break;289
case TERM_CODE_FLUSH:290
posix_flush(self, data, len);291
break;292
default:293
break;294
}295
}297
static void *posix_fetch(term_t *self, term_code_t code, void *data, size_t len, size_t ret_len) {298
(void) ret_len;299
switch (code) {300
case TERM_CODE_CURSOR_READ:301
return posix_read_cursor(self, data, len);302
case TERM_CODE_WINDOW_READ:303
return posix_read_window(self, data, len);304
case TERM_CODE_KEY_READ:305
return posix_read_key(self);306
case TERM_CODE_KEY_POLL:307
return posix_poll_key(self, data, len);308
default:309
return NULL;310
}311
}313
#endif // LIB_TERM_POSIX315
#ifdef LIB_TERM_WINDOWS317
#include <windows.h>318
#include <conio.h>320
static DWORD _windows_console_mode;322
static void windows_rawmode_enable(term_t *self) {323
HANDLE handle = GetStdHandle(STD_INPUT_HANDLE);324
DWORD mode;325
if (!GetConsoleMode(handle, &mode)) {326
_term_panic(self, "GetConsoleMode");327
}328
_windows_console_mode = mode;329
mode &= ~(ENABLE_ECHO_INPUT | ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT);330
if (!SetConsoleMode(handle, mode)) {331
_term_panic(self, "SetConsoleMode");332
}333
}335
static void windows_rawmode_disable(term_t *self) {336
HANDLE handle = GetStdHandle(STD_INPUT_HANDLE);337
if (!SetConsoleMode(handle, _windows_console_mode)) {338
_term_panic(self, "SetConsoleMode");339
}340
}342
static void windows_write(term_t *self, void *data, size_t len) {343
DWORD written;344
WriteConsole(GetStdHandle(self->fd), data, (DWORD) len, &written, NULL);345
}347
static void windows_flush(term_t *self, void *data, size_t len) {348
(void) data;349
(void) len;350
if (self->len > 0) {351
DWORD written;352
WriteConsole(GetStdHandle(self->fd), self->buffer, (DWORD) self->len, &written, NULL);353
free(self->buffer);354
self->buffer = NULL;355
self->len = 0;356
}357
}359
static void *windows_read_cursor(term_t *self, void *data, size_t len) {360
(void) data;361
(void) len;362
CONSOLE_SCREEN_BUFFER_INFO console_info;363
if (!GetConsoleScreenBufferInfo(GetStdHandle(self->fd), &console_info)) {364
return NULL;365
}366
size_t *tuple = malloc(2 * sizeof(size_t));367
tuple[0] = console_info.dwCursorPosition.Y + 1;368
tuple[1] = console_info.dwCursorPosition.X + 1;369
return tuple;370
}372
static void *windows_read_window(term_t *self, void *data, size_t len) {373
(void) data;374
(void) len;375
CONSOLE_SCREEN_BUFFER_INFO console_info;376
if (!GetConsoleScreenBufferInfo(GetStdHandle(self->fd), &console_info)) {377
return NULL;378
}379
size_t *tuple = malloc(2 * sizeof(size_t));380
tuple[0] = console_info.srWindow.Bottom - console_info.srWindow.Top + 1;381
tuple[1] = console_info.srWindow.Right - console_info.srWindow.Left + 1;382
return tuple;383
}385
static void *windows_read_key(term_t *self) {386
(void) self;387
int c = _getch();388
if (c == 0 || c == 224) {389
int ext = _getch();390
switch (ext) {391
case 72:392
c = KEY_ARROW_UP;393
break;394
case 80:395
c = KEY_ARROW_DOWN;396
break;397
case 75:398
c = KEY_ARROW_LEFT;399
break;400
case 77:401
c = KEY_ARROW_RIGHT;402
break;403
case 71:404
c = KEY_HOME;405
break;406
case 79:407
c = KEY_END;408
break;409
case 73:410
c = KEY_PAGE_UP;411
break;412
case 81:413
c = KEY_PAGE_DOWN;414
break;415
case 83:416
c = KEY_DEL;417
break;418
default:419
c = 0;420
break;421
}422
}423
int *ret = malloc(sizeof(int));424
*ret = c;425
return ret;426
}428
static void *windows_poll_key(term_t *self, void *data, size_t len) {429
(void) len;430
int timeout_ms = *(int *) data;431
DWORD start = GetTickCount();432
while (GetTickCount() - start < (DWORD) timeout_ms) {433
if (_kbhit()) {434
return windows_read_key(self);435
}436
Sleep(1);437
}438
int *ret = malloc(sizeof(int));439
*ret = 0;440
return ret;441
}443
static void windows_dispatch(term_t *self, term_code_t code, void *data, size_t len) {444
switch (code) {445
case TERM_CODE_RAWMODE_ENABLE:446
windows_rawmode_enable(self);447
break;448
case TERM_CODE_RAWMODE_DISABLE:449
windows_rawmode_disable(self);450
break;451
case TERM_CODE_WRITE:452
windows_write(self, data, len);453
break;454
case TERM_CODE_FLUSH:455
windows_flush(self, data, len);456
break;457
default:458
break;459
}460
}462
static void *windows_fetch(term_t *self, term_code_t code, void *data, size_t len, size_t ret_len) {463
(void) ret_len;464
switch (code) {465
case TERM_CODE_CURSOR_READ:466
return windows_read_cursor(self, data, len);467
case TERM_CODE_WINDOW_READ:468
return windows_read_window(self, data, len);469
case TERM_CODE_KEY_READ:470
return windows_read_key(self);471
case TERM_CODE_KEY_POLL:472
return windows_poll_key(self, data, len);473
default:474
return NULL;475
}476
}478
#endif // LIB_TERM_WINDOWS480
term_t term_init(void) {481
term_t self = {482
.buffer = NULL,483
.len = 0,484
.dispatch = NULL,485
.fetch = NULL,486
.fd = 0,487
};488
#ifdef LIB_TERM_POSIX489
self.dispatch = posix_dispatch;490
self.fetch = posix_fetch;491
self.fd = STDOUT_FILENO;492
#endif /* LIB_TERM_POSIX */493
#ifdef LIB_TERM_WINDOWS494
self.dispatch = windows_dispatch;495
self.fetch = windows_fetch;496
self.fd = STD_OUTPUT_HANDLE;497
#endif /* LIB_TERM_WINDOWS */498
return self;499
}501
void term_cleanup(term_t *self) {502
if (self) {503
if (self->buffer) {504
free(self->buffer);505
self->buffer = NULL;506
self->len = 0;507
}508
}509
}511
void term_rawmode_enable(term_t *self) {512
self->dispatch(self, TERM_CODE_RAWMODE_ENABLE, NULL, 0);513
}515
void term_rawmode_disable(term_t *self) {516
self->dispatch(self, TERM_CODE_RAWMODE_DISABLE, NULL, 0);517
}519
void term_write(term_t *self, char *str) {520
self->dispatch(self, TERM_CODE_WRITE, NULL, 0);521
size_t str_len = strlen(str);522
char *new_buffer = (char *) realloc(self->buffer, sizeof(char) * (self->len + str_len + 1));523
if (new_buffer == NULL) {524
fprintf(stderr, "Cannot realloc memory!\n");525
return;526
}527
self->buffer = new_buffer;528
memcpy(self->buffer + self->len, str, str_len);529
self->len += str_len;530
self->buffer[self->len] = '\0';531
}533
void term_writef(term_t *self, const char *format, ...) {534
va_list args;535
va_start(args, format);536
char str[1000];537
vsnprintf(str, sizeof(str), format, args);538
term_write(self, str);539
va_end(args);540
}542
void term_flush(term_t *self) {543
self->dispatch(self, TERM_CODE_FLUSH, NULL, 0);544
}546
int term_read_cursor(term_t *self, size_t *rows, size_t *cols) {547
void *data = self->fetch(self, TERM_CODE_CURSOR_READ, NULL, 0, 2 * sizeof(size_t));548
if (data) {549
size_t *tuple = (size_t *) data;550
*rows = tuple[0];551
*cols = tuple[1];552
free(data);553
return 0;554
}555
return -1;556
}558
int term_read_window(term_t *self, size_t *rows, size_t *cols) {559
void *data = self->fetch(self, TERM_CODE_WINDOW_READ, NULL, 0, 2 * sizeof(size_t));560
if (data) {561
size_t *tuple = (size_t *) data;562
*rows = tuple[0];563
*cols = tuple[1];564
free(data);565
return 0;566
}567
return -1;568
}570
int term_read_key(term_t *self) {571
void *data = self->fetch(self, TERM_CODE_KEY_READ, NULL, 0, sizeof(int));572
if (data) {573
int *single = (int *) data;574
int key = *single;575
free(data);576
return key;577
}578
return -1;579
}581
int term_poll_key(term_t *self, int timeout_ms) {582
void *data = self->fetch(self, TERM_CODE_KEY_POLL, &timeout_ms, sizeof(timeout_ms), sizeof(int));583
if (data) {584
int *single = (int *) data;585
int key = *single;586
free(data);587
return key;588
}589
return -1;590
}592
#endif // LIB_TERM_IMPL593
#endif // _LIB_TERM_H
1
#ifndef _LIB_TERM_H2
#define _LIB_TERM_H4
#include <stddef.h>5
#include <stdbool.h>7
typedef enum {8
TERM_CODE_RAWMODE_ENABLE,9
TERM_CODE_RAWMODE_DISABLE,10
TERM_CODE_CURSOR_READ,11
TERM_CODE_WINDOW_READ,12
TERM_CODE_KEY_POLL,13
TERM_CODE_KEY_READ,14
TERM_CODE_WRITE,15
TERM_CODE_FLUSH,16
} term_code_t;18
typedef enum {19
KEY_ENTER = 13,20
KEY_ESC = 27,21
KEY_BACKSPACE = 127,22
KEY_ARROW_LEFT = 1000,23
KEY_ARROW_RIGHT,24
KEY_ARROW_UP,25
KEY_ARROW_DOWN,26
KEY_DEL,27
KEY_HOME,28
KEY_END,29
KEY_PAGE_UP,30
KEY_PAGE_DOWN,31
} term_key_t;33
typedef struct term term_t;35
typedef void (*term_dispatch_fn_t)(term_t *self, term_code_t code, void *data, size_t len);36
typedef void *(*term_fetch_fn_t)(term_t *self, term_code_t code, void *data, size_t len, size_t ret_len);38
struct term {39
char *buffer;40
size_t len;41
term_dispatch_fn_t dispatch;42
term_fetch_fn_t fetch;43
int fd;44
};46
term_t term_init(void);47
void term_cleanup(term_t *self);48
void term_rawmode_enable(term_t *self);49
void term_rawmode_disable(term_t *self);50
void term_write(term_t *self, char *str);51
void term_writef(term_t *self, const char *format, ...);52
void term_flush(term_t *self);53
int term_read_cursor(term_t *self, size_t *rows, size_t *cols);54
int term_read_window(term_t *self, size_t *rows, size_t *cols);55
int term_read_key(term_t *self);56
int term_poll_key(term_t *self, int timeout_ms);58
#ifdef LIB_TERM_IMPL60
#include <stdio.h>61
#include <stdlib.h>62
#include <string.h>63
#include <unistd.h>64
#include <errno.h>65
#include <stdarg.h>67
void _term_panic(term_t *self, const char *str) {68
write(self->fd, "\x1b[2J", 4);69
write(self->fd, "\x1b[H", 3);70
write(self->fd, "\x1b[?25h", 6);71
perror(str);72
exit(EXIT_FAILURE);73
}75
#ifdef LIB_TERM_POSIX77
#include <termios.h>78
#include <sys/ioctl.h>79
#include <poll.h>81
static struct termios _posix_termios;83
static void posix_rawmode_enable(term_t *self) {84
if (tcgetattr(STDIN_FILENO, &_posix_termios) == -1) {85
_term_panic(self, "tcgetattr");86
}87
struct termios raw = _posix_termios;88
raw.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON);89
raw.c_oflag &= ~(OPOST);90
raw.c_cflag |= (CS8);91
raw.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG);92
raw.c_cc[VMIN] = 0;93
raw.c_cc[VTIME] = 1;94
if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw) == -1) {95
_term_panic(self, "tcsetattr");96
}97
}99
static void posix_rawmode_disable(term_t *self) {100
if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &_posix_termios) == -1) {101
_term_panic(self, "tcsetattr");102
}103
}105
static void posix_write(term_t *self, void *data, size_t len) {106
(void) self;107
(void) data;108
(void) len;109
}111
static void posix_flush(term_t *self, void *data, size_t len) {112
(void) data;113
(void) len;114
if (self->len > 0) {115
write(self->fd, self->buffer, self->len);116
free(self->buffer);117
self->buffer = NULL;118
self->len = 0;119
}120
}122
static void *posix_read_cursor(term_t *self, void *data, size_t len) {123
(void) data;124
(void) len;125
char buf[32];126
unsigned int i = 0;127
if (write(self->fd, "\x1b[6n", 4) != 4) {128
return NULL;129
}130
while (i < sizeof(buf) - 1) {131
if (read(STDIN_FILENO, &buf[i], 1) != 1) {132
break;133
}134
if (buf[i] == 'R') {135
break;136
}137
i++;138
}139
buf[i] = '\0';140
if (buf[0] != '\x1b' || buf[1] != '[') {141
return NULL;142
}143
size_t *tuple = malloc(2 * sizeof(size_t));144
if (sscanf(&buf[2], "%zu;%zu", &tuple[0], &tuple[1]) != 2) {145
free(tuple);146
return NULL;147
}148
return tuple;149
}151
static void *posix_read_window(term_t *self, void *data, size_t len) {152
struct winsize ws;153
if (ioctl(self->fd, TIOCGWINSZ, &ws) == -1 || ws.ws_col == 0) {154
if (write(self->fd, "\x1b[999C\x1b[999B", 12) == 12) {155
return posix_read_cursor(self, data, len);156
}157
}158
else {159
size_t *tuple = malloc(2 * sizeof(size_t));160
tuple[0] = ws.ws_row;161
tuple[1] = ws.ws_col;162
return tuple;163
}164
return NULL;165
}167
static void *posix_read_key(term_t *self) {168
int len;169
char c;170
int key;171
while ((len = read(STDIN_FILENO, &c, 1)) != 1) {172
if (len == -1 && errno != EAGAIN) {173
_term_panic(self, "read");174
}175
}176
if (c == '\x1b') {177
char seq[3];178
if (read(STDIN_FILENO, &seq[0], 1) != 1) {179
key = '\x1b';180
}181
if (read(STDIN_FILENO, &seq[1], 1) != 1) {182
key = '\x1b';183
}184
if (seq[0] == '[') {185
if (seq[1] >= '0' && seq[1] <= '9') {186
if (read(STDIN_FILENO, &seq[2], 1) != 1) {187
key = '\x1b';188
}189
if (seq[2] == '~') {190
switch (seq[1]) {191
case '1':192
key = KEY_HOME;193
break;194
case '3':195
key = KEY_DEL;196
break;197
case '4':198
key = KEY_END;199
break;200
case '5':201
key = KEY_PAGE_UP;202
break;203
case '6':204
key = KEY_PAGE_DOWN;205
break;206
case '7':207
key = KEY_HOME;208
break;209
case '8':210
key = KEY_END;211
break;212
default:213
break;214
}215
}216
}217
else {218
switch (seq[1]) {219
case 'A':220
key = KEY_ARROW_UP;221
break;222
case 'B':223
key = KEY_ARROW_DOWN;224
break;225
case 'C':226
key = KEY_ARROW_RIGHT;227
break;228
case 'D':229
key = KEY_ARROW_LEFT;230
break;231
case 'H':232
key = KEY_HOME;233
break;234
case 'F':235
key = KEY_END;236
break;237
default:238
key = '\x1b';239
break;240
}241
}242
}243
else if (seq[0] == 'O') {244
switch (seq[1]) {245
case 'H':246
key = KEY_HOME;247
break;248
case 'F':249
key = KEY_END;250
break;251
default:252
break;253
}254
}255
}256
else {257
key = c;258
}259
int *ret = malloc(sizeof(int));260
*ret = key;261
return ret;262
}264
static void *posix_poll_key(term_t *self, void *data, size_t len) {265
(void) len;266
struct pollfd fds[1];267
fds[0].fd = STDIN_FILENO;268
fds[0].events = POLLIN | POLLPRI;269
int timeout_ms = *(int *) data;270
if (poll(fds, 1, timeout_ms)) {271
return posix_read_key(self);272
}273
int *ret = malloc(sizeof(int));274
*ret = 0;275
return ret;276
}278
static void posix_dispatch(term_t *self, term_code_t code, void *data, size_t len) {279
switch (code) {280
case TERM_CODE_RAWMODE_ENABLE:281
posix_rawmode_enable(self);282
break;283
case TERM_CODE_RAWMODE_DISABLE:284
posix_rawmode_disable(self);285
break;286
case TERM_CODE_WRITE:287
posix_write(self, data, len);288
break;289
case TERM_CODE_FLUSH:290
posix_flush(self, data, len);291
break;292
default:293
break;294
}295
}297
static void *posix_fetch(term_t *self, term_code_t code, void *data, size_t len, size_t ret_len) {298
(void) ret_len;299
switch (code) {300
case TERM_CODE_CURSOR_READ:301
return posix_read_cursor(self, data, len);302
case TERM_CODE_WINDOW_READ:303
return posix_read_window(self, data, len);304
case TERM_CODE_KEY_READ:305
return posix_read_key(self);306
case TERM_CODE_KEY_POLL:307
return posix_poll_key(self, data, len);308
default:309
return NULL;310
}311
}313
#endif // LIB_TERM_POSIX315
#ifdef LIB_TERM_WINDOWS317
#include <windows.h>318
#include <conio.h>320
static DWORD _windows_console_mode;322
static void windows_rawmode_enable(term_t *self) {323
HANDLE handle = GetStdHandle(STD_INPUT_HANDLE);324
DWORD mode;325
if (!GetConsoleMode(handle, &mode)) {326
_term_panic(self, "GetConsoleMode");327
}328
_windows_console_mode = mode;329
mode &= ~(ENABLE_ECHO_INPUT | ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT);330
if (!SetConsoleMode(handle, mode)) {331
_term_panic(self, "SetConsoleMode");332
}333
}335
static void windows_rawmode_disable(term_t *self) {336
HANDLE handle = GetStdHandle(STD_INPUT_HANDLE);337
if (!SetConsoleMode(handle, _windows_console_mode)) {338
_term_panic(self, "SetConsoleMode");339
}340
}342
static void windows_write(term_t *self, void *data, size_t len) {343
DWORD written;344
WriteConsole(GetStdHandle(self->fd), data, (DWORD) len, &written, NULL);345
}347
static void windows_flush(term_t *self, void *data, size_t len) {348
(void) data;349
(void) len;350
if (self->len > 0) {351
DWORD written;352
WriteConsole(GetStdHandle(self->fd), self->buffer, (DWORD) self->len, &written, NULL);353
free(self->buffer);354
self->buffer = NULL;355
self->len = 0;356
}357
}359
static void *windows_read_cursor(term_t *self, void *data, size_t len) {360
(void) data;361
(void) len;362
CONSOLE_SCREEN_BUFFER_INFO console_info;363
if (!GetConsoleScreenBufferInfo(GetStdHandle(self->fd), &console_info)) {364
return NULL;365
}366
size_t *tuple = malloc(2 * sizeof(size_t));367
tuple[0] = console_info.dwCursorPosition.Y + 1;368
tuple[1] = console_info.dwCursorPosition.X + 1;369
return tuple;370
}372
static void *windows_read_window(term_t *self, void *data, size_t len) {373
(void) data;374
(void) len;375
CONSOLE_SCREEN_BUFFER_INFO console_info;376
if (!GetConsoleScreenBufferInfo(GetStdHandle(self->fd), &console_info)) {377
return NULL;378
}379
size_t *tuple = malloc(2 * sizeof(size_t));380
tuple[0] = console_info.srWindow.Bottom - console_info.srWindow.Top + 1;381
tuple[1] = console_info.srWindow.Right - console_info.srWindow.Left + 1;382
return tuple;383
}385
static void *windows_read_key(term_t *self) {386
(void) self;387
int c = _getch();388
if (c == 0 || c == 224) {389
int ext = _getch();390
switch (ext) {391
case 72:392
c = KEY_ARROW_UP;393
break;394
case 80:395
c = KEY_ARROW_DOWN;396
break;397
case 75:398
c = KEY_ARROW_LEFT;399
break;400
case 77:401
c = KEY_ARROW_RIGHT;402
break;403
case 71:404
c = KEY_HOME;405
break;406
case 79:407
c = KEY_END;408
break;409
case 73:410
c = KEY_PAGE_UP;411
break;412
case 81:413
c = KEY_PAGE_DOWN;414
break;415
case 83:416
c = KEY_DEL;417
break;418
default:419
c = 0;420
break;421
}422
}423
int *ret = malloc(sizeof(int));424
*ret = c;425
return ret;426
}428
static void *windows_poll_key(term_t *self, void *data, size_t len) {429
(void) len;430
int timeout_ms = *(int *) data;431
DWORD start = GetTickCount();432
while (GetTickCount() - start < (DWORD) timeout_ms) {433
if (_kbhit()) {434
return windows_read_key(self);435
}436
Sleep(1);437
}438
int *ret = malloc(sizeof(int));439
*ret = 0;440
return ret;441
}443
static void windows_dispatch(term_t *self, term_code_t code, void *data, size_t len) {444
switch (code) {445
case TERM_CODE_RAWMODE_ENABLE:446
windows_rawmode_enable(self);447
break;448
case TERM_CODE_RAWMODE_DISABLE:449
windows_rawmode_disable(self);450
break;451
case TERM_CODE_WRITE:452
windows_write(self, data, len);453
break;454
case TERM_CODE_FLUSH:455
windows_flush(self, data, len);456
break;457
default:458
break;459
}460
}462
static void *windows_fetch(term_t *self, term_code_t code, void *data, size_t len, size_t ret_len) {463
(void) ret_len;464
switch (code) {465
case TERM_CODE_CURSOR_READ:466
return windows_read_cursor(self, data, len);467
case TERM_CODE_WINDOW_READ:468
return windows_read_window(self, data, len);469
case TERM_CODE_KEY_READ:470
return windows_read_key(self);471
case TERM_CODE_KEY_POLL:472
return windows_poll_key(self, data, len);473
default:474
return NULL;475
}476
}478
#endif // LIB_TERM_WINDOWS480
term_t term_init(void) {481
term_t self = {482
.buffer = NULL,483
.len = 0,484
.dispatch = NULL,485
.fetch = NULL,486
.fd = 0,487
};488
#ifdef LIB_TERM_POSIX489
self.dispatch = posix_dispatch;490
self.fetch = posix_fetch;491
self.fd = STDOUT_FILENO;492
#endif /* LIB_TERM_POSIX */493
#ifdef LIB_TERM_WINDOWS494
self.dispatch = windows_dispatch;495
self.fetch = windows_fetch;496
self.fd = STD_OUTPUT_HANDLE;497
#endif /* LIB_TERM_WINDOWS */498
return self;499
}501
void term_cleanup(term_t *self) {502
if (self) {503
if (self->buffer) {504
free(self->buffer);505
self->buffer = NULL;506
self->len = 0;507
}508
}509
}511
void term_rawmode_enable(term_t *self) {512
self->dispatch(self, TERM_CODE_RAWMODE_ENABLE, NULL, 0);513
}515
void term_rawmode_disable(term_t *self) {516
self->dispatch(self, TERM_CODE_RAWMODE_DISABLE, NULL, 0);517
}519
void term_write(term_t *self, char *str) {520
self->dispatch(self, TERM_CODE_WRITE, NULL, 0);521
size_t str_len = strlen(str);522
char *new_buffer = (char *) realloc(self->buffer, sizeof(char) * (self->len + str_len + 1));523
if (new_buffer == NULL) {524
fprintf(stderr, "Cannot realloc memory!\n");525
return;526
}527
self->buffer = new_buffer;528
memcpy(self->buffer + self->len, str, str_len);529
self->len += str_len;530
self->buffer[self->len] = '\0';531
}533
void term_writef(term_t *self, const char *format, ...) {534
va_list args;535
va_start(args, format);536
char str[1000];537
vsnprintf(str, sizeof(str), format, args);538
term_write(self, str);539
va_end(args);540
}542
void term_flush(term_t *self) {543
self->dispatch(self, TERM_CODE_FLUSH, NULL, 0);544
}546
int term_read_cursor(term_t *self, size_t *rows, size_t *cols) {547
void *data = self->fetch(self, TERM_CODE_CURSOR_READ, NULL, 0, 2 * sizeof(size_t));548
if (data) {549
size_t *tuple = (size_t *) data;550
*rows = tuple[0];551
*cols = tuple[1];552
free(data);553
return 0;554
}555
return -1;556
}558
int term_read_window(term_t *self, size_t *rows, size_t *cols) {559
void *data = self->fetch(self, TERM_CODE_WINDOW_READ, NULL, 0, 2 * sizeof(size_t));560
if (data) {561
size_t *tuple = (size_t *) data;562
*rows = tuple[0];563
*cols = tuple[1];564
free(data);565
return 0;566
}567
return -1;568
}570
int term_read_key(term_t *self) {571
void *data = self->fetch(self, TERM_CODE_KEY_READ, NULL, 0, sizeof(int));572
if (data) {573
int *single = (int *) data;574
int key = *single;575
free(data);576
return key;577
}578
return -1;579
}581
int term_poll_key(term_t *self, int timeout_ms) {582
void *data = self->fetch(self, TERM_CODE_KEY_POLL, &timeout_ms, sizeof(timeout_ms), sizeof(int));583
if (data) {584
int *single = (int *) data;585
int key = *single;586
free(data);587
return key;588
}589
return -1;590
}592
#endif // LIB_TERM_IMPL593
#endif // _LIB_TERM_H