summary refs log tree commit diff
diff options
context:
space:
mode:
authorJune McEnroe <june@causal.agency>2019-07-31 12:27:01 -0400
committerJune McEnroe <june@causal.agency>2019-07-31 12:27:01 -0400
commitf0f2fe362dbc50e9eacd03b2b3f1e1713470fe2c (patch)
treecfb9faef4cafaf6d3240ff14926c1936ca1b323d
parentPass original command to dispatch (diff)
downloadstream-f0f2fe362dbc50e9eacd03b2b3f1e1713470fe2c.tar.gz
stream-f0f2fe362dbc50e9eacd03b2b3f1e1713470fe2c.zip
Import terminal emulation from shotty
-rw-r--r--.gitignore1
-rw-r--r--Makefile9
-rw-r--r--ingest.c13
-rw-r--r--stream.h20
-rw-r--r--term.c339
5 files changed, 377 insertions, 5 deletions
diff --git a/.gitignore b/.gitignore
index 8dee7f0..1fe25c1 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,4 @@
+*.o
 chroot.tar
 config.mk
 dispatch
diff --git a/Makefile b/Makefile
index 94befab..e1fea15 100644
--- a/Makefile
+++ b/Makefile
@@ -16,8 +16,13 @@ BINS += view
 
 all: tags ${BINS}
 
-tags: *.c
-	ctags -w *.c
+ingest: ingest.o term.o
+	${CC} ${LDFLAGS} ingest.o term.o ${LDLIBS} -o $@
+
+ingest.o term.o: stream.h
+
+tags: *.c *.h
+	ctags -w *.c *.h
 
 chroot.tar: ${BINS}
 	mkdir -p root
diff --git a/ingest.c b/ingest.c
index fba2435..27035af 100644
--- a/ingest.c
+++ b/ingest.c
@@ -17,6 +17,7 @@
 #include <assert.h>
 #include <err.h>
 #include <fcntl.h>
+#include <locale.h>
 #include <poll.h>
 #include <stdint.h>
 #include <stdio.h>
@@ -26,12 +27,17 @@
 #include <sys/un.h>
 #include <sysexits.h>
 #include <unistd.h>
+#include <wchar.h>
+
+#include "stream.h"
 
 int main(void) {
 	int error;
+	setlocale(LC_CTYPE, "");
 
 	// TODO: Read info from file.
 	const char *path = "example.sock";
+	termInit(24, 80);
 	
 	int server = socket(PF_LOCAL, SOCK_STREAM, 0);
 	if (server < 0) err(EX_OSERR, "socket");
@@ -78,15 +84,16 @@ int main(void) {
 		if (fds[1].revents) {
 			int client = accept(server, NULL, NULL);
 			if (client < 0) err(EX_IOERR, "accept");
-			maxClient++;
-			assert(client == maxClient);
+			fcntl(client, F_SETFL, O_NONBLOCK);
 
 			int yes = 1;
-			fcntl(client, F_SETFL, O_NONBLOCK);
 			error = setsockopt(client, SOL_SOCKET, SO_NOSIGPIPE, &yes, sizeof(yes));
 			if (error) err(EX_IOERR, "setsockopt");
 
 			// TODO: Send snapshot.
+
+			maxClient++;
+			assert(client == maxClient);
 		}
 	}
 	err(EX_IOERR, "poll");
diff --git a/stream.h b/stream.h
new file mode 100644
index 0000000..d03d30d
--- /dev/null
+++ b/stream.h
@@ -0,0 +1,20 @@
+/* Copyright (C) 2019  C. McEnroe <june@causal.agency>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+typedef unsigned uint;
+
+void termInit(uint rows, uint cols);
+void termUpdate(wchar_t ch);
diff --git a/term.c b/term.c
new file mode 100644
index 0000000..c0395f9
--- /dev/null
+++ b/term.c
@@ -0,0 +1,339 @@
+/* Copyright (C) 2019  C. McEnroe <june@causal.agency>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <err.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sysexits.h>
+#include <wchar.h>
+
+#include "stream.h"
+
+#define MIN(a, b) ((a) < (b) ? (a) : (b))
+
+enum {
+	NUL, SOH, STX, ETX, EOT, ENQ, ACK, BEL,
+	BS, HT, NL, VT, NP, CR, SO, SI,
+	DLE, DC1, DC2, DC3, DC4, NAK, SYN, ETB,
+	CAN, EM, SUB, ESC, FS, GS, RS, US,
+	DEL = 0x7F,
+};
+
+struct Style {
+	bool bold, italic, underline, reverse;
+	uint bg, fg;
+};
+
+struct Cell {
+	struct Style style;
+	wchar_t ch;
+};
+
+static const struct Style Default = { .bg = -1, .fg = -1 };
+
+static uint rows, cols;
+static uint y, x;
+
+static bool insert;
+static struct {
+	uint top, bot;
+} scroll;
+
+static struct Style style;
+static struct Cell *cells;
+
+static struct Cell *cell(uint y, uint x) {
+	return &cells[y * cols + x];
+}
+
+static void clear(struct Cell *a, struct Cell *b) {
+	for (; a <= b; ++a) {
+		a->style = style;
+		a->ch = ' ';
+	}
+}
+
+static void move(struct Cell *dst, struct Cell *src, uint len) {
+	memmove(dst, src, sizeof(*dst) * len);
+}
+
+static char updateNUL(wchar_t ch) {
+	switch (ch) {
+		break; case ESC: return ESC;
+
+		break; case BS: if (x) x--;
+		break; case CR: x = 0;
+
+		break; case NL: {
+			if (y == scroll.bot) {
+				move(
+					cell(scroll.top, 0), cell(scroll.top + 1, 0),
+					cols * (scroll.bot - scroll.top)
+				);
+				clear(cell(scroll.bot, 0), cell(scroll.bot, cols - 1));
+			} else {
+				y = MIN(y + 1, rows - 1);
+			}
+		}
+
+		break; default: {
+			// FIXME: Nowhere for these warnings to go.
+			if (ch < ' ') {
+				warnx("unhandled \\x%02X", ch);
+				return NUL;
+			}
+
+			int width = wcwidth(ch);
+			if (width < 0) {
+				warnx("unhandled \\u%X", ch);
+				return NUL;
+			}
+			if (x + width > cols) {
+				warnx("cannot fit '%lc'", ch);
+				return NUL;
+			}
+
+			if (insert) {
+				move(cell(y, x + width), cell(y, x), cols - x - width);
+			}
+			cell(y, x)->style = style;
+			cell(y, x)->ch = ch;
+
+			for (int i = 1; i < width; ++i) {
+				cell(y, x + i)->style = style;
+				cell(y, x + i)->ch = '\0';
+			}
+			x = MIN(x + width, cols - 1);
+		}
+	}
+	return NUL;
+}
+
+#define ENUM_CHARS \
+	X('?', DEC) \
+	X('A', CUU) \
+	X('B', CUD) \
+	X('C', CUF) \
+	X('D', CUB) \
+	X('E', CNL) \
+	X('F', CPL) \
+	X('G', CHA) \
+	X('H', CUP) \
+	X('J', ED)  \
+	X('K', EL)  \
+	X('M', DL)  \
+	X('P', DCH) \
+	X('[', CSI) \
+	X('\\', ST) \
+	X(']', OSC) \
+	X('d', VPA) \
+	X('h', SM)  \
+	X('l', RM)  \
+	X('m', SGR) \
+	X('r', DECSTBM)
+
+enum {
+#define X(ch, name) name = ch,
+	ENUM_CHARS
+#undef X
+};
+
+static char updateESC(wchar_t ch) {
+	static bool discard;
+	if (discard) {
+		discard = false;
+		return NUL;
+	}
+	switch (ch) {
+		case '(': discard = true; return ESC;
+		case '=': return NUL;
+		case '>': return NUL;
+		case CSI: return CSI;
+		case OSC: return OSC;
+		// FIXME: Nowhere for this warning to go.
+		default: warnx("unhandled ESC %lc", ch); return NUL;
+	}
+}
+
+static char updateCSI(wchar_t ch) {
+	static bool dec;
+	if (ch == DEC) {
+		dec = true;
+		return CSI;
+	}
+
+	static uint n, p, ps[8];
+	if (ch == ';') {
+		n++;
+		p++;
+		ps[p %= 8] = 0;
+		return CSI;
+	}
+	if (ch >= '0' && ch <= '9') {
+		ps[p] *= 10;
+		ps[p] += ch - '0';
+		if (!n) n++;
+		return CSI;
+	}
+
+	switch (ch) {
+		break; case CUU: y -= MIN((n ? ps[0] : 1), y);
+		break; case CUD: y  = MIN(y + (n ? ps[0] : 1), rows - 1);
+		break; case CUF: x  = MIN(x + (n ? ps[0] : 1), cols - 1);
+		break; case CUB: x -= MIN((n ? ps[0] : 1), x);
+		break; case CNL: y  = MIN(y + (n ? ps[0] : 1), rows - 1); x = 0;
+		break; case CPL: y -= MIN((n ? ps[0] : 1), y); x = 0;
+		break; case CHA: x  = MIN((n ? ps[0] - 1 : 0), cols - 1);
+		break; case VPA: y  = MIN((n ? ps[0] - 1 : 0), rows - 1);
+		break; case CUP: {
+			y = MIN((n > 0 ? ps[0] - 1 : 0), rows - 1);
+			x = MIN((n > 1 ? ps[1] - 1 : 0), cols - 1);
+		}
+
+		break; case ED: {
+			struct Cell *a = cell(0, 0);
+			struct Cell *b = cell(rows - 1, cols - 1);
+			if (ps[0] == 0) a = cell(y, x);
+			if (ps[0] == 1) b = cell(y, x);
+			clear(a, b);
+		}
+		break; case EL: {
+			struct Cell *a = cell(y, 0);
+			struct Cell *b = cell(y, cols - 1);
+			if (ps[0] == 0) a = cell(y, x);
+			if (ps[0] == 1) b = cell(y, x);
+			clear(a, b);
+		}
+
+		break; case DL: {
+			uint i = MIN((n ? ps[0] : 1), rows - y);
+			move(cell(y, 0), cell(y + i, 0), cols * (rows - y - i));
+			clear(cell(rows - i, 0), cell(rows - 1, cols - 1));
+		}
+		break; case DCH: {
+			uint i = MIN((n ? ps[0] : 1), cols - x);
+			move(cell(y, x), cell(y, x + i), cols - x - i);
+			clear(cell(y, cols - i), cell(y, cols - 1));
+		}
+
+		// FIXME: Nowhere for these warnings to go.
+		break; case SM: {
+			if (dec) break;
+			switch (ps[0]) {
+				break; case 4: insert = true;
+				break; default: warnx("unhandled SM %u", ps[0]);
+			}
+		}
+		break; case RM: {
+			if (dec) break;
+			switch (ps[0]) {
+				break; case 4: insert = false;
+				break; default: warnx("unhandled RM %u", ps[0]);
+			}
+		}
+
+		break; case SGR: {
+			if (ps[0] == 38 && ps[1] == 5) {
+				style.fg = ps[2];
+				break;
+			}
+			if (ps[0] == 48 && ps[1] == 5) {
+				style.bg = ps[2];
+				break;
+			}
+			for (uint i = 0; i < p + 1; ++i) {
+				switch (ps[i]) {
+					break; case 0: style = Default;
+					break; case 1: style.bold = true;
+					break; case 3: style.italic = true;
+					break; case 4: style.underline = true;
+					break; case 7: style.reverse = true;
+					break; case 21: style.bold = false;
+					break; case 22: style.bold = false;
+					break; case 23: style.italic = false;
+					break; case 24: style.underline = false;
+					break; case 27: style.reverse = false;
+					break; case 39: style.fg = -1;
+					break; case 49: style.bg = -1;
+					break; default: {
+						if (ps[i] >= 30 && ps[i] <= 37) {
+							style.fg = ps[i] - 30;
+						} else if (ps[i] >= 40 && ps[i] <= 47) {
+							style.bg = ps[i] - 40;
+						} else if (ps[i] >= 90 && ps[i] <= 97) {
+							style.fg = 8 + ps[i] - 90;
+						} else if (ps[i] >= 100 && ps[i] <= 107) {
+							style.bg = 8 + ps[i] - 100;
+						} else {
+							// FIXME: Nowhere for this warning to go.
+							warnx("unhandled SGR %u", ps[i]);
+						}
+					}
+				}
+			}
+		}
+
+		break; case DECSTBM: {
+			scroll.top = (n > 0 ? ps[0] - 1 : 0);
+			scroll.bot = (n > 1 ? ps[1] - 1 : rows - 1);
+		}
+
+		break; case 't': // ignore
+		// FIXME: Nowhere for this warning to go.
+		break; default: warnx("unhandled CSI %lc", ch);
+	}
+
+	dec = false;
+	ps[n = p = 0] = 0;
+	return NUL;
+}
+
+static char updateOSC(wchar_t ch) {
+	static bool esc;
+	switch (ch) {
+		break; case BEL: return NUL;
+		break; case ESC: esc = true;
+		break; case ST: {
+			if (!esc) break;
+			esc = false;
+			return NUL;
+		}
+	}
+	return OSC;
+}
+
+void termUpdate(wchar_t ch) {
+	static char seq;
+	switch (seq) {
+		break; case NUL: seq = updateNUL(ch);
+		break; case ESC: seq = updateESC(ch);
+		break; case CSI: seq = updateCSI(ch);
+		break; case OSC: seq = updateOSC(ch);
+	}
+}
+
+void termInit(uint _rows, uint _cols) {
+	rows = _rows;
+	cols = _cols;
+	cells = calloc(rows * cols, sizeof(*cells));
+	if (!cells) err(EX_OSERR, "calloc");
+
+	style = Default;
+	clear(cell(0, 0), cell(rows - 1, cols - 1));
+	scroll.bot = rows - 1;
+}