diff options
author | June McEnroe <june@causal.agency> | 2019-07-31 12:27:01 -0400 |
---|---|---|
committer | June McEnroe <june@causal.agency> | 2019-07-31 12:27:01 -0400 |
commit | f0f2fe362dbc50e9eacd03b2b3f1e1713470fe2c (patch) | |
tree | cfb9faef4cafaf6d3240ff14926c1936ca1b323d | |
parent | Pass original command to dispatch (diff) | |
download | stream-f0f2fe362dbc50e9eacd03b2b3f1e1713470fe2c.tar.gz stream-f0f2fe362dbc50e9eacd03b2b3f1e1713470fe2c.zip |
Import terminal emulation from shotty
-rw-r--r-- | .gitignore | 1 | ||||
-rw-r--r-- | Makefile | 9 | ||||
-rw-r--r-- | ingest.c | 13 | ||||
-rw-r--r-- | stream.h | 20 | ||||
-rw-r--r-- | term.c | 339 |
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; +} |