QUOTE: Be the change, make a difference.

libterm

An easy-to-use terminal library

commit d91e336c23b7bdae3cb7c93f3d645c965062c8e1
parent a88e168a20d42e41ba3fb415b406e05e51dc98ce
Author: Sophie <info@soophie.de>
Date:   Sat, 22 Mar 2025 09:45:33 +0000

feat: Improved library structure & added teleterm integration

Diffstat:
Msrc/libterm.h | 266++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------------------
1 file changed, 188 insertions(+), 78 deletions(-)

diff --git a/src/libterm.h b/src/libterm.h @@ -1,5 +1,20 @@ -#ifndef LIB_TERM_H -#define LIB_TERM_H +#ifndef _LIB_TERM_H +#define _LIB_TERM_H + +#include <stddef.h> +#include <stdbool.h> +#include <termios.h> + +typedef enum { + TERM_CODE_RAWMODE_ENABLE, + TERM_CODE_RAWMODE_DISABLE, + TERM_CODE_CURSOR_READ, + TERM_CODE_WINDOW_READ, + TERM_CODE_KEY_POLL, + TERM_CODE_KEY_READ, + TERM_CODE_WRITE, + TERM_CODE_FLUSH, +} term_code_t; typedef enum { KEY_ENTER = 13, @@ -14,23 +29,34 @@ typedef enum { KEY_END, KEY_PAGE_UP, KEY_PAGE_DOWN, -} keycode_t; +} term_keycode_t; + +typedef void (*term_dispatch_fn_t)(int fd, term_code_t code, void *data, size_t len); +typedef void *(*term_fetch_fn_t)(int fd, term_code_t code, size_t len); typedef struct { + struct termios termios; char *buffer; - int len; + size_t len; + bool teleterm_mode; + term_dispatch_fn_t dispatch; + term_fetch_fn_t fetch; + int fd; } 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); +term_t term_init(void); +void term_cleanup(term_t *self); +void term_fd_set(term_t *self, int fd); +void term_teleterm_enable(term_t *self, term_dispatch_fn_t dispatch, term_fetch_fn_t fetch); +void term_rawmode_enable(term_t *self); +void term_rawmode_disable(term_t *self); +void term_write(term_t *self, char *str); +void term_writef(term_t *self, const char *format, ...); +void term_flush(term_t *self); +int term_read_cursor(term_t *self, size_t *rows, size_t *cols); +int term_read_window(term_t *self, size_t *rows, size_t *cols); +int term_read_key(term_t *self); +int term_poll_key(term_t *self, int timeout_ms); #ifdef LIB_TERM_IMPL @@ -38,35 +64,59 @@ void term_flush(void); #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); +void _term_panic(term_t *self, const char *str) { + write(self->fd, "\x1b[2J", 4); + write(self->fd, "\x1b[H", 3); + write(self->fd, "\x1b[?25h", 6); perror(str); exit(EXIT_FAILURE); } -void term_disable_raw_mode(void) { - if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &termios) == -1) { - term_panic("tcsetattr"); +term_t term_init(void) { + return (term_t) { + .buffer = NULL, + .len = 0, + .teleterm_mode = false, + .dispatch = NULL, + .fetch = NULL, + .fd = STDOUT_FILENO, + }; +} + +void term_cleanup(term_t *self) { + if (self) { + if (self->buffer) { + free(self->buffer); + self->buffer = NULL; + self->len = 0; + } } } -void term_enable_raw_mode(void) { - if (tcgetattr(STDIN_FILENO, &termios) == -1) { - term_panic("tcgetattr"); +void term_fd_set(term_t *self, int fd) { + self->fd = fd; +} + +void term_teleterm_enable(term_t *self, term_dispatch_fn_t dispatch, term_fetch_fn_t fetch) { + self->teleterm_mode = true; + self->dispatch = dispatch; + self->fetch = fetch; +} + +void term_rawmode_enable(term_t *self) { + if (self->teleterm_mode) { + self->dispatch(self->fd, TERM_CODE_RAWMODE_ENABLE, NULL, 0); + return; + } + if (tcgetattr(STDIN_FILENO, &self->termios) == -1) { + _term_panic(self, "tcgetattr"); } - atexit(term_disable_raw_mode); - struct termios raw = termios; + struct termios raw = self->termios; raw.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON); raw.c_oflag &= ~(OPOST); raw.c_cflag |= (CS8); @@ -74,14 +124,74 @@ void term_enable_raw_mode(void) { raw.c_cc[VMIN] = 0; raw.c_cc[VTIME] = 1; if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw) == -1) { - term_panic("tcsetattr"); + _term_panic(self, "tcsetattr"); + } +} + +void term_rawmode_disable(term_t *self) { + if (self->teleterm_mode) { + self->dispatch(self->fd, TERM_CODE_RAWMODE_DISABLE, NULL, 0); + return; + } + if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &self->termios) == -1) { + _term_panic(self, "tcsetattr"); + } +} + +void term_write(term_t *self, char *str) { + if (self->teleterm_mode) { + self->dispatch(self->fd, TERM_CODE_WRITE, NULL, 0); + } + size_t str_len = strlen(str); + char *new_buffer = (char *) realloc(self->buffer, sizeof(char) * (self->len + str_len + 1)); + if (new_buffer == NULL) { + fprintf(stderr, "Cannot realloc memory!\n"); + return; + } + self->buffer = new_buffer; + memcpy(self->buffer + self->len, str, str_len); + self->len += str_len; + self->buffer[self->len] = '\0'; +} + +void term_writef(term_t *self, const char *format, ...) { + va_list args; + va_start(args, format); + char str[1000]; + vsnprintf(str, sizeof(str), format, args); + term_write(self, str); + va_end(args); +} + +void term_flush(term_t *self) { + if (self->len > 0) { + if (self->teleterm_mode) { + self->dispatch(self->fd, TERM_CODE_FLUSH, self->buffer, self->len); + } + else { + write(self->fd, self->buffer, self->len); + } + free(self->buffer); + self->buffer = NULL; + self->len = 0; } } -int term_read_cursor_pos(int *rows, int *cols) { +int term_read_cursor(term_t *self, size_t *rows, size_t *cols) { + if (self->teleterm_mode) { + void *data = self->fetch(self->fd, TERM_CODE_CURSOR_READ, 2 * sizeof(size_t)); + if (data) { + size_t *tuple = (size_t *) data; + *rows = tuple[0]; + *cols = tuple[1]; + free(data); + return 0; + } + return -1; + } char buf[32]; unsigned int i = 0; - if (write(STDOUT_FILENO, "\x1b[6n", 4) != 4) { + if (write(self->fd, "\x1b[6n", 4) != 4) { return -1; } while (i < sizeof(buf) - 1) { @@ -97,19 +207,30 @@ int term_read_cursor_pos(int *rows, int *cols) { if (buf[0] != '\x1b' || buf[1] != '[') { return -1; } - if (sscanf(&buf[2], "%d;%d", rows, cols) != 2) { + if (sscanf(&buf[2], "%zu;%zu", rows, cols) != 2) { return -1; } - return -0; + return 0; } -int term_read_window_size(int *rows, int *cols) { +int term_read_window(term_t *self, size_t *rows, size_t *cols) { + if (self->teleterm_mode) { + void *data = self->fetch(self->fd, TERM_CODE_WINDOW_READ, 2 * sizeof(size_t)); + if (data) { + size_t *tuple = (size_t *) data; + *rows = tuple[0]; + *cols = tuple[1]; + free(data); + return 0; + } + return -1; + } struct winsize ws; - if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) == -1 || ws.ws_col == 0) { - if (write(STDOUT_FILENO, "\x1b[999C\x1b[999B", 12) != 12) { + if (ioctl(self->fd, TIOCGWINSZ, &ws) == -1 || ws.ws_col == 0) { + if (write(self->fd, "\x1b[999C\x1b[999B", 12) != 12) { return -1; } - return term_read_cursor_pos(rows, cols); + return term_read_cursor(self, rows, cols); } else { *cols = ws.ws_col; @@ -118,22 +239,22 @@ int term_read_window_size(int *rows, int *cols) { } } -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(); +int term_read_key(term_t *self) { + if (self->teleterm_mode) { + void *data = self->fetch(self->fd, TERM_CODE_KEY_READ, sizeof(int)); + if (data) { + int *single = (int *) data; + int key = *single; + free(data); + return key; + } + return -1; } - 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"); + _term_panic(self, "read"); } } if (c == '\x1b') { @@ -198,36 +319,25 @@ int term_read_key(void) { 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; +int term_poll_key(term_t *self, int timeout_ms) { + if (self->teleterm_mode) { + void *data = self->fetch(self->fd, TERM_CODE_KEY_POLL, sizeof(int)); + if (data) { + int *single = (int *) data; + int key = *single; + free(data); + return key; + } + return -1; } - 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; + struct pollfd fds[1]; + fds[0].fd = STDIN_FILENO; + fds[0].events = POLLIN | POLLPRI; + if (poll(fds, 1, timeout_ms)) { + return term_read_key(self); } + return 0; } #endif // LIB_TERM_IMPL -#endif // LIB_TERM_H +#endif // _LIB_TERM_H