Skip to content

Instantly share code, notes, and snippets.

@qookei
Created June 20, 2021 21:40
Show Gist options
  • Save qookei/19b6fe4522398401a3e8ea98eafda6b3 to your computer and use it in GitHub Desktop.
Save qookei/19b6fe4522398401a3e8ea98eafda6b3 to your computer and use it in GitHub Desktop.
Simple Xlib-based terminal emulator
// 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