QUOTE: Never too old to chase dreams.

libterm

An easy-to-use terminal library

commit a88e168a20d42e41ba3fb415b406e05e51dc98ce
parent e91e4c0b3f2ba826b677874f42c48dc7893c7026
Author: Sophie <info@soophie.de>
Date:   Sat,  4 Jan 2025 14:21:38 +0100

feat: Made single header file library

Diffstat:
MMakefile | 15+--------------
Dlibterm.c | 196-------------------------------------------------------------------------------
Dlibterm.h | 32--------------------------------
Asrc/libterm.h | 233+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
4 files changed, 234 insertions(+), 242 deletions(-)

diff --git a/Makefile b/Makefile @@ -1,18 +1,5 @@ -default: build - -build: - cc -o libterm.so -shared -fPIC -Wall -Wextra libterm.c - install: - cc -o libterm.so -shared -fPIC libterm.c - sudo cp libterm.h /usr/local/include/ - sudo cp libterm.so /usr/local/lib/ - sudo ldconfig - rm libterm.so + sudo cp src/libterm.h /usr/local/include/ uninstall: sudo rm /usr/local/include/libterm.h - sudo rm /usr/local/lib/libterm.so - -clean: - rm libterm.so diff --git a/libterm.c b/libterm.c @@ -1,196 +0,0 @@ -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <unistd.h> -#include <termios.h> -#include <sys/ioctl.h> -#include <errno.h> -#include <stdarg.h> -#include <poll.h> - -#include "libterm.h" - -struct termios termios; -term_t term = { NULL, 0 }; - -void term_panic(const char *str) { - write(STDOUT_FILENO, "\x1b[2J", 4); - write(STDOUT_FILENO, "\x1b[H", 3); - write(STDOUT_FILENO, "\x1b[?25h", 6); - perror(str); - exit(EXIT_FAILURE); -} - -void term_disable_raw_mode() { - if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &termios) == -1) { - term_panic("tcsetattr"); - } -} - -void term_enable_raw_mode() { - if (tcgetattr(STDIN_FILENO, &termios) == -1) { - term_panic("tcgetattr"); - } - atexit(term_disable_raw_mode); - struct termios raw = termios; - raw.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON); - raw.c_oflag &= ~(OPOST); - raw.c_cflag |= (CS8); - raw.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG); - raw.c_cc[VMIN] = 0; - raw.c_cc[VTIME] = 1; - if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw) == -1) { - term_panic("tcsetattr"); - } -} - -int term_read_cursor_pos(int *rows, int *cols) { - char buf[32]; - unsigned int i = 0; - if (write(STDOUT_FILENO, "\x1b[6n", 4) != 4) { - return -1; - } - while (i < sizeof(buf) - 1) { - if (read(STDIN_FILENO, &buf[i], 1) != 1) { - break; - } - if (buf[i] == 'R') { - break; - } - i++; - } - buf[i] = '\0'; - if (buf[0] != '\x1b' || buf[1] != '[') { - return -1; - } - if (sscanf(&buf[2], "%d;%d", rows, cols) != 2) { - return -1; - } - return -0; -} - -int term_read_window_size(int *rows, int *cols) { - struct winsize ws; - if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) == -1 || ws.ws_col == 0) { - if (write(STDOUT_FILENO, "\x1b[999C\x1b[999B", 12) != 12) { - return -1; - } - return term_read_cursor_pos(rows, cols); - } - else { - *cols = ws.ws_col; - *rows = ws.ws_row; - return 0; - } -} - -int term_poll_key(int timeout) { - struct pollfd fds[1]; - fds[0].fd = STDIN_FILENO; - fds[0].events = POLLIN | POLLPRI; - if (poll(fds, 1, timeout)) { - return term_read_key(); - } - return 0; -} - -int term_read_key(void) { - int len; - char c; - while ((len = read(STDIN_FILENO, &c, 1)) != 1) { - if (len == -1 && errno != EAGAIN) { - term_panic("read"); - } - } - if (c == '\x1b') { - char seq[3]; - if (read(STDIN_FILENO, &seq[0], 1) != 1) { - return '\x1b'; - } - if (read(STDIN_FILENO, &seq[1], 1) != 1) { - return '\x1b'; - } - if (seq[0] == '[') { - if (seq[1] >= '0' && seq[1] <= '9') { - if (read(STDIN_FILENO, &seq[2], 1) != 1) { - return '\x1b'; - } - if (seq[2] == '~') { - switch (seq[1]) { - case '1': - return KEY_HOME; - case '3': - return KEY_DEL; - case '4': - return KEY_END; - case '5': - return KEY_PAGE_UP; - case '6': - return KEY_PAGE_DOWN; - case '7': - return KEY_HOME; - case '8': - return KEY_END; - } - } - } - else { - switch (seq[1]) { - case 'A': - return KEY_ARROW_UP; - case 'B': - return KEY_ARROW_DOWN; - case 'C': - return KEY_ARROW_RIGHT; - case 'D': - return KEY_ARROW_LEFT; - case 'H': - return KEY_HOME; - case 'F': - return KEY_END; - } - return '\x1b'; - } - } - else if (seq[0] == 'O') { - switch (seq[1]) { - case 'H': - return KEY_HOME; - case 'F': - return KEY_END; - } - } - } - return c; -} - -void term_write(char *str) { - int str_len = strlen(str); - char *new_buffer = realloc(term.buffer, sizeof(char) * (term.len + str_len + 1)); - if (new_buffer == NULL) { - fprintf(stderr, "Cannot realloc memory!\n"); - return; - } - term.buffer = new_buffer; - memcpy(term.buffer + term.len, str, str_len); - term.len += str_len; - term.buffer[term.len] = '\0'; -} - -void term_writef(const char *format, ...) { - va_list args; - va_start(args, format); - char str[1000]; - vsnprintf(str, sizeof(str), format, args); - term_write(str); - va_end(args); -} - -void term_flush(void) { - if (term.len > 0) { - write(STDOUT_FILENO, term.buffer, term.len); - free(term.buffer); - term.buffer = NULL; - term.len = 0; - } -} diff --git a/libterm.h b/libterm.h @@ -1,32 +0,0 @@ -#pragma once - -typedef enum { - KEY_ENTER = 13, - KEY_ESC = 27, - KEY_BACKSPACE = 127, - KEY_ARROW_LEFT = 1000, - KEY_ARROW_RIGHT, - KEY_ARROW_UP, - KEY_ARROW_DOWN, - KEY_DEL, - KEY_HOME, - KEY_END, - KEY_PAGE_UP, - KEY_PAGE_DOWN, -} keycode_t; - -typedef struct { - char *buffer; - int len; -} term_t; - -void term_panic(const char *s); -void term_disable_raw_mode(void); -void term_enable_raw_mode(void); -int term_read_cursor_pos(int *rows, int *cols); -int term_read_window_size(int *rows, int *cols); -int term_poll_key(int timeout); -int term_read_key(void); -void term_write(char *str); -void term_writef(const char *format, ...); -void term_flush(void); diff --git a/src/libterm.h b/src/libterm.h @@ -0,0 +1,233 @@ +#ifndef LIB_TERM_H +#define LIB_TERM_H + +typedef enum { + KEY_ENTER = 13, + KEY_ESC = 27, + KEY_BACKSPACE = 127, + KEY_ARROW_LEFT = 1000, + KEY_ARROW_RIGHT, + KEY_ARROW_UP, + KEY_ARROW_DOWN, + KEY_DEL, + KEY_HOME, + KEY_END, + KEY_PAGE_UP, + KEY_PAGE_DOWN, +} keycode_t; + +typedef struct { + char *buffer; + int len; +} term_t; + +void term_panic(const char *s); +void term_disable_raw_mode(void); +void term_enable_raw_mode(void); +int term_read_cursor_pos(int *rows, int *cols); +int term_read_window_size(int *rows, int *cols); +int term_poll_key(int timeout); +int term_read_key(void); +void term_write(char *str); +void term_writef(const char *format, ...); +void term_flush(void); + +#ifdef LIB_TERM_IMPL + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <termios.h> +#include <sys/ioctl.h> +#include <errno.h> +#include <stdarg.h> +#include <poll.h> + +struct termios termios; +term_t term = { NULL, 0 }; + +void term_panic(const char *str) { + write(STDOUT_FILENO, "\x1b[2J", 4); + write(STDOUT_FILENO, "\x1b[H", 3); + write(STDOUT_FILENO, "\x1b[?25h", 6); + perror(str); + exit(EXIT_FAILURE); +} + +void term_disable_raw_mode(void) { + if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &termios) == -1) { + term_panic("tcsetattr"); + } +} + +void term_enable_raw_mode(void) { + if (tcgetattr(STDIN_FILENO, &termios) == -1) { + term_panic("tcgetattr"); + } + atexit(term_disable_raw_mode); + struct termios raw = termios; + raw.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON); + raw.c_oflag &= ~(OPOST); + raw.c_cflag |= (CS8); + raw.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG); + raw.c_cc[VMIN] = 0; + raw.c_cc[VTIME] = 1; + if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw) == -1) { + term_panic("tcsetattr"); + } +} + +int term_read_cursor_pos(int *rows, int *cols) { + char buf[32]; + unsigned int i = 0; + if (write(STDOUT_FILENO, "\x1b[6n", 4) != 4) { + return -1; + } + while (i < sizeof(buf) - 1) { + if (read(STDIN_FILENO, &buf[i], 1) != 1) { + break; + } + if (buf[i] == 'R') { + break; + } + i++; + } + buf[i] = '\0'; + if (buf[0] != '\x1b' || buf[1] != '[') { + return -1; + } + if (sscanf(&buf[2], "%d;%d", rows, cols) != 2) { + return -1; + } + return -0; +} + +int term_read_window_size(int *rows, int *cols) { + struct winsize ws; + if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) == -1 || ws.ws_col == 0) { + if (write(STDOUT_FILENO, "\x1b[999C\x1b[999B", 12) != 12) { + return -1; + } + return term_read_cursor_pos(rows, cols); + } + else { + *cols = ws.ws_col; + *rows = ws.ws_row; + return 0; + } +} + +int term_poll_key(int timeout) { + struct pollfd fds[1]; + fds[0].fd = STDIN_FILENO; + fds[0].events = POLLIN | POLLPRI; + if (poll(fds, 1, timeout)) { + return term_read_key(); + } + return 0; +} + +int term_read_key(void) { + int len; + char c; + while ((len = read(STDIN_FILENO, &c, 1)) != 1) { + if (len == -1 && errno != EAGAIN) { + term_panic("read"); + } + } + if (c == '\x1b') { + char seq[3]; + if (read(STDIN_FILENO, &seq[0], 1) != 1) { + return '\x1b'; + } + if (read(STDIN_FILENO, &seq[1], 1) != 1) { + return '\x1b'; + } + if (seq[0] == '[') { + if (seq[1] >= '0' && seq[1] <= '9') { + if (read(STDIN_FILENO, &seq[2], 1) != 1) { + return '\x1b'; + } + if (seq[2] == '~') { + switch (seq[1]) { + case '1': + return KEY_HOME; + case '3': + return KEY_DEL; + case '4': + return KEY_END; + case '5': + return KEY_PAGE_UP; + case '6': + return KEY_PAGE_DOWN; + case '7': + return KEY_HOME; + case '8': + return KEY_END; + } + } + } + else { + switch (seq[1]) { + case 'A': + return KEY_ARROW_UP; + case 'B': + return KEY_ARROW_DOWN; + case 'C': + return KEY_ARROW_RIGHT; + case 'D': + return KEY_ARROW_LEFT; + case 'H': + return KEY_HOME; + case 'F': + return KEY_END; + } + return '\x1b'; + } + } + else if (seq[0] == 'O') { + switch (seq[1]) { + case 'H': + return KEY_HOME; + case 'F': + return KEY_END; + } + } + } + return c; +} + +void term_write(char *str) { + int str_len = strlen(str); + char *new_buffer = realloc(term.buffer, sizeof(char) * (term.len + str_len + 1)); + if (new_buffer == NULL) { + fprintf(stderr, "Cannot realloc memory!\n"); + return; + } + term.buffer = new_buffer; + memcpy(term.buffer + term.len, str, str_len); + term.len += str_len; + term.buffer[term.len] = '\0'; +} + +void term_writef(const char *format, ...) { + va_list args; + va_start(args, format); + char str[1000]; + vsnprintf(str, sizeof(str), format, args); + term_write(str); + va_end(args); +} + +void term_flush(void) { + if (term.len > 0) { + write(STDOUT_FILENO, term.buffer, term.len); + free(term.buffer); + term.buffer = NULL; + term.len = 0; + } +} + +#endif // LIB_TERM_IMPL +#endif // LIB_TERM_H