From f3b05c392991d5518c8669f8915115e9aca3984f Mon Sep 17 00:00:00 2001 From: Rekketstone Date: Wed, 15 Jan 2025 20:48:15 -0700 Subject: [PATCH] Alternative buffering and ANSI Escape Codes This kind helped with stabilizing vim and htop, still gotta fix them tho. --- config.mk | 2 +- cowterm.c | 354 +++++++++++++++++++++++++++++++++++++++++++----------- 2 files changed, 285 insertions(+), 71 deletions(-) diff --git a/config.mk b/config.mk index e20f6f9..6727dca 100644 --- a/config.mk +++ b/config.mk @@ -11,7 +11,7 @@ INCS = -I/usr/X11R6/include -I${FREETYPEINC} LIBS = -L/usr/X11R6/lib -lX11 -lutil ${FREETYPELIBS} # flags -CPPFLAGS = -D_DEFAULT_SOURCE -D_BSD_SOURCE -D_XOPEN_SOURCE=700 +CPPFLAGS = -D_DEFAULT_SOURCE -D_XOPEN_SOURCE=700 CFLAGS = -std=c99 -pedantic -Wall -Wextra -Os ${INCS} ${CPPFLAGS} LDFLAGS = ${LIBS} diff --git a/cowterm.c b/cowterm.c index 58f3f9f..277d624 100644 --- a/cowterm.c +++ b/cowterm.c @@ -47,6 +47,14 @@ static XFontStruct *bold_font = NULL; static XFontStruct *italic_font = NULL; static int window_focused = 0; +// Alternatives to handle the ANSI escape codes +static char **saved_terminal = NULL; +static unsigned long **saved_colors = NULL; +static int **saved_attrs = NULL; +static int saved_cursor_x = 0; +static int saved_cursor_y = 0; +static int using_alternate = 0; + #define ATTR_BOLD 1 #define ATTR_ITALIC 2 @@ -375,6 +383,123 @@ static void scroll_up(void) { needs_refresh = 1; } +// Alternative buffer visual: +// +// Normal buffer: Alternate buffer: +// [C][o][w] [ ][ ][ ] +// [T][e][r] [ ][ ][ ] +// [m][!][ ] <-> [ ][ ][ ] +// +// When applications like vim switch to alternate buffer it SHOULD: +// 1. Save the current buffer state (saved_terminal etc.) +// 2. creates a new empty alternate buffer +// 3. The application draws to the alternate buffer +// 4. When done, alternate buffer is freed +// 5. And the original buffer state is restored +// +// References: +// saved_terminal = backup of normal buffer +// saved_colors/attrs = backup of attributes +// using_alternate = tracks which buffer is active +static void handle_alternate_buffer(const char *buf, int *idx, int max_len) { + char num_buf[32] = {0}; + size_t num_idx = 0; + + (*idx)++; + while (*idx < max_len && buf[*idx] != 'h' && buf[*idx] != 'l') { + if (num_idx >= sizeof(num_buf) - 1) + break; + if (buf[*idx] >= '0' && buf[*idx] <= '9') { + num_buf[num_idx++] = buf[*idx]; + } + (*idx)++; + } + if (num_buf[0] != '\0') { + int code = atoi(num_buf); + if (buf[*idx] == 'h' || buf[*idx] == 'l') { + switch (code) { + case 1047: // Alternate screen buffer + if (buf[*idx] == 'h') { + if (!saved_terminal) { + saved_terminal = terminal_buffer; + saved_colors = color_buffer; + saved_attrs = attr_buffer; + terminal_buffer = malloc(term_rows * sizeof(char *)); + color_buffer = malloc(term_rows * sizeof(unsigned long *)); + attr_buffer = malloc(term_rows * sizeof(int *)); + for (int i = 0; i < term_rows; i++) { + terminal_buffer[i] = calloc(term_cols, sizeof(char)); + color_buffer[i] = calloc(term_cols, sizeof(unsigned long)); + attr_buffer[i] = calloc(term_cols, sizeof(int)); + } + using_alternate = 1; + } + } else if (using_alternate) { + for (int i = 0; i < term_rows; i++) { + free(terminal_buffer[i]); + free(color_buffer[i]); + free(attr_buffer[i]); + } + free(terminal_buffer); + free(color_buffer); + free(attr_buffer); + terminal_buffer = saved_terminal; + color_buffer = saved_colors; + attr_buffer = saved_attrs; + saved_terminal = NULL; + using_alternate = 0; + } + break; + case 1048: // Save/restore cursor + if (buf[*idx] == 'h') { + saved_cursor_x = cursor_x; + saved_cursor_y = cursor_y; + } else { + cursor_x = saved_cursor_x; + cursor_y = saved_cursor_y; + } + break; + case 1049: // Combined alternate buffer + cursor + if (buf[*idx] == 'h') { + saved_cursor_x = cursor_x; + saved_cursor_y = cursor_y; + if (!saved_terminal) { + saved_terminal = terminal_buffer; + saved_colors = color_buffer; + saved_attrs = attr_buffer; + terminal_buffer = malloc(term_rows * sizeof(char *)); + color_buffer = malloc(term_rows * sizeof(unsigned long *)); + attr_buffer = malloc(term_rows * sizeof(int *)); + for (int i = 0; i < term_rows; i++) { + terminal_buffer[i] = calloc(term_cols, sizeof(char)); + color_buffer[i] = calloc(term_cols, sizeof(unsigned long)); + attr_buffer[i] = calloc(term_cols, sizeof(int)); + } + using_alternate = 1; + } + } else if (using_alternate) { + for (int i = 0; i < term_rows; i++) { + free(terminal_buffer[i]); + free(color_buffer[i]); + free(attr_buffer[i]); + } + free(terminal_buffer); + free(color_buffer); + free(attr_buffer); + terminal_buffer = saved_terminal; + color_buffer = saved_colors; + attr_buffer = saved_attrs; + saved_terminal = NULL; + cursor_x = saved_cursor_x; + cursor_y = saved_cursor_y; + using_alternate = 0; + } + break; + } + } + } +} + // Handles those pesky ANSI color escape codes that terminals use. // When \033[m is sent to the terminal, it changes text color: // 30 = Black 31 = Red 32 = Green 33 = Yellow @@ -382,85 +507,168 @@ static void scroll_up(void) { // 0 sets everything back to plain white // P.S. yes I had to google this up static void parse_ansi_code(const char *buf, int *idx, int max_len) { - char num_buf[16] = {0}; - int num_idx = 0; + char num_buf[32] = {0}; + int params[32] = {0}; + size_t num_idx = 0; + size_t param_idx = 0; (*idx)++; // Skip [ - while (*idx < max_len && buf[*idx] != 'm') { + // Handle '?' prefix for private sequences + if (buf[*idx] == '?') { + handle_alternate_buffer(buf, idx, max_len); + return; + } + + while (*idx < max_len && buf[*idx] != 'm' && buf[*idx] != 'H' && + buf[*idx] != 'A' && buf[*idx] != 'B' && buf[*idx] != 'C' && + buf[*idx] != 'D' && buf[*idx] != 'K' && buf[*idx] != 'J' && + buf[*idx] != 'f' && buf[*idx] != 'r' && buf[*idx] != 's' && + buf[*idx] != 'u' && buf[*idx] != 'h' && buf[*idx] != 'l') { + if (num_idx >= sizeof(num_buf) - 1 || + param_idx >= sizeof(params) / sizeof(params[0]) - 1) { + break; + } if (buf[*idx] >= '0' && buf[*idx] <= '9') { num_buf[num_idx++] = buf[*idx]; + } else if (buf[*idx] == ';') { + if (num_buf[0] != '\0') { + params[param_idx++] = atoi(num_buf); + memset(num_buf, 0, sizeof(num_buf)); + num_idx = 0; + } } (*idx)++; } - if (num_buf[0] != '\0') { - int code = atoi(num_buf); - switch (code) { - case 0: - current_color = XWhitePixel(display, DefaultScreen(display)); - current_attr = 0; - break; - case 1: - current_attr |= ATTR_BOLD; - break; - case 3: - current_attr |= ATTR_ITALIC; - break; - case 22: - current_attr &= ~ATTR_BOLD; - break; - case 23: - current_attr &= ~ATTR_ITALIC; - break; - case 30: - current_color = XBlackPixel(display, DefaultScreen(display)); - break; - case 31: - current_color = RED; // Red - break; - case 32: - current_color = GREEN; // Green - break; - case 33: - current_color = YELLOW; // Yellow - break; - case 34: - current_color = BLUE; // Blue - break; - case 35: - current_color = MAGENTA; // Magenta - break; - case 36: - current_color = CYAN; // Cyan - break; - case 37: - current_color = XWhitePixel(display, DefaultScreen(display)); - break; - case 90: - current_color = DARK_GRAY; // Dark gray - break; - case 91: - current_color = LIGHT_RED; // Light red - break; - case 92: - current_color = LIGHT_GREEN; // Light green - break; - case 93: - current_color = LIGHT_YELLOW; // Light yellow - break; - case 94: - current_color = LIGHT_BLUE; // Light blue - break; - case 95: - current_color = LIGHT_MAGENTA; // Light magenta - break; - case 96: - current_color = LIGHT_CYAN; // Light cyan - break; - case 97: - current_color = 0xFFFFFF; // Bright white teehee - break; + if (num_buf[0] != '\0' && param_idx < sizeof(params) / sizeof(params[0])) { + params[param_idx++] = atoi(num_buf); + } + + switch (buf[*idx]) { + case 'H': // Set cursor position + case 'f': // Alternative set cursor position + if (param_idx >= 2) { + cursor_y = + (params[0] - 1 < 0) + ? 0 + : ((params[0] - 1) >= term_rows ? term_rows - 1 : params[0] - 1); + cursor_x = + (params[1] - 1 < 0) + ? 0 + : ((params[1] - 1) >= term_cols ? term_cols - 1 : params[1] - 1); + } else { + cursor_x = cursor_y = 0; } + break; + case 'A': // Cursor up + cursor_y -= params[0] ? params[0] : 1; + if (cursor_y < 0) + cursor_y = 0; + break; + case 'B': // Cursor down + cursor_y += params[0] ? params[0] : 1; + if (cursor_y >= term_rows) + cursor_y = term_rows - 1; + break; + case 'C': // Cursor forward + cursor_x += params[0] ? params[0] : 1; + if (cursor_x >= term_cols) + cursor_x = term_cols - 1; + break; + case 'D': // Cursor back + cursor_x -= params[0] ? params[0] : 1; + if (cursor_x < 0) + cursor_x = 0; + break; + case 'K': // Clear line + for (int i = cursor_x; i < term_cols; i++) { + terminal_buffer[cursor_y][i] = ' '; + color_buffer[cursor_y][i] = current_color; + attr_buffer[cursor_y][i] = current_attr; + } + break; + case 'J': // Clear screen + if (params[0] == 2) { + for (int y = 0; y < term_rows; y++) { + memset(terminal_buffer[y], ' ', term_cols); + for (int x = 0; x < term_cols; x++) { + color_buffer[y][x] = current_color; + attr_buffer[y][x] = current_attr; + } + } + needs_refresh = 1; + } + break; + case 'm': // Handling of text + for (int i = 0; i < param_idx; i++) { + switch (params[i]) { + case 0: + current_color = XWhitePixel(display, DefaultScreen(display)); + current_attr = 0; + break; + case 1: + current_attr |= ATTR_BOLD; + break; + case 3: + current_attr |= ATTR_ITALIC; + break; + case 22: + current_attr &= ~ATTR_BOLD; + break; + case 23: + current_attr &= ~ATTR_ITALIC; + break; + case 30: + current_color = XBlackPixel(display, DefaultScreen(display)); + break; + case 31: + current_color = RED; + break; + case 32: + current_color = GREEN; + break; + case 33: + current_color = YELLOW; + break; + case 34: + current_color = BLUE; + break; + case 35: + current_color = MAGENTA; + break; + case 36: + current_color = CYAN; + break; + case 37: + current_color = XWhitePixel(display, DefaultScreen(display)); + break; + case 90: + current_color = DARK_GRAY; + break; + case 91: + current_color = LIGHT_RED; + break; + case 92: + current_color = LIGHT_GREEN; + break; + case 93: + current_color = LIGHT_YELLOW; + break; + case 94: + current_color = LIGHT_BLUE; + break; + case 95: + current_color = LIGHT_MAGENTA; + break; + case 96: + current_color = LIGHT_CYAN; + break; + case 97: + current_color = 0xFFFFFF; + break; + } + } + break; } } @@ -562,6 +770,12 @@ static void key_press_cb(XKeyEvent *event, void *data) { write(*master, "\r", 1); } else if (keysym == XK_BackSpace) { write(*master, "\b", 1); + } else if (keysym == XK_Left) { + write(*master, "\033[D", 3); + needs_refresh = 1; + } else if (keysym == XK_Right) { + write(*master, "\033[C", 3); + needs_refresh = 1; } else if (len > 0) { write(*master, buf, len); }