#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "config.h" // There isn't a need for MAX_ROWS or MAX_COLS, but ¯\_(ツ)_/¯ #define MAX_ROWS 1000 #define MAX_COLS 1000 #define CHAR_WIDTH 8 #define CHAR_HEIGHT 16 // General window/terminal stuff static pid_t child_pid; static char **terminal_buffer = NULL; static unsigned long **color_buffer = NULL; static int **attr_buffer = NULL; static int term_rows; static int term_cols; static unsigned long current_color; static int current_attr = 0; static GC gc = NULL; static GC gc_bold = NULL; static Display *display = NULL; static struct timeval last_refresh; static Pixmap buffer_pixmap = None; static int master_fd = -1; static int needs_refresh = 0; static int window_focused = 0; static int scroll_top = 0; // 0 means entire terminal static int scroll_bottom = 0; // 0 means entire terminal // Cursor stuff static int cursor_visible = 1; static struct timeval last_blink; static int cursor_x = 0; static int cursor_y = 0; // Font stuff static XFontStruct *regular_font = NULL; static XFontStruct *bold_font = NULL; static XFontStruct *italic_font = NULL; static unsigned long current_bg_color; static unsigned long **bg_color_buffer = NULL; // 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 << 0) #define ATTR_ITALIC (1 << 1) #define ATTR_REVERSE (1 << 2) #define ATTR_DIM (1 << 4) #define ATTR_BLINK (1 << 5) // Pesky static smth smth error, so we "declare" it early static void draw_terminal(Display *display, Window window); static void draw_char(Display *display, Drawable d, int x, int y); // Cursor gameplay <== ==> static void draw_cursor(Display *display, Window window) { if (!gc || !terminal_buffer || !color_buffer || cursor_y >= term_rows || cursor_x >= term_cols) return; // First restore the character that was under the cursor if (cursor_x < term_cols && cursor_y < term_rows) { draw_char(display, buffer_pixmap, cursor_x, cursor_y); } if (cursor_visible) { // Save original color unsigned long original_color = color_buffer[cursor_y][cursor_x]; if (!window_focused) { XSetForeground(display, gc, original_color); XDrawRectangle(display, buffer_pixmap, gc, cursor_x * CHAR_WIDTH, cursor_y * CHAR_HEIGHT, CHAR_WIDTH - 1, CHAR_HEIGHT - 1); } else { XSetForeground(display, gc, original_color); XFillRectangle(display, buffer_pixmap, gc, cursor_x * CHAR_WIDTH, cursor_y * CHAR_HEIGHT, CHAR_WIDTH, CHAR_HEIGHT); if (cursor_x < term_cols && cursor_y < term_rows) { char str[2] = {terminal_buffer[cursor_y][cursor_x], '\0'}; XSetForeground(display, gc, XBlackPixel(display, DefaultScreen(display))); XFontStruct *font = regular_font; if (attr_buffer[cursor_y][cursor_x] & ATTR_BOLD) { font = bold_font; } else if (attr_buffer[cursor_y][cursor_x] & ATTR_ITALIC) { font = italic_font; } XSetFont(display, gc, font->fid); XDrawString(display, buffer_pixmap, gc, cursor_x * CHAR_WIDTH, (cursor_y + 1) * CHAR_HEIGHT - 2, str, 1); } } } // Copy the relevant area to the window XCopyArea(display, buffer_pixmap, window, gc, cursor_x * CHAR_WIDTH, cursor_y * CHAR_HEIGHT, CHAR_WIDTH, CHAR_HEIGHT, cursor_x * CHAR_WIDTH, cursor_y * CHAR_HEIGHT); } static void update_cursor_state(Display *display, Window window) { if (window_focused) { cursor_visible = 1; return; } } // resize_buffers visual: // // Old buffer (3x3): New buffer (4x4): // [a][b][c] [a][b][c][ ] // [d][e][f] -> [d][e][f][ ] // [g][h][i] [g][h][i][ ] // [ ][ ][ ][ ] // // 1. Allocates new larger buffer // 2. Copies existing content // 3. Fills new space with blanks // 4. Updates cursor position // 5. Frees old buffer static void resize_buffers(int new_rows, int new_cols) { if (new_rows > MAX_ROWS) new_rows = MAX_ROWS; if (new_cols > MAX_COLS) new_cols = MAX_COLS; if (new_rows < 1) new_rows = 1; if (new_cols < 1) new_cols = 1; size_t row_size = ((new_cols * sizeof(char) + 15) & ~15); size_t color_row_size = ((new_cols * sizeof(unsigned long) + 15) & ~15); size_t attr_row_size = ((new_cols * sizeof(int) + 15) & ~15); size_t bg_color_row_size = ((new_cols * sizeof(unsigned long) + 15) & ~15); char **new_term_buffer = malloc(new_rows * sizeof(char *)); unsigned long **new_color_buffer = malloc(new_rows * sizeof(unsigned long *)); int **new_attr_buffer = malloc(new_rows * sizeof(int *)); unsigned long **new_bg_color_buffer = malloc(new_rows * sizeof(unsigned long *)); if (!new_term_buffer || !new_color_buffer || !new_attr_buffer || !new_bg_color_buffer) { fprintf(stderr, "Failed to allocate memory for buffers\n"); return; } for (int i = 0; i < new_rows; i++) { new_term_buffer[i] = malloc(row_size); new_color_buffer[i] = malloc(color_row_size); new_attr_buffer[i] = malloc(attr_row_size); new_bg_color_buffer[i] = malloc(bg_color_row_size); if (!new_term_buffer[i] || !new_color_buffer[i] || !new_attr_buffer[i] || !new_bg_color_buffer[i]) { fprintf(stderr, "Failed to allocate memory for buffer row\n"); // Clean up allocated memory for (int j = 0; j < i; j++) { free(new_term_buffer[j]); free(new_color_buffer[j]); free(new_attr_buffer[j]); free(new_bg_color_buffer[j]); } free(new_term_buffer); free(new_color_buffer); free(new_attr_buffer); free(new_bg_color_buffer); return; } if (i < term_rows && i < new_rows && terminal_buffer && color_buffer && attr_buffer && bg_color_buffer) { int copy_cols = (new_cols < term_cols) ? new_cols : term_cols; memcpy(new_term_buffer[i], terminal_buffer[i], copy_cols * sizeof(char)); memcpy(new_color_buffer[i], color_buffer[i], copy_cols * sizeof(unsigned long)); memcpy(new_attr_buffer[i], attr_buffer[i], copy_cols * sizeof(int)); memcpy(new_bg_color_buffer[i], bg_color_buffer[i], copy_cols * sizeof(unsigned long)); if (new_cols > term_cols) { memset(new_term_buffer[i] + term_cols, ' ', new_cols - term_cols); for (int x = term_cols; x < new_cols; x++) { new_color_buffer[i][x] = current_color; new_attr_buffer[i][x] = current_attr; new_bg_color_buffer[i][x] = current_bg_color; } } } else { memset(new_term_buffer[i], ' ', new_cols); for (int x = 0; x < new_cols; x++) { new_color_buffer[i][x] = current_color; new_attr_buffer[i][x] = current_attr; new_bg_color_buffer[i][x] = current_bg_color; } } } // Free old buffers after successful allocation if (terminal_buffer && color_buffer && attr_buffer && bg_color_buffer) { for (int i = 0; i < term_rows; i++) { free(terminal_buffer[i]); free(color_buffer[i]); free(attr_buffer[i]); free(bg_color_buffer[i]); } free(terminal_buffer); free(color_buffer); free(attr_buffer); free(bg_color_buffer); } terminal_buffer = new_term_buffer; color_buffer = new_color_buffer; attr_buffer = new_attr_buffer; bg_color_buffer = new_bg_color_buffer; term_rows = new_rows; term_cols = new_cols; cursor_x = (cursor_x >= term_cols) ? term_cols - 1 : cursor_x; cursor_y = (cursor_y >= term_rows) ? term_rows - 1 : cursor_y; // Resize buffer pixmap if (buffer_pixmap != None) { XFreePixmap(display, buffer_pixmap); } buffer_pixmap = XCreatePixmap(display, DefaultRootWindow(display), term_cols * CHAR_WIDTH, term_rows * CHAR_HEIGHT, DefaultDepth(display, DefaultScreen(display))); needs_refresh = 1; } static void destroy_cb(Display *display, Window window) { if (child_pid > 0) { kill(child_pid, SIGTERM); waitpid(child_pid, NULL, 0); } if (gc) XFreeGC(display, gc); if (gc_bold) XFreeGC(display, gc_bold); if (buffer_pixmap != None) XFreePixmap(display, buffer_pixmap); if (display) XCloseDisplay(display); if (terminal_buffer) { for (int i = 0; i < term_rows; i++) { free(terminal_buffer[i]); } free(terminal_buffer); } if (color_buffer) { for (int i = 0; i < term_rows; i++) { free(color_buffer[i]); } free(color_buffer); } if (attr_buffer) { for (int i = 0; i < term_rows; i++) { free(attr_buffer[i]); } free(attr_buffer); } if (bg_color_buffer) { for (int i = 0; i < term_rows; i++) { free(bg_color_buffer[i]); } free(bg_color_buffer); } if (master_fd >= 0) close(master_fd); exit(0); } static void draw_char(Display *display, Drawable d, int x, int y) { if (!gc || !terminal_buffer || !color_buffer || !attr_buffer || !bg_color_buffer) return; if (x < 0 || x >= term_cols || y < 0 || y >= term_rows) return; char c = terminal_buffer[y][x]; if (c < 32 || c > 126) // Only allow printable ASCII characters return; unsigned long fg = color_buffer[y][x]; unsigned long bg = bg_color_buffer[y][x]; int attr = attr_buffer[y][x]; static unsigned long last_bg = 0; static int last_x = -1; static int last_y = -1; if (attr & ATTR_REVERSE) { unsigned long tmp = fg; fg = bg; bg = tmp; } if (last_x != x || last_y != y || last_bg != bg) { XSetForeground(display, gc, bg); XFillRectangle(display, d, gc, x * CHAR_WIDTH, y * CHAR_HEIGHT, CHAR_WIDTH + 1, CHAR_HEIGHT); last_x = x; last_y = y; last_bg = bg; } XSetForeground(display, gc, fg); char str[2] = {c, '\0'}; XFontStruct *font = regular_font; if (attr & ATTR_BOLD) font = bold_font; else if (attr & ATTR_ITALIC) font = italic_font; XSetFont(display, gc, font->fid); XDrawString(display, d, gc, x * CHAR_WIDTH, (y + 1) * CHAR_HEIGHT - 4, str, 1); } static void draw_terminal(Display *display, Window window) { if (!gc) { gc = XCreateGC(display, window, 0, NULL); regular_font = XLoadQueryFont(display, REGULAR_FONT); bold_font = XLoadQueryFont(display, BOLD_FONT); italic_font = XLoadQueryFont(display, ITALICS_FONT); if (!regular_font) regular_font = XLoadQueryFont(display, "fixed"); if (!bold_font) bold_font = regular_font; if (!italic_font) italic_font = regular_font; XSetFont(display, gc, regular_font->fid); } if (!terminal_buffer || !color_buffer || !attr_buffer) return; if (buffer_pixmap == None) buffer_pixmap = XCreatePixmap( display, DefaultRootWindow(display), term_cols * CHAR_WIDTH, term_rows * CHAR_HEIGHT, DefaultDepth(display, DefaultScreen(display))); if (needs_refresh) { int start_y = scroll_top ? scroll_top - 1 : 0; int end_y = scroll_bottom ? scroll_bottom : term_rows; if (end_y > term_rows) { end_y = term_rows; } if (scroll_top == 0 && scroll_bottom == 0) { // Full screen refresh for (int y = 0; y < term_rows; y++) { for (int x = 0; x < term_cols; x++) { draw_char(display, buffer_pixmap, x, y); } } } else { // Only refresh scroll region for (int y = start_y; y < end_y; y++) { for (int x = 0; x < term_cols; x++) { draw_char(display, buffer_pixmap, x, y); } } } } XCopyArea(display, buffer_pixmap, window, gc, 0, 0, term_cols * CHAR_WIDTH, term_rows * CHAR_HEIGHT, 0, 0); draw_cursor(display, window); XFlush(display); needs_refresh = 0; } static void check_refresh(Display *display, Window window) { if (!needs_refresh) return; struct timeval now; gettimeofday(&now, NULL); long elapsed = (now.tv_sec - last_refresh.tv_sec) * 1000000 + (now.tv_usec - last_refresh.tv_usec); if (elapsed >= REFRESH_INTERVAL) { draw_terminal(display, window); last_refresh = now; } } static void scroll_region_up(int amount) { int start = scroll_top ? scroll_top - 1 : 0; int end = scroll_bottom ? scroll_bottom - 1 : term_rows - 1; if (end >= term_rows) end = term_rows - 1; if (start > end) return; for (int i = start; i <= end - amount; i++) { memcpy(terminal_buffer[i], terminal_buffer[i + amount], term_cols); memcpy(color_buffer[i], color_buffer[i + amount], term_cols * sizeof(unsigned long)); memcpy(attr_buffer[i], attr_buffer[i + amount], term_cols * sizeof(int)); memcpy(bg_color_buffer[i], bg_color_buffer[i + amount], term_cols * sizeof(unsigned long)); } for (int i = end - amount + 1; i <= end; i++) { memset(terminal_buffer[i], ' ', term_cols); for (int x = 0; x < term_cols; x++) { color_buffer[i][x] = current_color; attr_buffer[i][x] = current_attr; bg_color_buffer[i][x] = current_bg_color; } } needs_refresh = 1; } static void scroll_region_down(int amount) { int start = ((scroll_top - 1) < 0) ? 0 : scroll_top - 1; int end = ((scroll_bottom - 1) < 0) ? 0 : scroll_bottom - 1; if (end >= term_rows) end = term_rows - 1; char **tbuf = malloc(term_rows * sizeof(char *)); unsigned long **cbuf = malloc(term_rows * sizeof(unsigned long *)); int **abuf = malloc(term_rows * sizeof(int *)); unsigned long **bbuf = malloc(term_rows * sizeof(unsigned long *)); if (!tbuf || !cbuf || !abuf || !bbuf) { fprintf(stderr, "scroll down: malloc failed\n"); goto cleanup; } for (int i = 0; i < term_rows; i++) { tbuf[i] = malloc(term_cols); cbuf[i] = malloc(term_cols * sizeof(unsigned long)); abuf[i] = malloc(term_cols * sizeof(int)); bbuf[i] = malloc(term_cols * sizeof(unsigned long)); if (!tbuf[i] || !cbuf[i] || !abuf[i] || !bbuf[i]) { fprintf(stderr, "scroll down: malloc failed\n"); goto cleanup; } } if (scroll_top == 0 && scroll_bottom == 0) { for (int i = 0; i < term_rows; i++) { memcpy(tbuf[i], terminal_buffer[i], term_cols); memcpy(cbuf[i], color_buffer[i], term_cols * sizeof(unsigned long)); memcpy(abuf[i], attr_buffer[i], term_cols * sizeof(int)); memcpy(bbuf[i], bg_color_buffer[i], term_cols * sizeof(unsigned long)); } for (int j = 0; j < amount; j++) { for (int y = term_rows - 1; y > 0; y--) { memcpy(terminal_buffer[y], tbuf[y - 1], term_cols); memcpy(color_buffer[y], cbuf[y - 1], term_cols * sizeof(unsigned long)); memcpy(attr_buffer[y], abuf[y - 1], term_cols * sizeof(int)); memcpy(bg_color_buffer[y], bbuf[y - 1], term_cols * sizeof(unsigned long)); } memset(terminal_buffer[0], ' ', term_cols); for (int x = 0; x < term_cols; x++) { color_buffer[0][x] = current_color; attr_buffer[0][x] = current_attr; bg_color_buffer[0][x] = current_bg_color; } } } else { for (int i = 0; i < term_rows; i++) { memcpy(tbuf[i], terminal_buffer[i], term_cols); memcpy(cbuf[i], color_buffer[i], term_cols * sizeof(unsigned long)); memcpy(abuf[i], attr_buffer[i], term_cols * sizeof(int)); memcpy(bbuf[i], bg_color_buffer[i], term_cols * sizeof(unsigned long)); } for (int j = 0; j < amount; j++) { for (int y = end; y > start; y--) { memcpy(terminal_buffer[y], tbuf[y - 1], term_cols); memcpy(color_buffer[y], cbuf[y - 1], term_cols * sizeof(unsigned long)); memcpy(attr_buffer[y], abuf[y - 1], term_cols * sizeof(int)); memcpy(bg_color_buffer[y], bbuf[y - 1], term_cols * sizeof(unsigned long)); } memset(terminal_buffer[start], ' ', term_cols); for (int x = 0; x < term_cols; x++) { color_buffer[start][x] = current_color; attr_buffer[start][x] = current_attr; bg_color_buffer[start][x] = current_bg_color; } } } cleanup: if (tbuf) { for (int i = 0; i < term_rows; i++) free(tbuf[i]); free(tbuf); } if (cbuf) { for (int i = 0; i < term_rows; i++) free(cbuf[i]); free(cbuf); } if (abuf) { for (int i = 0; i < term_rows; i++) free(abuf[i]); free(abuf); } if (bbuf) { for (int i = 0; i < term_rows; i++) free(bbuf[i]); free(bbuf); } 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, Window window) { 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]) return; int code = atoi(num_buf); if (buf[*idx] != 'h' && buf[*idx] != 'l') return; switch (code) { case 1047: // Alt buffer if (buf[*idx] == 'h') { if (saved_terminal) return; saved_terminal = terminal_buffer; saved_colors = color_buffer; saved_attrs = attr_buffer; saved_colors = bg_color_buffer; terminal_buffer = malloc(term_rows * sizeof(char *)); color_buffer = malloc(term_rows * sizeof(unsigned long *)); attr_buffer = malloc(term_rows * sizeof(int *)); bg_color_buffer = malloc(term_rows * sizeof(unsigned long *)); if (!terminal_buffer || !color_buffer || !attr_buffer || !bg_color_buffer) return; for (int i = 0; i < term_rows; i++) { terminal_buffer[i] = calloc(term_cols, 1); color_buffer[i] = calloc(term_cols, sizeof(unsigned long)); attr_buffer[i] = calloc(term_cols, sizeof(int)); bg_color_buffer[i] = calloc(term_cols, sizeof(unsigned long)); if (!terminal_buffer[i] || !color_buffer[i] || !attr_buffer[i] || !bg_color_buffer[i]) return; memset(terminal_buffer[i], ' ', term_cols); memset(attr_buffer[i], 0, term_cols * sizeof(int)); memset(color_buffer[i], current_color, term_cols * sizeof(unsigned long)); memset(bg_color_buffer[i], current_bg_color, term_cols * sizeof(unsigned long)); } using_alternate = 1; needs_refresh = 1; draw_terminal(display, window); } 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(bg_color_buffer[i]); } free(terminal_buffer); free(color_buffer); free(attr_buffer); free(bg_color_buffer); terminal_buffer = saved_terminal; color_buffer = saved_colors; attr_buffer = saved_attrs; bg_color_buffer = saved_colors; saved_terminal = NULL; using_alternate = 0; needs_refresh = 1; draw_terminal(display, window); } 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: // Alt buffer + cursor if (buf[*idx] == 'h') { saved_cursor_x = cursor_x; saved_cursor_y = cursor_y; if (saved_terminal) return; saved_terminal = terminal_buffer; saved_colors = color_buffer; saved_attrs = attr_buffer; saved_colors = bg_color_buffer; terminal_buffer = malloc(term_rows * sizeof(char *)); color_buffer = malloc(term_rows * sizeof(unsigned long *)); attr_buffer = malloc(term_rows * sizeof(int *)); bg_color_buffer = malloc(term_rows * sizeof(unsigned long *)); if (!terminal_buffer || !color_buffer || !attr_buffer || !bg_color_buffer) return; for (int i = 0; i < term_rows; i++) { terminal_buffer[i] = calloc(term_cols, 1); color_buffer[i] = calloc(term_cols, sizeof(unsigned long)); attr_buffer[i] = calloc(term_cols, sizeof(int)); bg_color_buffer[i] = calloc(term_cols, sizeof(unsigned long)); if (!terminal_buffer[i] || !color_buffer[i] || !attr_buffer[i] || !bg_color_buffer[i]) return; memset(terminal_buffer[i], ' ', term_cols); memset(attr_buffer[i], 0, term_cols * sizeof(int)); memset(color_buffer[i], current_color, term_cols * sizeof(unsigned long)); memset(bg_color_buffer[i], current_bg_color, term_cols * sizeof(unsigned long)); } using_alternate = 1; needs_refresh = 1; draw_terminal(display, window); } 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(bg_color_buffer[i]); } free(terminal_buffer); free(color_buffer); free(attr_buffer); free(bg_color_buffer); terminal_buffer = saved_terminal; color_buffer = saved_colors; attr_buffer = saved_attrs; bg_color_buffer = saved_colors; saved_terminal = NULL; cursor_x = saved_cursor_x; cursor_y = saved_cursor_y; using_alternate = 0; needs_refresh = 1; draw_terminal(display, window); } break; } } static unsigned long get_ansi_color(int index) { static const unsigned long colors[] = {0x000000, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, 0xFFFFFF}; return colors[index & 7]; } static unsigned long get_bright_ansi_color(int index) { static const unsigned long colors[] = { DARK_GRAY, LIGHT_RED, LIGHT_GREEN, LIGHT_YELLOW, LIGHT_BLUE, LIGHT_MAGENTA, LIGHT_CYAN, 0xFFFFFF}; return colors[index & 7]; } // 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 // 34 = Blue 35 = Magenta 36 = Cyan 37 = White // 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, Window window) { char num_buf[32] = {0}; int params[32] = {0}; size_t num_idx = 0; size_t param_idx = 0; (*idx)++; // Skip [ // Handle '?' prefix for private sequences if (buf[*idx] == '?') { handle_alternate_buffer(buf, idx, max_len, window); return; } // Parse parameters while (*idx < max_len) { char c = buf[*idx]; if (c == 'm' || c == 'H' || c == 'A' || c == 'B' || c == 'C' || c == 'D' || c == 'K' || c == 'J' || c == 'f' || c == 'r' || c == 's' || c == 'u' || c == 'h' || c == 'l' || c == 'E' || c == 'F' || c == 'G' || c == 'S' || c == 'T' || c == '@' || c == 'P' || c == 'M' || c == 'L' || c == 'd') { break; } if (num_idx >= sizeof(num_buf) - 1 || param_idx >= sizeof(params) / sizeof(params[0]) - 1) { break; } if (c >= '0' && c <= '9') { num_buf[num_idx++] = c; } else if (c == ';') { 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' && param_idx < sizeof(params) / sizeof(params[0])) { params[param_idx++] = atoi(num_buf); } switch (buf[*idx]) { case 'H': // 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 's': // Save cursor position if (param_idx == 0) { saved_cursor_x = cursor_x; saved_cursor_y = cursor_y; } break; case 'u': // Restore cursor position if (param_idx == 0) { cursor_x = saved_cursor_x; cursor_y = saved_cursor_y; } break; case 'h': // Set mode case 'l': // Reset mode if (param_idx >= 1) { switch (params[0]) { case 25: // Show/hide cursor cursor_visible = (buf[*idx] == 'h'); needs_refresh = 1; break; case 12: // Start/stop blinking cursor cursor_visible = 1; needs_refresh = 1; break; default: break; } } break; 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 'd': // Line position absolute cursor_y = params[0] - 1 < 0 ? 0 : params[0] - 1 >= term_rows ? term_rows - 1 : params[0] - 1; break; case 'G': // Column position absolute cursor_x = params[0] - 1 < 0 ? 0 : params[0] - 1 >= term_cols ? term_cols - 1 : params[0] - 1; 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 '@': // Insert blank characters { int n = params[0] ? params[0] : 1; if (n > term_cols - cursor_x) n = term_cols - cursor_x; memmove(&terminal_buffer[cursor_y][cursor_x + n], &terminal_buffer[cursor_y][cursor_x], term_cols - cursor_x - n); memset(&terminal_buffer[cursor_y][cursor_x], ' ', n); } break; case 'P': // Delete characters { int n = params[0] ? params[0] : 1; if (n > term_cols - cursor_x) n = term_cols - cursor_x; memmove(&terminal_buffer[cursor_y][cursor_x], &terminal_buffer[cursor_y][cursor_x + n], term_cols - cursor_x - n); memset(&terminal_buffer[cursor_y][term_cols - n], ' ', n); } break; case 'M': // Delete lines { int n = params[0] ? params[0] : 1; if (cursor_y < term_rows - n) { memmove(&terminal_buffer[cursor_y], &terminal_buffer[cursor_y + n], (term_rows - cursor_y - n) * sizeof(char *)); for (int i = term_rows - n; i < term_rows; i++) memset(terminal_buffer[i], ' ', term_cols); } } break; case 'L': // Insert lines { int n = params[0] ? params[0] : 1; if (cursor_y < term_rows - n) { memmove(&terminal_buffer[cursor_y + n], &terminal_buffer[cursor_y], (term_rows - cursor_y - n) * sizeof(char *)); for (int i = cursor_y; i < cursor_y + n; i++) memset(terminal_buffer[i], ' ', term_cols); } } 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; bg_color_buffer[cursor_y][i] = current_bg_color; } 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; bg_color_buffer[y][x] = current_bg_color; } } needs_refresh = 1; } break; case 'r': // Set scrolling region if (param_idx == 0) { scroll_top = 0; // Entire terminal scroll_bottom = 0; } else if (param_idx == 1) { scroll_top = params[0] <= 0 ? 1 : params[0] > term_rows ? term_rows : params[0]; scroll_bottom = 0; } else if (param_idx >= 2) { scroll_top = params[0] <= 0 ? 1 : params[0] > term_rows ? term_rows : params[0]; scroll_bottom = params[1] <= 0 ? term_rows : params[1] > term_rows ? term_rows : params[1]; // if top is bigger than bottom, swap values if (scroll_top > scroll_bottom) { int tmp = scroll_top; scroll_top = scroll_bottom; scroll_bottom = tmp; } } break; case 'S': // Scroll up scroll_region_up(params[0] ? params[0] : 1); needs_refresh = 1; break; case 'T': // Scroll down scroll_region_down(params[0] ? params[0] : 1); needs_refresh = 1; break; case 'E': // Cursor next line cursor_x = 0; cursor_y += params[0] ? params[0] : 1; if (cursor_y >= term_rows) cursor_y = term_rows - 1; break; case 'F': // Cursor line before cursor_x = 0; cursor_y -= params[0] ? params[0] : 1; if (cursor_y < 0) cursor_y = 0; break; case 'm': // Handling of text for (int i = 0; i < param_idx; i++) { if (params[i] == 0) { // Reset Everything current_color = XWhitePixel(display, DefaultScreen(display)); current_bg_color = XBlackPixel(display, DefaultScreen(display)); current_attr = 0; } else if (params[i] == 1) { // Apply ATTRS current_attr |= ATTR_BOLD; } else if (params[i] == 2) { current_attr |= ATTR_DIM; } else if (params[i] == 3) { current_attr |= ATTR_ITALIC; } else if (params[i] == 5) { // Slow blink current_attr |= ATTR_BLINK; } else if (params[i] == 7) { current_attr |= ATTR_REVERSE; } else if (params[i] == 22) { // Reset ATTRS current_attr &= ~ATTR_BOLD; } else if (params[i] == 23) { current_attr &= ~ATTR_ITALIC; } else if (params[i] == 25) { current_attr &= ~ATTR_BLINK; } else if (params[i] == 27) { current_attr &= ~ATTR_REVERSE; } else if (params[i] >= 30 && params[i] <= 37) { // Foreground colors current_color = get_ansi_color(params[i] - 30); } else if (params[i] >= 40 && params[i] <= 47) { // Background colors current_bg_color = get_ansi_color(params[i] - 40); } else if (params[i] >= 90 && params[i] <= 97) { // Bright foreground current_color = get_bright_ansi_color(params[i] - 90); } else if (params[i] >= 100 && params[i] <= 107) { // Bright background current_bg_color = get_bright_ansi_color(params[i] - 100); } } break; } } static int master_cb(int fd, void *data, Window window) { if (!terminal_buffer || !color_buffer || !attr_buffer || !bg_color_buffer) return 0; struct { Display *display; Window window; } *ctx = data; char buf[4096]; ssize_t n = read(fd, buf, sizeof(buf)); if (n <= 0 && errno != EAGAIN && errno != EINTR) { destroy_cb(ctx->display, ctx->window); return 0; } if (n <= 0) return 1; for (int i = 0; i < n; i++) { if (buf[i] == '\033' && i + 1 < n && buf[i + 1] == '[') { parse_ansi_code(buf, &i, n, window); continue; } switch (buf[i]) { case '\n': cursor_x = 0; if (++cursor_y >= term_rows) { scroll_region_up(1); cursor_y = term_rows - 1; } needs_refresh = 1; break; case '\r': cursor_x = 0; break; case '\b': if (cursor_x > 0) { cursor_x--; needs_refresh = 1; } else if (cursor_y > 0) { cursor_y--; cursor_x = term_cols - 1; while (cursor_x > 0 && terminal_buffer[cursor_y][cursor_x - 1] == ' ') cursor_x--; needs_refresh = 1; } break; default: if (cursor_y >= term_rows || cursor_x >= term_cols) break; terminal_buffer[cursor_y][cursor_x] = buf[i]; color_buffer[cursor_y][cursor_x] = current_color; attr_buffer[cursor_y][cursor_x] = current_attr; bg_color_buffer[cursor_y][cursor_x] = current_bg_color; needs_refresh = 1; if (++cursor_x >= term_cols) { cursor_x = 0; if (++cursor_y >= term_rows) { scroll_region_up(1); cursor_y = term_rows - 1; } } break; } } cursor_visible = 1; if (needs_refresh) check_refresh(ctx->display, ctx->window); else { draw_cursor(ctx->display, ctx->window); XFlush(ctx->display); } return 1; } static void key_press_cb(XKeyEvent *event, void *data) { int *master = (int *)data; char buf[32]; KeySym keysym; int len; len = XLookupString(event, buf, sizeof(buf), &keysym, NULL); if (event->state & ControlMask) { if ((keysym & 0x1f) == ('L' & 0x1f)) { write(*master, "\f", 1); return; } if ((keysym & 0x1f) == ('C' & 0x1f)) { write(*master, "\x03", 1); return; } if ((keysym & 0x1f) == ('U' & 0x1f)) { write(*master, "\025", 1); return; } if ((keysym & 0x1f) >= '@' && (keysym & 0x1f) <= '_') { char ctrl_char = '@' + (keysym & 0x1f); write(*master, &ctrl_char, 1); return; } return; } if (keysym == XK_Return || keysym == XK_KP_Enter) { write(*master, "\r", 1); // Shell handles return } else if (keysym == XK_BackSpace) { write(*master, "\177", 1); // Shell handles backspace } else if (keysym == XK_Left) { if (cursor_x > 0) { write(*master, "\033[D", 3); } else if (cursor_y > 0) { char seq[8]; snprintf(seq, sizeof(seq), "\033[A\033[%dC", term_cols); write(*master, seq, strlen(seq)); } needs_refresh = 1; } else if (keysym == XK_Right) { if (cursor_x < term_cols - 1) { write(*master, "\033[C", 3); } else if (cursor_y < term_rows - 1) { write(*master, "\033[B\033[G", 6); } needs_refresh = 1; } else if (keysym == XK_Up) { // Go up through history write(*master, "\033[A", 3); } else if (keysym == XK_Down) { // Go down through history write(*master, "\033[B", 3); } else if (keysym == XK_Tab) { write(*master, "\t", 1); // Shell handles autocomplete } else if (len > 0) { if (len > (int)sizeof(buf)) len = sizeof(buf); write(*master, buf, len); if (cursor_x >= term_cols - 1) { write(*master, "\r\n", 2); } } } int main(void) { int master, slave; struct winsize ws = {.ws_row = 24, .ws_col = 80, .ws_xpixel = 80 * CHAR_WIDTH, .ws_ypixel = 24 * CHAR_HEIGHT}; term_rows = ws.ws_row; term_cols = ws.ws_col; gettimeofday(&last_blink, 0); gettimeofday(&last_refresh, 0); if (!(terminal_buffer = calloc(term_rows, sizeof(char *))) || !(color_buffer = calloc(term_rows, sizeof(unsigned long *))) || !(attr_buffer = calloc(term_rows, sizeof(int *))) || !(bg_color_buffer = calloc(term_rows, sizeof(unsigned long *)))) return 1; for (int i = 0; i < term_rows; i++) { if (!(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))) || !(bg_color_buffer[i] = calloc(term_cols, sizeof(unsigned long)))) return 1; memset(terminal_buffer[i], ' ', term_cols); } if (!(display = XOpenDisplay(0))) return 1; XInitThreads(); current_color = XWhitePixel(display, DefaultScreen(display)); current_bg_color = XBlackPixel(display, DefaultScreen(display)); for (int i = 0; i < term_rows; i++) for (int j = 0; j < term_cols; j++) { bg_color_buffer[i][j] = current_bg_color; color_buffer[i][j] = current_color; } XSizeHints hints = {.flags = PResizeInc | PMinSize, .width_inc = CHAR_WIDTH, .height_inc = CHAR_HEIGHT, .min_width = CHAR_WIDTH * 4, .min_height = CHAR_HEIGHT}; Window win = XCreateSimpleWindow( display, DefaultRootWindow(display), 0, 0, term_cols * CHAR_WIDTH, term_rows * CHAR_HEIGHT, 0, XBlackPixel(display, DefaultScreen(display)), XBlackPixel(display, DefaultScreen(display))); buffer_pixmap = XCreatePixmap(display, DefaultRootWindow(display), term_cols * CHAR_WIDTH, term_rows * CHAR_HEIGHT, DefaultDepth(display, DefaultScreen(display))); XSetWMNormalHints(display, win, &hints); XStoreName(display, win, window_name); XSelectInput(display, win, KeyPressMask | ExposureMask | StructureNotifyMask | FocusChangeMask); XMapWindow(display, win); if (openpty(&master, &slave, 0, 0, &ws) == -1) return 1; master_fd = master; if ((child_pid = fork()) == -1) return 1; if (!child_pid) { close(master); for (int i = 0; i < 3; i++) close(i); setsid(); for (int i = 0; i < 3; i++) if (dup2(slave, i) == -1) return 1; if (slave > 2) close(slave); char *sh = getenv("SHELL"); setenv("TERM", "xterm-256color", 1); execl(sh ? sh : "/bin/sh", sh ? sh : "sh", 0); return 1; } close(slave); struct { Display *display; Window window; } ctx = {display, win}; XEvent ev; fd_set fds; struct timeval tv = {0, 1000}; for (;;) { if (waitpid(child_pid, 0, WNOHANG) == child_pid) { destroy_cb(display, win); return 0; } while (XPending(display)) { XNextEvent(display, &ev); switch (ev.type) { case ConfigureNotify: { XConfigureEvent *ce = &ev.xconfigure; int nr = ce->height / CHAR_HEIGHT; int nc = ce->width / CHAR_WIDTH; if (nr != term_rows || nc != term_cols) { resize_buffers(nr, nc); ws.ws_row = term_rows; ws.ws_col = term_cols; ws.ws_xpixel = term_cols * CHAR_WIDTH; ws.ws_ypixel = term_rows * CHAR_HEIGHT; ioctl(master, TIOCSWINSZ, &ws); } break; } case FocusIn: window_focused = needs_refresh = 1; break; case FocusOut: window_focused = 0; needs_refresh = 1; break; case KeyPress: if (ev.xkey.keycode < 128) { key_press_cb(&ev.xkey, &master); } break; case Expose: needs_refresh = 1; check_refresh(display, win); break; } } FD_ZERO(&fds); FD_SET(master, &fds); if (select(master + 1, &fds, 0, 0, &tv) > 0) master_cb(master, &ctx, win); check_refresh(display, win); update_cursor_state(display, win); } }