e7cb69fe4b
As long as you are using bash it will always work, not sure for fish and other shells so I'll leave it to the user to experiment soon, I'll add another thing to the config.h to do so.
972 lines
28 KiB
C
972 lines
28 KiB
C
#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
|
|
|
|
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 int cursor_x = 0;
|
|
static int cursor_y = 0;
|
|
static unsigned long current_color;
|
|
static int current_attr = 0;
|
|
static GC gc = NULL;
|
|
static GC gc_bold = NULL;
|
|
static Display *display = NULL;
|
|
static int cursor_visible = 1;
|
|
static struct timeval last_blink;
|
|
static struct timeval last_refresh;
|
|
static Pixmap buffer_pixmap = None;
|
|
static int master_fd = -1;
|
|
static int needs_refresh = 0;
|
|
static XFontStruct *regular_font = NULL;
|
|
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
|
|
|
|
// 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);
|
|
|
|
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 *));
|
|
|
|
if (!new_term_buffer || !new_color_buffer || !new_attr_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);
|
|
|
|
if (!new_term_buffer[i] || !new_color_buffer[i] || !new_attr_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_term_buffer);
|
|
free(new_color_buffer);
|
|
free(new_attr_buffer);
|
|
return;
|
|
}
|
|
|
|
if (i < term_rows && i < new_rows && terminal_buffer && color_buffer &&
|
|
attr_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));
|
|
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;
|
|
}
|
|
}
|
|
} 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;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Free old buffers after successful allocation
|
|
if (terminal_buffer && color_buffer && attr_buffer) {
|
|
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 = new_term_buffer;
|
|
color_buffer = new_color_buffer;
|
|
attr_buffer = new_attr_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 (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)
|
|
return;
|
|
if (x < 0 || x >= term_cols || y < 0 || y >= term_rows)
|
|
return;
|
|
|
|
// Clear the character position first
|
|
XSetForeground(display, gc, XBlackPixel(display, DefaultScreen(display)));
|
|
XFillRectangle(display, d, gc, x * CHAR_WIDTH, y * CHAR_HEIGHT, CHAR_WIDTH,
|
|
CHAR_HEIGHT);
|
|
|
|
char str[2] = {terminal_buffer[y][x], '\0'};
|
|
XSetForeground(display, gc, color_buffer[y][x]);
|
|
|
|
// 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);
|
|
}
|
|
|
|
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)));
|
|
}
|
|
|
|
// 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);
|
|
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 clear_terminal(Display *display, Window window) {
|
|
if (!terminal_buffer || !color_buffer || !attr_buffer)
|
|
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;
|
|
}
|
|
}
|
|
cursor_x = 0;
|
|
cursor_y = 0;
|
|
needs_refresh = 1;
|
|
}
|
|
|
|
// 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));
|
|
}
|
|
|
|
// 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;
|
|
}
|
|
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[<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
|
|
static void parse_ansi_code(const char *buf, int *idx, int max_len) {
|
|
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);
|
|
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' && 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;
|
|
}
|
|
}
|
|
|
|
static int master_cb(int fd, void *data) {
|
|
if (!terminal_buffer || !color_buffer || !attr_buffer)
|
|
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] == '[') {
|
|
parse_ansi_code(buf, &i, bytes_read);
|
|
continue;
|
|
}
|
|
if (buf[i] == '\n') {
|
|
cursor_x = 0;
|
|
cursor_y++;
|
|
if (cursor_y >= term_rows) {
|
|
scroll_up();
|
|
cursor_y = term_rows - 1;
|
|
needs_refresh = 1;
|
|
}
|
|
} else if (buf[i] == '\r') {
|
|
cursor_x = 0;
|
|
} else if (buf[i] == '\b') {
|
|
if (cursor_x > 0) {
|
|
cursor_x--;
|
|
terminal_buffer[cursor_y][cursor_x] = ' ';
|
|
needs_refresh = 1;
|
|
}
|
|
} 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;
|
|
needs_refresh = 1;
|
|
cursor_x++;
|
|
if (cursor_x >= term_cols) {
|
|
cursor_x = 0;
|
|
cursor_y++;
|
|
if (cursor_y >= term_rows) {
|
|
scroll_up();
|
|
cursor_y = term_rows - 1;
|
|
needs_refresh = 1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
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);
|
|
clear_terminal(event->display, event->window);
|
|
return;
|
|
}
|
|
if ((keysym & 0x1f) == ('C' & 0x1f)) {
|
|
write(*master, "\x03", 1);
|
|
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) {
|
|
write(*master, "\r", 1); // Shell handles return
|
|
} else if (keysym == XK_BackSpace) {
|
|
write(*master, "\b", 1); // Shell handles backspace
|
|
} 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 (keysym == XK_Up) { // Go up through history
|
|
write(*master, "\033[A", 3);
|
|
needs_refresh = 1;
|
|
} else if (keysym == XK_Down) { // Go down through history
|
|
write(*master, "\033[B", 3);
|
|
needs_refresh = 1;
|
|
} else if (keysym == XK_Tab) {
|
|
write(*master, "\t", 1); // Shell handles autocomplete
|
|
} else if (len > 0) {
|
|
write(*master, buf, len);
|
|
}
|
|
}
|
|
|
|
int main(void) {
|
|
int master, slave;
|
|
pid_t pid;
|
|
|
|
term_rows = 24;
|
|
term_cols = 80;
|
|
|
|
gettimeofday(&last_blink, NULL);
|
|
gettimeofday(&last_refresh, NULL);
|
|
|
|
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 *));
|
|
|
|
if (!terminal_buffer || !color_buffer || !attr_buffer) {
|
|
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));
|
|
|
|
if (!terminal_buffer[i] || !color_buffer[i] || !attr_buffer[i]) {
|
|
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));
|
|
current_attr = 0;
|
|
|
|
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,
|
|
KeyPressMask | ExposureMask | StructureNotifyMask |
|
|
FocusChangeMask);
|
|
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;
|
|
}
|
|
case FocusIn:
|
|
window_focused = 1;
|
|
needs_refresh = 1;
|
|
break;
|
|
case FocusOut:
|
|
window_focused = 0;
|
|
needs_refresh = 1;
|
|
break;
|
|
case KeyPress:
|
|
key_press_cb((XKeyEvent *)&event, &master);
|
|
break;
|
|
case Expose:
|
|
needs_refresh = 1;
|
|
check_refresh(display, window);
|
|
break;
|
|
}
|
|
}
|
|
|
|
fd_set fds;
|
|
struct timeval tv = {0, 1000}; // 1ms timeout for faster refresh
|
|
FD_ZERO(&fds);
|
|
FD_SET(master, &fds);
|
|
if (select(master + 1, &fds, NULL, NULL, &tv) > 0) {
|
|
master_cb(master, &ctx);
|
|
}
|
|
|
|
check_refresh(display, window);
|
|
update_cursor_state(display, window);
|
|
}
|
|
|
|
return 0;
|
|
}
|