Created
June 20, 2021 21:40
-
-
Save qookei/19b6fe4522398401a3e8ea98eafda6b3 to your computer and use it in GitHub Desktop.
Simple Xlib-based terminal emulator
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Compile with: gcc term.c -o term -lX11 ... | |
#define _XOPEN_SOURCE 600 | |
#include <X11/Xlib.h> | |
#include <stdio.h> | |
#include <unistd.h> | |
#include <fcntl.h> | |
#include <stdlib.h> | |
#include <string.h> | |
#include <poll.h> | |
#include <sys/ioctl.h> | |
#include <termios.h> | |
#define WIDTH 1024 | |
#define HEIGHT 768 | |
#define FONT_WIDTH 6 // 5 + 1 padding between characters | |
#define FONT_HEIGHT 10 // 9 + 1 padding between characters | |
#define TTY_WIDTH (WIDTH / FONT_WIDTH) | |
#define TTY_HEIGHT (HEIGHT / FONT_HEIGHT) | |
Display *d; | |
Window w; | |
int s; | |
char winbuf[TTY_WIDTH * TTY_HEIGHT]; | |
int x = 0, y = 0; | |
void repaint_window() { | |
XClearWindow(d, w); | |
for (int y = 0; y < TTY_HEIGHT; y++) { | |
int eff_x = 0; | |
for (int x = 0; x < TTY_WIDTH; x++) { | |
int times = 1; | |
char c = winbuf[x + y * TTY_WIDTH]; | |
if (!c) | |
break; | |
if (c == '\t') { | |
c = ' '; | |
times = 8 - (eff_x % 8); | |
} | |
while (times--) { | |
XDrawString(d, w, DefaultGC(d, s), eff_x * FONT_WIDTH, (y + 1) * FONT_HEIGHT, &c, 1); | |
eff_x++; | |
} | |
} | |
} | |
} | |
void insert_char(char c) { | |
if (c == '\n') { | |
x = 0; | |
y++; | |
} else if (c == '\b') { | |
winbuf[x + y * TTY_WIDTH] = 0; | |
if (x) | |
x--; | |
else { | |
if (y) { | |
y--; | |
for (int xi = 0; xi < TTY_WIDTH - 1; xi++) { | |
if (!winbuf[xi + y * TTY_WIDTH]) { | |
x = xi; | |
break; | |
} | |
} | |
} | |
} | |
} else if (c >= ' ' || c == '\t') { | |
winbuf[x + y * TTY_WIDTH] = c; | |
x++; | |
if (x == TTY_WIDTH) { | |
y++; | |
x = 0; | |
} | |
} | |
if (y == TTY_HEIGHT) { | |
y--; | |
memmove(winbuf, winbuf + TTY_WIDTH, TTY_WIDTH * (TTY_HEIGHT - 1)); | |
memset(winbuf + TTY_WIDTH * (TTY_HEIGHT - 1), 0, TTY_WIDTH); | |
} | |
} | |
int main() { | |
int master = posix_openpt(O_RDWR); | |
unlockpt(master); | |
grantpt(master); | |
pid_t child = fork(); | |
if (!child) { | |
setsid(); | |
char *slave_name = ptsname(master); | |
int slave = open(slave_name, O_RDWR); | |
setenv("TERM", "dumb", 1); | |
close(master); | |
close(STDIN_FILENO); | |
close(STDOUT_FILENO); | |
close(STDERR_FILENO); | |
dup2(slave, STDIN_FILENO); | |
dup2(slave, STDOUT_FILENO); | |
dup2(slave, STDERR_FILENO); | |
close(slave); | |
execl("/bin/bash", "bash", NULL); | |
} | |
struct winsize ws; | |
ws.ws_row = TTY_HEIGHT; | |
ws.ws_col = TTY_WIDTH; | |
ioctl(master, TIOCSWINSZ, &ws); | |
XEvent e; | |
d = XOpenDisplay(NULL); | |
s = DefaultScreen(d); | |
w = XCreateSimpleWindow(d, RootWindow(d, s), 10, 10, WIDTH, HEIGHT, 1, | |
BlackPixel(d, s), WhitePixel(d, s)); | |
XSelectInput(d, w, ExposureMask | KeyPressMask); | |
XMapWindow(d, w); | |
struct pollfd *fds = calloc(2, sizeof(struct pollfd)); | |
fds[0].fd = ConnectionNumber(d); | |
fds[0].events = POLLIN | POLLOUT; | |
fds[1].fd = master; | |
fds[1].events = POLLIN; | |
while (1) { | |
poll(fds, 2, -1); | |
if (fds[1].revents & POLLIN) { | |
int total; | |
int progress = 0; | |
ioctl(master, FIONREAD, &total); | |
while (progress < total) { | |
char buf[BUFSIZ]; | |
ssize_t size = read(master, buf, BUFSIZ); | |
if (size < 0) { | |
perror("read"); | |
abort(); | |
} | |
for (ssize_t i = 0; i < size; i++) { | |
insert_char(buf[i]); | |
} | |
repaint_window(); | |
progress += size; | |
} | |
} | |
if (fds[1].revents & POLLHUP) { | |
break; | |
} | |
if (fds[0].revents) { | |
while (XPending(d)) { | |
XNextEvent(d, &e); | |
if (e.type == Expose) | |
repaint_window(); | |
if (e.type == KeyPress) { | |
char buf[64]; | |
KeySym ksym; | |
Status status; | |
int len = XLookupString(&e.xkey, buf, 64, &ksym, &status); | |
for (int i = 0; i < len; i++) { | |
if (buf[i] == '\r') | |
buf[i] = '\n'; | |
} | |
if (write(master, buf, len) < 0) { | |
perror("write"); | |
abort(); | |
} | |
} | |
} | |
} | |
} | |
XCloseDisplay(d); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment