cowterm/cowterm.c
cowmonk 0436087761 UTF-8 Fixes, attempt at scrolling, & minor changes
Changed some global -> local const vars
2025-01-23 08:45:09 -07:00

1561 lines
47 KiB
C

#include <X11/X.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/keysym.h>
#include <errno.h>
#include <fcntl.h>
#include <locale.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 <wchar.h>
#include "config.h"
// There isn't a need for MAX_ROWS or MAX_COLS, but ¯\_(ツ)_/¯
const int MAX_ROWS = 1000;
const int MAX_COLS = 1000;
const int CHAR_WIDTH = 8;
const int CHAR_HEIGHT = 16;
// General window/terminal stuff
static pid_t child_pid;
static wchar_t **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 wchar_t **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_UNDERLINE (1 << 3)
#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(wchar_t) + 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);
wchar_t **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(wchar_t));
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));
for (int x = term_cols; x < new_cols; x++) {
new_term_buffer[i][x] = L' ';
new_color_buffer[i][x] = current_color;
new_attr_buffer[i][x] = current_attr;
new_bg_color_buffer[i][x] = current_bg_color;
}
} else {
for (int x = 0; x < new_cols; x++) {
new_term_buffer[i][x] = L' ';
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) {
for (int i = 0; i < term_rows; i++) {
if (terminal_buffer[i])
free(terminal_buffer[i]);
}
free(terminal_buffer);
}
if (color_buffer) {
for (int i = 0; i < term_rows; i++) {
if (color_buffer[i])
free(color_buffer[i]);
}
free(color_buffer);
}
if (attr_buffer) {
for (int i = 0; i < term_rows; i++) {
if (attr_buffer[i])
free(attr_buffer[i]);
}
free(attr_buffer);
}
if (bg_color_buffer) {
for (int i = 0; i < term_rows; i++) {
if (bg_color_buffer[i])
free(bg_color_buffer[i]);
}
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;
wchar_t c = terminal_buffer[y][x];
char utf8_buf[8] = {0};
if (c == 0 || c == L' ') {
utf8_buf[0] = ' ';
} else {
wchar_t wstr[2] = {c, 0};
wcstombs(utf8_buf, wstr, sizeof(utf8_buf));
}
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);
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,
utf8_buf, strlen(utf8_buf));
if (attr & ATTR_UNDERLINE) {
XDrawLine(display, d, gc, x * CHAR_WIDTH, (y + 1) * CHAR_HEIGHT - 1,
(x + 1) * CHAR_WIDTH - 1, (y + 1) * CHAR_HEIGHT - 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;
XSetForeground(display, gc, current_bg_color);
XFillRectangle(display, buffer_pixmap, gc, 0, 0, term_cols * CHAR_WIDTH,
term_rows * CHAR_HEIGHT);
// Draw all characters
for (int y = 0; y < term_rows; 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 * sizeof(wchar_t));
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++) {
for (int x = 0; x < term_cols; x++) {
terminal_buffer[i][x] = L' ';
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;
wchar_t **tbuf = malloc(term_rows * sizeof(wchar_t *));
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 * sizeof(wchar_t));
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 * sizeof(wchar_t));
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 * sizeof(wchar_t));
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));
}
for (int x = 0; x < term_cols; x++) {
terminal_buffer[0][x] = L' ';
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 * sizeof(wchar_t));
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 * sizeof(wchar_t));
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));
}
for (int x = 0; x < term_cols; x++) {
terminal_buffer[start][x] = L' ';
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];
}
#if COLOR_256_SUPPORT
static unsigned long get_256_color(int index) {
// Basic 16 colors
if (index < 16)
return index < 8 ? get_ansi_color(index) : get_bright_ansi_color(index - 8);
// 216 color cube
if (index < 232) {
index -= 16;
int r = (index / 36) * 51;
int g = ((index % 36) / 6) * 51;
int b = (index % 6) * 51;
return (r << 16) | (g << 8) | b;
}
// Grayscale
index -= 232;
int gray = index * 10 + 8;
return (gray << 16) | (gray << 8) | gray;
}
#endif
// 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,
Window window) {
if (*idx + 1 < max_len) {
// Skip CSI sequences that we don't need to process
if (buf[*idx] == '\033' && buf[*idx + 1] == '(') {
*idx += 2;
return;
}
// Skip other common terminal sequences
if (buf[*idx] == '\033' && strchr("=>", buf[*idx + 1])) {
*idx += 2;
return;
}
}
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
switch (params[0]) {
case 0: // Clear from cursor to end of line
for (int x = cursor_x; x < term_cols; x++) {
terminal_buffer[cursor_y][x] = L' ';
color_buffer[cursor_y][x] = current_color;
attr_buffer[cursor_y][x] = current_attr;
bg_color_buffer[cursor_y][x] = current_bg_color;
}
break;
case 1: // Clear from cursor to beginning of line
for (int x = 0; x <= cursor_x; x++) {
terminal_buffer[cursor_y][x] = L' ';
color_buffer[cursor_y][x] = current_color;
attr_buffer[cursor_y][x] = current_attr;
bg_color_buffer[cursor_y][x] = current_bg_color;
}
break;
case 2: // Clear entire line
for (int x = 0; x < term_cols; x++) {
terminal_buffer[cursor_y][x] = L' ';
color_buffer[cursor_y][x] = current_color;
attr_buffer[cursor_y][x] = current_attr;
bg_color_buffer[cursor_y][x] = current_bg_color;
}
break;
}
needs_refresh = 1;
draw_terminal(display, window);
break;
case 'J': // Clear screen
switch (params[0]) {
case 0: // Clear from cursor to end of screen
// Clear current line from cursor
for (int x = cursor_x; x < term_cols; x++) {
terminal_buffer[cursor_y][x] = L' ';
color_buffer[cursor_y][x] = current_color;
attr_buffer[cursor_y][x] = current_attr;
bg_color_buffer[cursor_y][x] = current_bg_color;
}
// Clear all lines below cursor
for (int y = cursor_y + 1; y < term_rows; y++) {
for (int x = 0; x < term_cols; x++) {
terminal_buffer[y][x] = L' ';
color_buffer[y][x] = current_color;
attr_buffer[y][x] = current_attr;
bg_color_buffer[y][x] = current_bg_color;
}
}
break;
case 1: // Clear from cursor to beginning of screen
// Clear current line up to cursor
for (int x = 0; x <= cursor_x; x++) {
terminal_buffer[cursor_y][x] = L' ';
color_buffer[cursor_y][x] = current_color;
attr_buffer[cursor_y][x] = current_attr;
bg_color_buffer[cursor_y][x] = current_bg_color;
}
// Clear all lines above cursor
for (int y = 0; y < cursor_y; y++) {
for (int x = 0; x < term_cols; x++) {
terminal_buffer[y][x] = L' ';
color_buffer[y][x] = current_color;
attr_buffer[y][x] = current_attr;
bg_color_buffer[y][x] = current_bg_color;
}
}
break;
case 2: // Clear entire screen
case 3: // Clear entire screen and scrollback (treat same as 2)
current_color = XWhitePixel(display, DefaultScreen(display));
current_bg_color = XBlackPixel(display, DefaultScreen(display));
current_attr = 0;
for (int y = 0; y < term_rows; y++) {
for (int x = 0; x < term_cols; x++) {
terminal_buffer[y][x] = L' ';
color_buffer[y][x] = current_color;
attr_buffer[y][x] = current_attr;
bg_color_buffer[y][x] = current_bg_color;
}
}
cursor_x = 0;
cursor_y = 0;
break;
}
needs_refresh = 1;
draw_terminal(display, window);
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
if (param_idx == 0 || (param_idx == 1 && params[0] == 0)) { // Handle ESC[0m
current_color = XWhitePixel(display, DefaultScreen(display));
current_bg_color = XBlackPixel(display, DefaultScreen(display));
current_attr = 0;
needs_refresh = 1;
break;
}
for (int i = 0; i < (int)param_idx; i++) {
switch (params[i]) {
case 0: /* Reset */
current_color = XWhitePixel(display, DefaultScreen(display));
current_bg_color = XBlackPixel(display, DefaultScreen(display));
current_attr = 0;
if (cursor_y >= 0 && cursor_y < term_rows) {
for (int x = cursor_x; x < term_cols; x++) {
color_buffer[cursor_y][x] = current_color;
bg_color_buffer[cursor_y][x] = current_bg_color;
attr_buffer[cursor_y][x] = current_attr;
}
}
break;
case 1: // Bold
current_attr |= ATTR_BOLD;
break;
case 2: // Dim
current_attr |= ATTR_DIM;
break;
case 3: // Italic
current_attr |= ATTR_ITALIC;
break;
case 5: // Blink (slow)
current_attr |= ATTR_BLINK;
break;
case 7: // Reverse
current_attr |= ATTR_REVERSE;
break;
case 22: // Reset Bold and Dim
current_attr &= ~(ATTR_BOLD | ATTR_DIM);
break;
case 23: // Reset Italic
current_attr &= ~ATTR_ITALIC;
break;
case 25: // Reset Blink
current_attr &= ~ATTR_BLINK;
break;
case 27: // Reset Reverse
current_attr &= ~ATTR_REVERSE;
break;
case 38: // Extended foreground color
#if COLOR_256_SUPPORT
if (i + 2 < (int)param_idx && params[i + 1] == 5) {
current_color = get_256_color(params[i + 2]);
i += 2;
}
#endif
break;
case 39: // Default foreground
current_color = XWhitePixel(display, DefaultScreen(display));
break;
case 48: // Extended background color
#if COLOR_256_SUPPORT
if (i + 2 < (int)param_idx && params[i + 1] == 5) {
current_bg_color = get_256_color(params[i + 2]);
i += 2;
}
#endif
break;
case 49: // Default background color
current_bg_color = XBlackPixel(display, DefaultScreen(display));
break;
default:
if (params[i] >= 30 && params[i] <= 37)
current_color = get_ansi_color(params[i] - 30);
else if (params[i] >= 40 && params[i] <= 47)
current_bg_color = get_ansi_color(params[i] - 40);
else if (params[i] >= 90 && params[i] <= 97)
current_color = get_bright_ansi_color(params[i] - 90);
else if (params[i] >= 100 && params[i] <= 107)
current_bg_color = get_bright_ansi_color(params[i] - 100);
break;
}
}
needs_refresh = 1;
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') {
if (i + 1 < n && buf[i + 1] == '[') {
parse_ansi_code(buf, &i, n, window);
continue;
}
if (i + 1 < n && (buf[i + 1] == '(' || buf[i + 1] == ')')) {
if (i + 2 < n) {
i += 2; // Skip ESC, (, and the character set identifier
}
continue;
}
// Handle other simple escape sequences
if (i + 1 < n) {
switch (buf[i + 1]) {
case '=': // Application keypad
case '>': // Normal keypad
case '7': // Save cursor position
case '8': // Restore cursor position
case 'E': // Next line
case 'M': // Reverse line feed
i++; // Skip the command character
continue;
}
}
continue;
}
if ((buf[i] >= 32 && buf[i] <= 126) || buf[i] == '\n' || buf[i] == '\r' ||
buf[i] == '\b' || buf[i] == '\t' || (buf[i] & 0x80)) {
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--;
terminal_buffer[cursor_y][cursor_x] = ' ';
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;
} 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;
if (buf[i] & 0x80) { // UTF-8
char mb_buf[MB_CUR_MAX];
int mb_len = 0;
int j = i;
while (mb_len < (int)MB_CUR_MAX && j < n) {
if (mb_len == 0) {
if ((buf[j] & 0x80) == 0)
break;
mb_buf[mb_len] = buf[j];
mb_len++;
} else if ((buf[j] & 0xC0) == 0x80) {
mb_buf[mb_len] = buf[j];
mb_len++;
} else {
break;
}
j++;
}
if (mb_len > 0) {
wchar_t wc;
mbstate_t ps;
memset(&ps, 0, sizeof(ps));
if (mbrtowc(&wc, mb_buf, mb_len, &ps) > 0) {
terminal_buffer[cursor_y][cursor_x] = wc;
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;
cursor_x++;
if (cursor_x >= term_cols) {
cursor_x = 0;
cursor_y++;
if (cursor_y >= term_rows) {
scroll_region_up(1);
cursor_y = term_rows - 1;
}
}
}
i += mb_len - 1;
}
} else if (buf[i] >= 32) {
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;
cursor_x++;
if (cursor_x >= term_cols) {
cursor_x = 0;
cursor_y++;
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 (event->state & ShiftMask) {
if (keysym == XK_Page_Up) {
scroll_region_up(term_rows / 2);
needs_refresh = 1;
return;
} else if (keysym == XK_Page_Down) {
scroll_region_down(term_rows / 2);
needs_refresh = 1;
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(wchar_t *))) ||
!(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(wchar_t))) ||
!(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;
for (int j = 0; j < term_cols; j++) {
terminal_buffer[i][j] = L' ';
color_buffer[i][j] = current_color;
attr_buffer[i][j] = current_attr;
bg_color_buffer[i][j] = current_bg_color;
}
}
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);
setlocale(LC_ALL, "");
char *sh = getenv("SHELL");
#if COLOR_256_SUPPORT
setenv("TERM", "xterm-256color", 1);
#else
setenv("TERM", "xterm", 1);
#endif
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);
}
}