aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNicholas Noll <nbnoll@eml.cc>2020-06-12 12:33:38 -0700
committerNicholas Noll <nbnoll@eml.cc>2020-06-12 12:33:38 -0700
commita79d1edc9ef2e29597faa723a05088a5d19ea8aa (patch)
tree0ca99e37b46750316b5db2ea3e6a2cdeb4aec5fd
parente41eb5691417ecacade402759231f64778e3147f (diff)
checkin: going to flesh out your own api
-rw-r--r--sys/cmd/dvtm/buffer.c122
-rw-r--r--sys/cmd/dvtm/driver.c335
-rw-r--r--sys/cmd/dvtm/dvtm.c10
-rw-r--r--sys/cmd/dvtm/dvtm.c.old1810
-rw-r--r--sys/cmd/dvtm/events.c180
-rw-r--r--sys/cmd/dvtm/rules.mk5
-rw-r--r--sys/cmd/dvtm/term.c395
-rw-r--r--sys/cmd/dvtm/term.h356
-rw-r--r--sys/cmd/dvtm/vt.c4
-rw-r--r--sys/cmd/dvtm/vt.c.old2074
-rw-r--r--sys/cmd/dvtm/window.c34
-rw-r--r--sys/cmd/term/term.c16
12 files changed, 4752 insertions, 589 deletions
diff --git a/sys/cmd/dvtm/buffer.c b/sys/cmd/dvtm/buffer.c
index f7e73c0..b903e71 100644
--- a/sys/cmd/dvtm/buffer.c
+++ b/sys/cmd/dvtm/buffer.c
@@ -1,4 +1,4 @@
-#include "buffer.h"
+#include "term.h"
/* row operations */
void
@@ -7,7 +7,7 @@ zero(Row *row, int start, int len)
int i;
Cell cell = {
.r = L'\0',
- .pen = {0};
+ .pen = {0},
};
for (i = start; i < len + start; i++)
@@ -48,9 +48,9 @@ bclear(Buffer *b)
},
};
- for (i = 0; i < b->rows; i++) {
- Row *row = b->lines + i;
- for (int j = 0; j < b->cols; j++) {
+ for (i = 0; i < b->nrow; i++) {
+ Row *row = b->row + i;
+ for (int j = 0; j < b->ncol; j++) {
row->cells[j] = cell;
row->dirty = true;
}
@@ -62,10 +62,10 @@ bfree(Buffer *b)
{
int i;
- for (i = 0; i < b->rows; i++)
- free(b->lines[i].cells);
+ for (i = 0; i < b->nrow; i++)
+ free(b->row[i].cells);
- free(b->lines);
+ free(b->row);
for (i = 0; i < b->scroll.size; i++)
free(b->scroll.buf[i].cells);
@@ -122,67 +122,67 @@ bscroll(Buffer *b, int s)
}
void
-bresize(Buffer *b, int rows, int cols)
+bresize(Buffer *b, int nrow, int ncol)
{
- Row *lines = b->lines;
+ Row *row = b->row;
- if (b->rows != rows) {
- if (b->crow >= lines + rows) {
+ if (b->nrow != nrow) {
+ if (b->crow >= row + nrow) {
/* scroll up instead of simply chopping off bottom */
- bscroll(b, (b->crow - b->lines) - rows + 1);
+ bscroll(b, (b->crow - b->row) - nrow + 1);
}
- while (b->rows > rows) {
- free(lines[b->rows - 1].cells);
- b->rows--;
+ while (b->nrow > nrow) {
+ free(row[b->nrow - 1].cells);
+ b->nrow--;
}
- lines = realloc(lines, sizeof(Row) * rows);
+ row = realloc(row, sizeof(Row) * nrow);
}
- if (b->maxcols < cols) {
- for (int row = 0; row < b->rows; row++) {
- lines[row].cells = realloc(lines[row].cells, sizeof(Cell) * cols);
- if (b->cols < cols)
- zero(lines + row, b->cols, cols - b->cols);
- lines[row].dirty = true;
+ if (b->maxcols < ncol) {
+ for (int r = 0; r < b->nrow; r++) {
+ row[r].cells = realloc(row[r].cells, sizeof(Cell) * ncol);
+ if (b->ncol < ncol)
+ zero(row + r, b->ncol, ncol - b->ncol);
+ row[r].dirty = true;
}
Row *sbuf = b->scroll.buf;
- for (int row = 0; row < b->scroll.size; row++) {
- sbuf[row].cells = realloc(sbuf[row].cells, sizeof(Cell) * cols);
- if (b->cols < cols)
- zero(sbuf + row, b->cols, cols - b->cols);
+ for (int r = 0; r < b->scroll.size; r++) {
+ sbuf[r].cells = realloc(sbuf[r].cells, sizeof(Cell) * ncol);
+ if (b->ncol < ncol)
+ zero(sbuf + r, b->ncol, ncol - b->ncol);
}
- b->tabs = realloc(b->tabs, sizeof(*b->tabs) * cols);
- for (int col = b->cols; col < cols; col++)
- b->tabs[col] = !(col & 7);
- b->maxcols = cols;
- b->cols = cols;
- } else if (b->cols != cols) {
- for (int row = 0; row < b->rows; row++)
- lines[row].dirty = true;
- b->cols = cols;
+ b->tabs = realloc(b->tabs, sizeof(*b->tabs) * ncol);
+ for (int c = b->ncol; c < ncol; c++)
+ b->tabs[c] = !(c & 7);
+ b->maxcols = ncol;
+ b->ncol = ncol;
+ } else if (b->ncol != ncol) {
+ for (int r = 0; r < b->nrow; r++)
+ row[r].dirty = true;
+ b->ncol = ncol;
}
int deltarows = 0;
- if (b->rows < rows) {
- while (b->rows < rows) {
- lines[b->rows].cells = calloc(b->maxcols, sizeof(Cell));
- zero(lines + b->rows, 0, b->maxcols);
- b->rows++;
+ if (b->nrow < nrow) {
+ while (b->nrow < nrow) {
+ row[b->nrow].cells = calloc(b->maxcols, sizeof(Cell));
+ zero(row + b->nrow, 0, b->maxcols);
+ b->nrow++;
}
/* prepare for backfill */
if (b->crow >= b->scroll.bot - 1) {
- deltarows = b->lines + rows - b->crow - 1;
+ deltarows = b->row + nrow - b->crow - 1;
if (deltarows > b->scroll.above)
deltarows = b->scroll.above;
}
}
- b->crow += lines - b->lines;
- b->scroll.top = lines;
- b->scroll.bot = lines + rows;
- b->lines = lines;
+ b->crow += row - b->row;
+ b->scroll.top = row;
+ b->scroll.bot = row + nrow;
+ b->row = row;
/* perform backfill */
if (deltarows > 0) {
@@ -240,7 +240,7 @@ browfirst(Buffer *b)
{
Row *bstart;
if (!b->scroll.size || !b->scroll.above)
- return b->lines;
+ return b->row;
bboundary(b, &bstart, nil, nil, nil);
return bstart;
}
@@ -250,7 +250,7 @@ browlast(Buffer *b)
{
Row *aend;
if (!b->scroll.size || !b->scroll.below)
- return b->lines + b->rows - 1;
+ return b->row + b->nrow - 1;
bboundary(b, nil, nil, nil, &aend);
return aend;
}
@@ -259,7 +259,7 @@ Row *
brownext(Buffer *b, Row *row)
{
Row *before_start, *before_end, *after_start, *after_end;
- Row *first = b->lines, *last = b->lines + b->rows - 1;
+ Row *first = b->row, *last = b->row + b->nrow - 1;
if (!row)
return nil;
@@ -283,7 +283,7 @@ Row *
bprevrow(Buffer *b, Row *row)
{
Row *before_start, *before_end, *after_start, *after_end;
- Row *first = b->lines, *last = b->lines + b->rows - 1;
+ Row *first = b->row, *last = b->row + b->nrow - 1;
if (!row)
return nil;
@@ -302,3 +302,25 @@ bprevrow(Buffer *b, Row *row)
return &b->scroll.buf[b->scroll.size - 1];
return --row;
}
+
+void
+brender(Buffer *b, Term *t)
+{
+ int r, c, n;
+ char u[UTFmax+1];
+ Row *row;
+ Cell *cell;
+
+ for (r = 0; r < b->nrow; r++) {
+ row = b->row + r;
+ if (!row->dirty)
+ continue;
+
+ for (c = 0; c < b->ncol; c++) {
+ cell = row->cells + c;
+ tsetpen(t, cell->pen);
+ n = utf8·runetobyte(u, &cell->r);
+ twrite(t, n, u);
+ }
+ }
+}
diff --git a/sys/cmd/dvtm/driver.c b/sys/cmd/dvtm/driver.c
deleted file mode 100644
index 3e6a518..0000000
--- a/sys/cmd/dvtm/driver.c
+++ /dev/null
@@ -1,335 +0,0 @@
-#include <u.h>
-#include <libn.h>
-#include <vendor/unibilium.h>
-
-typedef struct RGB8 RGB8;
-typedef struct Pen Pen;
-typedef struct Term Term;
-
-struct ExtraInfo
-{
- char *enteralt;
- char *exitalt;
-
- char *entermouse;
- char *exitmouse;
-};
-
-static
-struct ExtraInfo vt200 =
-{
- .enteralt = "\e[?1049h",
- .exitalt = "\e[?1049l",
-
- .entermouse = "\e[?1049h\e[?1006l",
- .exitmouse = "\e[?1002l\e[?1006l",
-};
-
-struct RGB8
-{
- uint8 r, g, b;
-};
-
-struct Pen
-{
- uint bold : 1;
- uint italic : 1;
- uint reverse : 1;
- uint strike : 1;
- uint blink : 1;
-
- /* colors */
- uint color : 3;
- union {
- /* 256 color (legacy) */
- struct {
- sint fg : 9, bg : 9; /* 0 - 255 or COLOUR_DEFAULT */
- } idx;
- /* true color (modern) */
- struct {
- RGB8 fg, bg;
- } rgb;
- };
-};
-
-struct Term
-{
- /* meta data */
- char *name;
- unibi_term *info;
- struct {
- uint altscreen : 1;
- uint cursorvis : 1;
- uint mouse : 1;
- } mode;
- struct {
- uint bce : 1;
- int colors;
- } cap;
-
- /* input capture */
- // Input *input;
-
- /* output display */
- int nrow, ncol;
-
- /* output raw text */
- int fd;
- struct {
- char *c, b[512];
- } buf;
-
- struct {
- int len;
- char *b;
- } tmp;
-
- /* info */
- struct {
- /* Positioning */
- char *cup; // cursor_address
- char *vpa; // row_address == vertical position absolute
- char *hpa; // column_address = horizontal position absolute
-
- /* Moving */
- char *cuu; char *cuu1; // Cursor Up
- char *cud; char *cud1; // Cursor Down
- char *cuf; char *cuf1; // Cursor Forward == Right
- char *cub; char *cub1; // Cursor Backward == Left
-
- /* Editing */
- char *ich; char *ich1; // Insert Character
- char *dch; char *dch1; // Delete Character
- char *il; char *il1; // Insert Line
- char *dl; char *dl1; // Delete Line
- char *ech; // Erase Character
- char *ed2; // Erase Data 2 == Clear screen
- char *stbm; // Set Top/Bottom Margins
-
- /* formatting */
- char *sgr; // Select Graphic Rendition
- char *sgr0; // Exit Attribute Mode
- char *sgr_i0, *sgr_i1; // SGR italic off/on
- char *sgr_fg; // SGR foreground colour
- char *sgr_bg; // SGR background colour
-
- // Mode setting/clearing
- char *sm_csr; char *rm_csr; // Set/reset mode: Cursor visible
- } esc;
-};
-
-// -----------------------------------------------------------------------
-// methods
-
-static
-char *
-ttmpbuf(Term *t, int len)
-{
- if (t->tmp.len >= len)
- return t->tmp.b;
-
- /* TODO: error handling */
- return (t->tmp.b = realloc(t->tmp.b, len));
-}
-
-void
-tflush(Term *t)
-{
- if (t->fd != -1)
- write(t->fd, t->buf.b, t->buf.c - t->buf.b);
-
- t->buf.c = t->buf.b;
-}
-
-void
-twrite(Term *t, long len, char *s)
-{
- int n;
- if (!len)
- len = strlen(s);
-
- while (len > 0) {
- n = MIN(len, arrend(t->buf.b) - t->buf.c);
- memcpy(t->buf.c, s, n);
- t->buf.c += n;
- len -= n;
- tflush(t);
- }
-}
-
-static
-void
-tfmt(Term *t, char *esc, int n, ...)
-{
- int i;
- long len;
- va_list args;
- unibi_var_t param[9];
- char buf[64], *c = buf;
-
- if (!esc)
- panicf("no terminfo escape string given");
-
- va_start(args, n);
- for (i = 0; i < arrlen(param) && i < n; i++)
- param[i] = unibi_var_from_num(va_arg(args, int));
- va_end(args);
-
- len = unibi_run(esc, param, c, sizeof(buf));
- if (len < arrlen(buf)) {
- c = ttmpbuf(t, len);
- unibi_run(esc, param, c, len);
- }
-
- twrite(t, len, c);
-}
-
-/* absolute move */
-int
-tgoto(Term *t, int row, int col)
-{
- if (row != -1 && col != -1)
- tfmt(t, t->esc.cup, 2, row, col);
- else if (row != -1) {
- if (!t->esc.vpa)
- return 0;
- tfmt(t, t->esc.vpa, 1, row);
- } else if (col != -1) {
- if (col == 0) {
- twrite(t, 1, "\r");
- return 1;
- }
- if (t->esc.hpa)
- tfmt(t, t->esc.hpa, 1, col);
- else if (t->esc.cuf) {
- twrite(t, 1, "\r");
- tfmt(t, t->esc.cuf, 1, col);
- } else
- return 0;
- } else
- return 0; /* unreachable */
-
- return 1;
-}
-
-/* relative move */
-void
-tjump(Term *t, int down, int right)
-{
- if (down == 1 && t->esc.cud1)
- tfmt(t, t->esc.cud1, 0);
- else if (down == -1 && t->esc.cuu1)
- tfmt(t, t->esc.cuu1, 0);
- else if (down > 0)
- tfmt(t, t->esc.cud, 1, down);
- else if (down < 0)
- tfmt(t, t->esc.cuu, 1, -down);
-
- if (right == 1 && t->esc.cuf1)
- tfmt(t, t->esc.cuf1, 0);
- else if (right == -1 && t->esc.cub1)
- tfmt (t, t->esc.cub1, 0);
- else if (right > 0)
- tfmt(t, t->esc.cuf, 1, right);
- else if( right < 0)
- tfmt(t, t->esc.cub, 1, -right);
-}
-
-void
-tdel(Term *t, int num)
-{
- char *c, buf[64];
- if (num < 1)
- return;
-
- /* TODO: allow for not moving */
- if (t->cap.bce) {
- tfmt(t, t->esc.ech, 1, num);
- tjump(t, 0, num);
- } else {
- c = buf;
- memset(c, ' ', arrlen(buf));
- while(num > 64) {
- twrite(t, arrlen(buf), c);
- num -= arrlen(buf);
- }
- twrite(t, num, c);
- }
-}
-
-void
-tclear(Term *t)
-{
- tfmt(t, t->esc.ed2, 0);
-}
-
-
-// -----------------------------------------------------------------------
-// database lookup
-
-static
-char*
-tryinfostr(Term *t, enum unibi_string s)
-{
- char *val = (char*)unibi_get_str(t->info, s);
- /* TODO: provide fallbacks */
- return val;
-}
-
-static
-char*
-getinfostr(Term *t, enum unibi_string s)
-{
- char *val = tryinfostr(t, s);
- if (!val)
- panicf("required term info string '%s' missing", unibi_name_str(s));
-
- return val;
-}
-
-int
-main()
-{
- Term t;
-
- t.name = getenv("TERM");
- t.info = unibi_from_term(t.name);
-
- t.mode.mouse = 0;
- t.mode.cursorvis = 1;
- t.mode.altscreen = 0;
-
- if (!t.info)
- panicf("could not identify terminal");
-
- t.esc.cup = getinfostr(&t, unibi_cursor_address);
- t.esc.vpa = tryinfostr(&t, unibi_row_address);
- t.esc.hpa = tryinfostr(&t, unibi_column_address);
- t.esc.cuu = getinfostr(&t, unibi_parm_up_cursor);
- t.esc.cuu1 = tryinfostr(&t, unibi_cursor_up);
- t.esc.cud = getinfostr(&t, unibi_parm_down_cursor);
- t.esc.cud1 = tryinfostr(&t, unibi_cursor_down);
- t.esc.cuf = getinfostr(&t, unibi_parm_right_cursor);
- t.esc.cuf1 = tryinfostr(&t, unibi_cursor_right);
- t.esc.cub = getinfostr(&t, unibi_parm_left_cursor);
- t.esc.cub1 = tryinfostr(&t, unibi_cursor_left);
- t.esc.ich = getinfostr(&t, unibi_parm_ich);
- t.esc.ich1 = tryinfostr(&t, unibi_insert_character);
- t.esc.dch = getinfostr(&t, unibi_parm_dch);
- t.esc.dch1 = tryinfostr(&t, unibi_delete_character);
- t.esc.il = getinfostr(&t, unibi_parm_insert_line);
- t.esc.il1 = tryinfostr(&t, unibi_insert_line);
- t.esc.dl = getinfostr(&t, unibi_parm_delete_line);
- t.esc.dl1 = tryinfostr(&t, unibi_delete_line);
- t.esc.ech = getinfostr(&t, unibi_erase_chars);
- t.esc.ed2 = getinfostr(&t, unibi_clear_screen);
- t.esc.stbm = getinfostr(&t, unibi_change_scroll_region);
- t.esc.sgr = getinfostr(&t, unibi_set_attributes);
- t.esc.sgr0 = getinfostr(&t, unibi_exit_attribute_mode);
- t.esc.sgr_i0 = tryinfostr(&t, unibi_exit_italics_mode);
- t.esc.sgr_i1 = tryinfostr(&t, unibi_enter_italics_mode);
- t.esc.sgr_fg = getinfostr(&t, unibi_set_a_foreground);
- t.esc.sgr_bg = getinfostr(&t, unibi_set_a_background);
- t.esc.sm_csr = getinfostr(&t, unibi_cursor_normal);
- t.esc.rm_csr = getinfostr(&t, unibi_cursor_invisible);
-}
diff --git a/sys/cmd/dvtm/dvtm.c b/sys/cmd/dvtm/dvtm.c
index 9d35b25..68f4b8a 100644
--- a/sys/cmd/dvtm/dvtm.c
+++ b/sys/cmd/dvtm/dvtm.c
@@ -17,7 +17,15 @@ static uint tagset[2] = { 1, 1 };
static bool mouse_events_enabled = ENABLE_MOUSE;
static Layout *layout = layouts;
-static StatusBar bar = { .fd = -1, .lastpos = BAR_POS, .pos = BAR_POS, .autohide = BAR_AUTOHIDE, .h = 1 };
+static StatusBar bar =
+{
+ .fd = -1,
+ .lastpos = BAR_POS,
+ .pos = BAR_POS,
+ .autohide = BAR_AUTOHIDE,
+ .h = 1
+};
+
static CmdFifo cmdfifo = { .fd = -1 };
static const char *shell;
static Register copyreg;
diff --git a/sys/cmd/dvtm/dvtm.c.old b/sys/cmd/dvtm/dvtm.c.old
new file mode 100644
index 0000000..749ca1d
--- /dev/null
+++ b/sys/cmd/dvtm/dvtm.c.old
@@ -0,0 +1,1810 @@
+#include "dvtm.h"
+
+/* global variables */
+uint waw, wah, wax, way;
+Client *clients = nil;
+char *title;
+const char *dvtm_name = "dvtm";
+
+Screen screen = { .mfact = MFACT, .nmaster = NMASTER, .history = SCROLL_HISTORY };
+static Client *stack = nil;
+static Client *sel = nil;
+static Client *lastsel = nil;
+static Client *msel = nil;
+
+static uint seltags;
+static uint tagset[2] = { 1, 1 };
+static bool mouse_events_enabled = ENABLE_MOUSE;
+static Layout *layout = layouts;
+
+static StatusBar bar = { .fd = -1, .lastpos = BAR_POS, .pos = BAR_POS, .autohide = BAR_AUTOHIDE, .h = 1 };
+static CmdFifo cmdfifo = { .fd = -1 };
+static const char *shell;
+static Register copyreg;
+static volatile sig_atomic_t running = true;
+static bool runinall = false;
+
+/* function implementations */
+static
+void
+eprint(const char *errstr, ...)
+{
+ va_list ap;
+ va_start(ap, errstr);
+ vfprintf(stderr, errstr, ap);
+ va_end(ap);
+}
+
+static
+void
+fatal(const char *errstr, ...)
+{
+ va_list ap;
+ va_start(ap, errstr);
+ vfprintf(stderr, errstr, ap);
+ va_end(ap);
+ exit(EXIT_FAILURE);
+}
+
+static
+bool
+isarrange(void (*func)())
+{
+ return func == layout->arrange;
+}
+
+static
+bool
+isvisible(Client *c)
+{
+ return c->tags & tagset[seltags];
+}
+
+static
+bool
+is_content_visible(Client *c)
+{
+ if (!c)
+ return false;
+ if (isarrange(fullscreen))
+ return sel == c;
+ return isvisible(c) && !c->minimized;
+}
+
+Client*
+nextvisible(Client *c)
+{
+ for (; c && !isvisible(c); c = c->next);
+ return c;
+}
+
+static
+void
+updatebarpos(void)
+{
+ bar.y = 0;
+ wax = 0;
+ way = 0;
+ wah = screen.h;
+ waw = screen.w;
+ if (bar.pos == BAR_TOP) {
+ wah -= bar.h;
+ way += bar.h;
+ } else if (bar.pos == BAR_BOTTOM) {
+ wah -= bar.h;
+ bar.y = wah;
+ }
+}
+
+static
+void
+hidebar(void)
+{
+ if (bar.pos != BAR_OFF) {
+ bar.lastpos = bar.pos;
+ bar.pos = BAR_OFF;
+ }
+}
+
+static
+void
+showbar(void)
+{
+ if (bar.pos == BAR_OFF)
+ bar.pos = bar.lastpos;
+}
+
+static
+void
+drawbar(void)
+{
+ int sx, sy, x, y, width;
+ uint occupied = 0, urgent = 0;
+ if (bar.pos == BAR_OFF)
+ return;
+
+ for (Client *c = clients; c; c = c->next) {
+ occupied |= c->tags;
+ if (c->urgent)
+ urgent |= c->tags;
+ }
+
+ getyx(stdscr, sy, sx);
+ attrset(BAR_ATTR);
+ move(bar.y, 0);
+
+ for (uint i = 0; i < arrlen(tags); i++){
+ if (tagset[seltags] & (1 << i))
+ attrset(TAG_SEL);
+ else if (urgent & (1 << i))
+ attrset(TAG_URGENT);
+ else if (occupied & (1 << i))
+ attrset(TAG_OCCUPIED);
+ else
+ attrset(TAG_NORMAL);
+ printw(TAG_SYMBOL, tags[i]);
+ }
+
+ attrset(runinall ? TAG_SEL : TAG_NORMAL);
+ addstr(layout->symbol);
+ attrset(TAG_NORMAL);
+
+ getyx(stdscr, y, x);
+ (void)y;
+ int maxwidth = screen.w - x - 2;
+
+ addch(BAR_BEGIN);
+ attrset(BAR_ATTR);
+
+ wchar_t wbuf[sizeof bar.text];
+ size_t numchars = mbstowcs(wbuf, bar.text, sizeof bar.text);
+
+ if (numchars != (size_t)-1 && (width = wcswidth(wbuf, maxwidth)) != -1) {
+ int pos;
+ for (pos = 0; pos + width < maxwidth; pos++)
+ addch(' ');
+
+ for (size_t i = 0; i < numchars; i++) {
+ pos += wcwidth(wbuf[i]);
+ if (pos > maxwidth)
+ break;
+ addnwstr(wbuf+i, 1);
+ }
+
+ clrtoeol();
+ }
+
+ attrset(TAG_NORMAL);
+ mvaddch(bar.y, screen.w - 1, BAR_END);
+ attrset(NORMAL_ATTR);
+ move(sy, sx);
+ wnoutrefresh(stdscr);
+}
+
+static
+int
+show_border(void) {
+ return (bar.pos != BAR_OFF) || (clients && clients->next);
+}
+
+static void
+draw_border(Client *c) {
+ char t = '\0';
+ int x, y, maxlen, attrs = NORMAL_ATTR;
+
+ if (!show_border())
+ return;
+ if (sel != c && c->urgent)
+ attrs = URGENT_ATTR;
+ if (sel == c || (runinall && !c->minimized))
+ attrs = SELECTED_ATTR;
+
+ wattrset(c->window, attrs);
+ getyx(c->window, y, x);
+ mvwhline(c->window, 0, 0, ACS_HLINE, c->w);
+ maxlen = c->w - 10;
+ if (maxlen < 0)
+ maxlen = 0;
+ if ((size_t)maxlen < sizeof(c->title)) {
+ t = c->title[maxlen];
+ c->title[maxlen] = '\0';
+ }
+
+ mvwprintw(c->window, 0, 2, "[%s%s#%d]",
+ *c->title ? c->title : "",
+ *c->title ? " | " : "",
+ c->order);
+ if (t)
+ c->title[maxlen] = t;
+ wmove(c->window, y, x);
+}
+
+static void
+draw_content(Client *c) {
+ vt_draw(c->term, c->window, c->has_title_line, 0);
+}
+
+static void
+draw(Client *c) {
+ if (is_content_visible(c)) {
+ redrawwin(c->window);
+ draw_content(c);
+ }
+ if (!isarrange(fullscreen) || sel == c)
+ draw_border(c);
+ wnoutrefresh(c->window);
+}
+
+static void
+draw_all(void) {
+ if (!nextvisible(clients)) {
+ sel = nil;
+ curs_set(0);
+ erase();
+ drawbar();
+ doupdate();
+ return;
+ }
+
+ if (!isarrange(fullscreen)) {
+ for (Client *c = nextvisible(clients); c; c = nextvisible(c->next)) {
+ if (c != sel)
+ draw(c);
+ }
+ }
+ /* as a last step the selected window is redrawn,
+ * this has the effect that the cursor position is
+ * accurate
+ */
+ if (sel)
+ draw(sel);
+}
+
+static void
+arrange(void) {
+ uint m = 0, n = 0;
+ for (Client *c = nextvisible(clients); c; c = nextvisible(c->next)) {
+ c->order = ++n;
+ if (c->minimized)
+ m++;
+ }
+ erase();
+ attrset(NORMAL_ATTR);
+ if (bar.fd == -1 && bar.autohide) {
+ if ((!clients || !clients->next) && n == 1)
+ hidebar();
+ else
+ showbar();
+ updatebarpos();
+ }
+ if (m && !isarrange(fullscreen))
+ wah--;
+ layout->arrange();
+ if (m && !isarrange(fullscreen)) {
+ uint i = 0, nw = waw / m, nx = wax;
+ for (Client *c = nextvisible(clients); c; c = nextvisible(c->next)) {
+ if (c->minimized) {
+ resize(c, nx, way+wah, ++i == m ? waw - nx : nw, 1);
+ nx += nw;
+ }
+ }
+ wah++;
+ }
+ focus(nil);
+ wnoutrefresh(stdscr);
+ drawbar();
+ draw_all();
+}
+
+static void
+attach(Client *c) {
+ if (clients)
+ clients->prev = c;
+ c->next = clients;
+ c->prev = nil;
+ clients = c;
+ for (int o = 1; c; c = nextvisible(c->next), o++)
+ c->order = o;
+}
+
+static void
+attachafter(Client *c, Client *a) { /* attach c after a */
+ if (c == a)
+ return;
+ if (!a)
+ for (a = clients; a && a->next; a = a->next);
+
+ if (a) {
+ if (a->next)
+ a->next->prev = c;
+ c->next = a->next;
+ c->prev = a;
+ a->next = c;
+ for (int o = a->order; c; c = nextvisible(c->next))
+ c->order = ++o;
+ }
+}
+
+static void
+attachstack(Client *c) {
+ c->snext = stack;
+ stack = c;
+}
+
+static void
+detach(Client *c) {
+ Client *d;
+ if (c->prev)
+ c->prev->next = c->next;
+ if (c->next) {
+ c->next->prev = c->prev;
+ for (d = nextvisible(c->next); d; d = nextvisible(d->next))
+ --d->order;
+ }
+ if (c == clients)
+ clients = c->next;
+ c->next = c->prev = nil;
+}
+
+static void
+settitle(Client *c) {
+ char *term, *t = title;
+ if (!t && sel == c && *c->title)
+ t = c->title;
+ if (t && (term = getenv("TERM")) && !strstr(term, "linux")) {
+ printf("\033]0;%s\007", t);
+ fflush(stdout);
+ }
+}
+
+static void
+detachstack(Client *c) {
+ Client **tc;
+ for (tc = &stack; *tc && *tc != c; tc = &(*tc)->snext);
+ *tc = c->snext;
+}
+
+void
+focus(Client *c) {
+ if (!c)
+ for (c = stack; c && !isvisible(c); c = c->snext);
+ if (sel == c)
+ return;
+ lastsel = sel;
+ sel = c;
+ if (lastsel) {
+ lastsel->urgent = false;
+ if (!isarrange(fullscreen)) {
+ draw_border(lastsel);
+ wnoutrefresh(lastsel->window);
+ }
+ }
+
+ if (c) {
+ detachstack(c);
+ attachstack(c);
+ settitle(c);
+ c->urgent = false;
+ if (isarrange(fullscreen)) {
+ draw(c);
+ } else {
+ draw_border(c);
+ wnoutrefresh(c->window);
+ }
+ }
+ curs_set(c && !c->minimized && vt_cursor_visible(c->term));
+}
+
+static
+void
+applycolorrules(Client *c)
+{
+ const ColorRule *r = colorrules;
+ int fg = r->color->fg, bg = r->color->bg;
+ attr_t attrs = r->attrs;
+
+ for (uint i = 1; i < arrlen(colorrules); i++) {
+ r = &colorrules[i];
+ if (strstr(c->title, r->title)) {
+ attrs = r->attrs;
+ fg = r->color->fg;
+ bg = r->color->bg;
+ break;
+ }
+ }
+
+ vt_default_colors_set(c->term, attrs, fg, bg);
+}
+
+static
+void
+term_title_handler(Vt *term, const char *title) {
+ Client *c = (Client *)vt_data_get(term);
+ if (title)
+ strncpy(c->title, title, sizeof(c->title) - 1);
+ c->title[title ? sizeof(c->title) - 1 : 0] = '\0';
+ settitle(c);
+ if (!isarrange(fullscreen) || sel == c)
+ draw_border(c);
+ applycolorrules(c);
+}
+
+static
+void
+term_urgent_handler(Vt *term) {
+ Client *c = (Client *)vt_data_get(term);
+ c->urgent = true;
+ printf("\a");
+ fflush(stdout);
+ drawbar();
+ if (!isarrange(fullscreen) && sel != c && isvisible(c))
+ draw_border(c);
+}
+
+static
+void
+move_client(Client *c, int x, int y)
+{
+ if (c->x == x && c->y == y)
+ return;
+ debug("moving, x: %d y: %d\n", x, y);
+ if (mvwin(c->window, y, x) == ERR) {
+ eprint("error moving, x: %d y: %d\n", x, y);
+ } else {
+ c->x = x;
+ c->y = y;
+ }
+}
+
+static
+void
+resize_client(Client *c, int w, int h)
+{
+ bool has_title_line = show_border();
+ bool resize_window = c->w != w || c->h != h;
+ if (resize_window) {
+ debug("resizing, w: %d h: %d\n", w, h);
+ if (wresize(c->window, h, w) == ERR) {
+ eprint("error resizing, w: %d h: %d\n", w, h);
+ } else {
+ c->w = w;
+ c->h = h;
+ }
+ }
+ if (resize_window || c->has_title_line != has_title_line) {
+ c->has_title_line = has_title_line;
+ vt_resize(c->app, h - has_title_line, w);
+ if (c->editor)
+ vt_resize(c->editor, h - has_title_line, w);
+ }
+}
+
+void
+resize(Client *c, int x, int y, int w, int h)
+{
+ resize_client(c, w, h);
+ move_client(c, x, y);
+}
+
+static
+Client*
+get_client_by_coord(uint x, unsigned int y) {
+ if (y < way || y >= way+wah)
+ return nil;
+ if (isarrange(fullscreen))
+ return sel;
+ for (Client *c = nextvisible(clients); c; c = nextvisible(c->next)) {
+ if (x >= c->x && x < c->x + c->w && y >= c->y && y < c->y + c->h) {
+ debug("mouse event, x: %d y: %d client: %d\n", x, y, c->order);
+ return c;
+ }
+ }
+ return nil;
+}
+
+static
+void
+sigchld_handler(int sig) {
+ int errsv = errno;
+ int status;
+ pid_t pid;
+
+ while ((pid = waitpid(-1, &status, WNOHANG)) != 0) {
+ if (pid == -1) {
+ if (errno == ECHILD) {
+ /* no more child processes */
+ break;
+ }
+ eprint("waitpid: %s\n", strerror(errno));
+ break;
+ }
+
+ debug("child with pid %d died\n", pid);
+
+ for (Client *c = clients; c; c = c->next) {
+ if (c->pid == pid) {
+ c->died = true;
+ break;
+ }
+ if (c->editor && vt_pid_get(c->editor) == pid) {
+ c->editor_died = true;
+ break;
+ }
+ }
+ }
+
+ errno = errsv;
+}
+
+static
+void
+sigwinch_handler(int sig) {
+ screen.need_resize = true;
+}
+
+static
+void
+sigterm_handler(int sig) {
+ running = false;
+}
+
+static
+void
+resize_screen(void)
+{
+ struct winsize ws;
+
+ if (ioctl(0, TIOCGWINSZ, &ws) == -1) {
+ getmaxyx(stdscr, screen.h, screen.w);
+ } else {
+ screen.w = ws.ws_col;
+ screen.h = ws.ws_row;
+ }
+
+ debug("resize_screen(), w: %d h: %d\n", screen.w, screen.h);
+
+ resizeterm(screen.h, screen.w);
+ wresize(stdscr, screen.h, screen.w);
+ updatebarpos();
+ clear();
+ arrange();
+}
+
+static
+KeyBinding*
+keybinding(KeyCombo keys, uint keycount)
+{
+ for (uint b = 0; b < arrlen(bindings); b++) {
+ for (uint k = 0; k < keycount; k++) {
+ if (keys[k] != bindings[b].keys[k])
+ break;
+ if (k == keycount - 1)
+ return &bindings[b];
+ }
+ }
+ return nil;
+}
+
+static
+uint
+bitoftag(const char *tag)
+{
+ uint i;
+ if (!tag)
+ return ~0;
+ for (i = 0; (i < arrlen(tags)) && strcmp(tags[i], tag); i++);
+ return (i < arrlen(tags)) ? (1 << i) : 0;
+}
+
+static void
+tagschanged() {
+ bool allminimized = true;
+ for (Client *c = nextvisible(clients); c; c = nextvisible(c->next)) {
+ if (!c->minimized) {
+ allminimized = false;
+ break;
+ }
+ }
+ if (allminimized && nextvisible(clients)) {
+ focus(nil);
+ toggleminimize(nil);
+ }
+ arrange();
+}
+
+void
+tag(const char *args[])
+{
+ if (!sel)
+ return;
+ sel->tags = bitoftag(args[0]) & TAGMASK;
+ tagschanged();
+}
+
+void
+tagid(const char *args[])
+{
+ if (!args[0] || !args[1])
+ return;
+
+ const int win_id = atoi(args[0]);
+ for (Client *c = clients; c; c = c->next) {
+ if (c->id == win_id) {
+ uint ntags = c->tags;
+ for (uint i = 1; i < MAX_ARGS && args[i]; i++) {
+ if (args[i][0] == '+')
+ ntags |= bitoftag(args[i]+1);
+ else if (args[i][0] == '-')
+ ntags &= ~bitoftag(args[i]+1);
+ else
+ ntags = bitoftag(args[i]);
+ }
+ ntags &= TAGMASK;
+ if (ntags) {
+ c->tags = ntags;
+ tagschanged();
+ }
+ return;
+ }
+ }
+}
+
+void
+toggletag(const char *args[])
+{
+ if (!sel)
+ return;
+ uint newtags = sel->tags ^ (bitoftag(args[0]) & TAGMASK);
+ if (newtags) {
+ sel->tags = newtags;
+ tagschanged();
+ }
+}
+
+void
+toggleview(const char *args[])
+{
+ uint newtagset = tagset[seltags] ^ (bitoftag(args[0]) & TAGMASK);
+ if (newtagset) {
+ tagset[seltags] = newtagset;
+ tagschanged();
+ }
+}
+
+void
+view(const char *args[])
+{
+ uint newtagset = bitoftag(args[0]) & TAGMASK;
+ if (tagset[seltags] != newtagset && newtagset) {
+ seltags ^= 1; /* toggle sel tagset */
+ tagset[seltags] = newtagset;
+ tagschanged();
+ }
+}
+
+void
+viewprevtag(const char *args[])
+{
+ seltags ^= 1;
+ tagschanged();
+}
+
+static
+void
+keypress(int code)
+{
+ int key = -1;
+ uint len = 1;
+ char buf[8] = { '\e' };
+
+ if (code == '\e') {
+ /* pass characters following escape to the underlying app */
+ nodelay(stdscr, TRUE);
+ for (int t; len < sizeof(buf) && (t = getch()) != ERR; len++) {
+ if (t > 255) {
+ key = t;
+ break;
+ }
+ buf[len] = t;
+ }
+ nodelay(stdscr, FALSE);
+ }
+
+ for (Client *c = runinall ? nextvisible(clients) : sel; c; c = nextvisible(c->next)) {
+ if (is_content_visible(c)) {
+ c->urgent = false;
+ if (code == '\e')
+ vt_write(c->term, buf, len);
+ else
+ vt_keypress(c->term, code);
+
+ if (key != -1)
+ vt_keypress(c->term, key);
+ }
+ if (!runinall)
+ break;
+ }
+}
+
+static
+void
+mouse_setup(void)
+{
+#ifdef CONFIG_MOUSE
+ mmask_t mask = 0;
+
+ if (mouse_events_enabled) {
+ mask = BUTTON1_CLICKED | BUTTON2_CLICKED;
+ for (uint i = 0; i < arrlen(buttons); i++)
+ mask |= buttons[i].mask;
+ }
+ mousemask(mask, nil);
+#endif /* CONFIG_MOUSE */
+}
+
+static bool
+checkshell(const char *shell) {
+ if (shell == nil || *shell == '\0' || *shell != '/')
+ return false;
+ if (!strcmp(strrchr(shell, '/')+1, dvtm_name))
+ return false;
+ if (access(shell, X_OK))
+ return false;
+ return true;
+}
+
+static const char *
+getshell(void) {
+ const char *shell = getenv("SHELL");
+ struct passwd *pw;
+
+ if (checkshell(shell))
+ return shell;
+ if ((pw = getpwuid(getuid())) && checkshell(pw->pw_shell))
+ return pw->pw_shell;
+ return "/bin/sh";
+}
+
+static
+void
+setup(void)
+{
+ shell = getshell();
+ setlocale(LC_CTYPE, "");
+ initscr();
+ start_color();
+ noecho();
+ nonl();
+ keypad(stdscr, TRUE);
+ mouse_setup();
+ raw();
+ vt_init();
+ vt_keytable_set(keytable, arrlen(keytable));
+ for (uint i = 0; i < arrlen(colors); i++) {
+ if (COLORS == 256) {
+ if (colors[i].fg256)
+ colors[i].fg = colors[i].fg256;
+ if (colors[i].bg256)
+ colors[i].bg = colors[i].bg256;
+ }
+ colors[i].pair = vt_color_reserve(colors[i].fg, colors[i].bg);
+ }
+ resize_screen();
+
+ struct sigaction sa;
+ memset(&sa, 0, sizeof sa);
+ sa.sa_flags = 0;
+ sigemptyset(&sa.sa_mask);
+ sa.sa_handler = sigwinch_handler;
+ sigaction(SIGWINCH, &sa, nil);
+ sa.sa_handler = sigchld_handler;
+ sigaction(SIGCHLD, &sa, nil);
+ sa.sa_handler = sigterm_handler;
+ sigaction(SIGTERM, &sa, nil);
+ sa.sa_handler = SIG_IGN;
+ sigaction(SIGPIPE, &sa, nil);
+}
+
+static
+void
+destroy(Client *c) {
+ if (sel == c)
+ focusnextnm(nil);
+ detach(c);
+ detachstack(c);
+ if (sel == c) {
+ Client *next = nextvisible(clients);
+ if (next) {
+ focus(next);
+ toggleminimize(nil);
+ } else {
+ sel = nil;
+ }
+ }
+ if (lastsel == c)
+ lastsel = nil;
+ werase(c->window);
+ wnoutrefresh(c->window);
+ vt_destroy(c->term);
+ delwin(c->window);
+ if (!clients && arrlen(actions)) {
+ if (!strcmp(c->cmd, shell))
+ quit(nil);
+ else
+ create(nil);
+ }
+ free(c);
+ arrange();
+}
+
+static
+void
+cleanup(void) {
+ while (clients)
+ destroy(clients);
+ vt_shutdown();
+ endwin();
+ free(copyreg.data);
+ if (bar.fd > 0)
+ close(bar.fd);
+ if (bar.file)
+ unlink(bar.file);
+ if (cmdfifo.fd > 0)
+ close(cmdfifo.fd);
+ if (cmdfifo.file)
+ unlink(cmdfifo.file);
+}
+
+static
+char *getcwd_by_pid(Client *c) {
+ if (!c)
+ return nil;
+ char buf[32];
+ snprintf(buf, sizeof buf, "/proc/%d/cwd", c->pid);
+ return realpath(buf, nil);
+}
+
+void
+create(const char *args[])
+{
+ const char *pargs[4] = { shell, nil };
+ char buf[8], *cwd = nil;
+ const char *env[] = {
+ "DVTM_WINDOW_ID", buf,
+ nil
+ };
+
+ if (args && args[0]) {
+ pargs[1] = "-c";
+ pargs[2] = args[0];
+ pargs[3] = nil;
+ }
+ Client *c = calloc(1, sizeof(Client));
+ if (!c)
+ return;
+ c->tags = tagset[seltags];
+ c->id = ++cmdfifo.id;
+ snprintf(buf, sizeof buf, "%d", c->id);
+
+ if (!(c->window = newwin(wah, waw, way, wax))) {
+ free(c);
+ return;
+ }
+
+ c->term = c->app = vt_create(screen.h, screen.w, screen.history);
+ if (!c->term) {
+ delwin(c->window);
+ free(c);
+ return;
+ }
+
+ if (args && args[0]) {
+ c->cmd = args[0];
+ char name[PATH_MAX];
+ strncpy(name, args[0], sizeof(name));
+ name[sizeof(name)-1] = '\0';
+ strncpy(c->title, basename(name), sizeof(c->title));
+ } else {
+ c->cmd = shell;
+ }
+
+ if (args && args[1])
+ strncpy(c->title, args[1], sizeof(c->title));
+ c->title[sizeof(c->title)-1] = '\0';
+
+ if (args && args[2])
+ cwd = !strcmp(args[2], "$CWD") ? getcwd_by_pid(sel) : (char*)args[2];
+ c->pid = vt_forkpty(c->term, shell, pargs, cwd, env, nil, nil);
+ if (args && args[2] && !strcmp(args[2], "$CWD"))
+ free(cwd);
+ vt_data_set(c->term, c);
+ vt_title_handler_set(c->term, term_title_handler);
+ vt_urgent_handler_set(c->term, term_urgent_handler);
+ applycolorrules(c);
+ c->x = wax;
+ c->y = way;
+ debug("client with pid %d forked\n", c->pid);
+ attach(c);
+ focus(c);
+ arrange();
+}
+
+void
+copymode(const char *args[])
+{
+ if (!args || !args[0] || !sel || sel->editor)
+ return;
+
+ bool colored = strstr(args[0], "pager") != nil;
+
+ if (!(sel->editor = vt_create(sel->h - sel->has_title_line, sel->w, 0)))
+ return;
+
+ int *to = &sel->editor_fds[0];
+ int *from = strstr(args[0], "editor") ? &sel->editor_fds[1] : nil;
+ sel->editor_fds[0] = sel->editor_fds[1] = -1;
+
+ const char *argv[3] = { args[0], nil, nil };
+ char argline[32];
+ int line = vt_content_start(sel->app);
+ snprintf(argline, sizeof(argline), "+%d", line);
+ argv[1] = argline;
+
+ if (vt_forkpty(sel->editor, args[0], argv, nil, nil, to, from) < 0) {
+ vt_destroy(sel->editor);
+ sel->editor = nil;
+ return;
+ }
+
+ sel->term = sel->editor;
+
+ if (sel->editor_fds[0] != -1) {
+ char *buf = nil;
+ size_t len = vt_content_get(sel->app, &buf, colored);
+ char *cur = buf;
+ while (len > 0) {
+ ssize_t res = write(sel->editor_fds[0], cur, len);
+ if (res < 0) {
+ if (errno == EAGAIN || errno == EINTR)
+ continue;
+ break;
+ }
+ cur += res;
+ len -= res;
+ }
+ free(buf);
+ close(sel->editor_fds[0]);
+ sel->editor_fds[0] = -1;
+ }
+
+ if (args[1])
+ vt_write(sel->editor, args[1], strlen(args[1]));
+}
+
+void
+focusn(const char *args[])
+{
+ for (Client *c = nextvisible(clients); c; c = nextvisible(c->next)) {
+ if (c->order == atoi(args[0])) {
+ focus(c);
+ if (c->minimized)
+ toggleminimize(nil);
+ return;
+ }
+ }
+}
+
+void
+focusid(const char *args[])
+{
+ if (!args[0])
+ return;
+
+ const int win_id = atoi(args[0]);
+ for (Client *c = clients; c; c = c->next) {
+ if (c->id == win_id) {
+ focus(c);
+ if (c->minimized)
+ toggleminimize(nil);
+ if (!isvisible(c)) {
+ c->tags |= tagset[seltags];
+ tagschanged();
+ }
+ return;
+ }
+ }
+}
+
+void
+focusnext(const char *args[])
+{
+ Client *c;
+ if (!sel)
+ return;
+ for (c = sel->next; c && !isvisible(c); c = c->next);
+ if (!c)
+ for (c = clients; c && !isvisible(c); c = c->next);
+ if (c)
+ focus(c);
+}
+
+void
+focusnextnm(const char *args[])
+{
+ if (!sel)
+ return;
+ Client *c = sel;
+ do {
+ c = nextvisible(c->next);
+ if (!c)
+ c = nextvisible(clients);
+ } while (c->minimized && c != sel);
+ focus(c);
+}
+
+void
+focusprev(const char *args[])
+{
+ Client *c;
+ if (!sel)
+ return;
+ for (c = sel->prev; c && !isvisible(c); c = c->prev);
+ if (!c) {
+ for (c = clients; c && c->next; c = c->next);
+ for (; c && !isvisible(c); c = c->prev);
+ }
+ if (c)
+ focus(c);
+}
+
+void
+focusprevnm(const char *args[])
+{
+ if (!sel)
+ return;
+ Client *c = sel;
+ do {
+ for (c = c->prev; c && !isvisible(c); c = c->prev);
+ if (!c) {
+ for (c = clients; c && c->next; c = c->next);
+ for (; c && !isvisible(c); c = c->prev);
+ }
+ } while (c && c != sel && c->minimized);
+ focus(c);
+}
+
+void
+focuslast(const char *args[])
+{
+ if (lastsel)
+ focus(lastsel);
+}
+
+void
+focusup(const char *args[])
+{
+ if (!sel)
+ return;
+ /* avoid vertical separator, hence +1 in x direction */
+ Client *c = get_client_by_coord(sel->x + 1, sel->y - 1);
+ if (c)
+ focus(c);
+ else
+ focusprev(args);
+}
+
+void
+focusdown(const char *args[])
+{
+ if (!sel)
+ return;
+ Client *c = get_client_by_coord(sel->x, sel->y + sel->h);
+ if (c)
+ focus(c);
+ else
+ focusnext(args);
+}
+
+void
+focusleft(const char *args[])
+{
+ if (!sel)
+ return;
+ Client *c = get_client_by_coord(sel->x - 2, sel->y);
+ if (c)
+ focus(c);
+ else
+ focusprev(args);
+}
+
+void
+focusright(const char *args[])
+{
+ if (!sel)
+ return;
+ Client *c = get_client_by_coord(sel->x + sel->w + 1, sel->y);
+ if (c)
+ focus(c);
+ else
+ focusnext(args);
+}
+
+void
+killclient(const char *args[])
+{
+ if (!sel)
+ return;
+ debug("killing client with pid: %d\n", sel->pid);
+ kill(-sel->pid, SIGKILL);
+}
+
+void
+paste(const char *args[])
+{
+ if (sel && copyreg.data)
+ vt_write(sel->term, copyreg.data, copyreg.len);
+}
+
+void
+quit(const char *args[])
+{
+ cleanup();
+ exit(EXIT_SUCCESS);
+}
+
+void
+redraw(const char *args[])
+{
+ for (Client *c = clients; c; c = c->next) {
+ if (!c->minimized) {
+ vt_dirty(c->term);
+ wclear(c->window);
+ wnoutrefresh(c->window);
+ }
+ }
+ resize_screen();
+}
+
+void
+scrollback(const char *args[])
+{
+ if (!is_content_visible(sel))
+ return;
+
+ if (!args[0] || atoi(args[0]) < 0)
+ vt_scroll(sel->term, -sel->h/2);
+ else
+ vt_scroll(sel->term, sel->h/2);
+
+ draw(sel);
+ curs_set(vt_cursor_visible(sel->term));
+}
+
+void
+send(const char *args[])
+{
+ if (sel && args && args[0])
+ vt_write(sel->term, args[0], strlen(args[0]));
+}
+
+void
+setlayout(const char *args[])
+{
+ uint i;
+
+ if (!args || !args[0]) {
+ if (++layout == &layouts[arrlen(layouts)])
+ layout = &layouts[0];
+ } else {
+ for (i = 0; i < arrlen(layouts); i++)
+ if (!strcmp(args[0], layouts[i].symbol))
+ break;
+ if (i == arrlen(layouts))
+ return;
+ layout = &layouts[i];
+ }
+ arrange();
+}
+
+void
+incnmaster(const char *args[])
+{
+ int delta;
+
+ if (isarrange(fullscreen) || isarrange(grid))
+ return;
+ /* arg handling, manipulate nmaster */
+ if (args[0] == nil) {
+ screen.nmaster = NMASTER;
+ } else if (sscanf(args[0], "%d", &delta) == 1) {
+ if (args[0][0] == '+' || args[0][0] == '-')
+ screen.nmaster += delta;
+ else
+ screen.nmaster = delta;
+ if (screen.nmaster < 1)
+ screen.nmaster = 1;
+ }
+ arrange();
+}
+
+void
+setmfact(const char *args[])
+{
+ float delta;
+
+ if (isarrange(fullscreen) || isarrange(grid))
+ return;
+ /* arg handling, manipulate mfact */
+ if (args[0] == nil) {
+ screen.mfact = MFACT;
+ } else if (sscanf(args[0], "%f", &delta) == 1) {
+ if (args[0][0] == '+' || args[0][0] == '-')
+ screen.mfact += delta;
+ else
+ screen.mfact = delta;
+ if (screen.mfact < 0.1)
+ screen.mfact = 0.1;
+ else if (screen.mfact > 0.9)
+ screen.mfact = 0.9;
+ }
+ arrange();
+}
+
+void
+startup(const char *args[])
+{
+ for (uint i = 0; i < arrlen(actions); i++)
+ actions[i].cmd(actions[i].args);
+}
+
+void
+togglebar(const char *args[])
+{
+ if (bar.pos == BAR_OFF)
+ showbar();
+ else
+ hidebar();
+ bar.autohide = false;
+ updatebarpos();
+ redraw(nil);
+}
+
+void
+togglebarpos(const char *args[])
+{
+ switch (bar.pos == BAR_OFF ? bar.lastpos : bar.pos) {
+ case BAR_TOP:
+ bar.pos = BAR_BOTTOM;
+ break;
+ case BAR_BOTTOM:
+ bar.pos = BAR_TOP;
+ break;
+ }
+ updatebarpos();
+ redraw(nil);
+}
+
+void
+toggleminimize(const char *args[])
+{
+ Client *c, *m, *t;
+ uint n;
+ if (!sel)
+ return;
+ /* the last window can't be minimized */
+ if (!sel->minimized) {
+ for (n = 0, c = nextvisible(clients); c; c = nextvisible(c->next))
+ if (!c->minimized)
+ n++;
+ if (n == 1)
+ return;
+ }
+ sel->minimized = !sel->minimized;
+ m = sel;
+ /* check whether the master client was minimized */
+ if (sel == nextvisible(clients) && sel->minimized) {
+ c = nextvisible(sel->next);
+ detach(c);
+ attach(c);
+ focus(c);
+ detach(m);
+ for (; c && (t = nextvisible(c->next)) && !t->minimized; c = t);
+ attachafter(m, c);
+ } else if (m->minimized) {
+ /* non master window got minimized move it above all other
+ * minimized ones */
+ focusnextnm(nil);
+ detach(m);
+ for (c = nextvisible(clients); c && (t = nextvisible(c->next)) && !t->minimized; c = t);
+ attachafter(m, c);
+ } else { /* window is no longer minimized, move it to the master area */
+ vt_dirty(m->term);
+ detach(m);
+ attach(m);
+ }
+ arrange();
+}
+
+void
+togglemouse(const char *args[])
+{
+ mouse_events_enabled = !mouse_events_enabled;
+ mouse_setup();
+}
+
+void
+togglerunall(const char *args[])
+{
+ runinall = !runinall;
+ drawbar();
+ draw_all();
+}
+
+void
+zoom(const char *args[])
+{
+ Client *c;
+
+ if (!sel)
+ return;
+ if (args && args[0])
+ focusn(args);
+ if ((c = sel) == nextvisible(clients))
+ if (!(c = nextvisible(c->next)))
+ return;
+ detach(c);
+ attach(c);
+ focus(c);
+ if (c->minimized)
+ toggleminimize(nil);
+ arrange();
+}
+
+/* commands for use by mouse bindings */
+void
+mouse_focus(const char *args[])
+{
+ focus(msel);
+ if (msel->minimized)
+ toggleminimize(nil);
+}
+
+void
+mouse_fullscreen(const char *args[])
+{
+ mouse_focus(nil);
+ setlayout(isarrange(fullscreen) ? nil : args);
+}
+
+void
+mouse_minimize(const char *args[])
+{
+ focus(msel);
+ toggleminimize(nil);
+}
+
+void
+mouse_zoom(const char *args[])
+{
+ focus(msel);
+ zoom(nil);
+}
+
+static
+Cmd *
+get_cmd_by_name(const char *name) {
+ for (uint i = 0; i < arrlen(commands); i++) {
+ if (!strcmp(name, commands[i].name))
+ return &commands[i];
+ }
+ return nil;
+}
+
+static
+void
+handle_cmdfifo(void) {
+ int r;
+ char *p, *s, cmdbuf[512], c;
+ Cmd *cmd;
+
+ r = read(cmdfifo.fd, cmdbuf, sizeof cmdbuf - 1);
+ if (r <= 0) {
+ cmdfifo.fd = -1;
+ return;
+ }
+
+ cmdbuf[r] = '\0';
+ p = cmdbuf;
+ while (*p) {
+ /* find the command name */
+ for (; *p == ' ' || *p == '\n'; p++);
+ for (s = p; *p && *p != ' ' && *p != '\n'; p++);
+ if ((c = *p))
+ *p++ = '\0';
+ if (*s && (cmd = get_cmd_by_name(s)) != nil) {
+ bool quote = false;
+ int argc = 0;
+ const char *args[MAX_ARGS], *arg;
+ memset(args, 0, sizeof(args));
+ /* if arguments were specified in config.h ignore the one given via
+ * the named pipe and thus skip everything until we find a new line
+ */
+ if (cmd->action.args[0] || c == '\n') {
+ debug("execute %s", s);
+ cmd->action.cmd(cmd->action.args);
+ while (*p && *p != '\n')
+ p++;
+ continue;
+ }
+ /* no arguments were given in config.h so we parse the command line */
+ while (*p == ' ')
+ p++;
+ arg = p;
+ for (; (c = *p); p++) {
+ switch (*p) {
+ case '\\':
+ /* remove the escape character '\\' move every
+ * following character to the left by one position
+ */
+ switch (p[1]) {
+ case '\\':
+ case '\'':
+ case '\"': {
+ char *t = p+1;
+ do {
+ t[-1] = *t;
+ } while (*t++);
+ }
+ }
+ break;
+ case '\'':
+ case '\"':
+ quote = !quote;
+ break;
+ case ' ':
+ if (!quote) {
+ case '\n':
+ /* remove trailing quote if there is one */
+ if (*(p - 1) == '\'' || *(p - 1) == '\"')
+ *(p - 1) = '\0';
+ *p++ = '\0';
+ /* remove leading quote if there is one */
+ if (*arg == '\'' || *arg == '\"')
+ arg++;
+ if (argc < MAX_ARGS)
+ args[argc++] = arg;
+
+ while (*p == ' ')
+ ++p;
+ arg = p--;
+ }
+ break;
+ }
+
+ if (c == '\n' || *p == '\n') {
+ if (!*p)
+ p++;
+ debug("execute %s", s);
+ for(int i = 0; i < argc; i++)
+ debug(" %s", args[i]);
+ debug("\n");
+ cmd->action.cmd(args);
+ break;
+ }
+ }
+ }
+ }
+}
+
+static void
+handle_mouse(void) {
+#ifdef CONFIG_MOUSE
+ MEVENT event;
+ uint i;
+ if (getmouse(&event) != OK)
+ return;
+ msel = get_client_by_coord(event.x, event.y);
+
+ if (!msel)
+ return;
+
+ debug("mouse x:%d y:%d cx:%d cy:%d mask:%d\n", event.x, event.y, event.x - msel->x, event.y - msel->y, event.bstate);
+
+ vt_mouse(msel->term, event.x - msel->x, event.y - msel->y, event.bstate);
+
+ for (i = 0; i < arrlen(buttons); i++) {
+ if (event.bstate & buttons[i].mask)
+ buttons[i].action.cmd(buttons[i].action.args);
+ }
+
+ msel = nil;
+#endif /* CONFIG_MOUSE */
+}
+
+static void
+handle_statusbar(void) {
+ char *p;
+ int r;
+ switch (r = read(bar.fd, bar.text, sizeof bar.text - 1)) {
+ case -1:
+ strncpy(bar.text, strerror(errno), sizeof bar.text - 1);
+ bar.text[sizeof bar.text - 1] = '\0';
+ bar.fd = -1;
+ break;
+ case 0:
+ bar.fd = -1;
+ break;
+ default:
+ bar.text[r] = '\0';
+ p = bar.text + r - 1;
+ for (; p >= bar.text && *p == '\n'; *p-- = '\0');
+ for (; p >= bar.text && *p != '\n'; --p);
+ if (p >= bar.text)
+ memmove(bar.text, p + 1, strlen(p));
+ drawbar();
+ }
+}
+
+static void
+handle_editor(Client *c) {
+ if (!copyreg.data && (copyreg.data = malloc(screen.history)))
+ copyreg.size = screen.history;
+ copyreg.len = 0;
+ while (c->editor_fds[1] != -1 && copyreg.len < copyreg.size) {
+ ssize_t len = read(c->editor_fds[1], copyreg.data + copyreg.len, copyreg.size - copyreg.len);
+ if (len == -1) {
+ if (errno == EINTR)
+ continue;
+ break;
+ }
+ if (len == 0)
+ break;
+ copyreg.len += len;
+ if (copyreg.len == copyreg.size) {
+ copyreg.size *= 2;
+ if (!(copyreg.data = realloc(copyreg.data, copyreg.size))) {
+ copyreg.size = 0;
+ copyreg.len = 0;
+ }
+ }
+ }
+ c->editor_died = false;
+ c->editor_fds[1] = -1;
+ vt_destroy(c->editor);
+ c->editor = nil;
+ c->term = c->app;
+ vt_dirty(c->term);
+ draw_content(c);
+ wnoutrefresh(c->window);
+}
+
+static int
+open_or_create_fifo(const char *name, const char **name_created) {
+ struct stat info;
+ int fd;
+
+ do {
+ if ((fd = open(name, O_RDWR|O_NONBLOCK)) == -1) {
+ if (errno == ENOENT && !mkfifo(name, S_IRUSR|S_IWUSR)) {
+ *name_created = name;
+ continue;
+ }
+ fatal("%s\n", strerror(errno));
+ }
+ } while (fd == -1);
+
+ if (fstat(fd, &info) == -1)
+ fatal("%s\n", strerror(errno));
+ if (!S_ISFIFO(info.st_mode))
+ fatal("%s is not a named pipe\n", name);
+ return fd;
+}
+
+static
+void
+usage(void) {
+ cleanup();
+ eprint("usage: dvtm [-v] [-M] [-m mod] [-d delay] [-h lines] [-t title] "
+ "[-s status-fifo] [-c cmd-fifo] [cmd...]\n");
+ exit(EXIT_FAILURE);
+}
+
+static
+bool
+parse_args(int argc, char *argv[]) {
+ bool init = false;
+ const char *name = argv[0];
+
+ if (name && (name = strrchr(name, '/')))
+ dvtm_name = name + 1;
+ if (!getenv("ESCDELAY"))
+ set_escdelay(100);
+ for (int arg = 1; arg < argc; arg++) {
+ if (argv[arg][0] != '-') {
+ const char *args[] = { argv[arg], nil, nil };
+ if (!init) {
+ setup();
+ init = true;
+ }
+ create(args);
+ continue;
+ }
+ if (argv[arg][1] != 'v' && argv[arg][1] != 'M' && (arg + 1) >= argc)
+ usage();
+ switch (argv[arg][1]) {
+ case 'v':
+ puts("dvtm-"VERSION);
+ exit(EXIT_SUCCESS);
+ case 'M':
+ mouse_events_enabled = !mouse_events_enabled;
+ break;
+ case 'm': {
+ char *mod = argv[++arg];
+ if (mod[0] == '^' && mod[1])
+ *mod = CTRL(mod[1]);
+ for (uint b = 0; b < arrlen(bindings); b++)
+ if (bindings[b].keys[0] == MOD)
+ bindings[b].keys[0] = *mod;
+ break;
+ }
+ case 'd':
+ set_escdelay(atoi(argv[++arg]));
+ if (ESCDELAY < 50)
+ set_escdelay(50);
+ else if (ESCDELAY > 1000)
+ set_escdelay(1000);
+ break;
+ case 'h':
+ screen.history = atoi(argv[++arg]);
+ break;
+ case 't':
+ title = argv[++arg];
+ break;
+ case 's':
+ bar.fd = open_or_create_fifo(argv[++arg], &bar.file);
+ updatebarpos();
+ break;
+ case 'c': {
+ const char *fifo;
+ cmdfifo.fd = open_or_create_fifo(argv[++arg], &cmdfifo.file);
+ if (!(fifo = realpath(argv[arg], nil)))
+ fatal("%s\n", strerror(errno));
+ setenv("DVTM_CMD_FIFO", fifo, 1);
+ break;
+ }
+ default:
+ usage();
+ }
+ }
+ return init;
+}
+
+int
+main(int argc, char *argv[])
+{
+ KeyCombo keys;
+ uint key_index = 0;
+ memset(keys, 0, sizeof(keys));
+ sigset_t emptyset, blockset;
+
+ setenv("DVTM", VERSION, 1);
+ if (!parse_args(argc, argv)) {
+ setup();
+ startup(nil);
+ }
+
+ sigemptyset(&emptyset);
+ sigemptyset(&blockset);
+ sigaddset(&blockset, SIGWINCH);
+ sigaddset(&blockset, SIGCHLD);
+ sigprocmask(SIG_BLOCK, &blockset, nil);
+
+ while (running) {
+ int r, nfds = 0;
+ fd_set rd;
+
+ if (screen.need_resize) {
+ resize_screen();
+ screen.need_resize = false;
+ }
+
+ FD_ZERO(&rd);
+ FD_SET(STDIN_FILENO, &rd);
+
+ if (cmdfifo.fd != -1) {
+ FD_SET(cmdfifo.fd, &rd);
+ nfds = cmdfifo.fd;
+ }
+
+ if (bar.fd != -1) {
+ FD_SET(bar.fd, &rd);
+ nfds = MAX(nfds, bar.fd);
+ }
+
+ for (Client *c = clients; c;) {
+ if (c->editor && c->editor_died)
+ handle_editor(c);
+ if (!c->editor && c->died) {
+ Client *t = c->next;
+ destroy(c);
+ c = t;
+ continue;
+ }
+ int pty = c->editor ? vt_pty_get(c->editor) : vt_pty_get(c->app);
+ FD_SET(pty, &rd);
+ nfds = MAX(nfds, pty);
+ c = c->next;
+ }
+
+ doupdate();
+ r = pselect(nfds + 1, &rd, nil, nil, nil, &emptyset);
+
+ if (r < 0) {
+ if (errno == EINTR)
+ continue;
+ perror("select()");
+ exit(EXIT_FAILURE);
+ }
+
+ if (FD_ISSET(STDIN_FILENO, &rd)) {
+ /* NOTE: this is the input handling step */
+ int code = getch();
+ if (code >= 0) {
+ keys[key_index++] = code;
+ KeyBinding *binding = nil;
+
+ if (code == KEY_MOUSE) {
+ key_index = 0;
+ handle_mouse();
+ } else if ((binding = keybinding(keys, key_index))) {
+ uint key_length = MAX_KEYS;
+ while (key_length > 1 && !binding->keys[key_length-1])
+ key_length--;
+ if (key_index == key_length) {
+ binding->action.cmd(binding->action.args);
+ key_index = 0;
+ memset(keys, 0, sizeof(keys));
+ }
+ } else {
+ key_index = 0;
+ memset(keys, 0, sizeof(keys));
+ keypress(code);
+ }
+ }
+ if (r == 1) /* no data available on pty's */
+ continue;
+ }
+
+ if (cmdfifo.fd != -1 && FD_ISSET(cmdfifo.fd, &rd))
+ handle_cmdfifo();
+
+ if (bar.fd != -1 && FD_ISSET(bar.fd, &rd))
+ handle_statusbar();
+
+ for (Client *c = clients; c; c = c->next) {
+ if (FD_ISSET(vt_pty_get(c->term), &rd)) {
+ if (vt_process(c->term) < 0 && errno == EIO) {
+ if (c->editor)
+ c->editor_died = true;
+ else
+ c->died = true;
+ continue;
+ }
+ }
+
+ if (c != sel && is_content_visible(c)) {
+ draw_content(c);
+ wnoutrefresh(c->window);
+ }
+ }
+
+ if (is_content_visible(sel)) {
+ draw_content(sel);
+ curs_set(vt_cursor_visible(sel->term));
+ wnoutrefresh(sel->window);
+ }
+ }
+
+ cleanup();
+ return 0;
+}
diff --git a/sys/cmd/dvtm/events.c b/sys/cmd/dvtm/events.c
index 12b0518..c4f544b 100644
--- a/sys/cmd/dvtm/events.c
+++ b/sys/cmd/dvtm/events.c
@@ -1,133 +1,14 @@
-#include <u.h>
-#include <libn.h>
-#include <poll.h>
-#include <termios.h>
+#include "term.h"
-#include <vendor/unibilium.h>
+#include <poll.h>
-#define iota(x) 1 << (x)
#define bufcount(in) in->buf.c - in->buf.b
-/* symbols */
-enum
-{
- SymUnknown = -1,
- SymNone = 0,
-
- /* special names in c0 */
- SymBackspace, SymTab, SymEnter, SymEscape,
-
- /* special names in g0 */
- SymSpace, SymDel,
-
- /* special keys */
- SymUp, SymDown, SymLeft, SymRight, SymBegin, SymFind, SymInsert,
- SymDelete, SymSelect, SymPageup, SymPagedown, SymHome, SymEnd,
-
- /* special keys from terminfo */
- SymCancel, SymClear, SymClose, SymCommand, SymCopy, SymExit,
- SymHelp, SymMark, SymMessage, SymMove, SymOpen, SymOptions,
- SymPrint, SymRedo, SymReference, SymRefresh, SymReplace,
- SymRestart, SymResume, SymSave, SymSuspend, SymUndo,
-
- /* numeric keypad special keys */
- SymKp0, SymKp1, SymKp2, SymKp3, SymKp4, SymKp5, SymKp6, SymKp7, SymKp8,
- SymKp9, SymKpenter, SymKpplus, SymKpminus, SymKpmult, SymKpdiv, SymKpcomma,
- SymKpperiod, SymKpequals,
-
- /* et cetera ad nauseum */
- NumSyms
-};
-
-/* key type */
-enum
-{
- KeyUnicode,
- KeyFunc,
- KeySym,
- KeyMouse,
- KeyPosition,
- KeyModeReport,
- KeyDCS,
- KeyOSC,
- /* add other recognised types here */
-
- KeyUnknownCSI = -1
-};
-
-/* key events */
-enum KeyEvent
-{
- EvNil,
- EvKey,
- EvEOF,
- EvAgain,
- EvErr,
-};
-
-enum MouseEvent
-{
- MouseNil,
- MousePress,
- MouseDrag,
- MouseRelease,
-};
-
-enum
-{
- ModShift = iota(0),
- ModAlt = iota(1),
- ModCtrl = iota(2),
-};
-
-enum
-{
- FlagNoInterpret = iota(0),
- FlagConvertKP = iota(1),
- FlagRaw = iota(2),
- FlagUTF8 = iota(3),
- FlagNoTermIOS = iota(4),
- FlagSpaceSymbol = iota(5),
- FlagCtrlC = iota(6),
- FlagEintr = iota(7),
-};
-
-enum
-{
- HarmonizeSpace = iota(0),
- HarmonizeDelBS = iota(1),
-};
-
enum {
NodeKey,
NodeArr,
};
-typedef struct Key Key;
-typedef struct Node Node;
-typedef struct Input Input;
-
-struct Key
-{
- int type;
- int mods;
- uchar utf8[UTFmax+1];
- union {
- rune pt;
- int num;
- int sym;
- char mouse[4];
- } code;
-};
-
-struct KeyInfo
-{
- int type;
- int sym;
- int modmask;
- int modset;
-};
-
struct Node
{
int type;
@@ -136,7 +17,7 @@ struct Node
struct KeyNode
{
struct Node;
- struct KeyInfo key;
+ struct KeyInfo key;
};
struct ArrNode
@@ -146,37 +27,6 @@ struct ArrNode
Node *arr[];
};
-struct Input
-{
- int fd;
- int flags;
- int hflag;
- int wait; /* in ms */
-
- struct termios oldterm;
-
- /* buffer */
- struct {
- long off;
- uchar *b, *c, *e, bytes[256];
- } buf;
-
- /* modifiers */
- char closed : 1;
- char started : 1;
- char hasold : 1;
-
- /* key data */
- Node *keys;
- struct KeyInfo c0[32];
-
- char **keynm;
- int nkeynm;
-
- int nsavedcsi;
- char *savedcsi;
-};
-
// -----------------------------------------------------------------------
// loads data into trie
@@ -589,11 +439,11 @@ inline
void
getpos(Key *key, int *row, int *col)
{
- if(col)
- *col = (uchar)key->code.mouse[1] | ((uchar)key->code.mouse[3] & 0x0f) << 8;
+ if (col)
+ *col = ((uchar)key->code.mouse[1] | ((uchar)key->code.mouse[3] & 0x0f) << 8) - 1;
- if(row)
- *row = (uchar)key->code.mouse[2] | ((uchar)key->code.mouse[3] & 0x70) << 4;
+ if (row)
+ *row = ((uchar)key->code.mouse[2] | ((uchar)key->code.mouse[3] & 0x70) << 4) - 1;
}
// -----------------------------------------------------------------------
@@ -621,8 +471,8 @@ do_csi_full(Input *in, Key *key, int cmd, int narg, long *arg)
key->type = csiss3[cmd - 0x40].type;
key->code.sym = csiss3[cmd - 0x40].sym;
- key->mods &= ~(csiss3[cmd - 0x40].modmask);
- key->mods |= csiss3[cmd - 0x40].modset;
+ key->mods &= ~(csiss3[cmd - 0x40].modmask);
+ key->mods |= csiss3[cmd - 0x40].modset;
if(key->code.sym == SymUnknown)
return EvNil;
@@ -1577,7 +1427,7 @@ demandkey(Input *in, Key *key)
}
enum KeyEvent
-readkey(Input *in)
+isreadablekey(Input *in)
{
int n;
if (in->fd == -1) {
@@ -1628,7 +1478,7 @@ waitkey(Input *in, Key *key)
case EvKey: case EvEOF: case EvErr:
return ev;
case EvNil:
- ev = readkey(in);
+ ev = isreadablekey(in);
if (ev == EvErr)
return ev;
break;
@@ -1648,7 +1498,7 @@ waitkey(Input *in, Key *key)
}
if (p.revents & (POLLIN|POLLHUP|POLLERR))
- ev = readkey(in);
+ ev = isreadablekey(in);
else
ev = EvNil;
@@ -1665,10 +1515,10 @@ waitkey(Input *in, Key *key)
enum KeyEvent
decodemouse(Input *in, Key *key, enum MouseEvent *ev, int *button, int *row, int *col)
{
- if (key->type != KeyMouse)
+ if(key->type != KeyMouse)
return EvNil;
- if (button)
+ if(button)
*button = 0;
getpos(key, row, col);
@@ -1676,7 +1526,7 @@ decodemouse(Input *in, Key *key, enum MouseEvent *ev, int *button, int *row, int
if(!ev)
return EvKey;
- int btn = 0;
+ int btn = 0;
int code = key->code.mouse[0];
int drag = code & 0x20;
code &= ~0x3c;
diff --git a/sys/cmd/dvtm/rules.mk b/sys/cmd/dvtm/rules.mk
index 3ef4225..5b681af 100644
--- a/sys/cmd/dvtm/rules.mk
+++ b/sys/cmd/dvtm/rules.mk
@@ -2,9 +2,8 @@ include share/push.mk
# Local sources
SRCS_$(d) := \
-$(d)/events.c \
-# $(d)/driver.c \
-# $(d)/hook.c \
+$(d)/term.c \
+# $(d)/events.c \
# $(d)/vt.c \
# $(d)/dvtm.c
diff --git a/sys/cmd/dvtm/term.c b/sys/cmd/dvtm/term.c
index e69de29..cda2bbe 100644
--- a/sys/cmd/dvtm/term.c
+++ b/sys/cmd/dvtm/term.c
@@ -0,0 +1,395 @@
+#include "term.h"
+
+struct ExtraInfo
+{
+ char *enteralt;
+ char *exitalt;
+
+ char *entermouse;
+ char *exitmouse;
+};
+
+static
+struct ExtraInfo vt200 =
+{
+ .enteralt = "\e[?1049h",
+ .exitalt = "\e[?1049l",
+
+ .entermouse = "\e[?1049h\e[?1006l",
+ .exitmouse = "\e[?1002l\e[?1006l",
+};
+
+// -----------------------------------------------------------------------
+// database lookup
+
+static
+char*
+tryinfostr(Term *t, enum unibi_string s)
+{
+ char *val = (char*)unibi_get_str(t->info, s);
+ /* TODO: provide fallbacks */
+ return val;
+}
+
+static
+char*
+guessinfostr(Term *t, enum unibi_string s, char *guess)
+{
+ char *val = (char*)unibi_get_str(t->info, s);
+ if (!val)
+ return guess;
+ return val;
+}
+
+static
+char*
+getinfostr(Term *t, enum unibi_string s)
+{
+ char *val = tryinfostr(t, s);
+ if (!val)
+ panicf("required term info string '%s' missing", unibi_name_str(s));
+
+ return val;
+}
+
+static
+char *
+tryextrastr(Term *t, char *name)
+{
+ const char *nm;
+ size_t max = unibi_count_ext_str(t->info);
+ for (size_t i = 0; i < max; i++) {
+ nm = unibi_get_ext_str_name(t->info, i);
+ if (nm && !strcmp(nm, name)) {
+ return (char *)nm;
+ }
+ }
+ return nil;
+}
+
+static
+char *
+guessextrastr(Term *t, char *name, char *guess)
+{
+ char *s;
+ if ((s = tryextrastr(t, name)))
+ return s;
+
+ return guess;
+}
+
+static
+int
+tryextrabool(Term *t, char *name)
+{
+ const char *nm;
+ size_t max = unibi_count_ext_bool(t->info);
+ for (size_t i = 0; i < max; i++) {
+ nm = unibi_get_ext_bool_name(t->info, i);
+ if (nm && !strcmp(nm, name)) {
+ return (int)i;
+ }
+ }
+ return -1;
+}
+
+/* formats escape strings and writes to output */
+static void tfmt(Term *t, char *esc, int n, ...);
+
+// -----------------------------------------------------------------------
+// exported pen methods
+
+// -----------------------------------------------------------------------
+// exported term methods
+
+static
+char *
+ttmpbuf(Term *t, int len)
+{
+ if (t->tmp.len >= len)
+ return t->tmp.b;
+
+ /* TODO: error handling */
+ return (t->tmp.b = realloc(t->tmp.b, len));
+}
+
+void
+tinit(Term *t)
+{
+ t->name = getenv("TERM");
+ t->info = unibi_from_term(t->name);
+ if (!t->info)
+ panicf("could not identify terminal");
+
+ t->mode.mouse = 0;
+ t->mode.cursorvis = 1;
+ t->mode.altscreen = 0;
+
+ t->cap.colors = unibi_get_num(t->info, unibi_max_colors);
+ t->cap.bce = unibi_get_bool(t->info, unibi_back_color_erase);
+
+ /* TODO */
+ t->input = nil;
+ t->root = nil;
+ t->pen = (Pen){0};
+
+ /* fill in buffers */
+ t->buf.c = t->buf.b;
+ t->tmp.b = nil;
+ t->tmp.len = 0;
+
+ /* get all term info format strings */
+ t->esc.cup = getinfostr(t, unibi_cursor_address);
+ t->esc.vpa = tryinfostr(t, unibi_row_address);
+ t->esc.hpa = tryinfostr(t, unibi_column_address);
+ t->esc.cuu = getinfostr(t, unibi_parm_up_cursor);
+ t->esc.cuu1 = tryinfostr(t, unibi_cursor_up);
+ t->esc.cud = getinfostr(t, unibi_parm_down_cursor);
+ t->esc.cud1 = tryinfostr(t, unibi_cursor_down);
+ t->esc.cuf = getinfostr(t, unibi_parm_right_cursor);
+ t->esc.cuf1 = tryinfostr(t, unibi_cursor_right);
+ t->esc.cub = getinfostr(t, unibi_parm_left_cursor);
+ t->esc.cub1 = tryinfostr(t, unibi_cursor_left);
+ t->esc.ich = getinfostr(t, unibi_parm_ich);
+ t->esc.ich1 = tryinfostr(t, unibi_insert_character);
+ t->esc.dch = getinfostr(t, unibi_parm_dch);
+ t->esc.dch1 = tryinfostr(t, unibi_delete_character);
+ t->esc.il = getinfostr(t, unibi_parm_insert_line);
+ t->esc.il1 = tryinfostr(t, unibi_insert_line);
+ t->esc.dl = getinfostr(t, unibi_parm_delete_line);
+ t->esc.dl1 = tryinfostr(t, unibi_delete_line);
+ t->esc.ech = getinfostr(t, unibi_erase_chars);
+ t->esc.ed2 = getinfostr(t, unibi_clear_screen);
+ t->esc.stbm = getinfostr(t, unibi_change_scroll_region);
+ t->esc.sgr = getinfostr(t, unibi_set_attributes);
+ t->esc.sgr0 = getinfostr(t, unibi_exit_attribute_mode);
+ t->esc.sgr_i0 = tryinfostr(t, unibi_exit_italics_mode);
+ t->esc.sgr_i1 = tryinfostr(t, unibi_enter_italics_mode);
+ t->esc.sgr_fg = getinfostr(t, unibi_set_a_foreground);
+ t->esc.sgr_bg = getinfostr(t, unibi_set_a_background);
+ t->esc.sm_csr = getinfostr(t, unibi_cursor_normal);
+ t->esc.rm_csr = getinfostr(t, unibi_cursor_invisible);
+
+ /* extensions to terminfo */
+ t->esc.ext.rgbf = guessextrastr(t, "setrgbf", "\x1b[38;2;%p1%d;%p2%d;%p3%dm");
+ t->esc.ext.rgbb = guessextrastr(t, "setrgbb", "\x1b[48;2;%p1%d;%p2%d;%p3%dm");
+
+ /* acs characters */
+ t->acs.vline = tryinfostr(t, unibi_acs_vline);
+ t->acs.hline = tryinfostr(t, unibi_acs_hline);
+ t->acs.plus = tryinfostr(t, unibi_acs_plus);
+ t->acs.ltee = tryinfostr(t, unibi_acs_ltee);
+ t->acs.rtee = tryinfostr(t, unibi_acs_rtee);
+ t->acs.ttee = tryinfostr(t, unibi_acs_ttee);
+ t->acs.btee = tryinfostr(t, unibi_acs_btee);
+ t->acs.ulcorner = tryinfostr(t, unibi_acs_ulcorner);
+ t->acs.urcorner = tryinfostr(t, unibi_acs_urcorner);
+ t->acs.llcorner = tryinfostr(t, unibi_acs_llcorner);
+ t->acs.lrcorner = tryinfostr(t, unibi_acs_lrcorner);
+}
+
+void
+tfini(Term *t)
+{
+ if (t->mode.mouse)
+ twrite(t, 0, vt200.exitmouse);
+ if (!t->mode.cursorvis)
+ tfmt(t, t->esc.rm_csr, 0);
+ if (t->mode.altscreen)
+ twrite(t, 0, vt200.exitalt);
+
+ tfmt(t, t->esc.sgr0, 0);
+}
+
+void
+tflush(Term *t)
+{
+ if (t->fd != -1)
+ write(t->fd, t->buf.b, t->buf.c - t->buf.b);
+
+ t->buf.c = t->buf.b;
+}
+
+void
+twrite(Term *t, long len, char *s)
+{
+ int n;
+ if (!len)
+ len = strlen(s);
+
+ while (len > 0) {
+ n = MIN(len, arrend(t->buf.b) - t->buf.c);
+ memcpy(t->buf.c, s, n);
+ t->buf.c += n;
+ len -= n;
+ tflush(t);
+ }
+}
+
+void
+tsetpen(Term *t, Pen new)
+{
+ int c;
+ ushort ic, in;
+ Pen cur = t->pen;
+ if (!memcmp(&new, &cur, sizeof(new)))
+ return;
+
+ /* attributes */
+ tfmt(t, t->esc.sgr, 9,
+ 0, /* standout */
+ new.state & PenUnderline,
+ new.state & PenReverse,
+ new.state & PenBlink,
+ new.state & PenDim,
+ new.state & PenBold,
+ new.state & PenInvis,
+ 0, /* protect */
+ 0); /* alt */
+
+ ic = cur.state & PenItalic;
+ in = new.state & PenItalic;
+ if (ic & ~in)
+ tfmt(t, t->esc.sgr_i0, 0);
+ else if (~ic & in)
+ tfmt(t, t->esc.sgr_i1, 0);
+
+ /* fg/bg color */
+ /* TODO: add a check for if the terminal supports true color */
+ /* TODO: add a check for negative indices */
+ if (new.state & PenTrueClr) {
+ tfmt(t, t->esc.ext.rgbf, 3, new.rgb.fg.r, new.rgb.fg.g, new.rgb.fg.b);
+ tfmt(t, t->esc.ext.rgbb, 3, new.rgb.bg.r, new.rgb.bg.g, new.rgb.bg.b);
+ } else {
+ tfmt(t, t->esc.sgr_fg, 1, new.col.fg);
+ tfmt(t, t->esc.sgr_bg, 1, new.col.bg);
+ }
+
+ t->pen = new;
+}
+
+static
+void
+tfmt(Term *t, char *esc, int n, ...)
+{
+ int i;
+ long len;
+ va_list args;
+ unibi_var_t param[9];
+ char buf[64], *c = buf;
+
+ if (!esc)
+ panicf("no terminfo escape string given");
+
+ va_start(args, n);
+ for (i = 0; i < arrlen(param) && i < n; i++)
+ param[i] = unibi_var_from_num(va_arg(args, int));
+ va_end(args);
+
+ len = unibi_run(esc, param, c, sizeof(buf));
+ if (len < arrlen(buf)) {
+ c = ttmpbuf(t, len);
+ unibi_run(esc, param, c, len);
+ }
+
+ twrite(t, len, c);
+}
+
+/* absolute move */
+int
+tgoto(Term *t, int row, int col)
+{
+ if (row != -1 && col != -1)
+ tfmt(t, t->esc.cup, 2, row, col);
+ else if (row != -1) {
+ if (!t->esc.vpa)
+ return 0;
+ tfmt(t, t->esc.vpa, 1, row);
+ } else if (col != -1) {
+ if (col == 0) {
+ twrite(t, 1, "\r");
+ return 1;
+ }
+ if (t->esc.hpa)
+ tfmt(t, t->esc.hpa, 1, col);
+ else if (t->esc.cuf) {
+ twrite(t, 1, "\r");
+ tfmt(t, t->esc.cuf, 1, col);
+ } else
+ return 0;
+ } else
+ return 0; /* unreachable */
+
+ return 1;
+}
+
+/* relative move */
+void
+tjump(Term *t, int down, int right)
+{
+ if (down == 1 && t->esc.cud1)
+ tfmt(t, t->esc.cud1, 0);
+ else if (down == -1 && t->esc.cuu1)
+ tfmt(t, t->esc.cuu1, 0);
+ else if (down > 0)
+ tfmt(t, t->esc.cud, 1, down);
+ else if (down < 0)
+ tfmt(t, t->esc.cuu, 1, -down);
+
+ if (right == 1 && t->esc.cuf1)
+ tfmt(t, t->esc.cuf1, 0);
+ else if (right == -1 && t->esc.cub1)
+ tfmt (t, t->esc.cub1, 0);
+ else if (right > 0)
+ tfmt(t, t->esc.cuf, 1, right);
+ else if( right < 0)
+ tfmt(t, t->esc.cub, 1, -right);
+}
+
+void
+tdel(Term *t, int num)
+{
+ char *c, buf[64];
+ if (num < 1)
+ return;
+
+ /* TODO: allow for not moving */
+ if (t->cap.bce) {
+ tfmt(t, t->esc.ech, 1, num);
+ tjump(t, 0, num);
+ } else {
+ c = buf;
+ memset(c, ' ', arrlen(buf));
+ while(num > 64) {
+ twrite(t, arrlen(buf), c);
+ num -= arrlen(buf);
+ }
+ twrite(t, num, c);
+ }
+}
+
+void
+tclear(Term *t)
+{
+ tfmt(t, t->esc.ed2, 0);
+}
+
+// -----------------------------------------------------------------------
+// testing entry
+
+int
+main()
+{
+ Term t;
+ tinit(&t);
+
+ printf("%s: %s\n", unibi_short_name_str(unibi_set_a_foreground), t.esc.sgr_fg);
+ printf("%s: %s\n", unibi_short_name_str(unibi_set_a_background), t.esc.sgr_bg);
+ tfmt(&t, t.esc.ext.rgbf, 3, 10, 10, 100);
+ tfmt(&t, t.esc.ext.rgbb, 3, 100, 0, 100);
+ twrite(&t, 0, "hello world\n");
+ twrite(&t, 0, "hello world\n");
+ twrite(&t, 0, t.acs.hline);
+
+ tfini(&t);
+}
diff --git a/sys/cmd/dvtm/term.h b/sys/cmd/dvtm/term.h
index 3102458..53c0850 100644
--- a/sys/cmd/dvtm/term.h
+++ b/sys/cmd/dvtm/term.h
@@ -3,13 +3,179 @@
#include <u.h>
#include <libn.h>
-#define iota(x) 1 << (x)
+#include <termios.h>
+#include <unibilium.h>
+#define iota(x) 1 << (x)
+/*
+ * obtained from:
+ * https://invisible-island.net/ncurses/man/curs_add_wch.3x.html
+ */
+#define ACSRUNES \
+ /* name utf8 ascii acsc*/ \
+ ACSRUNE("block", 0x25ae, '#', '0') \
+ ACSRUNE("board", 0x2592, '#', 'h') \
+ ACSRUNE("btee", 0x2534, '+', 'v') \
+ ACSRUNE("bullet", 0x00b7, 'o', '~') \
+ ACSRUNE("ckboard", 0x2592, ':', 'a') \
+ ACSRUNE("darrow", 0x2193, 'v', '.') \
+ ACSRUNE("degree", 0x00b0, '\'','f') \
+ ACSRUNE("diamond", 0x25c6, '+', '`') \
+ ACSRUNE("gequal", 0x2265, '>', '>') \
+ ACSRUNE("hline", 0x2500, '-', 'q') \
+ ACSRUNE("antern", 0x2603, '#', 'i') \
+ ACSRUNE("larrow", 0x2190, '<', ',') \
+ ACSRUNE("lequal", 0x2264, '<', 'y') \
+ ACSRUNE("llcorner", 0x2514, '+', 'm') \
+ ACSRUNE("lrcorner", 0x2518, '+', 'j') \
+ ACSRUNE("ltee", 0x2524, '+', 't') \
+ ACSRUNE("nequal", 0x2260, '!', '|') \
+ ACSRUNE("pi", 0x03c0, '*', '{') \
+ ACSRUNE("plminus", 0x00b1, '#', 'g') \
+ ACSRUNE("plus", 0x253c, '+', 'n') \
+ ACSRUNE("rarrow", 0x2192, '>', '+') \
+ ACSRUNE("rtee", 0x251c, '+', 'u') \
+ ACSRUNE("s1", 0x23ba, '-', 'o') \
+ ACSRUNE("s3", 0x23bb, '-', 'p') \
+ ACSRUNE("s7", 0x23bc, '-', 'r') \
+ ACSRUNE("s9", 0x23bd, '_', 's') \
+ ACSRUNE("sterling", 0x00a3, 'f', '}') \
+ ACSRUNE("ttee", 0x252c, '+', 'w') \
+ ACSRUNE("uarrow", 0x2191, '^', '-') \
+ ACSRUNE("ulcorner", 0x250c, '+', 'l') \
+ ACSRUNE("urcorner", 0x2510, '+', 'k') \
+ ACSRUNE("vline", 0x2502, '|', 'x') \
+ /* thick versions */ \
+ ACSRUNE("t_btee", 0x253b, '+', 'V') \
+ ACSRUNE("t_hline", 0x2501, '-', 'Q') \
+ ACSRUNE("t_llcorner", 0x2517, '+', 'M') \
+ ACSRUNE("t_lrcorner", 0x251b, '+', 'J') \
+ ACSRUNE("t_ltee", 0x252b, '+', 'T') \
+ ACSRUNE("t_plus", 0x254b, '+', 'N') \
+ ACSRUNE("t_rtee", 0x2523, '+', 'U') \
+ ACSRUNE("t_ttee", 0x2533, '+', 'W') \
+ ACSRUNE("t_ulcorner", 0x250f, '+', 'L') \
+ ACSRUNE("t_urcorner", 0x2513, '+', 'K') \
+ ACSRUNE("t_vline", 0x2503, '|', 'X') \
+ /* double version */ \
+ ACSRUNE("d_btee", 0x2569, '+', 'H') \
+ ACSRUNE("d_hline", 0x2550, '-', 'R') \
+ ACSRUNE("d_llcorner", 0x255a, '+', 'D') \
+ ACSRUNE("d_lrcorner", 0x255d, '+', 'A') \
+ ACSRUNE("d_ltee", 0x2560, '+', 'F') \
+ ACSRUNE("d_plus", 0x256c, '+', 'E') \
+ ACSRUNE("d_rtee", 0x2563, '+', 'G') \
+ ACSRUNE("d_ttee", 0x2566, '+', 'I') \
+ ACSRUNE("d_ulcorner", 0x2554, '+', 'C') \
+ ACSRUNE("d_urcorner", 0x2557, '+', 'B') \
+ ACSRUNE("d_vline", 0x2551, '|', 'Y')
+
+/* enums */
+
+/* key symbols */
+enum
+{
+ SymUnknown = -1,
+ SymNone = 0,
+
+ /* special names in c0 */
+ SymBackspace, SymTab, SymEnter, SymEscape,
+
+ /* special names in g0 */
+ SymSpace, SymDel,
+
+ /* special keys */
+ SymUp, SymDown, SymLeft, SymRight, SymBegin, SymFind, SymInsert,
+ SymDelete, SymSelect, SymPageup, SymPagedown, SymHome, SymEnd,
+
+ /* special keys from terminfo */
+ SymCancel, SymClear, SymClose, SymCommand, SymCopy, SymExit,
+ SymHelp, SymMark, SymMessage, SymMove, SymOpen, SymOptions,
+ SymPrint, SymRedo, SymReference, SymRefresh, SymReplace,
+ SymRestart, SymResume, SymSave, SymSuspend, SymUndo,
+
+ /* numeric keypad special keys */
+ SymKp0, SymKp1, SymKp2, SymKp3, SymKp4, SymKp5, SymKp6, SymKp7, SymKp8,
+ SymKp9, SymKpenter, SymKpplus, SymKpminus, SymKpmult, SymKpdiv, SymKpcomma,
+ SymKpperiod, SymKpequals,
+
+ /* et cetera ad nauseum */
+ NumSyms
+};
+
+/* key type */
+enum
+{
+ KeyUnicode,
+ KeyFunc,
+ KeySym,
+ KeyMouse,
+ KeyPosition,
+ KeyModeReport,
+ KeyDCS,
+ KeyOSC,
+ /* add other recognised types here */
+
+ KeyUnknownCSI = -1
+};
+
+/* key events */
+enum KeyEvent
+{
+ EvNil,
+ EvKey,
+ EvEOF,
+ EvAgain,
+ EvErr,
+};
+
+enum MouseEvent
+{
+ MouseNil,
+ MousePress,
+ MouseDrag,
+ MouseRelease,
+};
+
+enum
+{
+ ModShift = iota(0),
+ ModAlt = iota(1),
+ ModCtrl = iota(2),
+};
+
+enum
+{
+ FlagNoInterpret = iota(0),
+ FlagConvertKP = iota(1),
+ FlagRaw = iota(2),
+ FlagUTF8 = iota(3),
+ FlagNoTermIOS = iota(4),
+ FlagSpaceSymbol = iota(5),
+ FlagCtrlC = iota(6),
+ FlagEintr = iota(7),
+};
+
+enum
+{
+ HarmonizeSpace = iota(0),
+ HarmonizeDelBS = iota(1),
+};
+
+/* types */
typedef struct RGB8 RGB8;
typedef struct Pen Pen;
+
typedef struct Cell Cell;
typedef struct Row Row;
typedef struct Buffer Buffer;
+typedef struct Window Window;
+
+typedef struct Node Node;
+typedef struct Key Key;
+typedef struct Input Input;
+
+typedef struct Term Term;
struct RGB8
{
@@ -28,13 +194,12 @@ enum
PenUnderline = iota(6),
PenBlink = iota(7),
/* ... */
- PenTC = iota(15),
+ PenTrueClr = iota(15),
};
struct Pen
{
ushort state;
- ushort color;
union {
/* 256 color (legacy) */
struct {
@@ -47,6 +212,7 @@ struct Pen
};
};
+/* outputs */
struct Cell {
rune r;
Pen pen;
@@ -104,8 +270,8 @@ struct Row {
*/
struct Buffer {
- Row *lines; /* array of Row pointers of size 'rows' */
- Row *crow; /* row on which the cursor currently resides */
+ Row *row; /* array of Row pointers of size 'rows' */
+ Row *crow; /* row on which the cursor currently resides */
bool *tabs; /* a boolean flag for each column whether it is a tab */
struct {
Row *buf; /* a ring buffer holding the scroll back content */
@@ -116,12 +282,161 @@ struct Buffer {
int above; /* number of lines above current viewport */
int below; /* number of lines below current viewport */
} scroll;
- int rows, cols; /* current dimension of buffer */
+ Pen pen, spen;
+ int nrow, ncol; /* current dimension of buffer */
int maxcols; /* allocated cells (maximal cols over time) */
+ int srow, scol; /* saved cursor row/column (zero based) */
+};
+
+/* input */
+struct Key
+{
+ int type;
+ int mods;
+ uchar utf8[UTFmax+1];
+ union {
+ rune pt;
+ int num;
+ int sym;
+ char mouse[4];
+ } code;
+};
+
+struct KeyInfo
+{
+ int type;
+ int sym;
+ int modmask;
+ int modset;
+};
+
+/* make opaque? */
+struct Input
+{
+ int fd;
+ int flags;
+ int hflag;
+ int wait; /* in ms */
+
+ struct termios oldterm;
+
+ /* buffer */
+ struct {
+ long off;
+ uchar *b, *c, *e, bytes[256];
+ } buf;
+
+ /* modifiers */
+ char closed : 1;
+ char started : 1;
+ char hasold : 1;
+
+ /* key data */
+ Node *keys;
+ struct KeyInfo c0[32];
+
+ char **keynm;
+ int nkeynm;
+
+ int nsavedcsi;
+ char *savedcsi;
+};
+
+struct Term
+{
+ /* meta data */
+ char *name;
+ unibi_term *info;
+ struct {
+ uint altscreen : 1;
+ uint cursorvis : 1;
+ uint mouse : 1;
+ } mode;
+ struct {
+ uint bce : 1;
+ int colors;
+ } cap;
+
+ /* input capture */
+ Input *input;
+
+ /* output display */
+ Window *root;
+ Pen pen;
+
+ /* output raw text */
+ int fd;
+ struct {
+ char *c, b[512];
+ } buf;
+
+ struct {
+ int len;
+ char *b;
+ } tmp;
+
+ /* info */
+ struct {
+ /* Positioning */
+ char *cup; // cursor_address
+ char *vpa; // row_address == vertical position absolute
+ char *hpa; // column_address = horizontal position absolute
+
+ /* Moving */
+ char *cuu; char *cuu1; // Cursor Up
+ char *cud; char *cud1; // Cursor Down
+ char *cuf; char *cuf1; // Cursor Forward == Right
+ char *cub; char *cub1; // Cursor Backward == Left
+
+ /* Editing */
+ char *ich; char *ich1; // Insert Character
+ char *dch; char *dch1; // Delete Character
+ char *il; char *il1; // Insert Line
+ char *dl; char *dl1; // Delete Line
+ char *ech; // Erase Character
+ char *ed2; // Erase Data 2 == Clear screen
+ char *stbm; // Set Top/Bottom Margins
+
+ /* formatting */
+ char *sgr; // Select Graphic Rendition
+ char *sgr0; // Exit Attribute Mode
+ char *sgr_i0, *sgr_i1; // SGR italic off/on
+ char *sgr_fg; // SGR foreground colour
+ char *sgr_bg; // SGR background colour
+
+ /* Mode setting/clearing */
+ char *sm_csr; char *rm_csr; // Set/reset mode: Cursor visible
+
+ /* augmentations to terminfo */
+ struct {
+ char *rgbf; // rgb foreground
+ char *rgbb; // rgb background
+ char *smxx; // strikethrough
+ char *smulx; // curly underline
+ } ext;
+ } esc;
+
+ /* basic shapes */
+ struct {
+ rune block;
+ rune board;
+ rune hline;
+ rune vline;
+ rune plus;
+ rune ltee;
+ rune rtee;
+ rune ttee;
+ rune btee;
+ rune ulcorner;
+ rune urcorner;
+ rune llcorner;
+ rune lrcorner;
+ } acs;
};
/* exported functions */
+/* buffer.c */
void zero(Row *row, int start, int len);
void roll(Row *start, Row *end, int count);
@@ -130,8 +445,37 @@ void bfree(Buffer *b);
void bscroll(Buffer *b, int s);
void bresize(Buffer *b, int rows, int cols);
bool binit(Buffer *b, int rows, int cols, int size);
+void brender(Buffer *b, Term *t);
void bboundary(Buffer *b, Row **bs, Row **be, Row **as, Row **ae) ;
Row *browfirst(Buffer *b);
Row *browlast(Buffer *b);
Row *brownext(Buffer *b, Row *row);
Row *bprevrow(Buffer *b, Row *row);
+
+/* input.c */
+Input *makeinput(int fd, int flags, unibi_term *info);
+void freeinput(Input *in);
+int startrecord(Input *in);
+int stoprecord(Input *in);
+char *keyname(Input *in, int sym);
+
+enum KeyEvent waitkey(Input *in, Key *key); /* block until next keypress */
+enum KeyEvent getkey(Input *in, Key *key); /* grab key if we can */
+enum KeyEvent demandkey(Input *in, Key *key); /* grab now and interpret as best we can */
+enum KeyEvent isreadablekey(Input *in); /* reads from input and reports the event w/o advance */
+
+/* unpack key event into useful data */
+enum KeyEvent decodemouse(Input *in, Key *key, enum MouseEvent *ev, int *button, int *row, int *col);
+enum KeyEvent decodepos(Input *in, Key *key, int *row, int *col);
+enum KeyEvent decodemode(Input *in, Key *key, int *init, int *mode, int *val);
+
+/* term.c */
+void tinit(Term *t);
+void tfini(Term *t);
+void tflush(Term *t);
+void twrite(Term *t, long len, char *s);
+int tgoto(Term *t, int row, int col);
+void tjump(Term *t, int down, int right);
+void tdel(Term *t, int num);
+void tsetpen(Term *t, Pen pen);
+void tclear(Term *t);
diff --git a/sys/cmd/dvtm/vt.c b/sys/cmd/dvtm/vt.c
index efaa980..4e1f427 100644
--- a/sys/cmd/dvtm/vt.c
+++ b/sys/cmd/dvtm/vt.c
@@ -9,7 +9,7 @@
#include <sys/ioctl.h>
#include <sys/types.h>
-#include "buffer.h"
+#include "term.h"
#if defined(__linux__) || defined(__CYGWIN__)
# include <pty.h>
@@ -98,7 +98,7 @@ clinedown(Vt *t)
bscroll(b, 1);
zero(b->crow, 0, b->cols);
}
-
+
static
void
csave(Vt *t)
diff --git a/sys/cmd/dvtm/vt.c.old b/sys/cmd/dvtm/vt.c.old
new file mode 100644
index 0000000..d858472
--- /dev/null
+++ b/sys/cmd/dvtm/vt.c.old
@@ -0,0 +1,2074 @@
+/* See license for details */
+#include <u.h>
+#include <libn.h>
+
+#include <langinfo.h>
+#include <signal.h>
+#include <termios.h>
+
+#include <sys/ioctl.h>
+#include <sys/types.h>
+
+#if defined(__linux__) || defined(__CYGWIN__)
+# include <pty.h>
+#elif defined(__FreeBSD__) || defined(__DragonFly__)
+# include <libutil.h>
+#elif defined(__OpenBSD__) || defined(__NetBSD__) || defined(__APPLE__)
+# include <util.h>
+#endif
+
+#include "vt.h"
+
+#ifndef NCURSES_ATTR_SHIFT
+# define NCURSES_ATTR_SHIFT 8
+#endif
+
+#ifndef NCURSES_ACS
+# ifdef PDCURSES
+# define NCURSES_ACS(c) (acs_map[(uchar)(c)])
+# else /* BSD curses */
+# define NCURSES_ACS(c) (_acs_map[(uchar)(c)])
+# endif
+#endif
+
+#ifdef NCURSES_VERSION
+
+#ifndef NCURSES_EXT_COLORS
+# define NCURSES_EXT_COLORS 0
+#endif
+
+#if !NCURSES_EXT_COLORS
+# define MAX_COLOR_PAIRS MIN(COLOR_PAIRS, 256)
+#endif
+
+#endif
+
+#ifndef MAX_COLOR_PAIRS
+# define MAX_COLOR_PAIRS COLOR_PAIRS
+#endif
+
+#if defined _AIX && defined CTRL
+# undef CTRL
+#endif
+#ifndef CTRL
+# define CTRL(k) ((k) & 0x1F)
+#endif
+
+#define IS_CONTROL(ch) !((ch) & 0xffffff60UL)
+
+static bool is_utf8, has_default_colors;
+static int color_pairs_reserved, color_pairs_max, color_pair_current;
+static int *color2palette, default_fg, default_bg;
+static char vt_term[32];
+
+typedef struct {
+ wchar_t text;
+ attr_t attr;
+ int fg;
+ int bg;
+} Cell;
+
+typedef struct {
+ Cell *cells;
+ uint dirty:1;
+} Row;
+
+/* Buffer holding the current terminal window content (as an array) as well
+ * as the scroll back buffer content (as a circular/ring buffer).
+ *
+ * If new content is added to terminal the view port slides down and the
+ * previously top most line is moved into the scroll back buffer at postion
+ * scroll_index. This index will eventually wrap around and thus overwrite
+ * the oldest lines.
+ *
+ * In the scenerio below a scroll up has been performed. That is 'scroll_above'
+ * lines still lie above the current view port. Further scrolling up will show
+ * them. Similarly 'scroll_below' is the amount of lines below the current
+ * viewport.
+ *
+ * The function buffer_boundary sets the row pointers to the start/end range
+ * of the section delimiting the region before/after the viewport. The functions
+ * buffer_row_{first,last} return the first/last logical row. And
+ * buffer_row_{next,prev} allows to iterate over the logical lines in either
+ * direction.
+ *
+ * scroll back buffer
+ *
+ * scroll_buf->+----------------+-----+
+ * | | | ^ \
+ * | before | | | |
+ * current terminal content | viewport | | | |
+ * | | | |
+ * +----------------+-----+\ | | | s > scroll_above
+ * ^ | | i | \ | | i | c |
+ * | | | n | \ | | n | r |
+ * | | v | \ | | v | o |
+ * r | | i | \ | | i | l /
+ * o | viewport | s | >|<- scroll_index | s | l \
+ * w | | i | / | | i | |
+ * s | | b | / | after | b | s > scroll_below
+ * | | l | / | viewport | l | i |
+ * v | | e | / | | e | z /
+ * +----------------+-----+/ | unused | | e
+ * <- maxcols -> | scroll back | |
+ * <- cols -> | buffer | | |
+ * | | | |
+ * | | | v
+ * roll_buf + scroll_size->+----------------+-----+
+ * <- maxcols ->
+ * <- cols ->
+ */
+typedef struct {
+ Row *lines; /* array of Row pointers of size 'rows' */
+ Row *curs_row; /* row on which the cursor currently resides */
+ Row *scroll_buf; /* a ring buffer holding the scroll back content */
+ Row *scroll_top; /* row in lines where scrolling region starts */
+ Row *scroll_bot; /* row in lines where scrolling region ends */
+ bool *tabs; /* a boolean flag for each column whether it is a tab */
+ int scroll_size; /* maximal capacity of scroll back buffer (in lines) */
+ int scroll_index; /* current index into the ring buffer */
+ int scroll_above; /* number of lines above current viewport */
+ int scroll_below; /* number of lines below current viewport */
+ int rows, cols; /* current dimension of buffer */
+ int maxcols; /* allocated cells (maximal cols over time) */
+ attr_t curattrs, savattrs; /* current and saved attributes for cells */
+ int curs_col; /* current cursor column (zero based) */
+ int curs_srow, curs_scol; /* saved cursor row/colmn (zero based) */
+ short curfg, curbg; /* current fore and background colors */
+ short savfg, savbg; /* saved colors */
+} Buffer;
+
+struct Vt {
+ Buffer buffer_normal; /* normal screen buffer */
+ Buffer buffer_alternate; /* alternate screen buffer */
+ Buffer *buffer; /* currently active buffer (one of the above) */
+ attr_t defattrs; /* attributes to use for normal/empty cells */
+ int deffg, defbg; /* colors to use for back normal/empty cells (white/black) */
+ int pty; /* master side pty file descriptor */
+ pid_t pid; /* process id of the process running in this vt */
+ /* flags */
+ uint seen_input:1;
+ uint insert:1;
+ uint escaped:1;
+ uint curshid:1;
+ uint curskeymode:1;
+ uint bell:1;
+ uint relposmode:1;
+ uint mousetrack:1;
+ uint graphmode:1;
+ uint savgraphmode:1;
+ bool charsets[2];
+ /* buffers and parsing state */
+ char rbuf[BUFSIZ];
+ char ebuf[BUFSIZ];
+ uint rlen, elen;
+ int srow, scol; /* last known offset to display start row, start column */
+ char title[256]; /* xterm style window title */
+ vt_title_handler_t title_handler; /* hook which is called when title changes */
+ vt_urgent_handler_t urgent_handler; /* hook which is called upon bell */
+ void *data; /* user supplied data */
+};
+
+#if 0
+static const char *keytable[KEY_MAX+1] = {
+ [KEY_ENTER] = "\r",
+ ['\n'] = "\n",
+ /* for the arrow keys the CSI / SS3 sequences are not stored here
+ * because they depend on the current cursor terminal mode
+ */
+ [KEY_UP] = "A",
+ [KEY_DOWN] = "B",
+ [KEY_RIGHT] = "C",
+ [KEY_LEFT] = "D",
+#ifdef KEY_SUP
+ [KEY_SUP] = "\e[1;2A",
+#endif
+#ifdef KEY_SDOWN
+ [KEY_SDOWN] = "\e[1;2B",
+#endif
+ [KEY_SRIGHT] = "\e[1;2C",
+ [KEY_SLEFT] = "\e[1;2D",
+ [KEY_BACKSPACE] = "\177",
+ [KEY_IC] = "\e[2~",
+ [KEY_DC] = "\e[3~",
+ [KEY_PPAGE] = "\e[5~",
+ [KEY_NPAGE] = "\e[6~",
+ [KEY_HOME] = "\e[7~",
+ [KEY_END] = "\e[8~",
+ [KEY_BTAB] = "\e[Z",
+ [KEY_SUSPEND] = "\x1A", /* Ctrl+Z gets mapped to this */
+ [KEY_F(1)] = "\e[11~",
+ [KEY_F(2)] = "\e[12~",
+ [KEY_F(3)] = "\e[13~",
+ [KEY_F(4)] = "\e[14~",
+ [KEY_F(5)] = "\e[15~",
+ [KEY_F(6)] = "\e[17~",
+ [KEY_F(7)] = "\e[18~",
+ [KEY_F(8)] = "\e[19~",
+ [KEY_F(9)] = "\e[20~",
+ [KEY_F(10)] = "\e[21~",
+ [KEY_F(11)] = "\e[23~",
+ [KEY_F(12)] = "\e[24~",
+ [KEY_F(13)] = "\e[23~",
+ [KEY_F(14)] = "\e[24~",
+ [KEY_F(15)] = "\e[25~",
+ [KEY_F(16)] = "\e[26~",
+ [KEY_F(17)] = "\e[28~",
+ [KEY_F(18)] = "\e[29~",
+ [KEY_F(19)] = "\e[31~",
+ [KEY_F(20)] = "\e[32~",
+ [KEY_F(21)] = "\e[33~",
+ [KEY_F(22)] = "\e[34~",
+ [KEY_RESIZE] = "",
+#ifdef KEY_EVENT
+ [KEY_EVENT] = "",
+#endif
+};
+#endif
+
+static void puttab(Vt *t, int count);
+static void process_nonprinting(Vt *t, wchar_t wc);
+static void send_curs(Vt *t);
+
+const static
+attr_t
+build_attrs(attr_t curattrs)
+{
+ return ((curattrs & ~A_COLOR) | COLOR_PAIR(curattrs & 0xff)) >> NCURSES_ATTR_SHIFT;
+}
+
+static
+void
+row_set(Row *row, int start, int len, Buffer *t)
+{
+ Cell cell = {
+ .text = L'\0',
+ .attr = t ? build_attrs(t->curattrs) : 0,
+ .fg = t ? t->curfg : -1,
+ .bg = t ? t->curbg : -1,
+ };
+
+ for (int i = start; i < len + start; i++)
+ row->cells[i] = cell;
+ row->dirty = true;
+}
+
+static
+void
+row_roll(Row *start, Row *end, int count)
+{
+ int n = end - start;
+
+ count %= n;
+ if (count < 0)
+ count += n;
+
+ if (count) {
+ char buf[count * sizeof(Row)];
+ memcpy(buf, start, count * sizeof(Row));
+ memmove(start, start + count, (n - count) * sizeof(Row));
+ memcpy(end - count, buf, count * sizeof(Row));
+ for (Row *row = start; row < end; row++)
+ row->dirty = true;
+ }
+}
+
+static
+void
+buffer_clear(Buffer *b)
+{
+ Cell cell = {
+ .text = L'\0',
+ .attr = A_NORMAL,
+ .fg = -1,
+ .bg = -1,
+ };
+
+ for (int i = 0; i < b->rows; i++) {
+ Row *row = b->lines + i;
+ for (int j = 0; j < b->cols; j++) {
+ row->cells[j] = cell;
+ row->dirty = true;
+ }
+ }
+}
+
+static
+void
+buffer_free(Buffer *b)
+{
+ for (int i = 0; i < b->rows; i++)
+ free(b->lines[i].cells);
+ free(b->lines);
+ for (int i = 0; i < b->scroll_size; i++)
+ free(b->scroll_buf[i].cells);
+ free(b->scroll_buf);
+ free(b->tabs);
+}
+
+static
+void
+buffer_scroll(Buffer *b, int s)
+{
+ /* work in screenfuls */
+ int ssz = b->scroll_bot - b->scroll_top;
+ if (s > ssz) {
+ buffer_scroll(b, ssz);
+ buffer_scroll(b, s - ssz);
+ return;
+ }
+ if (s < -ssz) {
+ buffer_scroll(b, -ssz);
+ buffer_scroll(b, s + ssz);
+ return;
+ }
+
+ b->scroll_above += s;
+ if (b->scroll_above >= b->scroll_size)
+ b->scroll_above = b->scroll_size;
+
+ if (s > 0 && b->scroll_size) {
+ for (int i = 0; i < s; i++) {
+ Row tmp = b->scroll_top[i];
+ b->scroll_top[i] = b->scroll_buf[b->scroll_index];
+ b->scroll_buf[b->scroll_index] = tmp;
+
+ b->scroll_index++;
+ if (b->scroll_index == b->scroll_size)
+ b->scroll_index = 0;
+ }
+ }
+ row_roll(b->scroll_top, b->scroll_bot, s);
+ if (s < 0 && b->scroll_size) {
+ for (int i = (-s) - 1; i >= 0; i--) {
+ b->scroll_index--;
+ if (b->scroll_index == -1)
+ b->scroll_index = b->scroll_size - 1;
+
+ Row tmp = b->scroll_top[i];
+ b->scroll_top[i] = b->scroll_buf[b->scroll_index];
+ b->scroll_buf[b->scroll_index] = tmp;
+ b->scroll_top[i].dirty = true;
+ }
+ }
+}
+
+static
+void
+buffer_resize(Buffer *b, int rows, int cols)
+{
+ Row *lines = b->lines;
+
+ if (b->rows != rows) {
+ if (b->curs_row >= lines + rows) {
+ /* scroll up instead of simply chopping off bottom */
+ buffer_scroll(b, (b->curs_row - b->lines) - rows + 1);
+ }
+ while (b->rows > rows) {
+ free(lines[b->rows - 1].cells);
+ b->rows--;
+ }
+
+ lines = realloc(lines, sizeof(Row) * rows);
+ }
+
+ if (b->maxcols < cols) {
+ for (int row = 0; row < b->rows; row++) {
+ lines[row].cells = realloc(lines[row].cells, sizeof(Cell) * cols);
+ if (b->cols < cols)
+ row_set(lines + row, b->cols, cols - b->cols, nil);
+ lines[row].dirty = true;
+ }
+ Row *sbuf = b->scroll_buf;
+ for (int row = 0; row < b->scroll_size; row++) {
+ sbuf[row].cells = realloc(sbuf[row].cells, sizeof(Cell) * cols);
+ if (b->cols < cols)
+ row_set(sbuf + row, b->cols, cols - b->cols, nil);
+ }
+ b->tabs = realloc(b->tabs, sizeof(*b->tabs) * cols);
+ for (int col = b->cols; col < cols; col++)
+ b->tabs[col] = !(col & 7);
+ b->maxcols = cols;
+ b->cols = cols;
+ } else if (b->cols != cols) {
+ for (int row = 0; row < b->rows; row++)
+ lines[row].dirty = true;
+ b->cols = cols;
+ }
+
+ int deltarows = 0;
+ if (b->rows < rows) {
+ while (b->rows < rows) {
+ lines[b->rows].cells = calloc(b->maxcols, sizeof(Cell));
+ row_set(lines + b->rows, 0, b->maxcols, b);
+ b->rows++;
+ }
+
+ /* prepare for backfill */
+ if (b->curs_row >= b->scroll_bot - 1) {
+ deltarows = b->lines + rows - b->curs_row - 1;
+ if (deltarows > b->scroll_above)
+ deltarows = b->scroll_above;
+ }
+ }
+
+ b->curs_row += lines - b->lines;
+ b->scroll_top = lines;
+ b->scroll_bot = lines + rows;
+ b->lines = lines;
+
+ /* perform backfill */
+ if (deltarows > 0) {
+ buffer_scroll(b, -deltarows);
+ b->curs_row += deltarows;
+ }
+}
+
+static
+bool
+buffer_init(Buffer *b, int rows, int cols, int scroll_size)
+{
+ b->curattrs = A_NORMAL; /* white text over black background */
+ b->curfg = b->curbg = -1;
+ if (scroll_size < 0)
+ scroll_size = 0;
+ if (scroll_size && !(b->scroll_buf = calloc(scroll_size, sizeof(Row))))
+ return false;
+ b->scroll_size = scroll_size;
+ buffer_resize(b, rows, cols);
+ return true;
+}
+
+static
+void
+buffer_boundry(Buffer *b, Row **bs, Row **be, Row **as, Row **ae)
+{
+ if (bs)
+ *bs = nil;
+ if (be)
+ *be = nil;
+ if (as)
+ *as = nil;
+ if (ae)
+ *ae = nil;
+ if (!b->scroll_size)
+ return;
+
+ if (b->scroll_above) {
+ if (bs)
+ *bs = &b->scroll_buf[(b->scroll_index - b->scroll_above + b->scroll_size) % b->scroll_size];
+ if (be)
+ *be = &b->scroll_buf[(b->scroll_index-1 + b->scroll_size) % b->scroll_size];
+ }
+ if (b->scroll_below) {
+ if (as)
+ *as = &b->scroll_buf[b->scroll_index];
+ if (ae)
+ *ae = &b->scroll_buf[(b->scroll_index + b->scroll_below-1) % b->scroll_size];
+ }
+}
+
+static
+Row *
+buffer_row_first(Buffer *b) {
+ Row *bstart;
+ if (!b->scroll_size || !b->scroll_above)
+ return b->lines;
+ buffer_boundry(b, &bstart, nil, nil, nil);
+ return bstart;
+}
+
+static
+Row *
+buffer_row_last(Buffer *b) {
+ Row *aend;
+ if (!b->scroll_size || !b->scroll_below)
+ return b->lines + b->rows - 1;
+ buffer_boundry(b, nil, nil, nil, &aend);
+ return aend;
+}
+
+static
+Row *
+buffer_row_next(Buffer *b, Row *row)
+{
+ Row *before_start, *before_end, *after_start, *after_end;
+ Row *first = b->lines, *last = b->lines + b->rows - 1;
+
+ if (!row)
+ return nil;
+
+ buffer_boundry(b, &before_start, &before_end, &after_start, &after_end);
+
+ if (row >= first && row < last)
+ return ++row;
+ if (row == last)
+ return after_start;
+ if (row == before_end)
+ return first;
+ if (row == after_end)
+ return nil;
+ if (row == &b->scroll_buf[b->scroll_size - 1])
+ return b->scroll_buf;
+ return ++row;
+}
+
+static
+Row *
+buffer_row_prev(Buffer *b, Row *row)
+{
+ Row *before_start, *before_end, *after_start, *after_end;
+ Row *first = b->lines, *last = b->lines + b->rows - 1;
+
+ if (!row)
+ return nil;
+
+ buffer_boundry(b, &before_start, &before_end, &after_start, &after_end);
+
+ if (row > first && row <= last)
+ return --row;
+ if (row == first)
+ return before_end;
+ if (row == before_start)
+ return nil;
+ if (row == after_start)
+ return last;
+ if (row == b->scroll_buf)
+ return &b->scroll_buf[b->scroll_size - 1];
+ return --row;
+}
+
+static
+void
+cursor_clamp(Vt *t)
+{
+ Buffer *b = t->buffer;
+ Row *lines = t->relposmode ? b->scroll_top : b->lines;
+ int rows = t->relposmode ? b->scroll_bot - b->scroll_top : b->rows;
+
+ if (b->curs_row < lines)
+ b->curs_row = lines;
+ if (b->curs_row >= lines + rows)
+ b->curs_row = lines + rows - 1;
+ if (b->curs_col < 0)
+ b->curs_col = 0;
+ if (b->curs_col >= b->cols)
+ b->curs_col = b->cols - 1;
+}
+
+static
+void
+cursor_line_down(Vt *t)
+{
+ Buffer *b = t->buffer;
+ row_set(b->curs_row, b->cols, b->maxcols - b->cols, nil);
+ b->curs_row++;
+ if (b->curs_row < b->scroll_bot)
+ return;
+
+ vt_noscroll(t);
+
+ b->curs_row = b->scroll_bot - 1;
+ buffer_scroll(b, 1);
+ row_set(b->curs_row, 0, b->cols, b);
+}
+
+static
+void
+cursor_save(Vt *t)
+{
+ Buffer *b = t->buffer;
+ b->curs_srow = b->curs_row - b->lines;
+ b->curs_scol = b->curs_col;
+}
+
+static
+void
+cursor_restore(Vt *t)
+{
+ Buffer *b = t->buffer;
+ b->curs_row = b->lines + b->curs_srow;
+ b->curs_col = b->curs_scol;
+ cursor_clamp(t);
+}
+
+static
+void
+attributes_save(Vt *t)
+{
+ Buffer *b = t->buffer;
+ b->savattrs = b->curattrs;
+ b->savfg = b->curfg;
+ b->savbg = b->curbg;
+ t->savgraphmode = t->graphmode;
+}
+
+static
+void
+attributes_restore(Vt *t)
+{
+ Buffer *b = t->buffer;
+ b->curattrs = b->savattrs;
+ b->curfg = b->savfg;
+ b->curbg = b->savbg;
+ t->graphmode = t->savgraphmode;
+}
+
+static
+void
+new_escape_sequence(Vt *t)
+{
+ t->escaped = true;
+ t->elen = 0;
+ t->ebuf[0] = '\0';
+}
+
+static
+void
+cancel_escape_sequence(Vt *t)
+{
+ t->escaped = false;
+ t->elen = 0;
+ t->ebuf[0] = '\0';
+}
+
+static
+bool
+is_valid_csi_ender(int c)
+{
+ return (c >= 'a' && c <= 'z')
+ || (c >= 'A' && c <= 'Z')
+ || (c == '@' || c == '`');
+}
+
+/* interprets a 'set attribute' (SGR) CSI escape sequence */
+static
+void
+interpret_csi_sgr(Vt *t, int param[], int pcount)
+{
+ Buffer *b = t->buffer;
+ if (pcount == 0) {
+ /* special case: reset attributes */
+ b->curattrs = A_NORMAL;
+ b->curfg = b->curbg = -1;
+ return;
+ }
+
+ for (int i = 0; i < pcount; i++) {
+ switch (param[i]) {
+ case 0:
+ b->curattrs = A_NORMAL;
+ b->curfg = b->curbg = -1;
+ break;
+ case 1:
+ b->curattrs |= A_BOLD;
+ break;
+ case 2:
+ b->curattrs |= A_DIM;
+ break;
+#ifdef A_ITALIC
+ case 3:
+ b->curattrs |= A_ITALIC;
+ break;
+#endif
+ case 4:
+ b->curattrs |= A_UNDERLINE;
+ break;
+ case 5:
+ b->curattrs |= A_BLINK;
+ break;
+ case 7:
+ b->curattrs |= A_REVERSE;
+ break;
+ case 8:
+ b->curattrs |= A_INVIS;
+ break;
+ case 22:
+ b->curattrs &= ~(A_BOLD | A_DIM);
+ break;
+#ifdef A_ITALIC
+ case 23:
+ b->curattrs &= ~A_ITALIC;
+ break;
+#endif
+ case 24:
+ b->curattrs &= ~A_UNDERLINE;
+ break;
+ case 25:
+ b->curattrs &= ~A_BLINK;
+ break;
+ case 27:
+ b->curattrs &= ~A_REVERSE;
+ break;
+ case 28:
+ b->curattrs &= ~A_INVIS;
+ break;
+ case 30 ... 37: /* fg */
+ b->curfg = param[i] - 30;
+ break;
+ case 38:
+ if ((i + 2) < pcount && param[i + 1] == 5) {
+ b->curfg = param[i + 2];
+ i += 2;
+ }
+ break;
+ case 39:
+ b->curfg = -1;
+ break;
+ case 40 ... 47: /* bg */
+ b->curbg = param[i] - 40;
+ break;
+ case 48:
+ if ((i + 2) < pcount && param[i + 1] == 5) {
+ b->curbg = param[i + 2];
+ i += 2;
+ }
+ break;
+ case 49:
+ b->curbg = -1;
+ break;
+ case 90 ... 97: /* hi fg */
+ b->curfg = param[i] - 82;
+ break;
+ case 100 ... 107: /* hi bg */
+ b->curbg = param[i] - 92;
+ break;
+ default:
+ break;
+ }
+ }
+}
+
+/* interprets an 'erase display' (ED) escape sequence */
+static
+void
+interpret_csi_ed(Vt *t, int param[], int pcount)
+{
+ Row *row, *start, *end;
+ Buffer *b = t->buffer;
+
+ attributes_save(t);
+ b->curattrs = A_NORMAL;
+ b->curfg = b->curbg = -1;
+
+ if (pcount && param[0] == 2) {
+ start = b->lines;
+ end = b->lines + b->rows;
+ } else if (pcount && param[0] == 1) {
+ start = b->lines;
+ end = b->curs_row;
+ row_set(b->curs_row, 0, b->curs_col + 1, b);
+ } else {
+ row_set(b->curs_row, b->curs_col, b->cols - b->curs_col, b);
+ start = b->curs_row + 1;
+ end = b->lines + b->rows;
+ }
+
+ for (row = start; row < end; row++)
+ row_set(row, 0, b->cols, b);
+
+ attributes_restore(t);
+}
+
+/* interprets a 'move cursor' (CUP) escape sequence */
+static
+void
+interpret_csi_cup(Vt *t, int param[], int pcount)
+{
+ Buffer *b = t->buffer;
+ Row *lines = t->relposmode ? b->scroll_top : b->lines;
+
+ if (pcount == 0) {
+ b->curs_row = lines;
+ b->curs_col = 0;
+ } else if (pcount == 1) {
+ b->curs_row = lines + param[0] - 1;
+ b->curs_col = 0;
+ } else {
+ b->curs_row = lines + param[0] - 1;
+ b->curs_col = param[1] - 1;
+ }
+
+ cursor_clamp(t);
+}
+
+/* Interpret the 'relative mode' sequences: CUU, CUD, CUF, CUB, CNL,
+ * CPL, CHA, HPR, VPA, VPR, HPA */
+static
+void
+interpret_csi_c(Vt *t, char verb, int param[], int pcount)
+{
+ Buffer *b = t->buffer;
+ int n = (pcount && param[0] > 0) ? param[0] : 1;
+
+ switch (verb) {
+ case 'A':
+ b->curs_row -= n;
+ break;
+ case 'B':
+ case 'e':
+ b->curs_row += n;
+ break;
+ case 'C':
+ case 'a':
+ b->curs_col += n;
+ break;
+ case 'D':
+ b->curs_col -= n;
+ break;
+ case 'E':
+ b->curs_row += n;
+ b->curs_col = 0;
+ break;
+ case 'F':
+ b->curs_row -= n;
+ b->curs_col = 0;
+ break;
+ case 'G':
+ case '`':
+ b->curs_col = n - 1;
+ break;
+ case 'd':
+ b->curs_row = b->lines + n - 1;
+ break;
+ }
+
+ cursor_clamp(t);
+}
+
+/* Interpret the 'erase line' escape sequence */
+static
+void
+interpret_csi_el(Vt *t, int param[], int pcount)
+{
+ Buffer *b = t->buffer;
+ switch (pcount ? param[0] : 0) {
+ case 1:
+ row_set(b->curs_row, 0, b->curs_col + 1, b);
+ break;
+ case 2:
+ row_set(b->curs_row, 0, b->cols, b);
+ break;
+ default:
+ row_set(b->curs_row, b->curs_col, b->cols - b->curs_col, b);
+ break;
+ }
+}
+
+/* Interpret the 'insert blanks' sequence (ICH) */
+static
+void
+interpret_csi_ich(Vt *t, int param[], int pcount)
+{
+ Buffer *b = t->buffer;
+ Row *row = b->curs_row;
+ int n = (pcount && param[0] > 0) ? param[0] : 1;
+
+ if (b->curs_col + n > b->cols)
+ n = b->cols - b->curs_col;
+
+ for (int i = b->cols - 1; i >= b->curs_col + n; i--)
+ row->cells[i] = row->cells[i - n];
+
+ row_set(row, b->curs_col, n, b);
+}
+
+/* Interpret the 'delete chars' sequence (DCH) */
+static
+void
+interpret_csi_dch(Vt *t, int param[], int pcount)
+{
+ Buffer *b = t->buffer;
+ Row *row = b->curs_row;
+ int n = (pcount && param[0] > 0) ? param[0] : 1;
+
+ if (b->curs_col + n > b->cols)
+ n = b->cols - b->curs_col;
+
+ for (int i = b->curs_col; i < b->cols - n; i++)
+ row->cells[i] = row->cells[i + n];
+
+ row_set(row, b->cols - n, n, b);
+}
+
+/* Interpret an 'insert line' sequence (IL) */
+static
+void
+interpret_csi_il(Vt *t, int param[], int pcount)
+{
+ Buffer *b = t->buffer;
+ int n = (pcount && param[0] > 0) ? param[0] : 1;
+
+ if (b->curs_row + n >= b->scroll_bot) {
+ for (Row *row = b->curs_row; row < b->scroll_bot; row++)
+ row_set(row, 0, b->cols, b);
+ } else {
+ row_roll(b->curs_row, b->scroll_bot, -n);
+ for (Row *row = b->curs_row; row < b->curs_row + n; row++)
+ row_set(row, 0, b->cols, b);
+ }
+}
+
+/* Interpret a 'delete line' sequence (DL) */
+static
+void
+interpret_csi_dl(Vt *t, int param[], int pcount)
+{
+ Buffer *b = t->buffer;
+ int n = (pcount && param[0] > 0) ? param[0] : 1;
+
+ if (b->curs_row + n >= b->scroll_bot) {
+ for (Row *row = b->curs_row; row < b->scroll_bot; row++)
+ row_set(row, 0, b->cols, b);
+ } else {
+ row_roll(b->curs_row, b->scroll_bot, n);
+ for (Row *row = b->scroll_bot - n; row < b->scroll_bot; row++)
+ row_set(row, 0, b->cols, b);
+ }
+}
+
+/* Interpret an 'erase characters' (ECH) sequence */
+static
+void
+interpret_csi_ech(Vt *t, int param[], int pcount)
+{
+ Buffer *b = t->buffer;
+ int n = (pcount && param[0] > 0) ? param[0] : 1;
+
+ if (b->curs_col + n > b->cols)
+ n = b->cols - b->curs_col;
+
+ row_set(b->curs_row, b->curs_col, n, b);
+}
+
+/* Interpret a 'set scrolling region' (DECSTBM) sequence */
+static
+void
+interpret_csi_decstbm(Vt *t, int param[], int pcount)
+{
+ Buffer *b = t->buffer;
+ int new_top, new_bot;
+
+ switch (pcount) {
+ case 0:
+ b->scroll_top = b->lines;
+ b->scroll_bot = b->lines + b->rows;
+ break;
+ case 2:
+ new_top = param[0] - 1;
+ new_bot = param[1];
+
+ /* clamp to bounds */
+ if (new_top < 0)
+ new_top = 0;
+ if (new_top >= b->rows)
+ new_top = b->rows - 1;
+ if (new_bot < 0)
+ new_bot = 0;
+ if (new_bot >= b->rows)
+ new_bot = b->rows;
+
+ /* check for range validity */
+ if (new_top < new_bot) {
+ b->scroll_top = b->lines + new_top;
+ b->scroll_bot = b->lines + new_bot;
+ }
+ break;
+ default:
+ return; /* malformed */
+ }
+ b->curs_row = b->scroll_top;
+ b->curs_col = 0;
+}
+
+static
+void
+interpret_csi_mode(Vt *t, int param[], int pcount, bool set)
+{
+ for (int i = 0; i < pcount; i++) {
+ switch (param[i]) {
+ case 4: /* insert/replace mode */
+ t->insert = set;
+ break;
+ }
+ }
+}
+
+static
+void
+interpret_csi_priv_mode(Vt *t, int param[], int pcount, bool set)
+{
+ for (int i = 0; i < pcount; i++) {
+ switch (param[i]) {
+ case 1: /* set application/normal cursor key mode (DECCKM) */
+ t->curskeymode = set;
+ break;
+ case 6: /* set origin to relative/absolute (DECOM) */
+ t->relposmode = set;
+ break;
+ case 25: /* make cursor visible/invisible (DECCM) */
+ t->curshid = !set;
+ break;
+ case 1049: /* combine 1047 + 1048 */
+ case 47: /* use alternate/normal screen buffer */
+ case 1047:
+ if (!set)
+ buffer_clear(&t->buffer_alternate);
+ t->buffer = set ? &t->buffer_alternate : &t->buffer_normal;
+ vt_dirty(t);
+ if (param[i] != 1049)
+ break;
+ /* fall through */
+ case 1048: /* save/restore cursor */
+ if (set)
+ cursor_save(t);
+ else
+ cursor_restore(t);
+ break;
+ case 1000: /* enable/disable normal mouse tracking */
+ t->mousetrack = set;
+ break;
+ }
+ }
+}
+
+static
+void
+interpret_csi(Vt *t)
+{
+ Buffer *b = t->buffer;
+ int csiparam[16];
+ uint param_count = 0;
+ const char *p = t->ebuf + 1;
+ char verb = t->ebuf[t->elen - 1];
+
+ /* parse numeric parameters */
+ for (p += (t->ebuf[1] == '?'); *p; p++) {
+ if (IS_CONTROL(*p)) {
+ process_nonprinting(t, *p);
+ } else if (*p == ';') {
+ if (param_count >= arrlen(csiparam))
+ return; /* too long! */
+ csiparam[param_count++] = 0;
+ } else if (isdigit((uchar)*p)) {
+ if (param_count == 0)
+ csiparam[param_count++] = 0;
+ csiparam[param_count - 1] *= 10;
+ csiparam[param_count - 1] += *p - '0';
+ }
+ }
+
+ if (t->ebuf[1] == '?') {
+ switch (verb) {
+ case 'h':
+ case 'l': /* private set/reset mode */
+ interpret_csi_priv_mode(t, csiparam, param_count, verb == 'h');
+ break;
+ }
+ return;
+ }
+
+ /* delegate handling depending on command character (verb) */
+ switch (verb) {
+ case 'h':
+ case 'l': /* set/reset mode */
+ interpret_csi_mode(t, csiparam, param_count, verb == 'h');
+ break;
+ case 'm': /* set attribute */
+ interpret_csi_sgr(t, csiparam, param_count);
+ break;
+ case 'J': /* erase display */
+ interpret_csi_ed(t, csiparam, param_count);
+ break;
+ case 'H':
+ case 'f': /* move cursor */
+ interpret_csi_cup(t, csiparam, param_count);
+ break;
+ case 'A':
+ case 'B':
+ case 'C':
+ case 'D':
+ case 'E':
+ case 'F':
+ case 'G':
+ case 'e':
+ case 'a':
+ case 'd':
+ case '`': /* relative move */
+ interpret_csi_c(t, verb, csiparam, param_count);
+ break;
+ case 'K': /* erase line */
+ interpret_csi_el(t, csiparam, param_count);
+ break;
+ case '@': /* insert characters */
+ interpret_csi_ich(t, csiparam, param_count);
+ break;
+ case 'P': /* delete characters */
+ interpret_csi_dch(t, csiparam, param_count);
+ break;
+ case 'L': /* insert lines */
+ interpret_csi_il(t, csiparam, param_count);
+ break;
+ case 'M': /* delete lines */
+ interpret_csi_dl(t, csiparam, param_count);
+ break;
+ case 'X': /* erase chars */
+ interpret_csi_ech(t, csiparam, param_count);
+ break;
+ case 'S': /* SU: scroll up */
+ vt_scroll(t, param_count ? -csiparam[0] : -1);
+ break;
+ case 'T': /* SD: scroll down */
+ vt_scroll(t, param_count ? csiparam[0] : 1);
+ break;
+ case 'Z': /* CBT: cursor backward tabulation */
+ puttab(t, param_count ? -csiparam[0] : -1);
+ break;
+ case 'g': /* TBC: tabulation clear */
+ switch (param_count ? csiparam[0] : 0) {
+ case 0:
+ b->tabs[b->curs_col] = false;
+ break;
+ case 3:
+ memset(b->tabs, 0, sizeof(*b->tabs) * b->maxcols);
+ break;
+ }
+ break;
+ case 'r': /* set scrolling region */
+ interpret_csi_decstbm(t, csiparam, param_count);
+ break;
+ case 's': /* save cursor location */
+ cursor_save(t);
+ break;
+ case 'u': /* restore cursor location */
+ cursor_restore(t);
+ break;
+ case 'n': /* query cursor location */
+ if (param_count == 1 && csiparam[0] == 6)
+ send_curs(t);
+ break;
+ default:
+ break;
+ }
+}
+
+/* Interpret an 'index' (IND) sequence */
+static
+void
+interpret_csi_ind(Vt *t)
+{
+ Buffer *b = t->buffer;
+ if (b->curs_row < b->lines + b->rows - 1)
+ b->curs_row++;
+}
+
+/* Interpret a 'reverse index' (RI) sequence */
+static
+void
+interpret_csi_ri(Vt *t)
+{
+ Buffer *b = t->buffer;
+ if (b->curs_row > b->scroll_top)
+ b->curs_row--;
+ else {
+ row_roll(b->scroll_top, b->scroll_bot, -1);
+ row_set(b->scroll_top, 0, b->cols, b);
+ }
+}
+
+/* Interpret a 'next line' (NEL) sequence */
+static
+void
+interpret_csi_nel(Vt *t)
+{
+ Buffer *b = t->buffer;
+ if (b->curs_row < b->lines + b->rows - 1) {
+ b->curs_row++;
+ b->curs_col = 0;
+ }
+}
+
+/* Interpret a 'select character set' (SCS) sequence */
+static
+void
+interpret_csi_scs(Vt *t)
+{
+ /* ESC ( sets G0, ESC ) sets G1 */
+ t->charsets[!!(t->ebuf[0] == ')')] = (t->ebuf[1] == '0');
+ t->graphmode = t->charsets[0];
+}
+
+/* Interpret an 'operating system command' (OSC) sequence */
+static
+void
+interpret_osc(Vt *t)
+{
+ /* ESC ] command ; data BEL
+ * ESC ] command ; data ESC \\
+ * Note that BEL or ESC \\ have already been replaced with NUL.
+ */
+ char *data = nil;
+ int command = strtoul(t->ebuf + 1, &data, 10);
+ if (data && *data == ';') {
+ switch (command) {
+ case 0: /* icon name and window title */
+ case 2: /* window title */
+ if (t->title_handler)
+ t->title_handler(t, data+1);
+ break;
+ case 1: /* icon name */
+ break;
+ default:
+#ifndef NDEBUG
+ fprintf(stderr, "unknown OSC command: %d\n", command);
+#endif
+ break;
+ }
+ }
+}
+
+static
+void
+try_interpret_escape_seq(Vt *t)
+{
+ char lastchar = t->ebuf[t->elen - 1];
+
+ if (!*t->ebuf)
+ return;
+
+ switch (*t->ebuf) {
+ case '#': /* ignore DECDHL, DECSWL, DECDWL, DECHCP, DECFPP */
+ if (t->elen == 2) {
+ if (lastchar == '8') { /* DECALN */
+ interpret_csi_ed(t, (int []){ 2 }, 1);
+ goto handled;
+ }
+ goto cancel;
+ }
+ break;
+ case '(':
+ case ')':
+ if (t->elen == 2) {
+ interpret_csi_scs(t);
+ goto handled;
+ }
+ break;
+ case ']': /* OSC - operating system command */
+ if (lastchar == '\a' ||
+ (lastchar == '\\' && t->elen >= 2 && t->ebuf[t->elen - 2] == '\e')) {
+ t->elen -= lastchar == '\a' ? 1 : 2;
+ t->ebuf[t->elen] = '\0';
+ interpret_osc(t);
+ goto handled;
+ }
+ break;
+ case '[': /* CSI - control sequence introducer */
+ if (is_valid_csi_ender(lastchar)) {
+ interpret_csi(t);
+ goto handled;
+ }
+ break;
+ case '7': /* DECSC: save cursor and attributes */
+ attributes_save(t);
+ cursor_save(t);
+ goto handled;
+ case '8': /* DECRC: restore cursor and attributes */
+ attributes_restore(t);
+ cursor_restore(t);
+ goto handled;
+ case 'D': /* IND: index */
+ interpret_csi_ind(t);
+ goto handled;
+ case 'M': /* RI: reverse index */
+ interpret_csi_ri(t);
+ goto handled;
+ case 'E': /* NEL: next line */
+ interpret_csi_nel(t);
+ goto handled;
+ case 'H': /* HTS: horizontal tab set */
+ t->buffer->tabs[t->buffer->curs_col] = true;
+ goto handled;
+ default:
+ goto cancel;
+ }
+
+ if (t->elen + 1 >= sizeof(t->ebuf)) {
+cancel:
+#ifndef NDEBUG
+ fprintf(stderr, "cancelled: \\033");
+ for (uint i = 0; i < t->elen; i++) {
+ if (isprint(t->ebuf[i])) {
+ fputc(t->ebuf[i], stderr);
+ } else {
+ fprintf(stderr, "\\%03o", t->ebuf[i]);
+ }
+ }
+ fputc('\n', stderr);
+#endif
+handled:
+ cancel_escape_sequence(t);
+ }
+}
+
+static
+void
+puttab(Vt *t, int count)
+{
+ Buffer *b = t->buffer;
+ int direction = count >= 0 ? 1 : -1;
+ for (int col = b->curs_col + direction; count; col += direction) {
+ if (col < 0) {
+ b->curs_col = 0;
+ break;
+ }
+ if (col >= b->cols) {
+ b->curs_col = b->cols - 1;
+ break;
+ }
+ if (b->tabs[col]) {
+ b->curs_col = col;
+ count -= direction;
+ }
+ }
+}
+
+static
+void
+process_nonprinting(Vt *t, wchar_t wc)
+{
+ Buffer *b = t->buffer;
+ switch (wc) {
+ case '\e': /* ESC */
+ new_escape_sequence(t);
+ break;
+ case '\a': /* BEL */
+ if (t->urgent_handler)
+ t->urgent_handler(t);
+ break;
+ case '\b': /* BS */
+ if (b->curs_col > 0)
+ b->curs_col--;
+ break;
+ case '\t': /* HT */
+ puttab(t, 1);
+ break;
+ case '\r': /* CR */
+ b->curs_col = 0;
+ break;
+ case '\v': /* VT */
+ case '\f': /* FF */
+ case '\n': /* LF */
+ cursor_line_down(t);
+ break;
+ case '\016': /* SO: shift out, invoke the G1 character set */
+ t->graphmode = t->charsets[1];
+ break;
+ case '\017': /* SI: shift in, invoke the G0 character set */
+ t->graphmode = t->charsets[0];
+ break;
+ }
+}
+
+static
+void
+is_utf8_locale(void)
+{
+ const char *cset = nl_langinfo(CODESET);
+ if (!cset)
+ cset = "ANSI_X3.4-1968";
+ is_utf8 = !strcmp(cset, "UTF-8");
+}
+
+static
+wchar_t
+get_vt100_graphic(char c)
+{
+ static char vt100_acs[] = "`afgjklmnopqrstuvwxyz{|}~";
+
+ /*
+ * 5f-7e standard vt100
+ * 40-5e rxvt extension for extra curses acs chars
+ */
+ static uint16_t const vt100_utf8[62] = {
+ 0x2191, 0x2193, 0x2192, 0x2190, 0x2588, 0x259a, 0x2603, // 41-47
+ 0, 0, 0, 0, 0, 0, 0, 0, // 48-4f
+ 0, 0, 0, 0, 0, 0, 0, 0, // 50-57
+ 0, 0, 0, 0, 0, 0, 0, 0x0020, // 58-5f
+ 0x25c6, 0x2592, 0x2409, 0x240c, 0x240d, 0x240a, 0x00b0, 0x00b1, // 60-67
+ 0x2424, 0x240b, 0x2518, 0x2510, 0x250c, 0x2514, 0x253c, 0x23ba, // 68-6f
+ 0x23bb, 0x2500, 0x23bc, 0x23bd, 0x251c, 0x2524, 0x2534, 0x252c, // 70-77
+ 0x2502, 0x2264, 0x2265, 0x03c0, 0x2260, 0x00a3, 0x00b7, // 78-7e
+ };
+
+ if (is_utf8)
+ return vt100_utf8[c - 0x41];
+ else if (strchr(vt100_acs, c))
+ return NCURSES_ACS(c);
+ return '\0';
+}
+
+static
+void
+put_wc(Vt *t, wchar_t wc)
+{
+ int width = 0;
+
+ if (!t->seen_input) {
+ t->seen_input = 1;
+ kill(-t->pid, SIGWINCH);
+ }
+
+ if (t->escaped) {
+ if (t->elen + 1 < sizeof(t->ebuf)) {
+ t->ebuf[t->elen] = wc;
+ t->ebuf[++t->elen] = '\0';
+ try_interpret_escape_seq(t);
+ } else {
+ cancel_escape_sequence(t);
+ }
+ } else if (IS_CONTROL(wc)) {
+ process_nonprinting(t, wc);
+ } else {
+ if (t->graphmode) {
+ if (wc >= 0x41 && wc <= 0x7e) {
+ wchar_t gc = get_vt100_graphic(wc);
+ if (gc)
+ wc = gc;
+ }
+ width = 1;
+ } else if ((width = wcwidth(wc)) < 1) {
+ width = 1;
+ }
+ Buffer *b = t->buffer;
+ Cell blank_cell = { L'\0', build_attrs(b->curattrs), b->curfg, b->curbg };
+ if (width == 2 && b->curs_col == b->cols - 1) {
+ b->curs_row->cells[b->curs_col++] = blank_cell;
+ b->curs_row->dirty = true;
+ }
+
+ if (b->curs_col >= b->cols) {
+ b->curs_col = 0;
+ cursor_line_down(t);
+ }
+
+ if (t->insert) {
+ Cell *src = b->curs_row->cells + b->curs_col;
+ Cell *dest = src + width;
+ size_t len = b->cols - b->curs_col - width;
+ memmove(dest, src, len * sizeof *dest);
+ }
+
+ b->curs_row->cells[b->curs_col] = blank_cell;
+ b->curs_row->cells[b->curs_col++].text = wc;
+ b->curs_row->dirty = true;
+ if (width == 2)
+ b->curs_row->cells[b->curs_col++] = blank_cell;
+ }
+}
+
+int
+vt_process(Vt *t)
+{
+ int res;
+ uint pos = 0;
+ mbstate_t ps;
+ memset(&ps, 0, sizeof(ps));
+
+ if (t->pty < 0) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ res = read(t->pty, t->rbuf + t->rlen, sizeof(t->rbuf) - t->rlen);
+ if (res < 0)
+ return -1;
+
+ t->rlen += res;
+ while (pos < t->rlen) {
+ wchar_t wc;
+ ssize_t len;
+
+ len = (ssize_t)mbrtowc(&wc, t->rbuf + pos, t->rlen - pos, &ps);
+ if (len == -2) {
+ t->rlen -= pos;
+ memmove(t->rbuf, t->rbuf + pos, t->rlen);
+ return 0;
+ }
+
+ if (len == -1) {
+ len = 1;
+ wc = t->rbuf[pos];
+ }
+
+ pos += len ? len : 1;
+ put_wc(t, wc);
+ }
+
+ t->rlen -= pos;
+ memmove(t->rbuf, t->rbuf + pos, t->rlen);
+ return 0;
+}
+
+void
+vt_default_colors_set(Vt *t, attr_t attrs, int fg, int bg)
+{
+ t->defattrs = attrs;
+ t->deffg = fg;
+ t->defbg = bg;
+}
+
+Vt *
+vt_create(int rows, int cols, int scroll_size)
+{
+ if (rows <= 0 || cols <= 0)
+ return nil;
+
+ Vt *t = calloc(1, sizeof(Vt));
+ if (!t)
+ return nil;
+
+ t->pty = -1;
+ t->deffg = t->defbg = -1;
+ t->buffer = &t->buffer_normal;
+
+ if (!buffer_init(&t->buffer_normal, rows, cols, scroll_size) ||
+ !buffer_init(&t->buffer_alternate, rows, cols, 0)) {
+ free(t);
+ return nil;
+ }
+
+ return t;
+}
+
+void
+vt_resize(Vt *t, int rows, int cols)
+{
+ struct winsize ws = { .ws_row = rows, .ws_col = cols };
+
+ if (rows <= 0 || cols <= 0)
+ return;
+
+ vt_noscroll(t);
+ buffer_resize(&t->buffer_normal, rows, cols);
+ buffer_resize(&t->buffer_alternate, rows, cols);
+ cursor_clamp(t);
+ ioctl(t->pty, TIOCSWINSZ, &ws);
+ kill(-t->pid, SIGWINCH);
+}
+
+void
+vt_destroy(Vt *t)
+{
+ if (!t)
+ return;
+ buffer_free(&t->buffer_normal);
+ buffer_free(&t->buffer_alternate);
+ close(t->pty);
+ free(t);
+}
+
+void
+vt_dirty(Vt *t)
+{
+ Buffer *b = t->buffer;
+ for (Row *row = b->lines, *end = row + b->rows; row < end; row++)
+ row->dirty = true;
+}
+
+void
+vt_draw(Vt *t, WINDOW *win, int srow, int scol)
+{
+ Buffer *b = t->buffer;
+
+ if (srow != t->srow || scol != t->scol) {
+ vt_dirty(t);
+ t->srow = srow;
+ t->scol = scol;
+ }
+
+ for (int i = 0; i < b->rows; i++) {
+ Row *row = b->lines + i;
+
+ if (!row->dirty)
+ continue;
+
+ wmove(win, srow + i, scol);
+ Cell *cell = nil;
+ for (int j = 0; j < b->cols; j++) {
+ Cell *prev_cell = cell;
+ cell = row->cells + j;
+ if (!prev_cell || cell->attr != prev_cell->attr
+ || cell->fg != prev_cell->fg
+ || cell->bg != prev_cell->bg) {
+ if (cell->attr == A_NORMAL)
+ cell->attr = t->defattrs;
+ if (cell->fg == -1)
+ cell->fg = t->deffg;
+ if (cell->bg == -1)
+ cell->bg = t->defbg;
+ wattrset(win, cell->attr << NCURSES_ATTR_SHIFT);
+ wcolor_set(win, vt_color_get(t, cell->fg, cell->bg), nil);
+ }
+
+ if (is_utf8 && cell->text >= 128) {
+ char buf[MB_CUR_MAX + 1];
+ size_t len = wcrtomb(buf, cell->text, nil);
+ if (len > 0) {
+ waddnstr(win, buf, len);
+ if (wcwidth(cell->text) > 1)
+ j++;
+ }
+ } else {
+ waddch(win, cell->text > ' ' ? cell->text : ' ');
+ }
+ }
+
+ int x, y;
+ getyx(win, y, x);
+ (void)y;
+ if (x && x < b->cols - 1)
+ whline(win, ' ', b->cols - x);
+
+ row->dirty = false;
+ }
+
+ wmove(win, srow + b->curs_row - b->lines, scol + b->curs_col);
+}
+
+void
+vt_scroll(Vt *t, int rows)
+{
+ Buffer *b = t->buffer;
+ if (!b->scroll_size)
+ return;
+ if (rows < 0) { /* scroll back */
+ if (rows < -b->scroll_above)
+ rows = -b->scroll_above;
+ } else { /* scroll forward */
+ if (rows > b->scroll_below)
+ rows = b->scroll_below;
+ }
+ buffer_scroll(b, rows);
+ b->scroll_below -= rows;
+}
+
+void
+vt_noscroll(Vt *t)
+{
+ int scroll_below = t->buffer->scroll_below;
+ if (scroll_below)
+ vt_scroll(t, scroll_below);
+}
+
+pid_t
+vt_forkpty(Vt *t, const char *p, const char *argv[], const char *cwd, const char *env[], int *to, int *from)
+{
+ int vt2ed[2], ed2vt[2];
+ struct winsize ws;
+ ws.ws_row = t->buffer->rows;
+ ws.ws_col = t->buffer->cols;
+ ws.ws_xpixel = ws.ws_ypixel = 0;
+
+ if (to && pipe(vt2ed)) {
+ *to = -1;
+ to = nil;
+ }
+ if (from && pipe(ed2vt)) {
+ *from = -1;
+ from = nil;
+ }
+
+ pid_t pid = forkpty(&t->pty, nil, nil, &ws);
+ if (pid < 0)
+ return -1;
+
+ if (pid == 0) {
+ setsid();
+
+ sigset_t emptyset;
+ sigemptyset(&emptyset);
+ sigprocmask(SIG_SETMASK, &emptyset, nil);
+
+ if (to) {
+ close(vt2ed[1]);
+ dup2(vt2ed[0], STDIN_FILENO);
+ close(vt2ed[0]);
+ }
+
+ if (from) {
+ close(ed2vt[0]);
+ dup2(ed2vt[1], STDOUT_FILENO);
+ close(ed2vt[1]);
+ }
+
+ int maxfd = sysconf(_SC_OPEN_MAX);
+ for (int fd = 3; fd < maxfd; fd++)
+ if (close(fd) == -1 && errno == EBADF)
+ break;
+
+ for (const char **envp = env; envp && envp[0]; envp += 2)
+ setenv(envp[0], envp[1], 1);
+ setenv("TERM", vt_term, 1);
+
+ if (cwd)
+ chdir(cwd);
+
+ execvp(p, (char *const *)argv);
+ fprintf(stderr, "\nexecv() failed.\nCommand: '%s'\n", argv[0]);
+ exit(1);
+ }
+
+ if (to) {
+ close(vt2ed[0]);
+ *to = vt2ed[1];
+ }
+
+ if (from) {
+ close(ed2vt[1]);
+ *from = ed2vt[0];
+ }
+
+ return t->pid = pid;
+}
+
+int
+vt_pty_get(Vt *t)
+{
+ return t->pty;
+}
+
+ssize_t
+vt_write(Vt *t, const char *buf, size_t len)
+{
+ ssize_t ret = len;
+
+ while (len > 0) {
+ ssize_t res = write(t->pty, buf, len);
+ if (res < 0) {
+ if (errno != EAGAIN && errno != EINTR)
+ return -1;
+ continue;
+ }
+ buf += res;
+ len -= res;
+ }
+
+ return ret;
+}
+
+static
+void
+send_curs(Vt *t)
+{
+ Buffer *b = t->buffer;
+ char keyseq[16];
+ snprintf(keyseq, sizeof keyseq, "\e[%d;%dR", (int)(b->curs_row - b->lines), b->curs_col);
+ vt_write(t, keyseq, strlen(keyseq));
+}
+
+void
+vt_keypress(Vt *t, int keycode)
+{
+ vt_noscroll(t);
+
+ if (keycode >= 0 && keycode <= KEY_MAX && keytable[keycode]) {
+ switch (keycode) {
+ case KEY_UP:
+ case KEY_DOWN:
+ case KEY_RIGHT:
+ case KEY_LEFT: {
+ char keyseq[3] = { '\e', (t->curskeymode ? 'O' : '['), keytable[keycode][0] };
+ vt_write(t, keyseq, sizeof keyseq);
+ break;
+ }
+ default:
+ vt_write(t, keytable[keycode], strlen(keytable[keycode]));
+ }
+ } else if (keycode <= UCHAR_MAX) {
+ char c = keycode;
+ vt_write(t, &c, 1);
+ } else {
+#ifndef NDEBUG
+ fprintf(stderr, "unhandled key %#o\n", keycode);
+#endif
+ }
+}
+
+void
+vt_mouse(Vt *t, int x, int y, mmask_t mask)
+{
+#ifdef NCURSES_MOUSE_VERSION
+ char seq[6] = { '\e', '[', 'M' }, state = 0, button = 0;
+
+ if (!t->mousetrack)
+ return;
+
+ if (mask & (BUTTON1_PRESSED | BUTTON1_CLICKED))
+ button = 0;
+ else if (mask & (BUTTON2_PRESSED | BUTTON2_CLICKED))
+ button = 1;
+ else if (mask & (BUTTON3_PRESSED | BUTTON3_CLICKED))
+ button = 2;
+ else if (mask & (BUTTON1_RELEASED | BUTTON2_RELEASED | BUTTON3_RELEASED))
+ button = 3;
+
+ if (mask & BUTTON_SHIFT)
+ state |= 4;
+ if (mask & BUTTON_ALT)
+ state |= 8;
+ if (mask & BUTTON_CTRL)
+ state |= 16;
+
+ seq[3] = 32 + button + state;
+ seq[4] = 32 + x;
+ seq[5] = 32 + y;
+
+ vt_write(t, seq, sizeof seq);
+
+ if (mask & (BUTTON1_CLICKED | BUTTON2_CLICKED | BUTTON3_CLICKED)) {
+ /* send a button release event */
+ button = 3;
+ seq[3] = 32 + button + state;
+ vt_write(t, seq, sizeof seq);
+ }
+#endif /* NCURSES_MOUSE_VERSION */
+}
+
+static
+uint
+color_hash(short fg, short bg)
+{
+ if (fg == -1)
+ fg = COLORS;
+ if (bg == -1)
+ bg = COLORS + 1;
+ return fg * (COLORS + 2) + bg;
+}
+
+int
+vt_color_get(Vt *t, int fg, int bg)
+{
+ if (fg >= COLORS)
+ fg = (t ? t->deffg : default_fg);
+ if (bg >= COLORS)
+ bg = (t ? t->defbg : default_bg);
+
+ if (!has_default_colors) {
+ if (fg == -1)
+ fg = (t && t->deffg != -1 ? t->deffg : default_fg);
+ if (bg == -1)
+ bg = (t && t->defbg != -1 ? t->defbg : default_bg);
+ }
+
+ if (!color2palette || (fg == -1 && bg == -1))
+ return 0;
+ uint index = color_hash(fg, bg);
+ if (color2palette[index] == 0) {
+ int oldfg, oldbg;
+ for (;;) {
+ if (++color_pair_current >= color_pairs_max)
+ color_pair_current = color_pairs_reserved + 1;
+ extended_pair_content(color_pair_current, &oldfg, &oldbg);
+ uint old_index = color_hash(oldfg, oldbg);
+ if (color2palette[old_index] >= 0) {
+ if (init_extended_pair(color_pair_current, fg, bg) == OK) {
+ color2palette[old_index] = 0;
+ color2palette[index] = color_pair_current;
+ }
+ break;
+ }
+ }
+ }
+
+ int color_pair = color2palette[index];
+ return color_pair >= 0 ? color_pair : -color_pair;
+}
+
+int
+vt_color_reserve(int fg, int bg)
+{
+ if (!color2palette || fg >= COLORS || bg >= COLORS)
+ return 0;
+
+ if (!has_default_colors && fg == -1)
+ fg = default_fg;
+ if (!has_default_colors && bg == -1)
+ bg = default_bg;
+ if (fg == -1 && bg == -1)
+ return 0;
+
+ uint index = color_hash(fg, bg);
+ if (color2palette[index] >= 0) {
+ if (init_extended_pair(color_pairs_reserved + 1, fg, bg) == OK)
+ color2palette[index] = -(++color_pairs_reserved);
+ }
+ int color_pair = color2palette[index];
+ return color_pair >= 0 ? color_pair : -color_pair;
+}
+
+static
+void
+init_colors(void)
+{
+ extended_pair_content(0, &default_fg, &default_bg);
+ if (default_fg == -1)
+ default_fg = COLOR_WHITE;
+ if (default_bg == -1)
+ default_bg = COLOR_BLACK;
+ has_default_colors = (use_default_colors() == OK);
+ color_pairs_max = MIN(MAX_COLOR_PAIRS, SHRT_MAX);
+
+ if (COLORS)
+ color2palette = calloc((COLORS + 2) * (COLORS + 2), sizeof(int));
+ /*
+ * XXX: On undefined color-pairs NetBSD curses pair_content() set fg
+ * and bg to default colors while ncurses set them respectively to
+ * 0 and 0. Initialize all color-pairs in order to have consistent
+ * behaviour despite the implementation used.
+ */
+ for (short i = 1; i < color_pairs_max; i++)
+ init_extended_pair(i, 0, 0);
+
+ vt_color_reserve(COLOR_WHITE, COLOR_BLACK);
+}
+
+void
+vt_init(void)
+{
+ init_colors();
+ is_utf8_locale();
+ char *term = getenv("DVTM_TERM");
+ if (!term)
+ term = "dvtm";
+
+ snprintf(vt_term, sizeof vt_term, "%s%s", term, COLORS >= 256 ? "-256color" : "");
+}
+
+void
+vt_keytable_set(const char * const keytable_overlay[], int count)
+{
+ for (int k = 0; k < count && k < KEY_MAX; k++) {
+ const char *keyseq = keytable_overlay[k];
+ if (keyseq)
+ keytable[k] = keyseq;
+ }
+}
+
+void
+vt_shutdown(void)
+{
+ free(color2palette);
+}
+
+void
+vt_title_handler_set(Vt *t, vt_title_handler_t handler)
+{
+ t->title_handler = handler;
+}
+
+void
+vt_urgent_handler_set(Vt *t, vt_urgent_handler_t handler)
+{
+ t->urgent_handler = handler;
+}
+
+void
+vt_data_set(Vt *t, void *data)
+{
+ t->data = data;
+}
+
+void *
+vt_data_get(Vt *t)
+{
+ return t->data;
+}
+
+bool
+vt_cursor_visible(Vt *t)
+{
+ return t->buffer->scroll_below ? false : !t->curshid;
+}
+
+pid_t
+vt_pid_get(Vt *t)
+{
+ return t->pid;
+}
+
+size_t
+vt_content_get(Vt *t, char **buf, bool colored)
+{
+ Buffer *b = t->buffer;
+ int lines = b->scroll_above + b->scroll_below + b->rows + 1;
+ size_t size = lines * ((b->cols + 1) * ((colored ? 64 : 0) + MB_CUR_MAX));
+ mbstate_t ps;
+ memset(&ps, 0, sizeof(ps));
+
+ if (!(*buf = malloc(size)))
+ return 0;
+
+ char *s = *buf;
+ Cell *prev_cell = nil;
+
+ for (Row *row = buffer_row_first(b); row; row = buffer_row_next(b, row)) {
+ size_t len = 0;
+ char *last_non_space = s;
+ for (int col = 0; col < b->cols; col++) {
+ Cell *cell = row->cells + col;
+ if (colored) {
+ int esclen = 0;
+ if (!prev_cell || cell->attr != prev_cell->attr) {
+ attr_t attr = cell->attr << NCURSES_ATTR_SHIFT;
+ esclen = sprintf(s, "\033[0%s%s%s%s%s%sm",
+ attr & A_BOLD ? ";1" : "",
+ attr & A_DIM ? ";2" : "",
+ attr & A_UNDERLINE ? ";4" : "",
+ attr & A_BLINK ? ";5" : "",
+ attr & A_REVERSE ? ";7" : "",
+ attr & A_INVIS ? ";8" : "");
+ if (esclen > 0)
+ s += esclen;
+ }
+ if (!prev_cell || cell->fg != prev_cell->fg || cell->attr != prev_cell->attr) {
+ if (cell->fg == -1)
+ esclen = sprintf(s, "\033[39m");
+ else
+ esclen = sprintf(s, "\033[38;5;%dm", cell->fg);
+ if (esclen > 0)
+ s += esclen;
+ }
+ if (!prev_cell || cell->bg != prev_cell->bg || cell->attr != prev_cell->attr) {
+ if (cell->bg == -1)
+ esclen = sprintf(s, "\033[49m");
+ else
+ esclen = sprintf(s, "\033[48;5;%dm", cell->bg);
+ if (esclen > 0)
+ s += esclen;
+ }
+ prev_cell = cell;
+ }
+ if (cell->text) {
+ len = wcrtomb(s, cell->text, &ps);
+ if (len > 0)
+ s += len;
+ last_non_space = s;
+ } else if (len) {
+ len = 0;
+ } else {
+ *s++ = ' ';
+ }
+ }
+
+ s = last_non_space;
+ *s++ = '\n';
+ }
+
+ return s - *buf;
+}
+
+int
+vt_content_start(Vt *t)
+{
+ return t->buffer->scroll_above;
+}
diff --git a/sys/cmd/dvtm/window.c b/sys/cmd/dvtm/window.c
index 44690c9..fec3997 100644
--- a/sys/cmd/dvtm/window.c
+++ b/sys/cmd/dvtm/window.c
@@ -1,15 +1,13 @@
#include <u.h>
#include <libn.h>
-#include "buffer.h"
+#include "term.h"
typedef struct Rect Rect;
-typedef struct Window Window;
-/* origin upper left corner */
struct Rect
{
- int r0, c0, rows, cols;
+ int top, left, rows, cols;
};
struct Window
@@ -17,26 +15,30 @@ struct Window
Buffer buffer[2], *buf;
Rect area; /* on screen */
Pen pen, spen; /* current and saved pen */
- struct {
- uint visible : 1;
- int row, col; /* saved cursor row/colmn (zero based) */
- int srow, scol; /* saved cursor row/colmn (zero based) */
- } c;
-}
+ uint curvis : 1;
+ uint damage : 1;
+};
/* functions */
Window *
-makewindow(
+makewindow(Window *root, Rect area, int history)
+{
+ Window *w;
+ w = calloc(1, sizeof(*w));
+ if (!w)
+ panicf("out of memory");
- t->pen = (Pen) {
+ w->pen = (Pen) {
.state = PenNormal,
.col = {-1, -1},
};
- t->buffer = &t->buf[0];
- if (!binit(&t->buf[0], rows, cols, size) ||
- !binit(&t->buf[1], rows, cols, 0)) {
- free(t);
+ if (!binit(w->buffer+0, area.rows, area.cols, history) ||
+ !binit(w->buffer+1, area.rows, area.cols, 0)) {
+ free(w);
return nil;
}
+ w->buf = w->buffer;
+ return w;
+}
diff --git a/sys/cmd/term/term.c b/sys/cmd/term/term.c
index c5212b3..6a27a10 100644
--- a/sys/cmd/term/term.c
+++ b/sys/cmd/term/term.c
@@ -1097,9 +1097,7 @@ tdefcolor(int *attr, int *npar, int l)
switch (attr[*npar + 1]) {
case 2: /* direct color in RGB space */
if (*npar + 4 >= l) {
- fprintf(stderr,
- "erresc(38): Incorrect number of parameters (%d)\n",
- *npar);
+ fprintf(stderr, "erresc(38): Incorrect number of parameters (%d)\n", *npar);
break;
}
r = attr[*npar + 2];
@@ -1107,16 +1105,14 @@ tdefcolor(int *attr, int *npar, int l)
b = attr[*npar + 4];
*npar += 4;
if (!BETWEEN(r, 0, 255) || !BETWEEN(g, 0, 255) || !BETWEEN(b, 0, 255))
- fprintf(stderr, "erresc: bad rgb color (%u,%u,%u)\n",
- r, g, b);
+ fprintf(stderr, "erresc(38): bad rgb color (%u,%u,%u)\n", r, g, b);
else
idx = TRUECOLOR(r, g, b);
break;
case 5: /* indexed color */
if (*npar + 2 >= l) {
fprintf(stderr,
- "erresc(38): Incorrect number of parameters (%d)\n",
- *npar);
+ "erresc(38): Incorrect number of parameters (%d)\n", *npar);
break;
}
*npar += 2;
@@ -1130,8 +1126,7 @@ tdefcolor(int *attr, int *npar, int l)
case 3: /* direct color in CMY space */
case 4: /* direct color in CMYK space */
default:
- fprintf(stderr,
- "erresc(38): gfx attr %d unknown\n", attr[*npar]);
+ fprintf(stderr, "erresc(38): gfx attr %d unknown\n", attr[*npar]);
break;
}
@@ -1564,8 +1559,7 @@ csihandle(void)
break;
case 'n': /* DSR – Device Status Report (cursor position) */
if (csiescseq.arg[0] == 6) {
- len = snprintf(buf, sizeof(buf), "\033[%i;%iR",
- term.c.y+1, term.c.x+1);
+ len = snprintf(buf, sizeof(buf), "\033[%i;%iR", term.c.y+1, term.c.x+1);
ttywrite(buf, len, 0);
}
break;