Very epico progaming
Yes
This commit is contained in:
parent
79de3abd33
commit
78df3c131a
35
Makefile
Normal file
35
Makefile
Normal file
@ -0,0 +1,35 @@
|
||||
# cowterm - simple terminal emulator
|
||||
include config.mk
|
||||
|
||||
SRC = cowterm.c
|
||||
OBJ = ${SRC:.c=.o}
|
||||
|
||||
all: options cowterm
|
||||
|
||||
options:
|
||||
@echo cowterm build options:
|
||||
@echo "CFLAGS = ${CFLAGS}"
|
||||
@echo "LDFLAGS = ${LDFLAGS}"
|
||||
@echo "CC = ${CC}"
|
||||
|
||||
.c.o:
|
||||
${CC} -c ${CFLAGS} $<
|
||||
|
||||
${OBJ}: config.mk
|
||||
|
||||
cowterm: ${OBJ}
|
||||
${CC} -o $@ ${OBJ} ${LDFLAGS}
|
||||
@rm ${OBJ}
|
||||
|
||||
clean:
|
||||
rm -f cowterm
|
||||
|
||||
install: all
|
||||
mkdir -p ${DESTDIR}${PREFIX}/bin
|
||||
cp -f cowterm ${DESTDIR}${PREFIX}/bin
|
||||
chmod 755 ${DESTDIR}${PREFIX}/bin/cowterm
|
||||
|
||||
uninstall:
|
||||
rm -f ${DESTDIR}${PREFIX}/bin/cowterm
|
||||
|
||||
.PHONY: all options clean install uninstall
|
48
README.md
48
README.md
@ -1,3 +1,49 @@
|
||||
# cowterm
|
||||
What the hell is a cowmonk?
|
||||
|
||||
A simple terminal
|
||||
## What is cowterm?
|
||||
cowterm is a really simple terminal that is written in pure C, using gtk. I don't recommend anyone seriously using this.
|
||||
If you wish to, then good luck I guess? Here are some features that are in cowterm:
|
||||
- You can kill processes in it using Ctrl+C
|
||||
- Yes, this had to be manually implemented
|
||||
- You can clear it using Ctrl+L
|
||||
- It won't clear if you type in "clear"
|
||||
- You can delete everything behind the cursor using Ctrl+U
|
||||
- Essentials amirite?
|
||||
- It can be resized
|
||||
- So many Segfaults to get to this revolutionary moment
|
||||
- It's hella lightweight
|
||||
- like seriously, it is only like around 600 sloc so like no ram usage at all
|
||||
|
||||
Here are some ~~downsides~~ upsides to using cowterm:
|
||||
- no scrolling
|
||||
- who needs it amirite? bloat booo
|
||||
- boot it up like a chainsaw
|
||||
- Might segfault once or twice before it actually decides to work, I have no idea how to fix this
|
||||
- Fonts are hell to configure
|
||||
- No wayland
|
||||
- Actually this might be the best "feature" yet
|
||||
- ~~ Missing critical features for a terminal~~ Removed lots of bloat
|
||||
|
||||
## Building & Installation
|
||||
### Dependencies:
|
||||
- C compiler (gcc or clang)
|
||||
- C libraries
|
||||
- Xorg Libs (xfont, xutil, and xlibs)
|
||||
- GTK 3.0
|
||||
|
||||
### Compilation & Installation
|
||||
The steps are very simple, nothing like you've never seen before:
|
||||
```bash
|
||||
git clone https://git.based.pt/cowmonk/cowterm.git
|
||||
cd cowterm
|
||||
# Recomended to check config.h and see if it's to your liking
|
||||
# vim/nano config.h
|
||||
# Also recommended to check config.mk (advanced pro-gamers only!)
|
||||
make
|
||||
doas/sudo make install
|
||||
```
|
||||
Enjoy your new ~~awful~~ great terminal
|
||||
|
||||
# Credits
|
||||
- Me (cowmonk)
|
||||
|
35
config.h
Normal file
35
config.h
Normal file
@ -0,0 +1,35 @@
|
||||
#ifndef CONFIG_H_
|
||||
#define CONFIG_H_
|
||||
|
||||
// Colors are just configured like RBG, if you don't recognize
|
||||
// this color format, search it up and find it out yourself
|
||||
static const unsigned long int RED = 0xFF0000;
|
||||
static const unsigned long int GREEN = 0x00FF00;
|
||||
static const unsigned long int YELLOW = 0xFFFF00;
|
||||
static const unsigned long int BLUE = 0x0000FF;
|
||||
static const unsigned long int MAGENTA = 0xFF00FF;
|
||||
static const unsigned long int CYAN = 0x00FFFF;
|
||||
static const unsigned long int DARK_GRAY = 0x808080;
|
||||
static const unsigned long int LIGHT_RED = 0xFF8080;
|
||||
static const unsigned long int LIGHT_GREEN = 0x80FF80;
|
||||
static const unsigned long int LIGHT_YELLOW = 0xFFFF80;
|
||||
static const unsigned long int LIGHT_BLUE = 0x8080FF;
|
||||
static const unsigned long int LIGHT_MAGENTA = 0xFF80FF;
|
||||
static const unsigned long int LIGHT_CYAN = 0x80FFFF;
|
||||
|
||||
// Configuring Fonts (for dummies)
|
||||
// They are formated like this:
|
||||
// -foundry-family-weight-slant-width--pixels-points-horz-vert-spacing-width-charset
|
||||
// Here are some common examples:
|
||||
// "-misc-fixed-medium-r-normal--13-120-75-75-c-70-iso8859-1"
|
||||
// "-adobe-courier-medium-r-normal--14-140-75-75-m-90-iso8859-1"
|
||||
// "-bitstream-terminal-medium-r-normal--18-140-100-100-c-110-iso8859-1"
|
||||
// Use 'xlsfonts' command to list available fonts
|
||||
static const char REGULAR_FONT[] = "-misc-fixed-medium-r-normal--13-120-75-75-c-70-iso8859-1";
|
||||
static const char BOLD_FONT[] = "-misc-fixed-bold-r-normal--13-120-75-75-c-70-iso8859-1";
|
||||
static const char ITALICS_FONT[] = "-misc-fixed-medium-o-normal--13-120-75-75-c-70-iso8859-1";
|
||||
|
||||
// Change the window name to whatever you want lmao
|
||||
static const char window_name[] = "CowTerm";
|
||||
|
||||
#endif // CONFIG_H_
|
16
config.mk
Normal file
16
config.mk
Normal file
@ -0,0 +1,16 @@
|
||||
# cowterm - a simple terminal emulator
|
||||
|
||||
# paths
|
||||
PREFIX = /usr/local/
|
||||
|
||||
# includes and libs
|
||||
INCS = -I/usr/X11R6/include
|
||||
LIBS = -L/usr/X11R6/lib -lX11 -lutil
|
||||
|
||||
# flags
|
||||
CPPFLAGS = -D_DEFAULT_SOURCE -D_BSD_SOURCE -D_XOPEN_SOURCE=700
|
||||
CFLAGS = -std=c99 -pedantic -Wall -Wextra -Os ${INCS} ${CPPFLAGS}
|
||||
LDFLAGS = ${LIBS}
|
||||
|
||||
# compiler and linker
|
||||
CC = cc
|
724
cowterm.c
Normal file
724
cowterm.c
Normal file
@ -0,0 +1,724 @@
|
||||
#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
|
||||
#define CURSOR_BLINK_INTERVAL 500000 // 500ms in microseconds
|
||||
|
||||
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 Pixmap buffer_pixmap = None;
|
||||
static int master_fd = -1;
|
||||
static XFontStruct *regular_font = NULL;
|
||||
static XFontStruct *bold_font = NULL;
|
||||
static XFontStruct *italic_font = NULL;
|
||||
|
||||
#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];
|
||||
|
||||
// Draw inverted cursor background
|
||||
XSetForeground(display, gc, original_color);
|
||||
XFillRectangle(display, buffer_pixmap, gc, cursor_x * CHAR_WIDTH,
|
||||
cursor_y * CHAR_HEIGHT, CHAR_WIDTH, CHAR_HEIGHT);
|
||||
|
||||
// Draw the character in inverted color
|
||||
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_blink(Display *display, Window window) {
|
||||
struct timeval now;
|
||||
gettimeofday(&now, NULL);
|
||||
|
||||
long elapsed = (now.tv_sec - last_blink.tv_sec) * 1000000 +
|
||||
(now.tv_usec - last_blink.tv_usec);
|
||||
|
||||
if (elapsed >= CURSOR_BLINK_INTERVAL) {
|
||||
cursor_visible = !cursor_visible;
|
||||
last_blink = now;
|
||||
draw_cursor(display, window);
|
||||
XFlush(display);
|
||||
}
|
||||
}
|
||||
|
||||
// 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)));
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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;
|
||||
draw_terminal(display, window);
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
||||
// 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[16] = {0};
|
||||
int num_idx = 0;
|
||||
(*idx)++; // Skip [
|
||||
|
||||
while (*idx < max_len && buf[*idx] != 'm') {
|
||||
if (buf[*idx] >= '0' && buf[*idx] <= '9') {
|
||||
num_buf[num_idx++] = buf[*idx];
|
||||
}
|
||||
(*idx)++;
|
||||
}
|
||||
|
||||
if (num_buf[0] != '\0') {
|
||||
int code = atoi(num_buf);
|
||||
switch (code) {
|
||||
case 0:
|
||||
current_color = XWhitePixel(display, DefaultScreen(display));
|
||||
current_attr = 0;
|
||||
break;
|
||||
case 1:
|
||||
current_attr |= ATTR_BOLD;
|
||||
break;
|
||||
case 3:
|
||||
current_attr |= ATTR_ITALIC;
|
||||
break;
|
||||
case 22:
|
||||
current_attr &= ~ATTR_BOLD;
|
||||
break;
|
||||
case 23:
|
||||
current_attr &= ~ATTR_ITALIC;
|
||||
break;
|
||||
case 30:
|
||||
current_color = XBlackPixel(display, DefaultScreen(display));
|
||||
break;
|
||||
case 31:
|
||||
current_color = RED; // Red
|
||||
break;
|
||||
case 32:
|
||||
current_color = GREEN; // Green
|
||||
break;
|
||||
case 33:
|
||||
current_color = YELLOW; // Yellow
|
||||
break;
|
||||
case 34:
|
||||
current_color = BLUE; // Blue
|
||||
break;
|
||||
case 35:
|
||||
current_color = MAGENTA; // Magenta
|
||||
break;
|
||||
case 36:
|
||||
current_color = CYAN; // Cyan
|
||||
break;
|
||||
case 37:
|
||||
current_color = XWhitePixel(display, DefaultScreen(display));
|
||||
break;
|
||||
case 90:
|
||||
current_color = DARK_GRAY; // Dark gray
|
||||
break;
|
||||
case 91:
|
||||
current_color = LIGHT_RED; // Light red
|
||||
break;
|
||||
case 92:
|
||||
current_color = LIGHT_GREEN; // Light green
|
||||
break;
|
||||
case 93:
|
||||
current_color = LIGHT_YELLOW; // Light yellow
|
||||
break;
|
||||
case 94:
|
||||
current_color = LIGHT_BLUE; // Light blue
|
||||
break;
|
||||
case 95:
|
||||
current_color = LIGHT_MAGENTA; // Light magenta
|
||||
break;
|
||||
case 96:
|
||||
current_color = LIGHT_CYAN; // Light cyan
|
||||
break;
|
||||
case 97:
|
||||
current_color = 0xFFFFFF; // Bright white teehee
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
draw_terminal(ctx->display, ctx->window);
|
||||
}
|
||||
} 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] = ' ';
|
||||
draw_char(ctx->display, buffer_pixmap, cursor_x, cursor_y);
|
||||
XCopyArea(ctx->display, buffer_pixmap, ctx->window, gc,
|
||||
cursor_x * CHAR_WIDTH, cursor_y * CHAR_HEIGHT, CHAR_WIDTH,
|
||||
CHAR_HEIGHT, cursor_x * CHAR_WIDTH, cursor_y * CHAR_HEIGHT);
|
||||
}
|
||||
} 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;
|
||||
draw_char(ctx->display, buffer_pixmap, cursor_x, cursor_y);
|
||||
XCopyArea(ctx->display, buffer_pixmap, ctx->window, gc,
|
||||
cursor_x * CHAR_WIDTH, cursor_y * CHAR_HEIGHT, CHAR_WIDTH,
|
||||
CHAR_HEIGHT, cursor_x * CHAR_WIDTH, cursor_y * CHAR_HEIGHT);
|
||||
cursor_x++;
|
||||
if (cursor_x >= term_cols) {
|
||||
cursor_x = 0;
|
||||
cursor_y++;
|
||||
if (cursor_y >= term_rows) {
|
||||
scroll_up();
|
||||
cursor_y = term_rows - 1;
|
||||
draw_terminal(ctx->display, ctx->window);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
cursor_visible = 1;
|
||||
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)) {
|
||||
kill(child_pid, SIGINT);
|
||||
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);
|
||||
} else if (keysym == XK_BackSpace) {
|
||||
write(*master, "\b", 1);
|
||||
} 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);
|
||||
|
||||
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);
|
||||
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 KeyPress:
|
||||
key_press_cb((XKeyEvent *)&event, &master);
|
||||
break;
|
||||
case Expose:
|
||||
draw_terminal(display, window);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
fd_set fds;
|
||||
struct timeval tv = {0, 100000}; // 100ms timeout for cursor blink
|
||||
FD_ZERO(&fds);
|
||||
FD_SET(master, &fds);
|
||||
if (select(master + 1, &fds, NULL, NULL, &tv) > 0) {
|
||||
master_cb(master, &ctx);
|
||||
}
|
||||
|
||||
update_cursor_blink(display, window);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
Loading…
Reference in New Issue
Block a user