2025-01-16 20:55:08 +00:00
|
|
|
#include <X11/X.h>
|
2025-01-15 23:36:48 +00:00
|
|
|
#include <X11/Xlib.h>
|
|
|
|
#include <X11/Xutil.h>
|
|
|
|
#include <X11/keysym.h>
|
|
|
|
#include <errno.h>
|
|
|
|
#include <fcntl.h>
|
|
|
|
#include <pty.h>
|
|
|
|
#include <signal.h>
|
|
|
|
#include <stdio.h>
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include <string.h>
|
|
|
|
#include <sys/ioctl.h>
|
|
|
|
#include <sys/time.h>
|
|
|
|
#include <sys/types.h>
|
|
|
|
#include <sys/wait.h>
|
|
|
|
#include <termios.h>
|
|
|
|
#include <unistd.h>
|
|
|
|
|
|
|
|
#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
|
|
|
|
|
2025-01-16 17:03:58 +00:00
|
|
|
// General window/terminal stuff
|
2025-01-15 23:36:48 +00:00
|
|
|
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;
|
2025-01-16 00:54:45 +00:00
|
|
|
static struct timeval last_refresh;
|
2025-01-15 23:36:48 +00:00
|
|
|
static Pixmap buffer_pixmap = None;
|
|
|
|
static int master_fd = -1;
|
2025-01-16 00:54:45 +00:00
|
|
|
static int needs_refresh = 0;
|
2025-01-16 17:03:58 +00:00
|
|
|
static int window_focused = 0;
|
2025-01-16 20:55:08 +00:00
|
|
|
static int scroll_top = 0; // 0 means entire terminal
|
|
|
|
static int scroll_bottom = 0; // 0 means entire terminal
|
2025-01-16 17:03:58 +00:00
|
|
|
|
|
|
|
// Cursor stuff
|
|
|
|
static int cursor_visible = 1;
|
|
|
|
static struct timeval last_blink;
|
|
|
|
static int cursor_x = 0;
|
|
|
|
static int cursor_y = 0;
|
|
|
|
|
|
|
|
// Font stuff
|
2025-01-15 23:36:48 +00:00
|
|
|
static XFontStruct *regular_font = NULL;
|
|
|
|
static XFontStruct *bold_font = NULL;
|
|
|
|
static XFontStruct *italic_font = NULL;
|
2025-01-16 17:03:58 +00:00
|
|
|
static unsigned long current_bg_color;
|
|
|
|
static unsigned long **bg_color_buffer = NULL;
|
2025-01-15 23:36:48 +00:00
|
|
|
|
2025-01-16 03:48:15 +00:00
|
|
|
// 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;
|
|
|
|
|
2025-01-16 17:03:58 +00:00
|
|
|
#define ATTR_BOLD (1 << 0)
|
|
|
|
#define ATTR_ITALIC (1 << 1)
|
|
|
|
#define ATTR_REVERSE (1 << 2)
|
2025-01-16 18:29:27 +00:00
|
|
|
// #define ATTR_UNDERLINE (1 << 3) // Underline currently is buggy
|
2025-01-16 17:03:58 +00:00
|
|
|
#define ATTR_DIM (1 << 4)
|
|
|
|
#define ATTR_BLINK (1 << 5)
|
2025-01-15 23:36:48 +00:00
|
|
|
|
|
|
|
// 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];
|
|
|
|
|
2025-01-16 00:54:45 +00:00
|
|
|
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);
|
2025-01-15 23:36:48 +00:00
|
|
|
|
2025-01-16 00:54:45 +00:00
|
|
|
XDrawString(display, buffer_pixmap, gc, cursor_x * CHAR_WIDTH,
|
|
|
|
(cursor_y + 1) * CHAR_HEIGHT - 2, str, 1);
|
|
|
|
}
|
2025-01-15 23:36:48 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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);
|
|
|
|
}
|
|
|
|
|
2025-01-16 00:54:45 +00:00
|
|
|
static void update_cursor_state(Display *display, Window window) {
|
|
|
|
if (window_focused) {
|
|
|
|
cursor_visible = 1;
|
|
|
|
return;
|
2025-01-15 23:36:48 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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);
|
2025-01-16 17:03:58 +00:00
|
|
|
size_t bg_color_row_size = ((new_cols * sizeof(unsigned long) + 15) & ~15);
|
2025-01-15 23:36:48 +00:00
|
|
|
|
|
|
|
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 *));
|
2025-01-16 17:03:58 +00:00
|
|
|
unsigned long **new_bg_color_buffer =
|
|
|
|
malloc(new_rows * sizeof(unsigned long *));
|
2025-01-15 23:36:48 +00:00
|
|
|
|
2025-01-16 17:03:58 +00:00
|
|
|
if (!new_term_buffer || !new_color_buffer || !new_attr_buffer ||
|
|
|
|
!new_bg_color_buffer) {
|
2025-01-15 23:36:48 +00:00
|
|
|
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);
|
2025-01-16 17:03:58 +00:00
|
|
|
new_bg_color_buffer[i] = malloc(bg_color_row_size);
|
2025-01-15 23:36:48 +00:00
|
|
|
|
2025-01-16 17:03:58 +00:00
|
|
|
if (!new_term_buffer[i] || !new_color_buffer[i] || !new_attr_buffer[i] ||
|
|
|
|
!new_bg_color_buffer[i]) {
|
2025-01-15 23:36:48 +00:00
|
|
|
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]);
|
2025-01-16 17:03:58 +00:00
|
|
|
free(new_bg_color_buffer[j]);
|
2025-01-15 23:36:48 +00:00
|
|
|
}
|
|
|
|
free(new_term_buffer);
|
|
|
|
free(new_color_buffer);
|
|
|
|
free(new_attr_buffer);
|
2025-01-16 17:03:58 +00:00
|
|
|
free(new_bg_color_buffer);
|
2025-01-15 23:36:48 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (i < term_rows && i < new_rows && terminal_buffer && color_buffer &&
|
2025-01-16 17:03:58 +00:00
|
|
|
attr_buffer && bg_color_buffer) {
|
2025-01-15 23:36:48 +00:00
|
|
|
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));
|
2025-01-16 17:03:58 +00:00
|
|
|
memcpy(new_bg_color_buffer[i], bg_color_buffer[i],
|
|
|
|
copy_cols * sizeof(unsigned long));
|
2025-01-15 23:36:48 +00:00
|
|
|
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;
|
2025-01-16 17:03:58 +00:00
|
|
|
new_bg_color_buffer[i][x] = current_bg_color;
|
2025-01-15 23:36:48 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
} 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;
|
2025-01-16 17:03:58 +00:00
|
|
|
new_bg_color_buffer[i][x] = current_bg_color;
|
2025-01-15 23:36:48 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Free old buffers after successful allocation
|
2025-01-16 17:03:58 +00:00
|
|
|
if (terminal_buffer && color_buffer && attr_buffer && bg_color_buffer) {
|
2025-01-15 23:36:48 +00:00
|
|
|
for (int i = 0; i < term_rows; i++) {
|
|
|
|
free(terminal_buffer[i]);
|
|
|
|
free(color_buffer[i]);
|
|
|
|
free(attr_buffer[i]);
|
2025-01-16 17:03:58 +00:00
|
|
|
free(bg_color_buffer[i]);
|
2025-01-15 23:36:48 +00:00
|
|
|
}
|
|
|
|
free(terminal_buffer);
|
|
|
|
free(color_buffer);
|
|
|
|
free(attr_buffer);
|
2025-01-16 17:03:58 +00:00
|
|
|
free(bg_color_buffer);
|
2025-01-15 23:36:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
terminal_buffer = new_term_buffer;
|
|
|
|
color_buffer = new_color_buffer;
|
|
|
|
attr_buffer = new_attr_buffer;
|
2025-01-16 17:03:58 +00:00
|
|
|
bg_color_buffer = new_bg_color_buffer;
|
2025-01-15 23:36:48 +00:00
|
|
|
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)));
|
2025-01-16 00:54:45 +00:00
|
|
|
needs_refresh = 1;
|
2025-01-15 23:36:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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);
|
|
|
|
}
|
2025-01-16 17:03:58 +00:00
|
|
|
if (bg_color_buffer) {
|
|
|
|
for (int i = 0; i < term_rows; i++) {
|
|
|
|
free(bg_color_buffer[i]);
|
|
|
|
}
|
|
|
|
free(bg_color_buffer);
|
|
|
|
}
|
2025-01-15 23:36:48 +00:00
|
|
|
if (master_fd >= 0)
|
|
|
|
close(master_fd);
|
|
|
|
exit(0);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void draw_char(Display *display, Drawable d, int x, int y) {
|
2025-01-16 17:03:58 +00:00
|
|
|
if (!gc || !terminal_buffer || !color_buffer || !attr_buffer ||
|
|
|
|
!bg_color_buffer)
|
2025-01-15 23:36:48 +00:00
|
|
|
return;
|
|
|
|
if (x < 0 || x >= term_cols || y < 0 || y >= term_rows)
|
|
|
|
return;
|
|
|
|
|
2025-01-16 17:03:58 +00:00
|
|
|
unsigned long fg_color = color_buffer[y][x];
|
|
|
|
unsigned long bg_color = bg_color_buffer[y][x];
|
|
|
|
int attrs = attr_buffer[y][x];
|
|
|
|
|
|
|
|
if (attrs & ATTR_REVERSE) {
|
|
|
|
unsigned long temp = fg_color;
|
|
|
|
fg_color = bg_color;
|
|
|
|
bg_color = temp;
|
|
|
|
}
|
|
|
|
|
2025-01-15 23:36:48 +00:00
|
|
|
// Clear the character position first
|
2025-01-16 17:03:58 +00:00
|
|
|
XSetForeground(display, gc, bg_color);
|
2025-01-15 23:36:48 +00:00
|
|
|
XFillRectangle(display, d, gc, x * CHAR_WIDTH, y * CHAR_HEIGHT, CHAR_WIDTH,
|
|
|
|
CHAR_HEIGHT);
|
2025-01-16 17:03:58 +00:00
|
|
|
XSetForeground(display, gc, fg_color);
|
2025-01-15 23:36:48 +00:00
|
|
|
|
|
|
|
char str[2] = {terminal_buffer[y][x], '\0'};
|
|
|
|
|
|
|
|
// Select appropriate font based on attributes
|
|
|
|
XFontStruct *font = regular_font;
|
|
|
|
if (attr_buffer[y][x] & ATTR_BOLD) {
|
|
|
|
font = bold_font;
|
|
|
|
} else if (attr_buffer[y][x] & ATTR_ITALIC) {
|
|
|
|
font = italic_font;
|
|
|
|
}
|
|
|
|
XSetFont(display, gc, font->fid);
|
|
|
|
|
|
|
|
XDrawString(display, d, gc, x * CHAR_WIDTH, (y + 1) * CHAR_HEIGHT - 2, str,
|
|
|
|
1);
|
2025-01-16 17:03:58 +00:00
|
|
|
|
2025-01-16 18:29:27 +00:00
|
|
|
// if (attrs & ATTR_UNDERLINE) {
|
|
|
|
// XDrawLine(display, d, gc, x * CHAR_WIDTH, (y + 1) * CHAR_HEIGHT - 1,
|
|
|
|
// (x + 1) * CHAR_WIDTH, (y + 1) * CHAR_HEIGHT - 1);
|
|
|
|
// }
|
2025-01-15 23:36:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static void draw_terminal(Display *display, Window window) {
|
|
|
|
if (!gc) {
|
|
|
|
gc = XCreateGC(display, window, 0, NULL);
|
|
|
|
|
2025-01-16 00:54:45 +00:00
|
|
|
regular_font = XLoadQueryFont(display, REGULAR_FONT);
|
|
|
|
bold_font = XLoadQueryFont(display, BOLD_FONT);
|
|
|
|
italic_font = XLoadQueryFont(display, ITALICS_FONT);
|
2025-01-15 23:36:48 +00:00
|
|
|
|
|
|
|
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)));
|
|
|
|
}
|
|
|
|
|
|
|
|
// Draw to buffer first
|
|
|
|
for (int y = 0; y < term_rows; y++) {
|
|
|
|
for (int x = 0; x < term_cols; x++) {
|
|
|
|
draw_char(display, buffer_pixmap, x, y);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Copy buffer to window
|
|
|
|
XCopyArea(display, buffer_pixmap, window, gc, 0, 0, term_cols * CHAR_WIDTH,
|
|
|
|
term_rows * CHAR_HEIGHT, 0, 0);
|
|
|
|
|
|
|
|
draw_cursor(display, window);
|
|
|
|
XFlush(display);
|
2025-01-16 00:54:45 +00:00
|
|
|
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;
|
|
|
|
}
|
2025-01-15 23:36:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static void clear_terminal(Display *display, Window window) {
|
2025-01-16 17:03:58 +00:00
|
|
|
if (!terminal_buffer || !color_buffer || !attr_buffer || !bg_color_buffer)
|
2025-01-15 23:36:48 +00:00
|
|
|
return;
|
|
|
|
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;
|
2025-01-16 17:03:58 +00:00
|
|
|
bg_color_buffer[y][x] = current_bg_color;
|
2025-01-15 23:36:48 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
cursor_x = 0;
|
|
|
|
cursor_y = 0;
|
2025-01-16 00:54:45 +00:00
|
|
|
needs_refresh = 1;
|
2025-01-15 23:36:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Scroll the terminal up one line
|
|
|
|
static void scroll_up(void) {
|
|
|
|
// Move all lines up one position
|
|
|
|
for (int y = 0; y < term_rows - 1; y++) {
|
|
|
|
memcpy(terminal_buffer[y], terminal_buffer[y + 1], term_cols);
|
|
|
|
memcpy(color_buffer[y], color_buffer[y + 1],
|
|
|
|
term_cols * sizeof(unsigned long));
|
|
|
|
memcpy(attr_buffer[y], attr_buffer[y + 1], term_cols * sizeof(int));
|
2025-01-16 17:03:58 +00:00
|
|
|
memcpy(bg_color_buffer[y], bg_color_buffer[y + 1],
|
|
|
|
term_cols * sizeof(unsigned long));
|
2025-01-15 23:36:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Clear the bottom line
|
|
|
|
memset(terminal_buffer[term_rows - 1], ' ', term_cols);
|
|
|
|
for (int x = 0; x < term_cols; x++) {
|
|
|
|
color_buffer[term_rows - 1][x] = current_color;
|
|
|
|
attr_buffer[term_rows - 1][x] = current_attr;
|
2025-01-16 17:03:58 +00:00
|
|
|
bg_color_buffer[term_rows - 1][x] = current_bg_color;
|
2025-01-15 23:36:48 +00:00
|
|
|
}
|
2025-01-16 00:54:45 +00:00
|
|
|
needs_refresh = 1;
|
2025-01-15 23:36:48 +00:00
|
|
|
}
|
|
|
|
|
2025-01-16 20:55:08 +00:00
|
|
|
static void scroll_region_up(int amount) {
|
|
|
|
char **temp_buffer = NULL;
|
|
|
|
unsigned long **temp_color_buffer = NULL;
|
|
|
|
int **temp_attr_buffer = NULL;
|
|
|
|
unsigned long **temp_bg_color_buffer = NULL;
|
|
|
|
|
|
|
|
temp_buffer = malloc(term_rows * sizeof(char *));
|
|
|
|
temp_color_buffer = malloc(term_rows * sizeof(unsigned long *));
|
|
|
|
temp_attr_buffer = malloc(term_rows * sizeof(int *));
|
|
|
|
temp_bg_color_buffer = malloc(term_rows * sizeof(unsigned long *));
|
|
|
|
|
|
|
|
if (!temp_buffer || !temp_color_buffer || !temp_attr_buffer ||
|
|
|
|
!temp_bg_color_buffer) {
|
|
|
|
fprintf(stderr, "Failed to allocate memory for temp buffers\n");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (int i = 0; i < term_rows; i++) {
|
|
|
|
temp_buffer[i] = malloc(term_cols * sizeof(char));
|
|
|
|
temp_color_buffer[i] = malloc(term_cols * sizeof(unsigned long));
|
|
|
|
temp_attr_buffer[i] = malloc(term_cols * sizeof(int));
|
|
|
|
temp_bg_color_buffer[i] = malloc(term_cols * sizeof(unsigned long));
|
|
|
|
if (!temp_buffer[i] || !temp_color_buffer[i] || !temp_attr_buffer[i] ||
|
|
|
|
!temp_bg_color_buffer[i]) {
|
|
|
|
fprintf(stderr, "Failed to allocate memory for temp buffer row\n");
|
|
|
|
free(temp_buffer);
|
|
|
|
free(temp_color_buffer);
|
|
|
|
free(temp_attr_buffer);
|
|
|
|
free(temp_bg_color_buffer);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (scroll_top == 0 && scroll_bottom == 0) {
|
|
|
|
for (int i = 0; i < term_rows; i++) {
|
|
|
|
memcpy(temp_buffer[i], terminal_buffer[i], term_cols * sizeof(char));
|
|
|
|
memcpy(temp_color_buffer[i], color_buffer[i],
|
|
|
|
term_cols * sizeof(unsigned long));
|
|
|
|
memcpy(temp_attr_buffer[i], attr_buffer[i], term_cols * sizeof(int));
|
|
|
|
memcpy(temp_bg_color_buffer[i], bg_color_buffer[i],
|
|
|
|
term_cols * sizeof(unsigned long));
|
|
|
|
}
|
|
|
|
for (int j = 0; j < amount; j++) {
|
|
|
|
// Move all lines up one position
|
|
|
|
for (int y = 0; y < term_rows - 1; y++) {
|
|
|
|
memcpy(terminal_buffer[y], temp_buffer[y + 1], term_cols);
|
|
|
|
memcpy(color_buffer[y], temp_color_buffer[y + 1],
|
|
|
|
term_cols * sizeof(unsigned long));
|
|
|
|
memcpy(attr_buffer[y], temp_attr_buffer[y + 1],
|
|
|
|
term_cols * sizeof(int));
|
|
|
|
memcpy(bg_color_buffer[y], temp_bg_color_buffer[y + 1],
|
|
|
|
term_cols * sizeof(unsigned long));
|
|
|
|
}
|
|
|
|
|
|
|
|
// Clear the bottom line
|
|
|
|
memset(terminal_buffer[term_rows - 1], ' ', term_cols);
|
|
|
|
for (int x = 0; x < term_cols; x++) {
|
|
|
|
color_buffer[term_rows - 1][x] = current_color;
|
|
|
|
attr_buffer[term_rows - 1][x] = current_attr;
|
|
|
|
bg_color_buffer[term_rows - 1][x] = current_bg_color;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
for (int i = 0; i < term_rows; i++) {
|
|
|
|
memcpy(temp_buffer[i], terminal_buffer[i], term_cols * sizeof(char));
|
|
|
|
memcpy(temp_color_buffer[i], color_buffer[i],
|
|
|
|
term_cols * sizeof(unsigned long));
|
|
|
|
memcpy(temp_attr_buffer[i], attr_buffer[i], term_cols * sizeof(int));
|
|
|
|
memcpy(temp_bg_color_buffer[i], bg_color_buffer[i],
|
|
|
|
term_cols * sizeof(unsigned long));
|
|
|
|
}
|
|
|
|
for (int j = 0; j < amount; j++) {
|
|
|
|
int start = (scroll_top - 1);
|
|
|
|
int end = (scroll_bottom - 1);
|
|
|
|
if (start < 0)
|
|
|
|
start = 0;
|
|
|
|
if (end < 0)
|
|
|
|
end = 0;
|
|
|
|
if (end >= term_rows)
|
|
|
|
end = term_rows - 1;
|
|
|
|
// Move all lines up one position
|
|
|
|
for (int y = start; y < end; y++) {
|
|
|
|
memcpy(terminal_buffer[y], temp_buffer[y + 1], term_cols);
|
|
|
|
memcpy(color_buffer[y], temp_color_buffer[y + 1],
|
|
|
|
term_cols * sizeof(unsigned long));
|
|
|
|
memcpy(attr_buffer[y], temp_attr_buffer[y + 1],
|
|
|
|
term_cols * sizeof(int));
|
|
|
|
memcpy(bg_color_buffer[y], temp_bg_color_buffer[y + 1],
|
|
|
|
term_cols * sizeof(unsigned long));
|
|
|
|
}
|
|
|
|
|
|
|
|
// Clear the bottom line
|
|
|
|
memset(terminal_buffer[end], ' ', term_cols);
|
|
|
|
for (int x = 0; x < term_cols; x++) {
|
|
|
|
color_buffer[end][x] = current_color;
|
|
|
|
attr_buffer[end][x] = current_attr;
|
|
|
|
bg_color_buffer[end][x] = current_bg_color;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for (int i = 0; i < term_rows; i++) {
|
|
|
|
free(temp_buffer[i]);
|
|
|
|
free(temp_color_buffer[i]);
|
|
|
|
free(temp_attr_buffer[i]);
|
|
|
|
free(temp_bg_color_buffer[i]);
|
|
|
|
}
|
|
|
|
free(temp_buffer);
|
|
|
|
free(temp_color_buffer);
|
|
|
|
free(temp_attr_buffer);
|
|
|
|
free(temp_bg_color_buffer);
|
|
|
|
needs_refresh = 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void scroll_region_down(int amount) {
|
|
|
|
char **temp_buffer = NULL;
|
|
|
|
unsigned long **temp_color_buffer = NULL;
|
|
|
|
int **temp_attr_buffer = NULL;
|
|
|
|
unsigned long **temp_bg_color_buffer = NULL;
|
|
|
|
|
|
|
|
temp_buffer = malloc(term_rows * sizeof(char *));
|
|
|
|
temp_color_buffer = malloc(term_rows * sizeof(unsigned long *));
|
|
|
|
temp_attr_buffer = malloc(term_rows * sizeof(int *));
|
|
|
|
temp_bg_color_buffer = malloc(term_rows * sizeof(unsigned long *));
|
|
|
|
|
|
|
|
if (!temp_buffer || !temp_color_buffer || !temp_attr_buffer ||
|
|
|
|
!temp_bg_color_buffer) {
|
|
|
|
fprintf(stderr, "Failed to allocate memory for temp buffers\n");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
for (int i = 0; i < term_rows; i++) {
|
|
|
|
temp_buffer[i] = malloc(term_cols * sizeof(char));
|
|
|
|
temp_color_buffer[i] = malloc(term_cols * sizeof(unsigned long));
|
|
|
|
temp_attr_buffer[i] = malloc(term_cols * sizeof(int));
|
|
|
|
temp_bg_color_buffer[i] = malloc(term_cols * sizeof(unsigned long));
|
|
|
|
|
|
|
|
if (!temp_buffer[i] || !temp_color_buffer[i] || !temp_attr_buffer[i] ||
|
|
|
|
!temp_bg_color_buffer[i]) {
|
|
|
|
fprintf(stderr, "Failed to allocate memory for temp buffer row\n");
|
|
|
|
free(temp_buffer);
|
|
|
|
free(temp_color_buffer);
|
|
|
|
free(temp_attr_buffer);
|
|
|
|
free(temp_bg_color_buffer);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (scroll_top == 0 && scroll_bottom == 0) {
|
|
|
|
for (int i = 0; i < term_rows; i++) {
|
|
|
|
memcpy(temp_buffer[i], terminal_buffer[i], term_cols * sizeof(char));
|
|
|
|
memcpy(temp_color_buffer[i], color_buffer[i],
|
|
|
|
term_cols * sizeof(unsigned long));
|
|
|
|
memcpy(temp_attr_buffer[i], attr_buffer[i], term_cols * sizeof(int));
|
|
|
|
memcpy(temp_bg_color_buffer[i], bg_color_buffer[i],
|
|
|
|
term_cols * sizeof(unsigned long));
|
|
|
|
}
|
|
|
|
for (int j = 0; j < amount; j++) {
|
|
|
|
// Move all lines down one position
|
|
|
|
for (int y = term_rows - 1; y > 0; y--) {
|
|
|
|
memcpy(terminal_buffer[y], temp_buffer[y - 1], term_cols);
|
|
|
|
memcpy(color_buffer[y], temp_color_buffer[y - 1],
|
|
|
|
term_cols * sizeof(unsigned long));
|
|
|
|
memcpy(attr_buffer[y], temp_attr_buffer[y - 1],
|
|
|
|
term_cols * sizeof(int));
|
|
|
|
memcpy(bg_color_buffer[y], temp_bg_color_buffer[y - 1],
|
|
|
|
term_cols * sizeof(unsigned long));
|
|
|
|
}
|
|
|
|
// Clear the top line
|
|
|
|
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(temp_buffer[i], terminal_buffer[i], term_cols * sizeof(char));
|
|
|
|
memcpy(temp_color_buffer[i], color_buffer[i],
|
|
|
|
term_cols * sizeof(unsigned long));
|
|
|
|
memcpy(temp_attr_buffer[i], attr_buffer[i], term_cols * sizeof(int));
|
|
|
|
memcpy(temp_bg_color_buffer[i], bg_color_buffer[i],
|
|
|
|
term_cols * sizeof(unsigned long));
|
|
|
|
}
|
|
|
|
for (int j = 0; j < amount; j++) {
|
|
|
|
int start = (scroll_top - 1);
|
|
|
|
int end = (scroll_bottom - 1);
|
|
|
|
if (start < 0)
|
|
|
|
start = 0;
|
|
|
|
if (end < 0)
|
|
|
|
end = 0;
|
|
|
|
if (end >= term_rows)
|
|
|
|
end = term_rows - 1;
|
|
|
|
|
|
|
|
// Move all lines down one position
|
|
|
|
for (int y = end; y > start; y--) {
|
|
|
|
memcpy(terminal_buffer[y], temp_buffer[y - 1], term_cols);
|
|
|
|
memcpy(color_buffer[y], temp_color_buffer[y - 1],
|
|
|
|
term_cols * sizeof(unsigned long));
|
|
|
|
memcpy(attr_buffer[y], temp_attr_buffer[y - 1],
|
|
|
|
term_cols * sizeof(int));
|
|
|
|
memcpy(bg_color_buffer[y], temp_bg_color_buffer[y - 1],
|
|
|
|
term_cols * sizeof(unsigned long));
|
|
|
|
}
|
|
|
|
|
|
|
|
// Clear the top line
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for (int i = 0; i < term_rows; i++) {
|
|
|
|
free(temp_buffer[i]);
|
|
|
|
free(temp_color_buffer[i]);
|
|
|
|
free(temp_attr_buffer[i]);
|
|
|
|
free(temp_bg_color_buffer[i]);
|
|
|
|
}
|
|
|
|
free(temp_buffer);
|
|
|
|
free(temp_color_buffer);
|
|
|
|
free(temp_attr_buffer);
|
|
|
|
free(temp_bg_color_buffer);
|
|
|
|
needs_refresh = 1;
|
|
|
|
}
|
|
|
|
|
2025-01-16 03:48:15 +00:00
|
|
|
// 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
|
2025-01-16 20:55:08 +00:00
|
|
|
static void handle_alternate_buffer(const char *buf, int *idx, int max_len,
|
|
|
|
Window window) {
|
2025-01-16 03:48:15 +00:00
|
|
|
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;
|
2025-01-16 17:03:58 +00:00
|
|
|
saved_colors = bg_color_buffer;
|
2025-01-16 03:48:15 +00:00
|
|
|
terminal_buffer = malloc(term_rows * sizeof(char *));
|
|
|
|
color_buffer = malloc(term_rows * sizeof(unsigned long *));
|
|
|
|
attr_buffer = malloc(term_rows * sizeof(int *));
|
2025-01-16 17:03:58 +00:00
|
|
|
bg_color_buffer = malloc(term_rows * sizeof(unsigned long *));
|
2025-01-16 03:48:15 +00:00
|
|
|
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));
|
2025-01-16 17:03:58 +00:00
|
|
|
bg_color_buffer[i] = calloc(term_cols, sizeof(unsigned long));
|
2025-01-16 03:48:15 +00:00
|
|
|
}
|
|
|
|
using_alternate = 1;
|
2025-01-16 20:55:08 +00:00
|
|
|
needs_refresh = 1;
|
|
|
|
draw_terminal(display, window);
|
2025-01-16 03:48:15 +00:00
|
|
|
}
|
|
|
|
} else if (using_alternate) {
|
|
|
|
for (int i = 0; i < term_rows; i++) {
|
|
|
|
free(terminal_buffer[i]);
|
|
|
|
free(color_buffer[i]);
|
|
|
|
free(attr_buffer[i]);
|
2025-01-16 17:03:58 +00:00
|
|
|
free(bg_color_buffer[i]);
|
2025-01-16 03:48:15 +00:00
|
|
|
}
|
|
|
|
free(terminal_buffer);
|
|
|
|
free(color_buffer);
|
|
|
|
free(attr_buffer);
|
2025-01-16 17:03:58 +00:00
|
|
|
free(bg_color_buffer);
|
2025-01-16 03:48:15 +00:00
|
|
|
terminal_buffer = saved_terminal;
|
|
|
|
color_buffer = saved_colors;
|
|
|
|
attr_buffer = saved_attrs;
|
2025-01-16 17:03:58 +00:00
|
|
|
bg_color_buffer = saved_colors;
|
2025-01-16 03:48:15 +00:00
|
|
|
saved_terminal = NULL;
|
|
|
|
using_alternate = 0;
|
2025-01-16 20:55:08 +00:00
|
|
|
needs_refresh = 1;
|
|
|
|
draw_terminal(display, window);
|
2025-01-16 03:48:15 +00:00
|
|
|
}
|
|
|
|
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;
|
2025-01-16 17:03:58 +00:00
|
|
|
saved_colors = bg_color_buffer;
|
2025-01-16 03:48:15 +00:00
|
|
|
terminal_buffer = malloc(term_rows * sizeof(char *));
|
|
|
|
color_buffer = malloc(term_rows * sizeof(unsigned long *));
|
|
|
|
attr_buffer = malloc(term_rows * sizeof(int *));
|
2025-01-16 17:03:58 +00:00
|
|
|
bg_color_buffer = malloc(term_rows * sizeof(unsigned long *));
|
2025-01-16 03:48:15 +00:00
|
|
|
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));
|
2025-01-16 17:03:58 +00:00
|
|
|
bg_color_buffer[i] = calloc(term_cols, sizeof(unsigned long));
|
2025-01-16 03:48:15 +00:00
|
|
|
}
|
|
|
|
using_alternate = 1;
|
2025-01-16 20:55:08 +00:00
|
|
|
needs_refresh = 1;
|
|
|
|
draw_terminal(display, window);
|
2025-01-16 03:48:15 +00:00
|
|
|
}
|
|
|
|
} else if (using_alternate) {
|
|
|
|
for (int i = 0; i < term_rows; i++) {
|
|
|
|
free(terminal_buffer[i]);
|
|
|
|
free(color_buffer[i]);
|
|
|
|
free(attr_buffer[i]);
|
2025-01-16 17:03:58 +00:00
|
|
|
free(bg_color_buffer[i]);
|
2025-01-16 03:48:15 +00:00
|
|
|
}
|
|
|
|
free(terminal_buffer);
|
|
|
|
free(color_buffer);
|
|
|
|
free(attr_buffer);
|
2025-01-16 17:03:58 +00:00
|
|
|
free(bg_color_buffer);
|
2025-01-16 03:48:15 +00:00
|
|
|
terminal_buffer = saved_terminal;
|
|
|
|
color_buffer = saved_colors;
|
|
|
|
attr_buffer = saved_attrs;
|
2025-01-16 17:03:58 +00:00
|
|
|
bg_color_buffer = saved_colors;
|
2025-01-16 03:48:15 +00:00
|
|
|
saved_terminal = NULL;
|
|
|
|
cursor_x = saved_cursor_x;
|
|
|
|
cursor_y = saved_cursor_y;
|
|
|
|
using_alternate = 0;
|
2025-01-16 20:55:08 +00:00
|
|
|
needs_refresh = 1;
|
|
|
|
draw_terminal(display, window);
|
2025-01-16 03:48:15 +00:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-01-16 17:03:58 +00:00
|
|
|
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];
|
|
|
|
}
|
|
|
|
|
2025-01-15 23:36:48 +00:00
|
|
|
// Handles those pesky ANSI color escape codes that terminals use.
|
|
|
|
// When \033[<number>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
|
2025-01-16 20:55:08 +00:00
|
|
|
static void parse_ansi_code(const char *buf, int *idx, int max_len, Window window) {
|
2025-01-16 03:48:15 +00:00
|
|
|
char num_buf[32] = {0};
|
|
|
|
int params[32] = {0};
|
|
|
|
size_t num_idx = 0;
|
|
|
|
size_t param_idx = 0;
|
2025-01-15 23:36:48 +00:00
|
|
|
(*idx)++; // Skip [
|
|
|
|
|
2025-01-16 03:48:15 +00:00
|
|
|
// Handle '?' prefix for private sequences
|
|
|
|
if (buf[*idx] == '?') {
|
2025-01-16 20:55:08 +00:00
|
|
|
handle_alternate_buffer(buf, idx, max_len, window);
|
2025-01-16 03:48:15 +00:00
|
|
|
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;
|
|
|
|
}
|
2025-01-15 23:36:48 +00:00
|
|
|
if (buf[*idx] >= '0' && buf[*idx] <= '9') {
|
|
|
|
num_buf[num_idx++] = buf[*idx];
|
2025-01-16 03:48:15 +00:00
|
|
|
} 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;
|
|
|
|
}
|
2025-01-15 23:36:48 +00:00
|
|
|
}
|
|
|
|
(*idx)++;
|
|
|
|
}
|
|
|
|
|
2025-01-16 03:48:15 +00:00
|
|
|
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;
|
2025-01-16 17:03:58 +00:00
|
|
|
bg_color_buffer[cursor_y][i] = current_bg_color;
|
2025-01-16 03:48:15 +00:00
|
|
|
}
|
|
|
|
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;
|
2025-01-16 17:03:58 +00:00
|
|
|
bg_color_buffer[y][x] = current_bg_color;
|
2025-01-16 03:48:15 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
needs_refresh = 1;
|
|
|
|
}
|
|
|
|
break;
|
2025-01-16 20:55:08 +00:00
|
|
|
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] == 0 ? 1 : params[0]);
|
|
|
|
needs_refresh = 1;
|
|
|
|
break;
|
|
|
|
case 'T': // Scroll down
|
|
|
|
scroll_region_down(params[0] == 0 ? 1 : params[0]);
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
2025-01-16 03:48:15 +00:00
|
|
|
case 'm': // Handling of text
|
|
|
|
for (int i = 0; i < param_idx; i++) {
|
2025-01-16 17:03:58 +00:00
|
|
|
if (params[i] == 0) { // Reset Everything
|
2025-01-16 03:48:15 +00:00
|
|
|
current_color = XWhitePixel(display, DefaultScreen(display));
|
2025-01-16 17:03:58 +00:00
|
|
|
current_bg_color = XBlackPixel(display, DefaultScreen(display));
|
2025-01-16 03:48:15 +00:00
|
|
|
current_attr = 0;
|
2025-01-16 17:03:58 +00:00
|
|
|
} else if (params[i] == 1) { // Apply ATTRS
|
2025-01-16 03:48:15 +00:00
|
|
|
current_attr |= ATTR_BOLD;
|
2025-01-16 17:03:58 +00:00
|
|
|
} else if (params[i] == 2) {
|
|
|
|
current_attr |= ATTR_DIM;
|
|
|
|
} else if (params[i] == 3) {
|
2025-01-16 03:48:15 +00:00
|
|
|
current_attr |= ATTR_ITALIC;
|
2025-01-16 18:29:27 +00:00
|
|
|
// } else if (params[i] == 4) {
|
|
|
|
// current_attr |= ATTR_UNDERLINE;
|
|
|
|
} else if (params[i] == 5) { // Slow blink
|
2025-01-16 17:03:58 +00:00
|
|
|
current_attr |= ATTR_BLINK;
|
|
|
|
} else if (params[i] == 7) {
|
|
|
|
current_attr |= ATTR_REVERSE;
|
|
|
|
} else if (params[i] == 22) { // Reset ATTRS
|
2025-01-16 03:48:15 +00:00
|
|
|
current_attr &= ~ATTR_BOLD;
|
2025-01-16 17:03:58 +00:00
|
|
|
} else if (params[i] == 23) {
|
2025-01-16 03:48:15 +00:00
|
|
|
current_attr &= ~ATTR_ITALIC;
|
2025-01-16 18:29:27 +00:00
|
|
|
// } else if (params[i] == 24) {
|
|
|
|
// current_attr &= ~ATTR_UNDERLINE;
|
|
|
|
} else if (params[i] == 25) {
|
|
|
|
current_attr &= ~ATTR_BLINK;
|
2025-01-16 17:03:58 +00:00
|
|
|
} 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 colors
|
|
|
|
current_color = get_bright_ansi_color(params[i] - 90);
|
|
|
|
} else if (params[i] >= 100 &&
|
|
|
|
params[i] <= 107) { // Bright background colors
|
|
|
|
current_bg_color = get_bright_ansi_color(params[i] - 100);
|
2025-01-16 03:48:15 +00:00
|
|
|
}
|
2025-01-15 23:36:48 +00:00
|
|
|
}
|
2025-01-16 03:48:15 +00:00
|
|
|
break;
|
2025-01-15 23:36:48 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-01-16 20:55:08 +00:00
|
|
|
static int master_cb(int fd, void *data, Window window) {
|
2025-01-16 17:03:58 +00:00
|
|
|
if (!terminal_buffer || !color_buffer || !attr_buffer || !bg_color_buffer)
|
2025-01-15 23:36:48 +00:00
|
|
|
return 0;
|
|
|
|
char buf[4096];
|
|
|
|
ssize_t bytes_read;
|
|
|
|
struct {
|
|
|
|
Display *display;
|
|
|
|
Window window;
|
|
|
|
} *ctx = data;
|
|
|
|
|
|
|
|
bytes_read = read(fd, buf, sizeof(buf));
|
|
|
|
if (bytes_read <= 0 && errno != EAGAIN && errno != EINTR) {
|
|
|
|
destroy_cb(ctx->display, ctx->window);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
if (bytes_read > 0) {
|
|
|
|
for (int i = 0; i < bytes_read; i++) {
|
|
|
|
if (buf[i] == '\033' && i + 1 < bytes_read && buf[i + 1] == '[') {
|
2025-01-16 20:55:08 +00:00
|
|
|
parse_ansi_code(buf, &i, bytes_read, window);
|
2025-01-15 23:36:48 +00:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (buf[i] == '\n') {
|
|
|
|
cursor_x = 0;
|
|
|
|
cursor_y++;
|
|
|
|
if (cursor_y >= term_rows) {
|
|
|
|
scroll_up();
|
|
|
|
cursor_y = term_rows - 1;
|
|
|
|
}
|
2025-01-16 17:03:58 +00:00
|
|
|
needs_refresh = 1;
|
2025-01-15 23:36:48 +00:00
|
|
|
} else if (buf[i] == '\r') {
|
|
|
|
cursor_x = 0;
|
|
|
|
} else if (buf[i] == '\b') {
|
|
|
|
if (cursor_x > 0) {
|
|
|
|
cursor_x--;
|
2025-01-16 00:54:45 +00:00
|
|
|
needs_refresh = 1;
|
2025-01-16 17:03:58 +00:00
|
|
|
} 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;
|
2025-01-15 23:36:48 +00:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if (cursor_y < term_rows && cursor_x < term_cols) {
|
|
|
|
terminal_buffer[cursor_y][cursor_x] = buf[i];
|
|
|
|
color_buffer[cursor_y][cursor_x] = current_color;
|
|
|
|
attr_buffer[cursor_y][cursor_x] = current_attr;
|
2025-01-16 17:03:58 +00:00
|
|
|
bg_color_buffer[cursor_y][cursor_x] = current_bg_color;
|
2025-01-16 00:54:45 +00:00
|
|
|
needs_refresh = 1;
|
2025-01-15 23:36:48 +00:00
|
|
|
cursor_x++;
|
|
|
|
if (cursor_x >= term_cols) {
|
|
|
|
cursor_x = 0;
|
|
|
|
cursor_y++;
|
|
|
|
if (cursor_y >= term_rows) {
|
|
|
|
scroll_up();
|
|
|
|
cursor_y = term_rows - 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
cursor_visible = 1;
|
2025-01-16 00:54:45 +00:00
|
|
|
if (needs_refresh) {
|
|
|
|
check_refresh(ctx->display, ctx->window);
|
|
|
|
} else {
|
|
|
|
draw_cursor(ctx->display, ctx->window);
|
|
|
|
XFlush(ctx->display);
|
|
|
|
}
|
2025-01-15 23:36:48 +00:00
|
|
|
}
|
|
|
|
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)) {
|
2025-01-16 03:56:31 +00:00
|
|
|
write(*master, "\x03", 1);
|
2025-01-15 23:36:48 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
if ((keysym & 0x1f) == ('U' & 0x1f)) {
|
|
|
|
write(*master, "\025", 1);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
char ctrl_char = '@' + (keysym & 0x1f);
|
|
|
|
write(*master, &ctrl_char, 1);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (keysym == XK_Return || keysym == XK_KP_Enter) {
|
2025-01-16 14:34:29 +00:00
|
|
|
write(*master, "\r", 1); // Shell handles return
|
2025-01-15 23:36:48 +00:00
|
|
|
} else if (keysym == XK_BackSpace) {
|
2025-01-16 17:03:58 +00:00
|
|
|
if (cursor_x > 0) {
|
|
|
|
write(*master, "\b \b", 3); // Shell handles backspace
|
|
|
|
} else if (cursor_y > 0) {
|
|
|
|
char seq[8];
|
|
|
|
snprintf(seq, sizeof(seq), "\033[A\033[%dC", term_cols);
|
|
|
|
write(*master, seq, strlen(seq));
|
|
|
|
}
|
2025-01-16 03:48:15 +00:00
|
|
|
} else if (keysym == XK_Left) {
|
2025-01-16 17:03:58 +00:00
|
|
|
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));
|
|
|
|
}
|
2025-01-16 03:48:15 +00:00
|
|
|
needs_refresh = 1;
|
|
|
|
} else if (keysym == XK_Right) {
|
2025-01-16 17:03:58 +00:00
|
|
|
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);
|
|
|
|
}
|
2025-01-16 03:48:15 +00:00
|
|
|
needs_refresh = 1;
|
2025-01-16 14:34:29 +00:00
|
|
|
} 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
|
2025-01-15 23:36:48 +00:00
|
|
|
} else if (len > 0) {
|
|
|
|
write(*master, buf, len);
|
2025-01-16 17:03:58 +00:00
|
|
|
if (cursor_x >= term_cols - 1) {
|
|
|
|
write(*master, "\r\n", 2);
|
|
|
|
}
|
2025-01-15 23:36:48 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
int main(void) {
|
|
|
|
int master, slave;
|
|
|
|
pid_t pid;
|
|
|
|
|
|
|
|
term_rows = 24;
|
|
|
|
term_cols = 80;
|
|
|
|
|
|
|
|
gettimeofday(&last_blink, NULL);
|
2025-01-16 00:54:45 +00:00
|
|
|
gettimeofday(&last_refresh, NULL);
|
2025-01-15 23:36:48 +00:00
|
|
|
|
|
|
|
struct winsize ws = {.ws_row = term_rows,
|
|
|
|
.ws_col = term_cols,
|
|
|
|
.ws_xpixel = term_cols * CHAR_WIDTH,
|
|
|
|
.ws_ypixel = term_rows * CHAR_HEIGHT};
|
|
|
|
|
|
|
|
terminal_buffer = malloc(term_rows * sizeof(char *));
|
|
|
|
color_buffer = malloc(term_rows * sizeof(unsigned long *));
|
|
|
|
attr_buffer = malloc(term_rows * sizeof(int *));
|
2025-01-16 17:03:58 +00:00
|
|
|
bg_color_buffer = malloc(term_rows * sizeof(unsigned long *));
|
2025-01-15 23:36:48 +00:00
|
|
|
|
2025-01-16 17:03:58 +00:00
|
|
|
if (!terminal_buffer || !color_buffer || !attr_buffer || !bg_color_buffer) {
|
2025-01-15 23:36:48 +00:00
|
|
|
fprintf(stderr, "Failed to allocate memory for buffers\n");
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (int i = 0; i < term_rows; i++) {
|
|
|
|
terminal_buffer[i] = malloc(term_cols * sizeof(char));
|
|
|
|
color_buffer[i] = malloc(term_cols * sizeof(unsigned long));
|
|
|
|
attr_buffer[i] = malloc(term_cols * sizeof(int));
|
2025-01-16 17:03:58 +00:00
|
|
|
bg_color_buffer[i] = malloc(term_cols * sizeof(unsigned long));
|
2025-01-15 23:36:48 +00:00
|
|
|
|
2025-01-16 17:03:58 +00:00
|
|
|
if (!terminal_buffer[i] || !color_buffer[i] || !attr_buffer[i] ||
|
|
|
|
!bg_color_buffer[i]) {
|
2025-01-15 23:36:48 +00:00
|
|
|
fprintf(stderr, "Failed to allocate memory for buffer row\n");
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
memset(terminal_buffer[i], ' ', term_cols);
|
|
|
|
memset(attr_buffer[i], 0, term_cols * sizeof(int));
|
|
|
|
}
|
|
|
|
|
|
|
|
display = XOpenDisplay(NULL);
|
|
|
|
if (!display) {
|
|
|
|
fprintf(stderr, "Cannot open display\n");
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
XInitThreads();
|
|
|
|
|
|
|
|
current_color = XWhitePixel(display, DefaultScreen(display));
|
2025-01-16 17:03:58 +00:00
|
|
|
current_bg_color = XBlackPixel(display, DefaultScreen(display));
|
2025-01-15 23:36:48 +00:00
|
|
|
current_attr = 0;
|
|
|
|
|
2025-01-16 17:03:58 +00:00
|
|
|
for (int y = 0; y < term_rows; y++) {
|
|
|
|
for (int x = 0; x < term_cols; x++) {
|
|
|
|
bg_color_buffer[y][x] = current_bg_color;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-01-15 23:36:48 +00:00
|
|
|
for (int y = 0; y < term_rows; y++) {
|
|
|
|
for (int x = 0; x < term_cols; x++) {
|
|
|
|
color_buffer[y][x] = current_color;
|
|
|
|
attr_buffer[y][x] = current_attr;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
XSizeHints hints;
|
|
|
|
hints.flags = PResizeInc | PMinSize;
|
|
|
|
hints.width_inc = CHAR_WIDTH;
|
|
|
|
hints.height_inc = CHAR_HEIGHT;
|
|
|
|
hints.min_width = CHAR_WIDTH * 4;
|
|
|
|
hints.min_height = CHAR_HEIGHT * 1;
|
|
|
|
|
|
|
|
Window window = 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, window, &hints);
|
|
|
|
XStoreName(display, window, window_name);
|
|
|
|
XSelectInput(display, window,
|
2025-01-16 00:54:45 +00:00
|
|
|
KeyPressMask | ExposureMask | StructureNotifyMask |
|
|
|
|
FocusChangeMask);
|
2025-01-15 23:36:48 +00:00
|
|
|
XMapWindow(display, window);
|
|
|
|
|
|
|
|
if (openpty(&master, &slave, NULL, NULL, &ws) == -1) {
|
|
|
|
perror("openpty");
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
master_fd = master;
|
|
|
|
|
|
|
|
pid = fork();
|
|
|
|
if (pid < 0) {
|
|
|
|
perror("fork");
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (pid == 0) {
|
|
|
|
close(master);
|
|
|
|
close(STDIN_FILENO);
|
|
|
|
close(STDOUT_FILENO);
|
|
|
|
close(STDERR_FILENO);
|
|
|
|
|
|
|
|
setsid();
|
|
|
|
dup2(slave, 0);
|
|
|
|
dup2(slave, 1);
|
|
|
|
dup2(slave, 2);
|
|
|
|
|
|
|
|
if (slave > 2)
|
|
|
|
close(slave);
|
|
|
|
|
|
|
|
char *shell = getenv("SHELL");
|
|
|
|
if (!shell)
|
|
|
|
shell = "/bin/sh";
|
|
|
|
|
|
|
|
setenv("TERM", "xterm-256color", 1);
|
|
|
|
execlp(shell, shell, NULL);
|
|
|
|
perror("execlp");
|
|
|
|
exit(1);
|
|
|
|
}
|
|
|
|
|
|
|
|
child_pid = pid;
|
|
|
|
close(slave);
|
|
|
|
|
|
|
|
struct {
|
|
|
|
Display *display;
|
|
|
|
Window window;
|
|
|
|
} ctx = {display, window};
|
|
|
|
|
|
|
|
XEvent event;
|
|
|
|
while (1) {
|
|
|
|
int status;
|
|
|
|
pid_t wpid = waitpid(child_pid, &status, WNOHANG);
|
|
|
|
if (wpid == child_pid) {
|
|
|
|
destroy_cb(display, window);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
while (XPending(display)) {
|
|
|
|
XNextEvent(display, &event);
|
|
|
|
switch (event.type) {
|
|
|
|
case ConfigureNotify: {
|
|
|
|
XConfigureEvent *ce = (XConfigureEvent *)&event;
|
|
|
|
int new_rows = ce->height / CHAR_HEIGHT;
|
|
|
|
int new_cols = ce->width / CHAR_WIDTH;
|
|
|
|
if (new_rows != term_rows || new_cols != term_cols) {
|
|
|
|
resize_buffers(new_rows, new_cols);
|
|
|
|
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;
|
|
|
|
}
|
2025-01-16 00:54:45 +00:00
|
|
|
case FocusIn:
|
|
|
|
window_focused = 1;
|
|
|
|
needs_refresh = 1;
|
|
|
|
break;
|
|
|
|
case FocusOut:
|
|
|
|
window_focused = 0;
|
|
|
|
needs_refresh = 1;
|
|
|
|
break;
|
2025-01-15 23:36:48 +00:00
|
|
|
case KeyPress:
|
|
|
|
key_press_cb((XKeyEvent *)&event, &master);
|
|
|
|
break;
|
|
|
|
case Expose:
|
2025-01-16 00:54:45 +00:00
|
|
|
needs_refresh = 1;
|
|
|
|
check_refresh(display, window);
|
2025-01-15 23:36:48 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fd_set fds;
|
2025-01-16 00:54:45 +00:00
|
|
|
struct timeval tv = {0, 1000}; // 1ms timeout for faster refresh
|
2025-01-15 23:36:48 +00:00
|
|
|
FD_ZERO(&fds);
|
|
|
|
FD_SET(master, &fds);
|
|
|
|
if (select(master + 1, &fds, NULL, NULL, &tv) > 0) {
|
2025-01-16 20:55:08 +00:00
|
|
|
master_cb(master, &ctx, window);
|
2025-01-15 23:36:48 +00:00
|
|
|
}
|
|
|
|
|
2025-01-16 00:54:45 +00:00
|
|
|
check_refresh(display, window);
|
|
|
|
update_cursor_state(display, window);
|
2025-01-15 23:36:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|