An easy-to-use terminal library
git clone git://soophie.de/so-term
1
default: build3
build:4
cc -o libterm.so -shared -fPIC -Wall -Wextra libterm.c1
install:7
cc -o libterm.so -shared -fPIC libterm.c8
sudo cp libterm.h /usr/local/include/9
sudo cp libterm.so /usr/local/lib/10
sudo ldconfig11
rm libterm.so2
sudo cp src/libterm.h /usr/local/include/4
uninstall:5
sudo rm /usr/local/include/libterm.h15
sudo rm /usr/local/lib/libterm.so17
clean:18
rm libterm.so
1
#include <stdio.h>2
#include <stdlib.h>3
#include <string.h>4
#include <unistd.h>5
#include <termios.h>6
#include <sys/ioctl.h>7
#include <errno.h>8
#include <stdarg.h>9
#include <poll.h>11
#include "libterm.h"13
struct termios termios;14
term_t term = { NULL, 0 };16
void term_panic(const char *str) {17
write(STDOUT_FILENO, "\x1b[2J", 4);18
write(STDOUT_FILENO, "\x1b[H", 3);19
write(STDOUT_FILENO, "\x1b[?25h", 6);20
perror(str);21
exit(EXIT_FAILURE);22
}24
void term_disable_raw_mode() {25
if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &termios) == -1) {26
term_panic("tcsetattr");27
}28
}30
void term_enable_raw_mode() {31
if (tcgetattr(STDIN_FILENO, &termios) == -1) {32
term_panic("tcgetattr");33
}34
atexit(term_disable_raw_mode);35
struct termios raw = termios;36
raw.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON);37
raw.c_oflag &= ~(OPOST);38
raw.c_cflag |= (CS8);39
raw.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG);40
raw.c_cc[VMIN] = 0;41
raw.c_cc[VTIME] = 1;42
if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw) == -1) {43
term_panic("tcsetattr");44
}45
}47
int term_read_cursor_pos(int *rows, int *cols) {48
char buf[32];49
unsigned int i = 0;50
if (write(STDOUT_FILENO, "\x1b[6n", 4) != 4) {51
return -1;52
}53
while (i < sizeof(buf) - 1) {54
if (read(STDIN_FILENO, &buf[i], 1) != 1) {55
break;56
}57
if (buf[i] == 'R') {58
break;59
}60
i++;61
}62
buf[i] = '\0';63
if (buf[0] != '\x1b' || buf[1] != '[') {64
return -1;65
}66
if (sscanf(&buf[2], "%d;%d", rows, cols) != 2) {67
return -1;68
}69
return -0;70
}72
int term_read_window_size(int *rows, int *cols) {73
struct winsize ws;74
if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) == -1 || ws.ws_col == 0) {75
if (write(STDOUT_FILENO, "\x1b[999C\x1b[999B", 12) != 12) {76
return -1;77
}78
return term_read_cursor_pos(rows, cols);79
}80
else {81
*cols = ws.ws_col;82
*rows = ws.ws_row;83
return 0;84
}85
}87
int term_poll_key(int timeout) {88
struct pollfd fds[1];89
fds[0].fd = STDIN_FILENO;90
fds[0].events = POLLIN | POLLPRI;91
if (poll(fds, 1, timeout)) {92
return term_read_key();93
}94
return 0;95
}97
int term_read_key(void) {98
int len;99
char c;100
while ((len = read(STDIN_FILENO, &c, 1)) != 1) {101
if (len == -1 && errno != EAGAIN) {102
term_panic("read");103
}104
}105
if (c == '\x1b') {106
char seq[3];107
if (read(STDIN_FILENO, &seq[0], 1) != 1) {108
return '\x1b';109
}110
if (read(STDIN_FILENO, &seq[1], 1) != 1) {111
return '\x1b';112
}113
if (seq[0] == '[') {114
if (seq[1] >= '0' && seq[1] <= '9') {115
if (read(STDIN_FILENO, &seq[2], 1) != 1) {116
return '\x1b';117
}118
if (seq[2] == '~') {119
switch (seq[1]) {120
case '1':121
return KEY_HOME;122
case '3':123
return KEY_DEL;124
case '4':125
return KEY_END;126
case '5':127
return KEY_PAGE_UP;128
case '6':129
return KEY_PAGE_DOWN;130
case '7':131
return KEY_HOME;132
case '8':133
return KEY_END;134
}135
}136
}137
else {138
switch (seq[1]) {139
case 'A':140
return KEY_ARROW_UP;141
case 'B':142
return KEY_ARROW_DOWN;143
case 'C':144
return KEY_ARROW_RIGHT;145
case 'D':146
return KEY_ARROW_LEFT;147
case 'H':148
return KEY_HOME;149
case 'F':150
return KEY_END;151
}152
return '\x1b';153
}154
}155
else if (seq[0] == 'O') {156
switch (seq[1]) {157
case 'H':158
return KEY_HOME;159
case 'F':160
return KEY_END;161
}162
}163
}164
return c;165
}167
void term_write(char *str) {168
int str_len = strlen(str);169
char *new_buffer = realloc(term.buffer, sizeof(char) * (term.len + str_len + 1));170
if (new_buffer == NULL) {171
fprintf(stderr, "Cannot realloc memory!\n");172
return;173
}174
term.buffer = new_buffer;175
memcpy(term.buffer + term.len, str, str_len);176
term.len += str_len;177
term.buffer[term.len] = '\0';178
}180
void term_writef(const char *format, ...) {181
va_list args;182
va_start(args, format);183
char str[1000];184
vsnprintf(str, sizeof(str), format, args);185
term_write(str);186
va_end(args);187
}189
void term_flush(void) {190
if (term.len > 0) {191
write(STDOUT_FILENO, term.buffer, term.len);192
free(term.buffer);193
term.buffer = NULL;194
term.len = 0;195
}196
}
1
#pragma once3
typedef enum {4
KEY_ENTER = 13,5
KEY_ESC = 27,6
KEY_BACKSPACE = 127,7
KEY_ARROW_LEFT = 1000,8
KEY_ARROW_RIGHT,9
KEY_ARROW_UP,10
KEY_ARROW_DOWN,11
KEY_DEL,12
KEY_HOME,13
KEY_END,14
KEY_PAGE_UP,15
KEY_PAGE_DOWN,16
} keycode_t;18
typedef struct {19
char *buffer;20
int len;21
} term_t;23
void term_panic(const char *s);24
void term_disable_raw_mode(void);25
void term_enable_raw_mode(void);26
int term_read_cursor_pos(int *rows, int *cols);27
int term_read_window_size(int *rows, int *cols);28
int term_poll_key(int timeout);29
int term_read_key(void);30
void term_write(char *str);31
void term_writef(const char *format, ...);32
void term_flush(void);
1
#ifndef LIB_TERM_H2
#define LIB_TERM_H4
typedef enum {5
KEY_ENTER = 13,6
KEY_ESC = 27,7
KEY_BACKSPACE = 127,8
KEY_ARROW_LEFT = 1000,9
KEY_ARROW_RIGHT,10
KEY_ARROW_UP,11
KEY_ARROW_DOWN,12
KEY_DEL,13
KEY_HOME,14
KEY_END,15
KEY_PAGE_UP,16
KEY_PAGE_DOWN,17
} keycode_t;19
typedef struct {20
char *buffer;21
int len;22
} 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);35
#ifdef LIB_TERM_IMPL37
#include <stdio.h>38
#include <stdlib.h>39
#include <string.h>40
#include <unistd.h>41
#include <termios.h>42
#include <sys/ioctl.h>43
#include <errno.h>44
#include <stdarg.h>45
#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);54
perror(str);55
exit(EXIT_FAILURE);56
}58
void term_disable_raw_mode(void) {59
if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &termios) == -1) {60
term_panic("tcsetattr");61
}62
}64
void term_enable_raw_mode(void) {65
if (tcgetattr(STDIN_FILENO, &termios) == -1) {66
term_panic("tcgetattr");67
}68
atexit(term_disable_raw_mode);69
struct termios raw = termios;70
raw.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON);71
raw.c_oflag &= ~(OPOST);72
raw.c_cflag |= (CS8);73
raw.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG);74
raw.c_cc[VMIN] = 0;75
raw.c_cc[VTIME] = 1;76
if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw) == -1) {77
term_panic("tcsetattr");78
}79
}81
int term_read_cursor_pos(int *rows, int *cols) {82
char buf[32];83
unsigned int i = 0;84
if (write(STDOUT_FILENO, "\x1b[6n", 4) != 4) {85
return -1;86
}87
while (i < sizeof(buf) - 1) {88
if (read(STDIN_FILENO, &buf[i], 1) != 1) {89
break;90
}91
if (buf[i] == 'R') {92
break;93
}94
i++;95
}96
buf[i] = '\0';97
if (buf[0] != '\x1b' || buf[1] != '[') {98
return -1;99
}100
if (sscanf(&buf[2], "%d;%d", rows, cols) != 2) {101
return -1;102
}103
return -0;104
}106
int term_read_window_size(int *rows, int *cols) {107
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) {110
return -1;111
}112
return term_read_cursor_pos(rows, cols);113
}114
else {115
*cols = ws.ws_col;116
*rows = ws.ws_row;117
return 0;118
}119
}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();127
}128
return 0;129
}131
int term_read_key(void) {132
int len;133
char c;134
while ((len = read(STDIN_FILENO, &c, 1)) != 1) {135
if (len == -1 && errno != EAGAIN) {136
term_panic("read");137
}138
}139
if (c == '\x1b') {140
char seq[3];141
if (read(STDIN_FILENO, &seq[0], 1) != 1) {142
return '\x1b';143
}144
if (read(STDIN_FILENO, &seq[1], 1) != 1) {145
return '\x1b';146
}147
if (seq[0] == '[') {148
if (seq[1] >= '0' && seq[1] <= '9') {149
if (read(STDIN_FILENO, &seq[2], 1) != 1) {150
return '\x1b';151
}152
if (seq[2] == '~') {153
switch (seq[1]) {154
case '1':155
return KEY_HOME;156
case '3':157
return KEY_DEL;158
case '4':159
return KEY_END;160
case '5':161
return KEY_PAGE_UP;162
case '6':163
return KEY_PAGE_DOWN;164
case '7':165
return KEY_HOME;166
case '8':167
return KEY_END;168
}169
}170
}171
else {172
switch (seq[1]) {173
case 'A':174
return KEY_ARROW_UP;175
case 'B':176
return KEY_ARROW_DOWN;177
case 'C':178
return KEY_ARROW_RIGHT;179
case 'D':180
return KEY_ARROW_LEFT;181
case 'H':182
return KEY_HOME;183
case 'F':184
return KEY_END;185
}186
return '\x1b';187
}188
}189
else if (seq[0] == 'O') {190
switch (seq[1]) {191
case 'H':192
return KEY_HOME;193
case 'F':194
return KEY_END;195
}196
}197
}198
return c;199
}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;207
}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;229
}230
}232
#endif // LIB_TERM_IMPL233
#endif // LIB_TERM_H