NEWS: Welcome to my new homepage! <3

Initial implementation - libterm - An easy-to-use terminal library

libterm

An easy-to-use terminal library
git clone git://192.168.2.2/libterm
Log | Files | Refs | README

commit 1b85f56e68ab878b749032a4533601bb38b9cfcc
Author: typable <contact@typable.dev>
Date:   Sun, 24 Mar 2024 12:56:36 +0100

Initial implementation

Diffstat:
AREADME.md | 3+++
Aterm.c | 196+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aterm.h | 31+++++++++++++++++++++++++++++++
3 files changed, 230 insertions(+), 0 deletions(-)

diff --git a/README.md b/README.md @@ -0,0 +1,3 @@ +# libterm + +An easy-to-use terminal library diff --git a/term.c b/term.c @@ -0,0 +1,196 @@ +#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 "term.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 HOME_KEY; + case '3': + return DEL_KEY; + case '4': + return END_KEY; + case '5': + return PAGE_UP; + case '6': + return PAGE_DOWN; + case '7': + return HOME_KEY; + case '8': + return END_KEY; + } + } + } + else { + switch (seq[1]) { + case 'A': + return ARROW_UP; + case 'B': + return ARROW_DOWN; + case 'C': + return ARROW_RIGHT; + case 'D': + return ARROW_LEFT; + case 'H': + return HOME_KEY; + case 'F': + return END_KEY; + } + return '\x1b'; + } + } + else if (seq[0] == 'O') { + switch (seq[1]) { + case 'H': + return HOME_KEY; + case 'F': + return END_KEY; + } + } + } + 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/term.h b/term.h @@ -0,0 +1,31 @@ +#pragma once + +typedef enum { + ESC = 27, + BACKSPACE = 127, + ARROW_LEFT = 1000, + ARROW_RIGHT, + ARROW_UP, + ARROW_DOWN, + DEL_KEY, + HOME_KEY, + END_KEY, + PAGE_UP, + 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);