From: Michael D. Lowis Date: Thu, 23 Feb 2023 02:46:44 +0000 (-0500) Subject: all tools in one place X-Git-Url: https://git.mdlowis.com/?a=commitdiff_plain;h=366aeea3687748879bf5a01974cfed202a37fe84;p=proto%2Faos.git all tools in one place --- diff --git a/bin/edit.c b/bin/edit.c index 3db827f..49e8b52 100644 --- a/bin/edit.c +++ b/bin/edit.c @@ -1,21 +1,185 @@ -#include +#include +#include +#include +#include +#include +#include -char* Usage = "edit [FLAGS...] [FILES...]"; +Atom XA_REGISTRAR, XA_OPEN, XA_DONE; -Option_T Options[] = { - { .s = 'h', .l = "help", .a = 0, .d = "print this help message" }, - { .s = 'v', .l = "verbose", .a = 0, .d = "enable verbose mode" }, - { .s = 'f', .l = "flag", .a = 1, .d = "flag with an arg" }, - { .s = 'F', .l = "noflag", .a = 0, .d = "flag without an arg" }, - { .s = 'a', .l = "noflag", .a = 0, .d = "flag without an arg" }, - { .s = 'b', .l = "noflag", .a = 0, .d = "flag without an arg" }, - { .s = 'c', .l = "noflag", .a = 0, .d = "flag without an arg" }, - {0} -}; +typedef struct XConf { + int state, fd, screen, width, height, mods; + Window root; + Display* display; + Visual* visual; + Colormap colormap; + unsigned depth; + Window self; +} XConf; + +char* EditCmd[] = { "editor", "-l", 0, 0, 0 }; + +char* abspath(char* path) +{ + char* ap = realpath(path, NULL); + if (!ap) + { + ap = strdup(path); + } + return ap; +} + +int spawn(char* cmd) +{ + int pid = fork(); + if (pid == 0) + { + exit(execvp(cmd, (char*[]){cmd, 0})); + } + return pid; +} + +int x11_init(XConf* x) +{ + int ret = -1; + /* open the X display and get basic attributes */ + if ( (x->display = XOpenDisplay(0)) ) + { + x->root = DefaultRootWindow(x->display); + XWindowAttributes wa; + XGetWindowAttributes(x->display, x->root, &wa); + x->visual = wa.visual; + x->colormap = wa.colormap; + x->screen = DefaultScreen(x->display); + x->depth = DefaultDepth(x->display, x->screen); + ret = 0; + } + return ret; +} + +void x11_mkwin(XConf* x, int width, int height, int evmask) +{ + /* create the main window */ + x->width = width, x->height = height; + XSetWindowAttributes attr; + attr.event_mask = evmask + | EnterWindowMask + | ExposureMask + | VisibilityChangeMask + | StructureNotifyMask + ; + x->self = XCreateWindow( + x->display, x->root, 0, 0, x->width, x->height, 0, x->depth, InputOutput, x->visual, + CWEventMask, + &attr); + + /* register interest in the delete window message */ + Atom wmDeleteMessage = XInternAtom(x->display, "WM_DELETE_WINDOW", False); + XSetWMProtocols(x->display, x->self, &wmDeleteMessage, 1); +} + +Window start_registrar(XConf* x) +{ + /* launch registrar if it isn't yet running */ + if (None == XGetSelectionOwner(x->display, XA_REGISTRAR)) + { + if (spawn("registrar") > 0) + { + sleep(1); + } + } + return XGetSelectionOwner(x->display, XA_REGISTRAR); +} + +void prop_set(XConf* x, Window win, char* prop, char* value) +{ + Atom xa_prop = XInternAtom(x->display, prop, False); + XChangeProperty( + x->display, win, xa_prop, XA_STRING, 8, PropModeReplace, + (const unsigned char *)value, strlen(value)+1); +} + +void edit_file(XConf* x, Window registrar, char* path, char* addr, int force) +{ + char host[8192]; + path = abspath(path); + prop_set(x, x->self, "FILE", path); + prop_set(x, x->self, "ADDR", addr); + if (!gethostname(host, sizeof(host))) + { + prop_set(x, x->self, "HOST", host); + } + XChangeProperty( + x->display, registrar, XA_OPEN, XA_WINDOW, 32, PropModeAppend, + (const unsigned char *)&(x->self), 1); + EditCmd[2] = addr, EditCmd[3] = path; + if (force) + { + if (!fork()) + exit(execvp(EditCmd[0], EditCmd)); + } + else + { + /* wait for the "done" message */ + for (XEvent e;;) + { + XNextEvent(x->display, &e); + if (e.type == ClientMessage) + { + if (e.xclient.message_type == XA_DONE) + { + break; + } + else if (e.xclient.message_type == XA_OPEN) + { + if (!fork()) + { + exit(execvp(EditCmd[0], EditCmd)); + } + break; + } + } + } + } + free(path); +} int main(int argc, char** argv) { - (void)argc; - (void)argv; - return 0; -} \ No newline at end of file + int ret = 0; + int force = 0; + if (argc == 0) + { + spawn("tide"); + } + else + { + XConf x = {0}; + x11_init(&x); + x11_mkwin(&x, 1, 1, 0); + XA_REGISTRAR = XInternAtom(x.display, "TIDE_REGISTRAR", PropertyChangeMask); + XA_OPEN = XInternAtom(x.display, "OPEN", 0); + XA_DONE = XInternAtom(x.display, "DONE", 0); + Window registrar = start_registrar(&x); + if (registrar != None) + { + /* Loop over files and send an OPEN message for each one. */ + for (int i = 0; i < argc; i++) + { + char* addr = strrchr(argv[i], ':'); + if (addr) + { + *addr = '\0', addr++; + } + edit_file(&x, registrar, argv[i], (addr ? addr : "0"), force); + } + XSync(x.display, False); + } + else + { + fprintf(stderr, "Failed to contact registrar.\n"); + ret = 1; + } + } + return ret; +} diff --git a/bin/editor/abspath.c b/bin/editor/abspath.c new file mode 100644 index 0000000..30e66e4 --- /dev/null +++ b/bin/editor/abspath.c @@ -0,0 +1,13 @@ +#define _XOPEN_SOURCE 700 +#include +#include + +char* abspath(char* path) +{ + char* ap = realpath(path, NULL); + if (!ap) + { + ap = strdup(path); + } + return ap; +} diff --git a/bin/editor/buf.c b/bin/editor/buf.c new file mode 100644 index 0000000..45b3403 --- /dev/null +++ b/bin/editor/buf.c @@ -0,0 +1,917 @@ +#include +#include "dbc.h" +#include "utf.h" +#include +#include +#include +#include + +#include "tide.h" +#include "config.h" + +#ifndef NDEBUG +static bool buf_valid(Buf* buf) +{ + return ( + (buf->contents.bufsize > 0) + && (buf->contents.bufstart != NULL) + && (buf->contents.bufend != NULL) + && (buf->contents.gapstart != NULL) + && (buf->contents.gapend != NULL) + && (buf->contents.bufstart < buf->contents.bufend) + && (buf->contents.gapstart <= buf->contents.gapend) + && (buf->contents.gapstart >= buf->contents.bufstart) + && (buf->contents.gapend >= buf->contents.bufstart) + && (buf->contents.gapstart <= buf->contents.bufend) + && (buf->contents.gapend <= buf->contents.bufend) + ); +} +#endif + +static void getcol(Buf* buf) +{ + require(buf != NULL); + Sel sel = buf->selection; + size_t pos = sel.end, curr = buf_bol(buf, pos); + for (sel.col = 0; curr < pos; curr = buf_byrune(buf, curr, 1)) + { + sel.col += runewidth(sel.col, buf_getrat(buf, curr)); + } + buf->selection = sel; +} + +static void setcol(Buf* buf) +{ + require(buf != NULL); + Sel sel = buf->selection; + size_t bol = buf_bol(buf, sel.end); + size_t curr = bol, len = 0, i = 0; + + /* determine the length of the line in columns */ + for (; !buf_iseol(buf, curr); curr++) + { + len += runewidth(len, buf_getrat(buf, curr)); + } + + /* iterate over the runes until we reach the target column */ + for (sel.end = bol, i = 0; i < sel.col && i < len;) + { + int width = runewidth(i, buf_getrat(buf, sel.end)); + sel.end = buf_byrune(buf, sel.end, 1); + if (sel.col >= i && sel.col < (i + width)) + { + break; + } + i += width; + } + buf->selection = sel; +} + +static Sel selswap(Sel sel) +{ + size_t off = sel.beg; + sel.beg = sel.end; + sel.end = off; + return sel; +} + +static Sel selget(Buf* buf) +{ + Sel sel = buf->selection; + return (sel.end < sel.beg ? selswap(sel) : sel); +} + +static void selset(Buf* buf, Sel sel) +{ + buf->selection = sel; + getcol(buf); +} + +static void putb(Buf* buf, char b, Sel* p_sel) +{ + range_add(&(buf->point), p_sel->end); + gapbuf_putb(&buf->contents, b, p_sel); +} + +static void putch(Buf* buf, char b, Sel* p_sel) +{ + if (b != '\r') + { + if (b == '\n' && DosLineFeed) + { + putb(buf, '\r', p_sel); + putb(buf, '\n', p_sel); + } + else + { + putb(buf, b, p_sel); + } + buf->status = MODIFIED; + } +} + +void buf_init(Buf* buf) +{ + require(buf != NULL); + + /* reset the state to defaults */ + gapbuf_init(&buf->contents); + buf->status = NORMAL; + buf->log.undo = NULL; + buf->log.redo = NULL; + buf->log.transid = -1; + buf->selection = (Sel){0,0,0}; + + ensure(buf_valid(buf)); +} + +void buf_setpath(Buf* buf, char* path) +{ + require(buf != NULL); + if (path) + { + free(buf->path); + buf->path = strdup(path); + ensure(buf->path != NULL); + } +} + +void buf_load(Buf* buf, char* path) +{ + require(buf != NULL); + if (path) + { + buf->path = strdup(path); + gapbuf_load(&buf->contents, buf->path); + + /* reset buffer state */ + buf->status = NORMAL; + buf_logclear(buf); + + /* use the EOL style of the first line to determine EOL style */ + DosLineFeed = (gapbuf_getb(&buf->contents, buf_eol(buf, 0)) == '\r'); + } + ensure(buf_valid(buf)); +} + +void buf_reload(Buf* buf) +{ + require(buf != NULL); + char* path = buf->path; + buf->path = NULL; + buf_init(buf); + buf_load(buf, path); +} + +static void trim_whitespace(Buf* buf) +{ + require(buf != NULL); + require(buf_end(buf) > 0); + + Sel sel = buf->selection; + bool swapped = (sel.beg > sel.end); + if (swapped) + { + sel = selswap(sel); + } + + unsigned prev = 1; + buf->selection.beg = buf->selection.end = 0; + buf_logstart(buf); + while (prev != buf->selection.end) + { + int r = gapbuf_getb(&buf->contents, buf->selection.end); + /* If we reached a newline, then delete whatever we have selected */ + if (r == '\r' || r == '\n') + { + buf->selection.beg = buf_byrune(buf, buf->selection.beg, +1); + Sel del = buf->selection; + sel.beg -= (del.beg < sel.beg ? (min(del.end, sel.beg) - del.beg) : 0); + sel.end -= (del.beg < sel.end ? (min(del.end, sel.end) - del.beg) : 0); + buf_del(buf); + } + + /* if current char is not whitespace, then shrink the selection */ + if (r != ' ' && r != '\t') + { + buf->selection.beg = buf->selection.end; + } + + /* move to the next character */ + prev = buf->selection.end; + buf->selection.end = buf_byrune(buf, buf->selection.end, +1); + } + buf_logstop(buf); + if (swapped) + { + sel = selswap(sel); + } + selset(buf, sel); +} + +int buf_save(Buf* buf, char* path) +{ + require(buf != NULL); + buf_setpath(buf, path); + if (buf_end(buf) > 0) + { + if (TrimOnSave && (buf_end(buf) > 0)) + { + trim_whitespace(buf); + } + + long nwrite = gapbuf_save(&buf->contents, buf->path); + buf->status = (nwrite >= 0 ? NORMAL : ERRORED); + + if (buf->status == NORMAL) + { + buf->log.save = buf->log.undo; + } + } + ensure(buf_valid(buf)); + return buf->status; +} + +/* Undo/Redo Operations + ******************************************************************************/ +void buf_logstart(Buf* buf) +{ + editlog_seqstart(&(buf->log)); +} + +void buf_logstop(Buf* buf) +{ + editlog_seqstop(&(buf->log)); +} + +void buf_logclear(Buf* buf) +{ + editlog_clear(&(buf->log)); +} + +void buf_lastins(Buf* buf) +{ + editlog_lastins(&(buf->log), &(buf->selection)); +} + +void buf_undo(Buf* buf) +{ + editlog_undo(buf); +} + +void buf_redo(Buf* buf) +{ + editlog_redo(buf); +} + +/* Basic Operations and Accessors + ******************************************************************************/ +size_t buf_end(Buf* buf) +{ + return gapbuf_end(&buf->contents); +} + +int buf_getrat(Buf* buf, size_t off) +{ + require(buf != NULL); + size_t rlen = 0; + Rune rune = 0; + if (gapbuf_getb(&buf->contents, off) == '\r' && gapbuf_getb(&buf->contents, off+1) == '\n') + { + rune = '\n'; + } + else + { + while ( !utf8decode(&rune, &rlen, gapbuf_getb(&buf->contents, off++)) ) + { + } + } + return rune; +} + +void buf_putc(Buf* buf, int c) +{ + require(buf != NULL); + char utf8buf[UTF_MAX+1] = {0}; + (void)utf8encode(utf8buf, c); + buf_puts(buf, utf8buf); + ensure(buf_valid(buf)); +} + +void buf_puts(Buf* buf, char* s) +{ + require(buf != NULL); + buf_del(buf); + size_t beg = buf_selbeg(buf); + if (s && *s) + { + for (; *s; s++) + { + putch(buf, *s, &(buf->selection)); + if (buf->oninsert) + { + buf->oninsert(*s); + } + } + editlog_add(buf, beg, buf_selend(buf), NULL); + } + ensure(buf_valid(buf)); +} + +int buf_getc(Buf* buf) +{ + require(buf != NULL); + return buf_getrat(buf, buf->selection.end); +} + +char* buf_gets(Buf* buf) +{ + require(buf != NULL); + Sel sel = selget(buf); + size_t nbytes = sel.end - sel.beg; + char* str = malloc(nbytes+1); + for (size_t i = 0; i < nbytes; i++) + { + str[i] = gapbuf_getb(&buf->contents, sel.beg + i); + } + str[nbytes] = '\0'; + return str; +} + +char* buf_getsat(Buf* buf, size_t beg, size_t end) +{ + require(buf != NULL); + Sel sel = selget(buf); + buf->selection = (Sel){ .beg = beg, .end = end }; + char* str = buf_gets(buf); + buf->selection = sel; + return str; +} + +void buf_del(Buf* buf) +{ + require(buf != NULL); + Sel sel = selget(buf); + size_t nbytes = sel.end - sel.beg; + if (nbytes > 0) + { + /* perform the delete */ + buf->status = MODIFIED; + char* str = buf_gets(buf); + range_del(&(buf->point), sel.beg, sel.end); + gapbuf_del(&buf->contents, sel.beg, nbytes); + sel.end = sel.beg; + buf->selection = sel; + editlog_add(buf, sel.beg, sel.end, str); + } + ensure(buf_valid(buf)); +} + +/* Positional and Movement Operations + ******************************************************************************/ +static Rune nextrune(Buf* buf, size_t off, int move, bool (*testfn)(Rune)) +{ + bool ret = false; + size_t end = buf_end(buf); + if (move < 0 && off > 0) + { + ret = testfn(buf_getrat(buf, off-1)); + } + else if (move > 0 && off < end) + { + ret = testfn(buf_getrat(buf, off+1)); + } + return ret; +} + +static void selline(Buf* buf) +{ + Sel sel = selget(buf); + sel.beg = buf_bol(buf, sel.end); + sel.end = buf_eol(buf, sel.end); + sel.end = buf_byrune(buf, sel.end, RIGHT); + selset(buf, sel); +} + +static void selblock(Buf* buf, Rune first, Rune last) +{ + Sel sel = selget(buf); + int balance = 0, dir = 0; + size_t beg, end = sel.end; + + /* figure out which end of the block we're starting at */ + if (buf_getrat(buf, end) == first) + { + dir = +1, balance++, beg = end++; + } + else if (buf_getrat(buf, end) == last) + { + dir = -1, balance--, beg = end--; + } + else + { + /* do nothing */ + } + + if (dir != 0) + { + /* scan for a blanced set of braces */ + while (true) + { + if (buf_getrat(buf, end) == first) + { + balance++; + } + else if (buf_getrat(buf, end) == last) + { + balance--; + } + + if (balance == 0 || end >= buf_end(buf) || end == 0) + { + break; + } + else + { + end += dir; + } + } + + /* update the selection if we found a block */ + if (balance == 0) + { + if (end > beg) + { + beg++; + } + else + { + end++; + } + buf->selection.beg = beg; + buf->selection.end = end; + } + } +} + +static int bytes_match(Buf* buf, size_t mbeg, size_t mend, char* str) +{ + int ret = 0; + for (; *str && mbeg < mend; str++, mbeg++) + { + int cmp = *str - gapbuf_getb(&buf->contents, mbeg); + if (cmp != 0) + { + ret = cmp; + break; + } + } + return ret; +} + +bool buf_isbol(Buf* buf, size_t off) +{ + require(buf != NULL); + size_t bol = buf_bol(buf, off); + return (bol == off); +} + +bool buf_iseol(Buf* buf, size_t off) +{ + require(buf != NULL); + Rune r = buf_getrat(buf, off); + return (r == '\r' || r == '\n'); +} + +size_t buf_bol(Buf* buf, size_t off) +{ + require(buf != NULL); + for (; !buf_iseol(buf, off-1); off--) + { + } + return off; +} + +size_t buf_eol(Buf* buf, size_t off) +{ + require(buf != NULL); + for (; !buf_iseol(buf, off); off++) + { + } + return off; +} + +void buf_selword(Buf* buf, bool (*isword)(Rune)) +{ + require(buf != NULL); + Sel sel = selget(buf); + for (; isword(buf_getrat(buf, sel.beg-1)); sel.beg--) + { + } + for (; isword(buf_getrat(buf, sel.end)); sel.end++) + { + } + buf->selection = sel; +} + +void buf_selall(Buf* buf) +{ + require(buf != NULL); + buf->selection = (Sel){ .beg = 0, .end = buf_end(buf) }; +} + +static bool selquote(Buf* buf, Rune c) +{ + bool selected = false; + Rune curr = buf_getc(buf); + size_t nextoff = buf_byrune(buf, buf->selection.end, RIGHT); + Rune prev = buf_getrat(buf, buf_byrune(buf, buf->selection.end, LEFT)); + Rune next = buf_getrat(buf, buf_byrune(buf, buf->selection.end, RIGHT)); + if (prev == c || curr == c) + { + size_t bend = buf_end(buf); + buf->selection.beg = buf->selection.end = (prev == c ? buf->selection.end : nextoff); + size_t selend = buf->selection.end; + for (; selend < bend && buf_getrat(buf, selend) != c; selend = buf_byrune(buf, selend, RIGHT)) + { + } + if (buf_getrat(buf, selend) == c) + { + buf->selection.end = selend; + } + selected = true; + } + else if (next == c) + { + buf->selection.beg = buf->selection.end; + buf->selection.end = nextoff; + size_t selbeg = buf->selection.beg; + for (; selbeg > 0 && buf_getrat(buf, selbeg) != c; selbeg = buf_byrune(buf, selbeg, LEFT)) + { + } + if (buf_getrat(buf, selbeg) == c) + { + buf->selection.beg = buf_byrune(buf, selbeg, RIGHT); + } + selected = true; + } + return selected; +} + +void buf_selctx(Buf* buf, bool (*isword)(Rune)) +{ + require(buf != NULL); + size_t bol = buf_bol(buf, buf->selection.end); + Rune curr = buf_getc(buf); + if (curr == '(' || curr == ')') + { + selblock(buf, '(', ')'); + } + else if (curr == '[' || curr == ']') + { + selblock(buf, '[', ']'); + } + else if (curr == '{' || curr == '}') + { + selblock(buf, '{', '}'); + } + else if (curr == '<' || curr == '>') + { + selblock(buf, '<', '>'); + } + else if (buf->selection.end == bol || curr == '\n') + { + selline(buf); + } + else if (selquote(buf, '"') || selquote(buf, '`') || selquote(buf, '\'')) + { + ; /* condition performs selection */ + } + else if (isword(curr)) + { + buf_selword(buf, isword); + } + else + { + buf_selword(buf, risbigword); + } + getcol(buf); +} + +size_t buf_byrune(Buf* buf, size_t pos, int count) +{ + require(buf != NULL); + int move = (count < 0 ? -1 : 1); + count *= move; // remove the sign if there is one + for (; count > 0; count--) + { + if (pos > 0 && move < 0) + { + if (gapbuf_getb(&buf->contents, pos-2) == '\r' && gapbuf_getb(&buf->contents, pos-1) == '\n') + { + pos -= 2; + } + else + { + pos += move; + } + } + else if (pos < buf_end(buf) && move > 0) + { + if (gapbuf_getb(&buf->contents, pos) == '\r' && gapbuf_getb(&buf->contents, pos+1) == '\n') + { + pos += 2; + } + else + { + pos += move; + } + } + } + return pos; +} + +static size_t byword(Buf* buf, size_t off, int count) +{ + require(buf != NULL); + int move = (count < 0 ? -1 : 1); + count *= move; // remove the sign if there is one + + for (; count > 0; count--) + { + while (nextrune(buf, off, move, risblank)) + { + off = buf_byrune(buf, off, move); + } + + if (nextrune(buf, off, move, risword)) + { + while (nextrune(buf, off, move, risword)) + { + off = buf_byrune(buf, off, move); + } + + if (move > 0) + { + off = buf_byrune(buf, off, move); + } + } + else + { + off = buf_byrune(buf, off, move); + } + } + return off; +} + +static size_t byline(Buf* buf, size_t pos, int count) +{ + require(buf != NULL); + int move = (count < 0 ? -1 : 1); + count *= move; // remove the sign if there is one + for (; count > 0; count--) + { + if (move < 0) + { + if (pos > buf_eol(buf, 0)) + { + pos = buf_byrune(buf, buf_bol(buf, pos), LEFT); + } + } + else + { + size_t next = buf_byrune(buf, buf_eol(buf, pos), RIGHT); + if (next <= buf_end(buf)) + { + pos = next; + } + } + } + return pos; +} + +bool buf_findstr(Buf* buf, int dir, char* str) +{ + require(buf != NULL); + bool found = false; + size_t len = strlen(str); + size_t start = buf->selection.beg; + size_t mbeg = (start + dir); + size_t mend = (mbeg + len); + size_t nleft = buf_end(buf); + for (; (mbeg != start) && nleft; nleft--) + { + if ((gapbuf_getb(&buf->contents, mbeg) == str[0]) && + (gapbuf_getb(&buf->contents, mend-1) == str[len-1]) && + (0 == bytes_match(buf, mbeg, mend, str))) + { + buf->selection.beg = mbeg, buf->selection.end = mend; + found = true; + break; + } + mbeg += dir, mend += dir; + if (mend > buf_end(buf)) + { + mbeg = (dir < 0 ? buf_end(buf)-len : 0), mend = mbeg + len; + } + } + return found; +} + +void buf_setln(Buf* buf, size_t line) +{ + require(buf != NULL); + size_t curr = 0, end = buf_end(buf); + while (line > 1 && curr < end) + { + size_t next = byline(buf, curr, DOWN); + if (curr == next) + { + break; + } + line--, curr = next; + } + buf->selection.beg = curr; + buf->selection.end = curr; +} + +void buf_getln(Buf* buf, size_t* begln, size_t* endln) +{ + require(buf != NULL); + size_t line = 1, curr = 0, end = buf_end(buf); + size_t sbeg = buf_selbeg(buf), send = buf_selend(buf); + while (curr < end) + { + size_t next = byline(buf, curr, DOWN); + + if (curr <= sbeg && sbeg < next) + { + *begln = line, *endln = line; + } + + if (curr <= send && send < next) + { + *endln = line; + break; + } + + if (curr == next) + { + break; + } + line++; + curr = next; + } +} + +size_t buf_selbeg(Buf* buf) +{ + require(buf != NULL); + return selget(buf).beg; +} + +size_t buf_selend(Buf* buf) +{ + require(buf != NULL); + return selget(buf).end; +} + +size_t buf_selsz(Buf* buf) +{ + require(buf != NULL); + return (selget(buf).end - selget(buf).beg); +} + +void buf_selln(Buf* buf) +{ + require(buf != NULL); + /* Expand the selection to completely select the lines covered */ + Sel sel = selget(buf); + sel.beg = buf_bol(buf, sel.beg); + if (!buf_iseol(buf, sel.end-1) || sel.end == sel.beg) + { + sel.end = buf_eol(buf, sel.end); + sel.end = buf_byrune(buf, sel.end, RIGHT); + } + selset(buf, sel); +} + +void buf_selclr(Buf* buf, int dir) +{ + require(buf != NULL); + Sel sel = selget(buf); + if (dir > 0) + { + sel.beg = sel.end; + } + else + { + sel.end = sel.beg; + } + selset(buf, sel); +} + +bool buf_insel(Buf* buf, size_t off) +{ + require(buf != NULL); + return (off >= buf_selbeg(buf) && off < buf_selend(buf)); +} + +bool buf_inpoint(Buf* buf, size_t off) +{ + require(buf != NULL); + bool empty_point = (buf->point.beg == buf->point.end) && (off == buf->point.beg); + bool in_range = (off >= buf->point.beg && off < buf->point.end); + return (buf->oninsert && (empty_point || in_range)); +} + +char* buf_fetch(Buf* buf, bool (*isword)(Rune), size_t off) +{ + require(buf != NULL); + require(isword != NULL); + char* str = NULL; + Sel prev = buf->selection; + if (!buf_insel(buf, off)) + { + buf->selection = (Sel){ .beg = off, .end = off }; + buf_selword(buf, isword); + } + str = buf_gets(buf); + buf->selection = prev; + return str; +} + +size_t buf_moveby(Buf* buf, int bything, size_t pos, int count) +{ + size_t newpos = pos; + switch (bything) + { + case BY_WORD: + newpos = byword(buf, pos, count); + break; + case BY_LINE: + newpos = byline(buf, pos, count); + break; + case BY_RUNE: + default: + newpos = buf_byrune(buf, pos, count); + break; + } + return newpos; +} + +void buf_selmove(Buf* buf, bool extsel, int move, int bything) +{ + if (buf_selsz(buf) && !extsel) + { + buf_selclr(buf, move); + if (bything == BY_LINE) + { + /* we perform one less move if we are going down but selection + ends on the beginning of the next line */ + if (move > 0 && buf_isbol(buf, buf_selend(buf))) + { + move--; + } + buf->selection.end = buf_moveby(buf, bything, buf->selection.end, move); + } + } + else + { + buf->selection.end = buf_moveby(buf, bything, buf->selection.end, move); + } + + if (bything == BY_LINE) + { + setcol(buf); // set cursor to correct column + } + else + { + getcol(buf); // update column tracking with cursor position + } + + /* collapse the selection if necessary, without updating column */ + if (!extsel) + { + Sel sel = selget(buf); + if (move > 0) + { + sel.beg = sel.end; + } + else + { + sel.end = sel.beg; + } + buf->selection = sel; + } +} + +void buf_selmoveto(Buf* buf, bool extsel, size_t off) +{ + buf->selection.end = (off > buf_end(buf) ? buf_end(buf) : off); + if (!extsel) + { + buf->selection.beg = buf->selection.end; + } + getcol(buf); +} diff --git a/bin/editor/config.h b/bin/editor/config.h new file mode 100644 index 0000000..e1ab943 --- /dev/null +++ b/bin/editor/config.h @@ -0,0 +1,100 @@ +/** @file */ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunused-variable" + +enum { Off = 0, On = 1 }; + +enum { /* Color Names */ + Red, Purple, Orange, + EditBg, EditFg, EditSel, + TagsBg, TagsFg, TagsSel, + ScrollBg, ScrollFg, + VerBdr, HorBdr, Point, + ClrCount +}; + +#define CMD_TIDE "!tide" +#define CMD_PICKFILE "!pickfile ." +#define CMD_COMPLETE "picktag print tags" +#define CMD_FCOMPLETE "fcomplete" +#define CMD_GOTO_TAG "!picktag fetch tags" + +/* Command used to open file in editor at a given line */ +static char* EditCmd[] = { "tide", "-l", 0, 0, 0 }; + +/* The shell: Filled in with $SHELL. Used to execute commands */ +static char* ShellCmd[] = { 0, "-c", 0, 0 }; + +/* Sed command used to execute commands marked with ':' sigil */ +static char* SedCmd[] = { "sed", "-Ee", 0, 0 }; + +/* Command used to fetch some text based on a set of rules */ +static char* FetchCmd[] = { "fetch", 0, 0 }; + +/* Default tag region text in editor windows */ +static char* TagString = "Del Put Get | Font Tabs Eol | x+ w+ !st !term | Find "; + +/* List of font patterns available to the editor */ +static char* Fonts[2] = { + "Verdana:size=12", + "Liberation Mono:size=12" +}; + +#ifdef INCLUDE_DEFS +/* Integer config options */ +int WinWidth = 640; /* default window width */ +int WinHeight = 480; /* default window height */ +int ScrollWidth = 8; /* width in pixels of the scrollbar */ +int Timeout = 100; /* number of milliseconds to wait before poll() times out */ +int TabWidth = 4; /* number of spaces tab characters will be expanded to */ +int ScrollBy = 1; /* number of lines to scroll by on mouse wheel events */ +int ClickTime = 500; /* number of milliseconds after a click wehre multi-clicks will be recognized */ +int CopyIndent = On; /* whether indentation should be copied from previous line or not */ +int TrimOnSave = On; /* whether trailing whitespace should be removed on save or not */ +int ExpandTabs = On; /* whether tab characters should be expanded to spaces or not */ +int DosLineFeed = Off; /* use \r\n line endings by default */ +int Margin = 50; +#else +extern int WinWidth, WinHeight, ScrollWidth, Timeout, TabWidth, ScrollBy, + ClickTime, CopyIndent, TrimOnSave, ExpandTabs, DosLineFeed, Margin; +#endif + +static int Palette[ClrCount] = { +#if 0 /* Original Acme Colors */ + [EditBg] = 0xFFFFEA, /* Edit region background */ + [EditFg] = 0x000000, /* Edit region text */ + [EditSel] = 0xEEEE9E, /* Edit region selection */ + [TagsBg] = 0xEAFFFF, /* Tags region background */ + [TagsFg] = 0x000000, /* Tags region text */ + [TagsSel] = 0x9EEEEE, /* Tags region selection */ + [ScrollBg] = 0x99994C, /* Scroll region background */ + [ScrollFg] = 0xFFFFEA, /* Scroll region foreground */ + [VerBdr] = 0x99994C, /* Vertical border */ + [HorBdr] = 0x000000, /* Horizontal border */ + [Point] = 0xEFEFDA, /* Point background */ + [Purple] = 0x6666CC, /* Purple */ + [Red] = 0xCC0000, /* Red */ + [Orange] = 0xFF7700, /* Orange */ +#else + [EditBg] = 0xF5F5F0, /* Edit region background */ + [EditFg] = 0x222222, /* Edit region text */ + [EditSel] = 0xAACCEE, /* Edit region selection */ + + [TagsBg] = 0xF5F5F0, /* Tags region background */ + [TagsFg] = 0x222222, /* Tags region text */ + [TagsSel] = 0xAACCEE, /* Tags region selection */ + + [ScrollBg] = 0x959590, /* Scroll region background */ + [ScrollFg] = 0xF5F5F0, /* Scroll region foreground */ + + [VerBdr] = 0x959590, /* Vertical border */ + [HorBdr] = 0x222222, /* Horizontal border */ + + [Point] = 0xD5D5D0, /* Point background */ + [Purple] = 0x6666CC, /* Purple */ + [Red] = 0xCC0000, /* Red */ + [Orange] = 0xFF7700, /* Orange */ +#endif +}; + +#pragma GCC diagnostic pop diff --git a/bin/editor/dbc.c b/bin/editor/dbc.c new file mode 100644 index 0000000..fec4af5 --- /dev/null +++ b/bin/editor/dbc.c @@ -0,0 +1,84 @@ +#include +#include "dbc.h" +#include +#include +#include "io.h" + +#ifndef NDEBUG + +static char ErrMsg[8192]; +static char* DumpPath = NULL; +static void (*DumpFn)(FILE*) = NULL; + +static void dump_and_abort(char* msg) +{ + telem_send("%s", msg); + FILE* f = (DumpPath ? fopen(DumpPath, "w") : stderr); + fprintf(f, "%s\n\n", msg); + if (DumpFn) + { + DumpFn(f); + } + fprintf(f, "\n%s\n", msg); + fclose(f); + _Exit(1); +} + +static void handle_signal(int sig) +{ + if (SIGABRT == sig) + { + dump_and_abort("SIGABRT - Process abort signal"); + } + else if (SIGBUS == sig) + { + dump_and_abort("SIGBUS - Access to an undefined portion of a memory object"); + } + else if (SIGFPE == sig) + { + dump_and_abort("SIGFPE - Erroneous arithmetic operation"); + } + else if (SIGILL == sig) + { + dump_and_abort("SIGILL - Illegal instruction"); + } + else if (SIGSEGV == sig) + { + dump_and_abort("SIGSEGV - Invalid memory reference"); + } + else if (SIGALRM == sig) + { + dump_and_abort("SIGALRM - Watchdog timer expired"); + } +} + +void dbc_init(char* path, void (*dumpfn)(FILE*)) +{ + DumpPath = path; + DumpFn = dumpfn; + signal(SIGABRT, handle_signal); + signal(SIGBUS, handle_signal); + signal(SIGFPE, handle_signal); + signal(SIGILL, handle_signal); + signal(SIGSEGV, handle_signal); +} + +void dbc_require(bool success, char* text, char* file, int line) +{ + if (!success) + { + snprintf(ErrMsg, sizeof(ErrMsg), "%s:%d: pre-condition failed (%s)", file, line, text); + dump_and_abort(ErrMsg); + } +} + +void dbc_ensure(bool success, char* text, char* file, int line) +{ + if (!success) + { + snprintf(ErrMsg, sizeof(ErrMsg), "%s:%d: post-condition failed (%s)", file, line, text); + dump_and_abort(ErrMsg); + } +} + +#endif diff --git a/bin/editor/dbc.h b/bin/editor/dbc.h new file mode 100644 index 0000000..5dc1575 --- /dev/null +++ b/bin/editor/dbc.h @@ -0,0 +1,16 @@ +/** @file */ +#define require(cond) \ + dbc_require(cond, #cond, __FILE__, __LINE__) + +#define ensure(cond) \ + dbc_ensure(cond, #cond, __FILE__, __LINE__) + +#ifndef NDEBUG + void dbc_init(char* path, void (*dumpfn)(FILE*)); + void dbc_require(bool success, char* text, char* file, int line); + void dbc_ensure(bool success, char* text, char* file, int line); +#else + #define dbc_init(a,b) ((void)0) + #define dbc_require(a,b,c,d) ((void)0) + #define dbc_ensure(a,b,c,d) ((void)0) +#endif diff --git a/bin/editor/draw.c b/bin/editor/draw.c new file mode 100644 index 0000000..11b79d5 --- /dev/null +++ b/bin/editor/draw.c @@ -0,0 +1,111 @@ +#include +#include "utf.h" + +#include "tide.h" +#include "config.h" + +void draw_rect(XConf* x, int color, int posx, int posy, int width, int height) +{ + x11_draw_rect(x, Palette[color], posx, posy, width, height); +} + +void draw_statbox(XConf* x, int status) +{ + draw_rect(x, VerBdr, ScrollWidth, 0, 1, x->height/4); + switch (status) { + case NORMAL: draw_rect(x, TagsBg, 0, 0, ScrollWidth, x->height/4); break; + case MODIFIED: draw_rect(x, Purple, 0, 0, ScrollWidth, x->height/4); break; + case OUTDATED: draw_rect(x, Orange, 0, 0, ScrollWidth, x->height/4); break; + case ERRORED: draw_rect(x, Red, 0, 0, ScrollWidth, x->height/4); break; + } +} + +int draw_hrule(XConf* x, drawcsr* csr) +{ + draw_rect(x, HorBdr, 0, csr->y + 1, x->width, 1); + csr->y += 2; + return (csr->y - 2); +} + +void draw_view(XConf* x, View* view, XftFont* font, size_t nrows, drawcsr* csr, int bg, int fg, int sel, bool csrsync) +{ + int nspecs = 0; + XftGlyphSpec* specs = NULL; + size_t fheight = font->height; + bool csr_drawn = false; + /* draw the view to the window */ + view_resize(view, (csr->w - csr->x - 2) - (2* Margin), nrows); + view_update(view); + draw_rect(x, bg, csr->x, csr->y, csr->w, ((nrows + 1) * fheight) + 9); + for (size_t i = 0; i < nrows; i++) + { + Row* row = view_getrow(view, i + view->index); + size_t posx = (csr->x + 2 + Margin), y = (csr->y + 2 + (i * fheight)); + if (row->off == buf_end(&(view->buffer))) + { + if (buf_end(&(view->buffer)) == buf_selend(&(view->buffer))) + { + draw_csr(x, view, fg, fheight, posx, y, csr_drawn); + } + break; + } + else + { + for (size_t i = 0; i < row->len; i++) + { + int rune = row->cols[i].rune; + if (rune == '\r' || rune == '\n' || rune == '\t') + rune = ' '; + if (buf_inpoint(&(view->buffer), row->cols[i].off)) + draw_rect(x, Point, posx, y, row->cols[i].width, fheight); + if (buf_insel(&(view->buffer), row->cols[i].off)) + draw_rect(x, sel, posx, y, row->cols[i].width, fheight); + if (row->cols[i].off == view->buffer.selection.end) + csr_drawn = draw_csr(x, view, fg, fheight, posx, y, csr_drawn); + if (csrsync && row->cols[i].off == view->buffer.selection.beg) + { + //XWarpPointer(x->display, None, x->self, 0, 0, x->width, x->height, posx + row->cols[i].width/2, y + fheight*3/4); + csrsync = false; + } + specs = realloc(specs, sizeof(XftGlyphSpec) * ++nspecs); + specs[nspecs-1].glyph = XftCharIndex(x->display, font, rune); + specs[nspecs-1].x = posx; + specs[nspecs-1].y = y + font->ascent; + posx += row->cols[i].width; + } + } + } + x11_draw_glyphs(x, Palette[fg], font, specs, nspecs); + csr->y += (nrows * fheight) + 3; + free(specs); +} + +bool draw_csr(XConf* x, View* view, int fg, size_t fheight, int posx, int posy, bool csrdrawn) +{ + if (!csrdrawn && !view_selsize(view)) + { + draw_rect(x, fg, posx-1, posy, 3, 3); + draw_rect(x, fg, posx, posy, 1, fheight); + draw_rect(x, fg, posx-1, posy+fheight-3, 3, 3); + csrdrawn = true; + } + return csrdrawn; +} + +void draw_scroll(XConf* x, drawcsr* csr, View* view, int divider) +{ + size_t bend = buf_end(&(view->buffer)); + if (bend == 0) bend = 1; + if (!view->rows || !view->nrows) return; + size_t vbeg = view->rows[0]->off; + size_t vend = view->rows[view->nrows-1]->off + view->rows[view->nrows-1]->len; + double scroll_vis = (double)(vend - vbeg) / (double)bend; + double scroll_off = ((double)vbeg / (double)bend); + size_t thumbreg = (csr->h - divider) + 4; + size_t thumboff = (size_t)((thumbreg * scroll_off) + divider); + size_t thumbsz = (size_t)(thumbreg * scroll_vis); + if (thumbsz < 5) thumbsz = 5; + draw_rect(x, VerBdr, ScrollWidth, divider + 2, 1, thumbreg); + draw_rect(x, ScrollBg, 0, divider + 2, ScrollWidth, thumbreg); + draw_rect(x, ScrollFg, 0, thumboff + 2, ScrollWidth, thumbsz); +} diff --git a/bin/editor/editlog.c b/bin/editor/editlog.c new file mode 100644 index 0000000..d016fc9 --- /dev/null +++ b/bin/editor/editlog.c @@ -0,0 +1,195 @@ +#include +#include "dbc.h" +#include "utf.h" + +#include "tide.h" +#include "config.h" + +static Log* mklog(Buf* buf, size_t beg, size_t end, char* data, Log* next) +{ + Log* log = calloc(1, sizeof(Log)); + log->transid = (buf->log.transid < 0 ? 0 : buf->log.transid); + log->beg = beg; + log->end = end; + log->data = data; + log->next = next; + return log; +} + +static void log_swap(Buf* buf, Log** src, Log** dest) +{ + Log* item = *src; + if (item) + { + *src = item->next; + buf->selection.beg = item->beg; + buf->selection.end = item->end; + if (item->data) + { + /* reinsert deleted bytes */ + for (char* s = item->data; s && *s; s++, item->end++) + { + range_add(&(buf->point), buf->selection.end); + gapbuf_putb(&buf->contents, *s, &(buf->selection)); + buf->status = MODIFIED; + } + free(item->data); + item->data = NULL; + buf->selection.beg = item->beg; + buf->selection.end = item->end; + } + else + { + /* delete the added bytes */ + Sel sel = buf->selection; + item->data = buf_gets(buf); + range_del(&(buf->point), item->beg, item->end); + gapbuf_del(&buf->contents, sel.beg, (item->end - item->beg)); + sel.end = sel.beg; + buf->selection = sel; + item->beg = sel.beg; + item->end = sel.end; + } + + /* push item onto destination stack */ + item->next = *dest; + *dest = item; + /* undo recursively if this is part of a transaction */ + if (*src && item->transid && item->transid == (*src)->transid) + { + log_swap(buf, src, dest); + } + + if (buf->log.save == buf->log.undo) + { + buf->status = NORMAL; + } + } +} + +static void log_clear(Log** list) +{ + while (*list) + { + Log* deadite = *list; + *list = (*list)->next; + if (deadite->data) + { + free(deadite->data); + } + free(deadite); + } +} + +void editlog_seqstart(EditLog* log) +{ + require(log != NULL); + log->transid = abs(log->transid); +} + +void editlog_seqstop(EditLog* log) +{ + require(log != NULL); + if (log->transid > 0) + { + log->transid = -(log->transid + 1); + } +} + +void editlog_clear(EditLog* log) +{ + require(log != NULL); + log_clear(&(log->redo)); + log_clear(&(log->undo)); +} + +void editlog_lastins(EditLog* elog, Sel* p_sel) +{ + require(elog != NULL); + Log* log = elog->undo; + if (log) + { + Sel sel = { .beg = log->beg, .end = log->end }; + size_t delsize = 0; + int transid = log->transid; + + /* try and expand the selected region to encompass related inserts */ + for (; log && (log->transid == transid); log = log->next) + { + if (!log->data) + { + size_t ibeg = log->beg, iend = log->end - delsize; + if (iend < ibeg || ibeg > sel.beg || iend < sel.beg) + { + break; + } + if (ibeg < sel.beg && iend > sel.end) + { + break; + } + sel.beg = ibeg, delsize = 0; + } + else + { + /* bail if the delete doesnt overlap */ + if (log->beg != sel.beg) + { + break; + } + delsize = strlen(log->data); + } + } + *p_sel = sel; + } +} + +void editlog_undo(Buf* buf) +{ + require(buf != NULL); + log_swap(buf, &(buf->log.undo), &(buf->log.redo)); +} + +void editlog_redo(Buf* buf) +{ + require(buf != NULL); + log_swap(buf, &(buf->log.redo), &(buf->log.undo)); +} + +void editlog_add(Buf* buf, size_t beg, size_t end, char* data) +{ + Log* prev = buf->log.undo; + log_clear(&(buf->log.redo)); + + /* check if this is new transaction or the first entry */ + if (!prev || (buf->log.transid > 0 && buf->log.transid != prev->transid)) + { + buf->log.undo = mklog(buf, beg, end, data, prev); + } + /* check if this is a sequential insert, just expand the previous one */ + else if (!data && !prev->data && prev->end == beg) + { + prev->end = end; + } + /* check if this is a sequential delete, append to deleted text */ + else if (prev->data && data && prev->beg == beg) + { + char* newdata = strmcat(prev->data, data, 0); + free(data); + free(prev->data); + prev->data = newdata; + } + /* check if this is a delete before the previous operation, prepend to deleted text */ + else if (prev->data && data && prev->beg == beg+1) + { + char* newdata = strmcat(data, prev->data, 0); + free(data); + free(prev->data); + prev->data = newdata; + prev->end = --prev->beg; + } + /* otherwise, make a fresh delete operation */ + else + { + buf->log.undo = mklog(buf, beg, end, data, prev); + } +} diff --git a/bin/editor/exec.c b/bin/editor/exec.c new file mode 100644 index 0000000..1d5ebac --- /dev/null +++ b/bin/editor/exec.c @@ -0,0 +1,129 @@ +#include +#include "utf.h" +#include "dbc.h" + +#include "tide.h" +#include "config.h" + +static Tag* Builtins = NULL; + +void exec_init(Tag* p_tags) +{ + Builtins = p_tags; + /* setup the shell */ + if (!ShellCmd[0]) ShellCmd[0] = getenv("SHELL"); + if (!ShellCmd[0]) ShellCmd[0] = "/bin/sh"; + require(ShellCmd[0]); +} + +static Tag* tag_lookup(char* cmd) +{ + size_t len = 0; + Tag* tags = Builtins; + + for (char* tag = cmd; *tag && !isspace(*tag); tag++, len++) + { + /* do nothing */ + } + + while (tags->tag) + { + if (!strncmp(tags->tag, cmd, len)) + break; + tags++; + } + return (tags->tag ? tags : NULL); +} + +static void shell_exec(char* cmd) +{ + /* parse the command sigils */ + char op = '\0', **execcmd = NULL; + if (rissigil(*cmd)) op = *(cmd++); + execcmd = (op == ':' ? SedCmd : ShellCmd); + execcmd[2] = cmd; + + /* get the selection that the command will operate on */ + if (op && op != '<' && op != '!' && op != '&' && !view_selsize(win_view(EDIT))) + { + view_selectall(win_view(EDIT)); + } + char* input = view_getstr(win_view(EDIT)); + size_t len = (input ? strlen(input) : 0); + View *tags = win_view(TAGS), *edit = win_view(EDIT), *curr = win_view(FOCUSED); + + /* execute the job */ + if (op == '!' || op == '&') + { + free(input); + if (op == '&') + { + xpty_run(win_view(EDIT), execcmd); + } + else + { + job_start(execcmd, NULL, 0, NULL); + } + } + else if (op == '>') + { + job_start(execcmd, input, len, tags); + } + else if (op == '|' || op == ':') + { + job_start(execcmd, input, len, edit); + } + else + { + job_start(execcmd, input, len, (op != '<' ? curr : edit)); + } +} + +void exec_cmd(char* str) +{ + if (str && *str) + { + bool shellcmd = (str[0] == ':' && str[1] == ';'); + if (xpty_active() && (!rissigil(*str) || shellcmd)) + { + xpty_send(str); + } + else + { + shell_exec(str); + } + } +} + +void exec_cmdwarg(char* cmd, char* arg) +{ + /* skip leading space */ + for (; *cmd && isspace(*cmd); cmd++); + if (!*cmd) return; + /* see if it matches a builtin tag */ + Tag* tag = tag_lookup(cmd); + if (tag) + { + for (; *cmd && !isspace(*cmd); cmd++); /* strip off tag name */ + for (; *cmd && isspace(*cmd); cmd++); /* strip off leading space */ + arg = (*cmd ? strdup(cmd) : arg); + tag->action(!arg || !*arg ? NULL : arg); + } + else if (arg) + { + cmd = (arg ? strmcat(cmd, " '", arg, "'", 0) : strmcat(cmd)); + exec_cmd(cmd); + free(cmd); + } + else + { + exec_cmd(cmd); + } +} + +void exec_rawwarg(char* cmd, char* arg) +{ + cmd = (arg ? strmcat(cmd, " '", arg, "'", 0) : strmcat(cmd)); + shell_exec(cmd); + free(cmd); +} diff --git a/bin/editor/gapbuf.c b/bin/editor/gapbuf.c new file mode 100644 index 0000000..076ea99 --- /dev/null +++ b/bin/editor/gapbuf.c @@ -0,0 +1,223 @@ +#include +#include "dbc.h" +#include "utf.h" +#include "io.h" +#include +#include +#include +#include +#include +#include + +#include "tide.h" +#include "config.h" + +size_t gapbuf_end(GapBuf* buf) +{ + require(buf != NULL); + size_t bufsz = buf->bufend - buf->bufstart; + size_t gapsz = buf->gapend - buf->gapstart; + return (bufsz - gapsz); +} + +static size_t pagealign(size_t sz) +{ + size_t pgsize = sysconf(_SC_PAGE_SIZE); + size_t alignmask = (pgsize - 1); + if (sz & alignmask) + { + sz += pgsize - (sz & alignmask); + } + return sz; +} + +static void resize(GapBuf* buf, size_t sz) +{ + /* allocate the new buffer and gap */ + GapBuf copy = *buf; + copy.bufsize = sz; + copy.bufstart = (char*)malloc(copy.bufsize); + copy.bufend = copy.bufstart + copy.bufsize; + copy.gapstart = copy.bufstart; + copy.gapend = copy.bufend; + + /* copy the data from the old buffer to the new one */ + for (char* curr = buf->bufstart; curr < buf->gapstart; curr++) + { + *(copy.gapstart++) = *(curr); + } + for (char* curr = buf->gapend; curr < buf->bufend; curr++) + { + *(copy.gapstart++) = *(curr); + } + + /* free the buffer and commit the changes */ + free(buf->bufstart); + memcpy(buf, ©, sizeof(GapBuf)); +} + +static void syncgap(GapBuf* buf, size_t off) +{ + assert(off <= gapbuf_end(buf)); + /* If the buffer is full, resize it before syncing */ + if (0 == (buf->gapend - buf->gapstart)) + { + resize(buf, buf->bufsize << 1); + } + + /* Move the gap to the desired offset */ + char* newpos = (buf->bufstart + off); + if (newpos < buf->gapstart) + { + while (newpos < buf->gapstart) + { + *(--buf->gapend) = *(--buf->gapstart); + } + } + else + { + while (newpos > buf->gapstart) + { + *(buf->gapstart++) = *(buf->gapend++); + } + } +} + +static int cmpnames(const void* a, const void* b) +{ + char *sa = *((char**)a), *sb = *((char**)b); + return strcmp(sa, sb); +} + +static void loaddir(GapBuf* buf, char* path, int fd) +{ + Sel sel = { 0 }; + vec_t entries; + vec_init(&entries, sizeof(char*)); + DIR* dir = fdopendir(fd); + struct dirent* entry = NULL; + struct stat attrs = {0}; + while ( (entry = readdir(dir)) ) + { + char* epath = strmcat(path, "/", entry->d_name, 0); + memset(&attrs, 0, sizeof(attrs)); + stat(epath, &attrs); + char* ename = strmcat(entry->d_name, (S_ISDIR(attrs.st_mode) ? "/" : 0), 0); + vec_push_back(&entries, &ename); + free(epath); + } + vec_sort(&entries, cmpnames); + for (size_t i = 0; i < vec_size(&entries); i++) + { + char* name = *((char**)vec_at(&entries, i)); + for (int i = 0; name[i]; i++) + { + gapbuf_putb(buf, name[i], &sel); + } + gapbuf_putb(buf, '\n', &sel); + free(name); + } + free(entries.elem_buffer); + int res = chdir(path); + (void)res; +} + +void gapbuf_init(GapBuf* buf) +{ + require(buf != NULL); + buf->bufsize = 8192; + buf->bufstart = malloc(buf->bufsize); + buf->bufend = buf->bufstart + buf->bufsize; + buf->gapstart = buf->bufstart; + buf->gapend = buf->bufend; +} + +long gapbuf_save(GapBuf* buf, char* path) +{ + require(buf != NULL); + long fd; + long nwrite = 0; + if (path && (fd = open(path, O_WRONLY|O_CREAT|O_TRUNC, 0644)) >= 0) + { + nwrite = writefd(fd, buf->bufstart, (buf->gapstart - buf->bufstart)); + if (nwrite >= 0) + { + nwrite = writefd(fd, buf->gapend, (buf->bufend - buf->gapend)); + } + close(fd); + } + else + { + nwrite = -1; + } + return nwrite; +} + +void gapbuf_load(GapBuf* buf, char* path) +{ + require(buf != NULL); + require(path != NULL); + /* load the contents from the file */ + int fd; + struct stat sb = {0}; + if (((fd = open(path, O_RDONLY, 0)) >= 0) && (fstat(fd, &sb) >= 0) && (sb.st_size > 0)) + { + /* allocate the buffer in advance */ + free(buf->bufstart); + buf->bufsize = pagealign(sb.st_size); + buf->bufstart = malloc(buf->bufsize); + buf->bufend = buf->bufstart + buf->bufsize; + buf->gapstart = buf->bufstart; + buf->gapend = buf->bufend; + + if (S_ISDIR(sb.st_mode)) + { + loaddir(buf, path, fd); + } + else + { + buf->gapstart += sb.st_size; + (void)readfd(fd, buf->bufstart, sb.st_size); + } + } + if (fd > 0) + { + close(fd); + } +} + +char gapbuf_getb(GapBuf* buf, size_t off) +{ + require(buf != NULL); + int c = '\n'; // TODO: get rid of this hack + if (off < gapbuf_end(buf)) + { + size_t bsz = (buf->gapstart - buf->bufstart); + if (off < bsz) + { + c = *(buf->bufstart + off); + } + else + { + c = *(buf->gapend + (off - bsz)); + } + } + return c; +} + +void gapbuf_putb(GapBuf* buf, char b, Sel* p_sel) +{ + require(buf != NULL); + require(p_sel != NULL); + syncgap(buf, p_sel->end); + *(buf->gapstart++) = b; + p_sel->end = p_sel->end + 1u; + p_sel->beg = p_sel->end; +} + +void gapbuf_del(GapBuf* buf, size_t off, size_t len) +{ + require(buf != NULL); + syncgap(buf, off); + buf->gapend += len; +} diff --git a/bin/editor/io.h b/bin/editor/io.h new file mode 100644 index 0000000..d5fe389 --- /dev/null +++ b/bin/editor/io.h @@ -0,0 +1,9 @@ +/** + @file + Helper functions for reading and writing file descriptors as well as outputting telemetry data. +*/ +void telem_send(char* fmt, ...); +long writefd(int fd, char* data, long towrite); +long readfd(int fd, char* data, long toread); +char* readfile(char* path); +char* abspath(char* path); diff --git a/bin/editor/job.c b/bin/editor/job.c new file mode 100644 index 0000000..1512732 --- /dev/null +++ b/bin/editor/job.c @@ -0,0 +1,239 @@ +#include +#include "utf.h" +#include "io.h" +#include +#include +#include +#include +#include +#include + +#include "tide.h" + +struct PipeData { + char* data; + ssize_t ndata; + size_t nwrite; + View* dest; +}; + +#define MAX_JOBS 256 +static struct pollfd JobFds[MAX_JOBS]; +static Job* JobList = NULL; + +static void pipe_read(Job* job) +{ + struct PipeData* pipedata = job->data; + char buffer[32769]; + errno = 0; + long nread = read(job->fd, buffer, sizeof(buffer)-1); + telem_send("PIPE_READ(fd : %d, ret: %ld)\n", job->fd, nread); + if (nread <= 0) + { + job->readfn = NULL; + buf_logstop(&pipedata->dest->buffer); + if (view_selsize(pipedata->dest)) + view_delete(pipedata->dest, RIGHT, false); + else + view_selprev(pipedata->dest); + } + else if (nread > 0) + { + buffer[nread] = '\0'; + buf_logstart(&pipedata->dest->buffer); + view_putraw(pipedata->dest, buffer); + } +} + +static void pipe_write(Job* job) +{ + struct PipeData* pipedata = job->data; + char* chunk = pipedata->data + pipedata->nwrite; + errno = 0; + long nwrite = write(job->fd, chunk, pipedata->ndata); + if (nwrite >= 0) + { + pipedata->ndata -= nwrite; + pipedata->nwrite += nwrite; + } + if ((nwrite < 0 && errno != EWOULDBLOCK) || pipedata->ndata <= 0) + { + free(pipedata->data); + pipedata->data = NULL; + job->writefn = NULL; + shutdown(job->fd, SHUT_WR); + } +} + +static Job* job_remove(Job* list, Job* job) +{ + Job* ret = NULL; + if (list == job) + { + ret = job->next; + } + else + { + list->next = job_remove(list->next, job); + ret = list; + } + return ret; +} + +static void job_finish(Job* job) +{ + JobList = job_remove(JobList, job); + close(job->fd); + free(job->data); + free(job); +} + +static void job_process(int fd, int events) +{ + Job* job = JobList; // Get job by fd + while (job && job->fd != fd) + job = job->next; + if (!job || !events) return; + telem_send("JOB(fd: %d, events: 0x%x)\n", fd, events); + if (job->readfn && (events & (POLLIN|POLLHUP))) + { + telem_send("JOB_READ(fd: %d)\n", job->fd); + job->readfn(job); + } + if (job->writefn && (events & POLLOUT)) + { + telem_send("JOB_WRITE(fd: %d)\n", job->fd); + job->writefn(job); + } + if ((events & POLLERR) || (!job->readfn && !job->writefn)) + { + telem_send("JOB_FINISH(fd: %d)\n", job->fd); + job_finish(job); + } +} + +static int job_exec(char** cmd, int* p_pid) +{ + int pid, fds[2] = {-1,-1}; + /* create the sockets */ + if (!socketpair(AF_UNIX, SOCK_STREAM, 0, fds)) + { + /* create the process */ + if ((pid = fork()) < 0) + { + close(fds[0]), close(fds[1]), fds[0] = -1; + } + else if (0 == pid) + { + /* redirect child process's io to the pipes */ + if ((dup2(fds[1], 0) < 0) || (dup2(fds[1], 1) < 0) || (dup2(fds[1], 2) < 0)) + { + perror("failed to pipe"); + exit(1); + } + /* execute the process */ + close(fds[0]); + exit(execvp(cmd[0], cmd)); + } + else + { + close(fds[1]); + fcntl(fds[0], F_SETFL, fcntl(fds[0], F_GETFL, 0) | O_NONBLOCK); + } + if (p_pid) *p_pid = pid; + } + return fds[0]; +} + +Job* job_list(void) +{ + return JobList; +} + +void job_kill(Job* job) +{ + job->readfn = NULL; + job->writefn = NULL; +} + +bool job_poll(int ms) +{ + int njobs = 0; + /* Add jobs from the job list */ + for (Job *job = JobList; job && njobs < MAX_JOBS; job = job->next) + { + JobFds[njobs].fd = job->fd; + JobFds[njobs].events = 0; + JobFds[njobs].revents = 0; + if (job->readfn) JobFds[njobs].events |= POLLIN; + if (job->writefn) JobFds[njobs].events |= POLLOUT; + if (JobFds[njobs].events) njobs++; + } + + /* Poll until a job is ready, call the functions based on events */ + long ret = poll(JobFds, njobs, ms); + if (ret != 0) + { + telem_send("POLL(njobs: %d, ms: %d, ret: %d)\n", njobs, ms, ret); + } + + /* process all jobs with events reported */ + for (int i = 0; i < njobs; i++) + { + job_process(JobFds[i].fd, JobFds[i].revents); + } + + /* reap zombie processes */ + for (int status; waitpid(-1, &status, WNOHANG) > 0;); + return (ret > 0); +} + +void job_spawn(int fd, jobfn_t readfn, jobfn_t writefn, void* data) +{ + Job *job = calloc(1, sizeof(Job)); + job->fd = fd; + job->readfn = readfn; + job->writefn = writefn; + job->data = data; + job->next = JobList; + JobList = job; +} + +void job_start(char** cmd, char* data, size_t ndata, View* dest) +{ + int fd = job_exec(cmd, 0); + if (fd >= 0 && (data || dest)) + { + struct PipeData* pipedata = NULL; + if (data) + { + pipedata = calloc(1, sizeof(struct PipeData)); + pipedata->data = data; + pipedata->ndata = ndata; + pipedata->dest = dest; + } + job_spawn(fd, (data ? pipe_read : NULL), (dest ? pipe_write : NULL), pipedata); + } + else + { + close(fd); + } +} + +int job_run(char** cmd) +{ + int pid = -1, status = 0; + job_exec(cmd, &pid); + do + { + waitpid(pid, &status, WUNTRACED|WCONTINUED); + } while (!WIFEXITED(status) && !WIFSIGNALED(status)); + return WEXITSTATUS(status); +} + +void job_readfd(int fd, View* view) +{ + struct PipeData* pipedata = calloc(1, sizeof(struct PipeData)); + pipedata->dest = view; + job_spawn(fd, pipe_read, NULL, pipedata); +} diff --git a/bin/editor/main.c b/bin/editor/main.c new file mode 100644 index 0000000..e148ba1 --- /dev/null +++ b/bin/editor/main.c @@ -0,0 +1,784 @@ +#include +#include "dbc.h" +#include "utf.h" +#include "io.h" +#include +#include + +#include "tide.h" + +#define INCLUDE_DEFS +#include "config.h" + +/* predeclare some things */ +void cut(char* arg); +void paste(char* arg); + +char* ARGV0; +Tag* Builtins; +static KeyBinding* Bindings; +static WinRegion Focused = EDIT; +static View Regions[NREGIONS]; +static int Divider; +static int FontSel; +static bool SyncMouse = false; +static int SearchDir = DOWN; +static char* SearchTerm = NULL; + +static int PRESSED(int mods, int btn) +{ + return ((mods & (1 << (btn + 7))) == (1 << (btn + 7))); +} + +/* Crash Dump Handling + ******************************************************************************/ +#ifndef NDEBUG +static void dumpdata(FILE* f) +{ + fprintf(f, "Focused:\t%d\n", Focused); + fprintf(f, "Divider:\t%d\n", Divider); + fprintf(f, "FontSel:\t%d\n", FontSel); + fprintf(f, "SyncMouse:\t%d\n", SyncMouse); + fprintf(f, "SearchDir:\t%d\n", SearchDir); + fprintf(f, "SearchTerm:\t'%s'\n", SearchTerm); + for (int i = 0; i < NREGIONS; i++) + { + fprintf(f, "Region[%d]:\n", i); + fprintf(f, "\t.sync_flags:\t%d\n", Regions[i].sync_flags); + fprintf(f, "\t.index:\t\t%zu\n", Regions[i].index); + fprintf(f, "\t.width:\t\t%zu\n", Regions[i].width); + fprintf(f, "\t.nvisible:\t%zu\n", Regions[i].nvisible); + fprintf(f, "\t.nrows:\t\t%zu\n", Regions[i].nrows); + fprintf(f, "\t.rows:\t\t%p\n", (void*)Regions[i].rows); + fprintf(f, "\t.buffer:\n"); + fprintf(f, "\t\t.status:\t%d\n", Regions[i].buffer.status); + fprintf(f, "\t\t.bufsize:\t%zu\n", Regions[i].buffer.contents.bufsize); + fprintf(f, "\t\t.bufstart:\t%p\n", (void*)Regions[i].buffer.contents.bufstart); + fprintf(f, "\t\t.bufend:\t%p\n", (void*)Regions[i].buffer.contents.bufend); + fprintf(f, "\t\t.gapstart:\t%p\n", (void*)Regions[i].buffer.contents.gapstart); + fprintf(f, "\t\t.gapend:\t%p\n", (void*)Regions[i].buffer.contents.gapend); + fprintf(f, "\t\t.undo:\t\t%p\n", (void*)Regions[i].buffer.log.undo); + fprintf(f, "\t\t.redo:\t\t%p\n", (void*)Regions[i].buffer.log.redo); + fprintf(f, "\t\t.save:\t\t%p\n", (void*)Regions[i].buffer.log.save); + fprintf(f, "\t\t.transid:\t%d\n", Regions[i].buffer.log.transid); + fprintf(f, "\t\t.selbeg:\t%zu\n", Regions[i].buffer.selection.beg); + fprintf(f, "\t\t.selend:\t%zu\n", Regions[i].buffer.selection.end); + fprintf(f, "\t\t.selcol:\t%zu\n", Regions[i].buffer.selection.col); + } +} +#endif + +/* X11 Window Code + ******************************************************************************/ +static void font_load(char* name) +{ + XftFont* font = x11_font_load(&X, name); + X.font = (font ? font : X.font); // Update if we found a new font +} + +static void get_position(WinRegion id, int x, int y, size_t* row, size_t* col) +{ + int starty = (id == EDIT ? Divider+3 : 0); + int startx = ScrollWidth + 3 + Margin; + int maxy = (id == EDIT + ? (Divider + (int)(win_view(EDIT)->nrows * X.font->height)) + : Divider - 4 + ); + x = (x < 0 ? 0 : (x > X.width ? X.width : x)); + y = (y < starty ? starty : (y > maxy ? maxy : y)); + *row = (y - starty) / X.font->height; + *col = (startx <= x ? x - startx : 0); +} + +static void tide_send(char* type) +{ + XEvent ev; + memset(&ev, 0, sizeof (ev)); + Atom xa_registrar = XInternAtom(X.display, "TIDE_REGISTRAR", 0); + Window regwin = XGetSelectionOwner(X.display, xa_registrar); + if (None != regwin) + { + ev.xclient.type = ClientMessage; + ev.xclient.window = X.self; + ev.xclient.message_type = xa_registrar; + ev.xclient.format = 32; + ev.xclient.data.l[0] = XInternAtom(X.display, type, 0); + XSendEvent(X.display, regwin, False, NoEventMask, &ev); + XFlush(X.display); + } +} + +size_t glyph_width(View* view, int c) +{ + size_t ret; + FcChar32 rune = (FcChar32)c; + XGlyphInfo extents; + XftFont* font = (&Regions[TAGS] == view ? X.tagfont : X.font); + XftTextExtents32(X.display, font, &rune, 1, &extents); + if (c == '\t') + { + ret = (TabWidth * extents.xOff); + } + else + { + ret = extents.xOff; + } + return ret; +} + +static void xkeypress(XConf* x, XEvent* e) +{ + Focused = (e->xkey.y <= Divider ? TAGS : EDIT); + uint32_t key = x11_process_key(x, e, Bindings); + telem_send("KEY(reg: %d, key: 0x%x)\n", Focused, key); + if (key != RUNE_ERR) + { + view_insert(win_view(FOCUSED), key); + } +} + +static void xmousebtn(XConf* x, XEvent* e) +{ + (void)x; + size_t row, col; + Focused = (e->xbutton.y <= Divider ? TAGS : EDIT); + get_position(Focused, e->xbutton.x, e->xbutton.y, &row, &col); + int action = process_mouse(e->xbutton.button, (e->type == ButtonPress)); + telem_send("BTNPRESS(reg: %d, btn: %d, press: %d, act: %d)\n", + Focused, e->xbutton.button, (e->type == ButtonPress), action); + switch (action) + { + case MouseActNone: + break; + case MouseActSel: + view_setcursor(win_view(Focused), row, col, win_keymodsset(ModShift)); + break; + case MouseActSelCtx: + view_select(win_view(Focused), row, col); + break; + case MouseActSelWord: + view_selword(win_view(Focused), row, col); + break; + case MouseActCut: + cut(NULL); + break; + case MouseActPaste: + paste(NULL); + break; + case MouseActExec: + { + char* str = view_fetch(win_view(Focused), row, col, riscmd); + exec_cmdwarg(str, NULL); + free(str); + break; + } + case MouseActExecArg: + { + /* if we didnt get an arg, find one in the selection */ + char* arg = NULL; + if (!arg || !*arg) arg = view_getstr(win_view(EDIT)); + if (!arg || !*arg) arg = view_getstr(win_view(TAGS)); + char* str = view_fetch(win_view(Focused), row, col, riscmd); + if (str) exec_cmdwarg(str, arg); + free(str); + free(arg); + break; + } + case MouseActFetch: + { + FetchCmd[1] = view_fetch(win_view(Focused), row, col, risfile); + if (job_run(FetchCmd) != 0) + { + SearchDir *= (win_keymodsset(ModShift) ? -1 : +1); + free(SearchTerm); + SearchTerm = view_fetch(win_view(Focused), row, col, risfile); + view_findstr(win_view(EDIT), SearchDir, SearchTerm); + SyncMouse = true; + } + free(FetchCmd[1]); + break; + } + case MouseActScrollUp: + view_scroll(win_view(Focused), -ScrollBy); + break; + case MouseActScrollDn: + view_scroll(win_view(Focused), +ScrollBy); + break; + } +} + +static void xbtnmotion(XConf* x, XEvent* e) +{ + XMotionEvent *ev = &e->xmotion; + while (XCheckTypedWindowEvent(x->display, ev->window, ev->type, e)); + size_t row, col; + int xpos = ev->x, ypos = ev->y; + get_position(Focused, xpos, ypos, &row, &col); + telem_send("BTNMOVE(reg: %d, btn: 0x%x, x: %d, y: %d, r: %d, c: %d)\n", + Focused, ev->state, xpos, ypos, row, col); + if (PRESSED(ev->state, MouseLeft)) + view_setcursor(win_view(Focused), row, col, true); +} + +static void xclientmsg(XConf* x, XEvent* e) +{ + if ((Atom)(e->xclient.data.l[0]) == XInternAtom(x->display, "WM_DELETE_WINDOW", False)) + { + win_quit(); + } + else if (e->xclient.message_type == XInternAtom(x->display, "GOTO", False)) + { + x11_seturgent(x, 1); + win_setln(e->xclient.data.l[0]); + } +} + +void xresize(XConf* x, XEvent* e) +{ + if (e->xconfigure.width != x->width || e->xconfigure.height != x->height) + { + view_sync(win_view(EDIT)); + } + x11_resize(x, e); +} + +static void xredraw(XConf* x) +{ + /* force update the title */ + win_title(NULL); + + /* determine draw height such that we can draw both regions */ + int height = x->tagfont->height * 2 + 7; + if (height < x->height) height = x->height; + + /* determine the size of the regions */ + size_t maxtagrows = ((height/4) / x->tagfont->height); + size_t tagrows = view_limitrows(win_view(TAGS), maxtagrows); + size_t tagregsz = (tagrows * x->tagfont->height) + 7; + size_t editrows = (height - tagregsz) / x->font->height ; + telem_send("REDRAW(tagrows: %d, editrows: %d)\n", tagrows, editrows); + + /* draw the regions to the window */ + int olddiv = Divider; + drawcsr csr = { .w = x->width, .h = x->height }; + csr.x += ScrollWidth + 1; + draw_statbox(x, win_view(EDIT)->buffer.status); + draw_view(x, &Regions[TAGS], x->tagfont, tagrows, &csr, TagsBg, TagsFg, TagsSel, false); + Divider = draw_hrule(x, &csr); + draw_view(x, &Regions[EDIT], x->font, editrows, &csr, EditBg, EditFg, EditSel, SyncMouse); + draw_scroll(x, &csr, win_view(EDIT), Divider); + XCopyArea(x->display, x->pixmap, x->self, x->gc, 0, 0, x->width, x->height, 0, 0); + SyncMouse = false; + if (Divider < olddiv && Focused == TAGS) + { + int ptrx = 0, ptry = 0; + x11_getptr(x, &ptrx, &ptry); + XWarpPointer(X.display, X.self, X.self, 0, 0, X.width, X.height, ptrx, Divider-2); + } + XFlush(x->display); +} + +static void xupdate(Job* job) +{ + /* redraw if we have changes or if we have input from a job */ + x11_process_events(&X); + if (!job && X.state == RUNNING) + { + xredraw(&X); + } +} + +void win_init(void) +{ + signal(SIGPIPE, SIG_IGN); // Ignore the SIGPIPE signal + setlocale(LC_CTYPE, ""); + XSetLocaleModifiers(""); + + /* open the X display and get basic attributes */ + x11_init(&X); + font_load(Fonts[FontSel = 0]); + X.tagfont = X.font; + if (!X.font) + { + perror("unable to load base font"); + exit(EXIT_FAILURE); + } + x11_mkwin(&X, 640, 480, 0 + | KeyPressMask + | ButtonPressMask + | ButtonReleaseMask + | ButtonMotionMask + | PropertyChangeMask + ); + x11_init_gc(&X); + x11_sel_init(&X); + /* register event handlers */ + X.eventfns[KeyPress] = xkeypress; + X.eventfns[ButtonPress] = xmousebtn; + X.eventfns[ButtonRelease] = xmousebtn; + X.eventfns[MotionNotify] = xbtnmotion; + X.eventfns[ClientMessage] = xclientmsg; + X.eventfns[ConfigureNotify] = xresize; +} + +void win_title(char* path) +{ + static char prevtitle[4096] = {0}; + char title[4096] = {0}; + if (!path) path = win_view(EDIT)->buffer.path; + if (!path) path = "*scratch*"; + snprintf(title, sizeof(title)-1, "[%c%c] %s", + (DosLineFeed ? 'C' : 'N'), + (ExpandTabs ? 'S' : 'T'), + path); + if (strcmp(prevtitle, title)) + XStoreName(X.display, X.self, title); + memcpy(prevtitle, title, sizeof(title)); +} + +void win_font(char* font) +{ + font_load(font ? font : Fonts[++FontSel % nelem(Fonts)]); +} + +void win_prop_set(char* xname, char* ename, char* value) +{ + if (!value) return; + Atom propname = XInternAtom(X.display, xname, 0); + XChangeProperty(X.display, X.self, propname, XA_STRING, 8, PropModeReplace, + (const unsigned char *)value, strlen(value)); + if (ename) setenv(ename, value, 1); +} + +void win_loop(void) +{ + tide_send("ADD"); + job_spawn(ConnectionNumber(X.display), xupdate, 0, 0); + XSync(X.display, False); + int maxcount = 1000 / Timeout; + int count = 0; + while (X.state != QUITTING) + { + bool ready = job_poll(Timeout); + count += (ready ? -count : 1); + if (count < maxcount) + { + xupdate(NULL); + } + } +} + +void win_quit(void) +{ + static uint64_t before = 0; + if ((win_buf(EDIT)->status != MODIFIED) || (X.now - before) <= (uint64_t)ClickTime) + { + tide_send("DEL"); + XWithdrawWindow(X.display, X.self, X.screen); + X.state = QUITTING; + if (x11_sel_ready(&X)) + { + X.state = SERVE_SEL; + x11_sel_serve(&X); + } + } + before = X.now; +} + +void win_togglefocus(void) +{ + int ypos = (Focused == EDIT ? Divider/2 : (X.height - ((X.height-Divider) / 2))); + XWarpPointer(X.display, X.self, X.self, 0, 0, X.width, X.height, X.width/2, ypos); +} + +View* win_view(WinRegion id) +{ + return &(Regions[id == FOCUSED ? Focused : id]); +} + +Buf* win_buf(WinRegion id) +{ + return &(Regions[id == FOCUSED ? Focused : id].buffer); +} + +bool win_keymodsset(int mask) +{ + return ((X.mods & mask) == mask); +} + +void win_setln(int line_num) +{ + view_setln(win_view(EDIT), line_num); + SyncMouse = true; +} + +/* Keyboard and Tag Handlers + ******************************************************************************/ +static void change_focus(char* arg) +{ + (void)arg; + win_togglefocus(); +} + +static void quit(char* arg) +{ + (void)arg; + win_quit(); +} + +static void put(char* arg) +{ + Buf* buf = win_buf(EDIT); + if (buf_save(buf, arg) == NORMAL) + { + /* convert saved path to absolute path */ + char* path = abspath(buf->path); + buf_setpath(buf, path); + free(path); + } + char* path = (buf->path ? buf->path : "*scratch*"); + win_title(path); + win_prop_set("FILE", "file", path); +} + +static void get(char* arg) +{ + if (arg) + view_init(win_view(EDIT), arg); + else + view_reload(win_view(EDIT)); +} + +static void tag_undo(char* arg) +{ + (void)arg; + view_undo(win_view(EDIT)); +} + +static void tag_redo(char* arg) +{ + (void)arg; + view_redo(win_view(EDIT)); +} + +static void search(char* arg) +{ + (void)arg; + char* str; + SearchDir *= (win_keymodsset(ModShift) ? UP : DOWN); + if (win_keymodsset(ModAlt) && SearchTerm) + str = strdup(SearchTerm); + else + str = view_getctx(win_view(FOCUSED)); + SyncMouse = view_findstr(win_view(EDIT), SearchDir, str); + free(SearchTerm); + SearchTerm = str; +} + +static void execute(char* arg) +{ + (void)arg; + char* str = view_getcmd(win_view(FOCUSED)); + exec_cmdwarg(str, NULL); + free(str); +} + +static void find(char* arg) +{ + SearchDir *= (win_keymodsset(ModShift) ? UP : DOWN); + view_findstr(win_view(EDIT), SearchDir, arg); +} + +static void open_file(char* arg) +{ + (void)arg; + exec_cmd(CMD_PICKFILE); +} + +static void pick_symbol(char* symbol) +{ + exec_rawwarg(CMD_GOTO_TAG, symbol); +} + +static void pick_ctag(char* arg) +{ + (void)arg; + pick_symbol(NULL); +} + +static void complete(char* arg) +{ + (void)arg; + View* view = win_view(FOCUSED); + view_selectobj(view, risword); + exec_rawwarg(CMD_COMPLETE, view_getstr(view)); +} + +static void fcomplete(char* arg) +{ + (void)arg; + View* view = win_view(FOCUSED); + view_selectobj(view, risfile); + exec_rawwarg(CMD_FCOMPLETE, view_getstr(view)); +} + +static void jump_to(char* arg) +{ + if (arg) + { + size_t line = strtoul(arg, NULL, 0); + if (line) + win_setln(line); + else + pick_symbol(arg); + } +} + +static void goto_ctag(char* arg) +{ + (void)arg; + char* str = view_getctx(win_view(FOCUSED)); + jump_to(str); + free(str); +} + +static void tabs(char* arg) +{ + (void)arg; + ExpandTabs = !ExpandTabs; +} + +static void indent(char* arg) +{ + (void)arg; + CopyIndent = !CopyIndent; +} + +static void eol_mode(char* arg) +{ + (void)arg; + DosLineFeed = !DosLineFeed; + View* view = win_view(EDIT); + view_selectall(view); + char* txt = view_getstr(view); + view_putstr(view, txt); + free(txt); +} + +static void new_win(char* arg) +{ + (void)arg; + exec_cmd(CMD_TIDE); +} + +static void lnexec(char* cmd) +{ + select_line(NULL); + exec_cmdwarg(cmd, NULL); +} + +static void tag_kill(char* cmd) +{ + (void)cmd; + Job* job = job_list(); + if (job && job->fd != ConnectionNumber(X.display)) + job_kill(job); +} + +static void tag_line(char* cmd) +{ + (void)cmd; + char buf[256] = {0}; + size_t lnbeg = 1, lnend = 1; + buf_getln(win_buf(EDIT), &lnbeg, &lnend); + if (lnbeg == lnend) + snprintf(buf, sizeof(buf)-1, "%lu", lnbeg); + else + snprintf(buf, sizeof(buf)-1, "%lu:%lu", lnbeg, lnend); + view_paste(win_view(TAGS), buf); +} + +static void do_scroll(char* arg) +{ + (void)arg; + xpty_togglescroll(); +} + +/* Main Routine + ******************************************************************************/ +Tag* Builtins = (Tag[]){ + { .tag = "Cut", .action = cut }, + { .tag = "Copy", .action = copy }, + { .tag = "Del", .action = quit }, + { .tag = "Eol", .action = eol_mode }, + { .tag = "Find", .action = find }, + { .tag = "GoTo", .action = jump_to }, + { .tag = "Get", .action = get }, + { .tag = "Indent", .action = indent }, + { .tag = "New", .action = new_win }, + { .tag = "Paste", .action = paste }, + { .tag = "Put", .action = put }, + { .tag = "Redo", .action = tag_redo }, + { .tag = "Tabs", .action = tabs }, + { .tag = "Undo", .action = tag_undo }, + { .tag = "Font", .action = win_font }, + { .tag = "Kill", .action = tag_kill }, + { .tag = "Line", .action = tag_line }, + { .tag = "Scroll", .action = do_scroll }, + { .tag = NULL, .action = NULL } +}; + +static KeyBinding* Bindings = (KeyBinding[]){ + /* Cursor Movements */ + { .mods = ModAny, .key = KEY_HOME, .fn = cursor_home }, + { .mods = ModAny, .key = KEY_END, .fn = cursor_end }, + { .mods = ModAny, .key = KEY_UP, .fn = cursor_up }, + { .mods = ModAny, .key = KEY_DOWN, .fn = cursor_dn }, + { .mods = ModAny, .key = KEY_LEFT, .fn = cursor_left }, + { .mods = ModAny, .key = KEY_RIGHT, .fn = cursor_right }, + + /* Standard Unix Shortcuts */ + { .mods = ModCtrl, .key = 'u', .fn = del_to_bol }, + { .mods = ModCtrl, .key = 'k', .fn = del_to_eol }, + { .mods = ModCtrl, .key = 'w', .fn = del_to_bow }, + { .mods = ModCtrl, .key = 'a', .fn = cursor_bol }, + { .mods = ModCtrl, .key = 'e', .fn = cursor_eol }, + + /* Standard Text Editing Shortcuts */ + { .mods = ModCtrl, .key = 's', .fn = put }, + { .mods = ModCtrl, .key = 'z', .fn = undo }, + { .mods = ModCtrl, .key = 'y', .fn = redo }, + { .mods = ModCtrl, .key = 'x', .fn = cut }, + { .mods = ModCtrl, .key = 'c', .fn = copy }, + { .mods = ModCtrl, .key = 'v', .fn = paste }, + { .mods = ModCtrl, .key = 'j', .fn = join_lines }, + { .mods = ModCtrl, .key = 'l', .fn = select_line }, + + /* Common Special Keys */ + { .mods = ModNone, .key = KEY_PGUP, .fn = page_up }, + { .mods = ModNone, .key = KEY_PGDN, .fn = page_dn }, + { .mods = ModAny, .key = KEY_DELETE, .fn = delete }, + { .mods = ModAny, .key = KEY_BACKSPACE, .fn = backspace }, + + /* External command shortcuts */ + { .mods = ModCtrl, .key = '[', .fn = lnexec, .arg = "|i-" }, + { .mods = ModCtrl, .key = ']', .fn = lnexec, .arg = "|i+" }, + { .mods = ModCtrl, .key = '/', .fn = lnexec, .arg = "|c+" }, + { .mods = ModCtrl|ModShift, .key = '?', .fn = lnexec, .arg = "|c-" }, + + /* Implementation Specific */ + { .mods = ModNone, .key = KEY_ESCAPE, .fn = select_prev }, + { .mods = ModCtrl, .key = 't', .fn = change_focus }, + { .mods = ModCtrl, .key = 'q', .fn = quit }, + { .mods = ModCtrl, .key = 'h', .fn = highlight }, + { .mods = ModOneOrMore, .key = 'f', .fn = search }, + { .mods = ModCtrl, .key = 'd', .fn = execute }, + { .mods = ModOneOrMore, .key = 'o', .fn = open_file }, + { .mods = ModCtrl, .key = 'p', .fn = pick_ctag }, + { .mods = ModOneOrMore, .key = 'g', .fn = goto_ctag }, + { .mods = ModCtrl, .key = 'n', .fn = new_win }, + { .mods = ModOneOrMore, .key = '\n', .fn = newline }, + { .mods = ModCtrl, .key = ' ', .fn = complete }, + { .mods = ModCtrl|ModShift, .key = ' ', .fn = fcomplete }, + + /* xpty commands */ + { .mods = ModCtrl|ModShift, .key = 'c', .fn = xpty_rawsend, .arg = "\x03" }, + { .mods = ModCtrl|ModShift, .key = 'd', .fn = xpty_rawsend, .arg = "\x04" }, + { .mods = ModCtrl|ModShift, .key = 'z', .fn = xpty_rawsend, .arg = "\x1A" }, + + { 0, 0, 0, 0 } +}; + +#ifndef TEST +static void usage(void) +{ + printf( + "Usage: %s [FLAGS] [FILE]\n" + "\n -I 0,1 Enable/disable automatic indenting" + "\n -W 0,1 Enable/disable trimming whitespace on save" + "\n -E 0,1 Enable/disable expanding tabs to spaces" + "\n -N 0,1 Enable/disable dos line ending style" + "\n -T str String to use for the tags region" + "\n -C str Set the shell to use for command execution\n" + "\n -c cmd Runs command interactively in the window\n", + ARGV0); + exit(1); +} + +static void edit_file(char* file, int line_num) +{ + file = abspath(file); + if (!strcmp("-", file)) + { + job_readfd(STDIN_FILENO, win_view(EDIT)); + } + else + { + view_init(win_view(EDIT), file); + win_setln(line_num); + win_title(file); + win_prop_set("FILE", "file", file); + } + free(file); +} + +int main(int argc, char** argv) +{ + char* termcmd = NULL; + long int line_num = 0; + #define BOOLARG() (EOPTARG(usage()), optarg_[0] == '0' ? 0 : 1) + #define STRARG() (EOPTARG(usage())) + #define NUMARG() (strtoul(EOPTARG(usage()),0,0)) + OPTBEGIN + { + case 'I': CopyIndent = BOOLARG(); break; + case 'W': TrimOnSave = BOOLARG(); break; + case 'E': ExpandTabs = BOOLARG(); break; + case 'N': DosLineFeed = BOOLARG(); break; + case 'T': TagString = STRARG(); break; + case 'S': ShellCmd[0] = STRARG(); break; + case 'c': termcmd = STRARG(); break; + case 'l': line_num = NUMARG(); break; + default: usage(); break; + } + OPTEND; + + /* Initialize the window and views */ + exec_init(Builtins); + win_init(); + view_init(&Regions[TAGS], NULL); + view_init(&Regions[EDIT], NULL); + view_putstr(win_view(TAGS), TagString); + view_resize(win_view(TAGS), 640, 1); + buf_logclear(win_buf(TAGS)); + win_prop_set("TIDE", "", "tide"); + dbc_init(NULL, dumpdata); + + /* if we still have args left we're going to open it in this instance */ + if (*argv) + { + edit_file(*argv, line_num); + } + + /* set host name property */ + char buf[8192]; + if (!gethostname(buf, sizeof(buf))) + win_prop_set("HOST", "host", buf); + + /* exit */ + if (termcmd) + { + termcmd = strmcat("&", termcmd, 0); + exec_cmd(termcmd); + free(termcmd); + } + + /* now create the window and start the event loop */ +#ifndef TEST + x11_show(&X); +#endif + xupdate(NULL); + win_loop(); + return 0; +} +#endif diff --git a/bin/editor/mouse.c b/bin/editor/mouse.c new file mode 100644 index 0000000..a59bb99 --- /dev/null +++ b/bin/editor/mouse.c @@ -0,0 +1,106 @@ +#include +#include "utf.h" +#include "dbc.h" + +#include "tide.h" +#include "config.h" + +static inline int PRESSED(int mods, int btn) +{ + return ((mods & (1 << (btn + 7))) == (1 << (btn + 7))); +} + +static int ExecRequest = 0; + +static int mouse_left(bool pressed) +{ + static int count = 0; + static Time before = 0; + int ret = MouseActNone; + if (pressed) + { + count = ((X.now - before) <= (uint64_t)ClickTime ? count+1 : 1); + before = X.now; + if (PRESSED(X.mods, MouseRight)) + { + ret = MouseActNone; + } + else if (PRESSED(X.mods, MouseMiddle)) + { + ExecRequest = 0; + ret = MouseActExecArg; + } + else if (count == 1) + { + ret = MouseActSel; + } + else if (count == 2) + { + ret = MouseActSelCtx; + } + else if (count == 3) + { + ret = MouseActSelWord; + } + else + { + ret = MouseActNone; + } + } + return ret; +} + +static int mouse_middle(bool pressed) +{ + int ret; + if (pressed) + { + ExecRequest = 1; + ret = MouseActNone; + } + else if (PRESSED(X.mods, MouseLeft)) + { + ret = MouseActCut; + } + else if (ExecRequest) + { + ret = MouseActExec; + } + else + { + ret = MouseActNone; + } + return ret; +} + +static int mouse_right(bool pressed) +{ + int ret; + if (pressed) + { + ret = MouseActNone; + } + else if (PRESSED(X.mods, MouseLeft)) + { + ret = MouseActPaste; + } + else + { + ret = MouseActFetch; + } + return ret; +} + +int process_mouse(int btn, bool pressed) +{ + int ret = MouseActNone; + switch(btn) + { + case MouseLeft: ret = mouse_left(pressed); break; + case MouseMiddle: ret = mouse_middle(pressed); break; + case MouseRight: ret = mouse_right(pressed); break; + case MouseWheelUp: ret = (pressed ? MouseActScrollUp : MouseActNone); break; + case MouseWheelDn: ret = (pressed ? MouseActScrollDn : MouseActNone); break; + } + return ret; +} diff --git a/bin/editor/range.c b/bin/editor/range.c new file mode 100644 index 0000000..fbd96e5 --- /dev/null +++ b/bin/editor/range.c @@ -0,0 +1,34 @@ +#include +#include "utf.h" + +#include "tide.h" + +void range_add(Sel* p_range, size_t off) +{ + if (off < p_range->beg) + { + p_range->beg++; + } + if (off <= p_range->end) + { + p_range->end++; + } +} + +void range_del(Sel* p_range, size_t beg, size_t end) +{ + /* adjust the point according to the characters deleted */ + size_t nbytes = end - beg; + size_t bpoint = 0; + if (beg <= p_range->beg) + { + bpoint = min(nbytes, p_range->beg - beg); + } + size_t inpoint = 0; + if (end >= p_range->beg) + { + inpoint = min(end, p_range->end) - max(beg, p_range->beg); + } + p_range->beg -= bpoint; + p_range->end -= (bpoint + inpoint); +} diff --git a/bin/editor/readfd.c b/bin/editor/readfd.c new file mode 100644 index 0000000..8223a38 --- /dev/null +++ b/bin/editor/readfd.c @@ -0,0 +1,13 @@ +#include +#include "io.h" + +long readfd(int fd, char* data, long toread) +{ + long nread = 0; + while (toread && ((nread = read(fd, data, toread)) >= 0)) + { + data += nread; + toread -= nread; + } + return nread; +} diff --git a/bin/editor/readfile.c b/bin/editor/readfile.c new file mode 100644 index 0000000..a690a4c --- /dev/null +++ b/bin/editor/readfile.c @@ -0,0 +1,22 @@ +#include +#include "io.h" +#include +#include + +char* readfile(char* path) +{ + int fd; + char* data = NULL; + struct stat sb = {0}; + if (((fd = open(path, O_RDONLY, 0)) >= 0) && (fstat(fd, &sb) >= 0) && (sb.st_size > 0)) + { + data = malloc(sb.st_size+1); + (void)readfd(fd, data, sb.st_size); + data[sb.st_size] = '\0'; + } + if (fd > 0) + { + close(fd); + } + return data; +} diff --git a/bin/editor/shortcuts.c b/bin/editor/shortcuts.c new file mode 100644 index 0000000..c0cb8bc --- /dev/null +++ b/bin/editor/shortcuts.c @@ -0,0 +1,229 @@ +#include +#include "utf.h" + +#include "tide.h" + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunused-parameter" + +void select_line(char* arg) +{ + buf_selln(win_buf(FOCUSED)); +} + +void join_lines(char* arg) +{ + View* view = win_view(FOCUSED); + buf_logstart(win_buf(FOCUSED)); + view_eol(view, false); + view_delete(view, RIGHT, false); + Rune r = view_getrune(view); + for (; r == '\t' || r == ' '; r = view_getrune(view)) + { + view_byrune(view, RIGHT, true); + } + if (r != '\n') + { + buf_putc(win_buf(FOCUSED), ' '); + } + buf_logstop(win_buf(FOCUSED)); +} + +void delete(char* arg) +{ + bool byword = win_keymodsset(ModCtrl); + view_delete(win_view(FOCUSED), RIGHT, byword); +} + +static void onpaste(char* text) +{ + view_paste(win_view(FOCUSED), text); +} + +void cut(char* arg) +{ + View* view = win_view(FOCUSED); + /* select the current line if no selection */ + if (!view_selsize(view)) + { + select_line(arg); + } + /* now perform the cut */ + char* str = view_getstr(view); + x11_sel_set(&X, CLIPBOARD, str); + if (str && *str) + { + delete(arg); + } +} + +void paste(char* arg) +{ + int pasted = x11_sel_get(&X, CLIPBOARD, onpaste); + assert(pasted); + (void)pasted; +} + +void copy(char* arg) +{ + /* select the current line if no selection */ + if (!view_selsize(win_view(FOCUSED))) + { + select_line(arg); + } + char* str = view_getstr(win_view(FOCUSED)); + x11_sel_set(&X, CLIPBOARD, str); +} + +static void del_to(void (*tofn)(View*, bool)) +{ + tofn(win_view(FOCUSED), true); + if (view_selsize(win_view(FOCUSED)) > 0) + { + delete(NULL); + } +} + +void del_to_bol(char* arg) +{ + del_to(view_bol); +} + +void del_to_eol(char* arg) +{ + del_to(view_eol); +} + +void del_to_bow(char* arg) +{ + view_byword(win_view(FOCUSED), LEFT, true); + if (view_selsize(win_view(FOCUSED)) > 0) + { + delete(arg); + } +} + +void backspace(char* arg) +{ + view_delete(win_view(FOCUSED), LEFT, win_keymodsset(ModCtrl)); +} + +void cursor_bol(char* arg) +{ + view_bol(win_view(FOCUSED), false); +} + +void cursor_eol(char* arg) +{ + view_eol(win_view(FOCUSED), false); +} + +void cursor_mvlr(int dir) +{ + bool extsel = win_keymodsset(ModShift); + if (win_keymodsset(ModCtrl)) + { + view_byword(win_view(FOCUSED), dir, extsel); + } + else + { + view_byrune(win_view(FOCUSED), dir, extsel); + } +} + +void cursor_mvupdn(int dir) +{ + bool extsel = win_keymodsset(ModShift); + view_byline(win_view(FOCUSED), dir, extsel); +} + +static void cursor_home_end( + void (*docfn)(View*, bool), + void (*linefn)(View*, bool) +) +{ + bool extsel = win_keymodsset(ModShift); + if (win_keymodsset(ModCtrl)) + { + docfn(win_view(FOCUSED), extsel); + } + else + { + linefn(win_view(FOCUSED), extsel); + } +} + +void cursor_home(char* arg) +{ + cursor_home_end(view_bof, view_bol); +} + +void cursor_end(char* arg) +{ + cursor_home_end(view_eof, view_eol); +} + +void cursor_up(char* arg) +{ + cursor_mvupdn(UP); +} + +void cursor_dn(char* arg) +{ + cursor_mvupdn(DOWN); +} + +void cursor_left(char* arg) +{ + cursor_mvlr(LEFT); +} + +void cursor_right(char* arg) +{ + cursor_mvlr(RIGHT); +} + +void page_up(char* arg) +{ + view_scrollpage(win_view(FOCUSED), UP); +} + +void page_dn(char* arg) +{ + view_scrollpage(win_view(FOCUSED), DOWN); +} + +void select_prev(char* arg) +{ + view_selprev(win_view(FOCUSED)); +} + +void undo(char* arg) +{ + view_undo(win_view(FOCUSED)); +} + +void redo(char* arg) +{ + view_redo(win_view(FOCUSED)); +} + +void newline(char* arg) +{ + (void)arg; + View* view = win_view(FOCUSED); + if (win_keymodsset(ModShift)) + { + view_byline(view, UP, false); + view_bol(view, false); + } + view_eol(view, false); + view_insert(view, '\n'); +} + +void highlight(char* arg) +{ + view_selctx(win_view(FOCUSED)); +} + +#pragma GCC diagnostic pop diff --git a/bin/editor/stdc.h b/bin/editor/stdc.h new file mode 100644 index 0000000..68e0e36 --- /dev/null +++ b/bin/editor/stdc.h @@ -0,0 +1,171 @@ +/** + @file + @brief Collection of useful C types and functions. + @author Michael D. Lowis + @license BSD 2-clause License +*/ +#define _POSIX_C_SOURCE 200809L +#define _XOPEN_SOURCE 700 +#define AUTOLIB(n) \ + int __autolib_##n __attribute__ ((weak)); +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunused-function" + +static char* strmcat(char* first, ...) { + va_list args; + /* calculate the length of the final string */ + size_t len = strlen(first); + va_start(args, first); + for (char* s = NULL; (s = va_arg(args, char*));) + len += strlen(s); + va_end(args); + /* allocate the final string and copy the args into it */ + char *str = malloc(len+1), *curr = str; + while (first && *first) *(curr++) = *(first++); + va_start(args, first); + for (char* s = NULL; (s = va_arg(args, char*));) + while (s && *s) *(curr++) = *(s++); + va_end(args); + /* null terminate and return */ + *curr = '\0'; + return str; +} + +/* Option Parsing + * + * This following macros implement a simple POSIX-style option parsing strategy. + * They are heavily influenced and inspired by the arg.h file from suckless.org + * (http://git.suckless.org/libsl/tree/arg.h). That file is in turn inspired by + * the corresponding macros defined in plan9. + * + * The interface assumes that the main function will have the following + * prototype: + * + * int main(int argc, char** argv); + * + * An example usage of the interface would look something like the follwoing: + * + * char* ARGV0; + * int main(int argc, char** argv) { + * OPTBEGIN { + * case 'a': printf("Simple option\n"); break; + * case 'b': printf("Option with arg: %s\n", OPTARG()); break; + * default: printf("Unknown option!\n"); + * } OPTEND; + * return 0; + * } + */ + +/* This variable contains the value of argv[0] so that it can be referenced + * again once the option parsing is done. This variable must be defined by the + * program. + * + * NOTE: Ensure that you define this variable with external linkage (i.e. not + * static) */ +extern char* ARGV0; + +/* This is a helper function used by the following macros to parse the next + * option from the command line. */ +static inline char* _getopt_(int* p_argc, char*** p_argv) { + if (!(*p_argv)[0][1] && !(*p_argv)[1]) { + return (char*)0; + } else if ((*p_argv)[0][1]) { + return &(*p_argv)[0][1]; + } else { + *p_argv = *p_argv + 1; + *p_argc = *p_argc - 1; + return (*p_argv)[0]; + } +} + +/* This macro is almost identical to the ARGBEGIN macro from suckless.org. If + * it ain't broke, don't fix it. */ +#define OPTBEGIN \ + for ( \ + ARGV0 = *argv, argc--, argv++; \ + argv[0] && argv[0][1] && argv[0][0] == '-'; \ + argc--, argv++ \ + ) { \ + int brk_; char argc_ , **argv_, *optarg_; \ + if (argv[0][1] == '-' && !argv[0][2]) { \ + (void)optarg_; argv++, argc--; break; \ + } \ + for (brk_=0, argv[0]++, argv_=argv; argv[0][0] && !brk_; argv[0]++) { \ + if (argv_ != argv) break; \ + argc_ = argv[0][0]; \ + switch (argc_) + +/* Terminate the option parsing. */ +#define OPTEND }} + +/* Get the current option character */ +#define OPTC() (argc_) + +/* Get an argument from the command line and return it as a string. If no + * argument is available, this macro returns NULL */ +#define OPTARG() \ + (optarg_ = _getopt_(&argc,&argv), brk_ = (optarg_!=0), optarg_) + +/* Get an argument from the command line and return it as a string. If no + * argument is available, this macro executes the provided code. If that code + * returns, then abort is called. */ +#define EOPTARG(code) \ + (optarg_ = _getopt_(&argc,&argv), \ + (!optarg_ ? ((code), abort(), (char*)0) : (brk_ = 1, optarg_))) + +/* Helper macro to recognize number options */ +#define OPTNUM \ + case '0': \ + case '1': \ + case '2': \ + case '3': \ + case '4': \ + case '5': \ + case '6': \ + case '7': \ + case '8': \ + case '9' + +/* Helper macro to recognize "long" options ala GNU style. */ +#define OPTLONG \ + case '-' + +/* Miscellaneous + *****************************************************************************/ +#ifndef nelem + #define nelem(x) \ + (sizeof(x)/sizeof((x)[0])) +#endif + +#ifndef container_of + #define container_of(obj, type, member) \ + ((type*)((uintptr_t)obj - offsetof(type, member))) +#endif + +#ifndef min + #define min(x,y) \ + ((x) < (y) ? (x) : (y)) +#endif + +#ifndef max + #define max(x,y) \ + ((x) > (y) ? (x) : (y)) +#endif + +#pragma GCC diagnostic pop diff --git a/bin/editor/telem.c b/bin/editor/telem.c new file mode 100644 index 0000000..913688d --- /dev/null +++ b/bin/editor/telem.c @@ -0,0 +1,94 @@ +#include +#include +#include +#include +#include +#include "io.h" + +static int TelemFd = -1; +static char TelemBuf[16384]; + +static int open_telem(char* path) +{ + static unsigned long prev = 0; + unsigned long curr = time(NULL); + if ((TelemFd < 0) && ((curr - prev) >= 1)) + { + struct stat st = {0}; + if ((stat(path, &st) >= 0) && S_ISFIFO(st.st_mode)) + { + TelemFd = open(path, O_WRONLY|O_NONBLOCK, 0); + prev = curr; + } + } + return TelemFd; +} + +static void close_telem(void) +{ + close(TelemFd); + TelemFd = -1; +} + +static char* advance(char* buf, size_t* p_len, long nwrite) +{ + if (nwrite >= 0) + { + *p_len -= nwrite; + buf = (buf + nwrite); + } + return buf; +} + +static char* add_timestamp(char* buf, size_t* p_len) +{ + time_t rawtime; + time(&rawtime); + struct tm* timeinfo = localtime(&rawtime); + long nwrite = strftime(buf, *p_len, "[%F %T] ", timeinfo); + return advance(buf, p_len, nwrite); +} + +static char* add_pid(char* buf, size_t* p_len) +{ + long nwrite = snprintf(buf, *p_len, "%s(%d) ", ARGV0, getpid()); + return advance(buf, p_len, nwrite); +} + +static char* add_message(char* buf, size_t* p_len, char* fmt, va_list args) +{ + long nwrite = vsnprintf(buf, *p_len, fmt, args); + return advance(buf, p_len, nwrite); +} + +static char* get_fifo_path(void) +{ + static char path[1024] = {0}; + if (!path[0]) + { + snprintf(path, sizeof(path)-1,"%s/tide-telem", getpwuid(getuid())->pw_dir); + } + return path; +} + +void telem_send(char* fmt, ...) +{ + char* str = TelemBuf; + size_t nleft = sizeof(TelemBuf); + int fd = open_telem(get_fifo_path()); + if (fd >= 0) + { + void (*sigfn)(int) = signal(SIGPIPE, SIG_IGN); + va_list args; + va_start(args, fmt); + str = add_timestamp(str, &nleft); + str = add_pid(str, &nleft); + str = add_message(str, &nleft, fmt, args); + va_end(args); + if (writefd(fd, TelemBuf, str - TelemBuf) < 0) + { + close_telem(); + } + signal(SIGPIPE, sigfn); + } +} diff --git a/bin/editor/tide.1.adoc b/bin/editor/tide.1.adoc new file mode 100644 index 0000000..c070045 --- /dev/null +++ b/bin/editor/tide.1.adoc @@ -0,0 +1,397 @@ += tide(1) +:doctype: manpage + + +== NAME + +tide - a text editor inspired by acme(1) from Plan 9 and Inferno + +== SYNOPSIS + +*tide* [FILE...] + +== DESCRIPTION + +*tide* is a text editor inspired by the Acme editor from the Plan 9 and Inferno +operating systems. Unlike Acme, *tide* is a single window, single file editor. +Instead of baking a window manager into the editor, this job is relegated to an +X11 window manager. It is recommended that *tide* be used with a tiling window +manager such as dwm(1) or spectrwm(1). These window managers will make dealing +with multiple windows much easier and more efficient. + +=== Windows + +*tide* windows are divided into three basic regions: an expanding tags region, a +main content region and an adjacent scroll region. The tags region acts as +scratch buffer for commands that can be executed to affect the file or the state +of the editor. As the content of this region grows it will expand up to a +quarter of the size of the window, shrinking the main content region in kind. +The main content region displays a view of the file currently being edited. To +the left of the content region is a narrow vertical region matching the height +of the content region. This region gives the user visibility into how much of +the document is currently visible as well as their position in the document. + +=== Typing and Editing + +Typed characters in *tide* are delivered to the currently active region. The +active region is the one currently under the mouse cursor. That is to say, the +focus follows the mouse much as it does in acme(1) but there is a keyboard +shortcut that allows users to toggle focus between the content region and the +tag region by warping the mouse cursor from one region to the next. + +The mechanics of editing text in the tag and content region is identical with +the exception of searching and saving. Edited content in the tag region is not +saved to disk while the content region is. This region is considered a scratch +buffer for commands, notes, and other bits of text that are placed there +temporarily. The content region displays the current view of the file being +edited and can be flushed to disk when requested. + +Searching with a term selected in the tags region will search for the term in +the content region rather than the tags region. In this way a user can edit the +search term incrementally and perform repeated searches through the content +region. Searching for a term in the content region will search for the term in +the content region however. + +=== Text Selection + +*tide* uses a series of rules to determine how much text to select when the user +executes a context sensitive selection, a search, or a context sensitive +execution. The following rules are applied in order until a match is found. + +1. *Cursor over '(' or ')':* + Highlight text between the parentheses including nested parentheses. + +2. *Cursor over '[' or ']':* + Highlight text between the brackets including nested brackets. + +3. *Cursor over '{' or '}':* + Highlight text between the braces including nested braces. + +4. *Cursor at beginning or end of line:* + Highlight the entire line (including the newline) + +5. *Cursor over alphanumeric character or underscore:* + Highlight the word under the cursor consisting of only alphanumeric and + underscore characters. + +If none of the above rules match, *tide* will simply highlight the block of +non-whitespace characters under the cursor. + +=== Mouse Handling + +*Left Button*:: + The left mouse button is used for selecting text or moving the cursor. A + single-click will move the mose to the clicked location. A double-click will + will select the object under the clicked location based on context as + described in <>. A triple-click will select the largest + contiguous chunk of non-whitespace characters at the clicked location. + +*Middle Button*:: + The middle mouse button is used for executing text at the clicked location. + The command to be executed is determined by the context rules defined in the + <> section. The cursor position is not changed on a + middle click. + +*Right Button*:: + The right button is used to search for the next occurrence of the clicked + text. The search term is determined by the context rules defined in the + <> section. The search direction follows the direction of + the previous search operation. The *Shift* key can be held in combination + with a click of the right mosue button in order to reverse the search + direction. + +=== Command Execution + +*tide* allows for the execution of any arbitrary text as a command. The input +and output to/from each command executed can be controlled by prepending one of +a set of sigils defined below. These sigils instruct *tide* from where the +command will receive its input and where it will place its output (both standard +and errors). + +*! - Run command detached from editor*:: + The command will be executed in the background and all of its input and + output file descriptors will be closed. + +*< - Input from command*: + The command will be executed in the background and its standard output will + be placed in the content region. Its error output will be placed in the tags + region. + +*> - Output to command*:: + The command will be executed in the background. The currently selected text + will be written to the command's standard input. The command's standard + output and standard error content will be written to the tags region. + +*| - Pipe through command*:: + The command will be executed in the background. The currently selected text + will be written to the command's standard input. The command's standard + output will replace the currently selected text. Any error output will be + placed in the tags region. + +*: - Pipe through sed(1)*:: + Identical to '|' except that the command is always sed(1). This is a + convenience shortcut to allow quick and easy access to sed for editing + blocks of text. + +*Commands with no sigil*:: + Commands with none of the aforementioned sigils will be executed in the + background and have their standard output placed in the content region and + their error output placed in the tags region. + +=== Keyboard Shortcuts + +*Unix Standard Shortcuts:* + +*Ctrl+u*:: + Delete from the cursor position to the beginning of the line. + +*Ctrl+k*:: + Delete from the cursor position to the end of the line. + +*Ctrl+w*:: + Delete the word to the left. + +*Ctrl+a*:: + Move cursor to the beginning of the line. + +*Ctrl+e*:: + Move cursor to the end of the line. + +*Cursor Movement and Selection:* + +The key combinations below are responsible for moving the cursor around the +document by character, by word, or by line. The *Shift* modifier key can be +applied to any of them to also extend the current selection to the new cursor +position. + +*Escape*:: + Highlight the last contiguous block of inserted text or clear the current + selection (deselect the currently selected text). + +*Left*:: + Move the cursor one character to the left. + +*Right*:: + Move the cursor one character to the right. + +*Up*:: + Move the cursor to the previous line. + +*Down*:: + Move the cursor to the next line. + +*Ctrl+Up*:: + Move the current line or selection up a line. + +*Ctrl+Down*:: + Move the current line or selection down a line. + +*Ctrl+Left*:: + Move the cursor to the beginning of the word to the left. + +*Ctrl+Right*:: + Move the cursor to the end of the word to the right. + +*Modern Text Editing Shortcuts:* + +*Ctrl+s*:: + Save the contents of the content region to disk. + +*Ctrl+z*:: + Undo the last change performed on the active region. + +*Ctrl+y*:: + Redo the previously undone change on the active region. + +*Ctrl+x*:: + Cut the selected text to the X11 CLIPBOARD selection. If no text is selected + then the current line is cut. + +*Ctrl+c*:: + Copy the selected text to the X11 CLIPBOARD selection. If no text is + selected then the current line is copied. + +*Ctrl+v*:: + Paste the contents of the X11 CLIPBOARD selection to the active region. + +*Ctrl+j*:: + Join the current line and the next line. + +*Ctrl+l*:: + Select the current line. + +*Ctrl+Shift+a*:: + Select all text in the buffer. + +*Delete*:: + Delete the character to the right. + +*Ctrl+Delete*:: + Delete the word to the right. + +*Backspace*:: + Delete the character to the left. + +*Ctrl+Backspace*:: + Delete the word to the left. + +*Ctrl+Enter*:: + Create a new line after the current line and place the cursor there. + +*Ctrl+Shift+Enter*:: + Create a new line before the current line and place the cursor there. + +*PageUp*:: + Scroll the active region up by one screenful of text. The cursor is not + affected by this operation. + +*PageDn*:: + Scroll the active region down by one screenful of text. The cursor is not + affected by this operation. + +*Search Shortcuts:* + +The shortcuts below allow the user to search for selected text or by context. +The direction of the search defaults to the forward direction with regard to the +position in the file. Each search follows the direction of the previous search +unless the *Shift* modifier is applied. The *Shift* modifier causes the current +search operation to be applied in the opposite direction of the previous. + +*Ctrl+f*:: + Search for the next occurrence of the selected text in the content region. + If no text is currently selected, the text under the cursor is selected + based on context as described in <>. + +*Ctrl+Alt+f*:: + Search for the next occurence previous search term in the content region. + +*Implementation-specific Shortcuts* + +*Ctrl+[*:: + Decrease the indent level of the selected text. + +*Ctrl+]*:: + Increase the indent level of the selected text. + +*Ctrl+h*:: + Highlight the item under cursor following the rules in <> + +*Ctrl+t*:: + Toggle focus between the tags region and the content region. + +*Ctrl+q*:: + Quit the editor. If the file is modified a warning will be printed in the + tags region and the editor will not quit. Executing the shortcut twice + within 250ms will ignore the warning and quit the editor without saving. + +*Ctrl+d*:: + Execute the selected text as described in <>. If no text is + selected, the text under cursor is selecte dbased on context as described in + <>. + +*Ctrl+o*:: + Launch pickfile(1) to choose a file from a recursive list of files in the + current directory and sub directories. This file will be opened in a new + instance of *tide*. + +*Ctrl+p*:: + Launch picktag(1) to select a tag from a ctags(1) generated index file. + *tide* will jump to the selected ctag definition in the current window if + the file is currently being edited. Otherwise, a new instance of *tide* will + be launched with the target file and the cursor set to the line containing + the definition. + +*Ctrl+g*:: + Lookup the selected symbol or symbol under the cursor in a ctags(1) + generated index file. Jump to the location of the definition if it exist in + the current file. Otherwise, a new instance of *tide* will be launched with + the target file and the cursor set to the line containing the definition. + +*Ctrl+n*:: + Open a new instance of *tide* with no filename. + +=== Builtin Tags + +*Cut*:: + Cut the selection to the X11 CLIPBOARD selection. + +*Copy*:: + Copy the selection to the X11 CLIPBOARD selection. + +*Eol*:: + Toggle the line-ending style for the buffers contents between LF and CRLF + +*Find [term]*:: + Find the next occurrence of the selected text. + +*GoTo [arg]*:: + Jump to a specific line number or symbol. + +*Indent*:: + Toggle the autoindent feature on or off. + +*Overwrite*:: + Save the file to disk even if the file has been modified externally. + +*Paste*:: + Paste the contents of the X11 CLIPBOARD selection into the buffer. + +*Quit*:: + Quit the editor. + +*Redo*:: + Redo the last undone change. + +*Reload*:: + Reload the buffer contents from the on-disk file. + +*Save*:: + Save the contents of the buffer to disk. + +*SaveAs [path]*:: + Save the contents of the buffer to disk. If a path argument is provided, the + buffer will be saved to the new path. + +*Tabs*:: + Toggle the expand tabs featuer on or off. + +*Undo*:: + Undo the previous edit. + +== OPTIONS + +*-I _OPT_*:: + Turn on/off auto indenting of new lines. + +*-W _OPT_*:: + Turn on/off trimming of trailing whitespace on save. + +*-E _OPT_*:: + Turn on/off expanding tabs to spaces. + +*-N _OPT_*:: + Turn on/off DOS line ending styles () + +*-T _STRING_*:: + Sets the text of the tags region. + +*-S _SHELL_*:: + Sets the shell to be used for all external command invocation. The shell + must use the -c flag to receive commands to execute + +*-c _CMD_*:: + Executes the given command in a new tide window. + +*-l _LINENUM_*:: + Jumps the view to LINENUM in the document. + +== ENVIRONMENT + +*SHELL*:: + The contents of this variable are used as the shell in which all non-builtin + commands are executed. If this variable is not defined, sh(1) is used as a + fallback shell. + +== SEE ALSO + +acme(1), edit(1), pick(1), pickfile(1), picktag(1) diff --git a/bin/editor/tide.h b/bin/editor/tide.h new file mode 100644 index 0000000..4de3902 --- /dev/null +++ b/bin/editor/tide.h @@ -0,0 +1,349 @@ +#include "x11.h" + +/* undo/redo list item */ +typedef struct Log { + struct Log* next; /* pointer to next operation in the stack */ + size_t beg; /* beginning of affected region */ + size_t end; /* end of affected region*/ + char* data; /* pointer to deleted character data */ + int transid; /* id of transaction this item is a part of */ +} Log; + +/* cursor/selection representation */ +typedef struct { + size_t beg; + size_t end; + size_t col; +} Sel; + +typedef struct { + size_t bufsize; /* size of the buffer in runes */ + char* bufstart; /* start of the data buffer */ + char* bufend; /* end of the data buffer */ + char* gapstart; /* start of the gap */ + char* gapend; /* end of the gap */ +} GapBuf; + +typedef struct { + int transid; /* id number of the current transaction */ + Log* undo; /* undo list */ + Log* redo; /* redo list */ + Log* save; /* pointer to last save position */ +} EditLog; + +/* gap buffer main data structure */ +typedef struct { + enum { + NORMAL = 0, MODIFIED, OUTDATED, ERRORED + } status; + char* path; /* the path to the open file */ + GapBuf contents; /* underlying sequence data structure */ + EditLog log; /* underlying log of edit operations */ + Sel selection; /* the currently selected text */ + Sel point; /* tracks external command I/O */ + void (*oninsert)(int byte); +} Buf; + +enum { + BY_RUNE = 0, + BY_WORD, + BY_LINE +}; + +void range_add(Sel* p_range, size_t off); +void range_del(Sel* p_range, size_t beg, size_t end); + +void gapbuf_init(GapBuf* buf); +size_t gapbuf_end(GapBuf* buf); +long gapbuf_save(GapBuf* buf, char* path); +void gapbuf_load(GapBuf* buf, char* path); +char gapbuf_getb(GapBuf* buf, size_t off); +void gapbuf_putb(GapBuf* buf, char b, Sel* p_sel); +void gapbuf_del(GapBuf* buf, size_t off, size_t len); + +void editlog_seqstart(EditLog* log); +void editlog_seqstop(EditLog* log); +void editlog_clear(EditLog* log); +void editlog_lastins(EditLog* log, Sel* p_sel); +void editlog_undo(Buf* log); +void editlog_redo(Buf* log); +void editlog_add(Buf* buf, size_t beg, size_t end, char* data); + +void buf_init(Buf* buf); +void buf_setpath(Buf* buf, char* path); +void buf_load(Buf* buf, char* path); +void buf_reload(Buf* buf); +int buf_save(Buf* buf, char* path); +size_t buf_end(Buf* buf); + +int buf_getrat(Buf* buf, size_t off); +void buf_putc(Buf* buf, int c); +void buf_puts(Buf* buf, char* s); +int buf_getc(Buf* buf); +char* buf_gets(Buf* buf); +char* buf_getsat(Buf* buf, size_t beg, size_t end); +void buf_del(Buf* buf); + +void buf_logstart(Buf* buf); +void buf_logstop(Buf* buf); +void buf_logclear(Buf* buf); +void buf_lastins(Buf* buf); +void buf_undo(Buf* buf); +void buf_redo(Buf* buf); + +bool buf_isbol(Buf* buf, size_t pos); +bool buf_iseol(Buf* buf, size_t pos); +size_t buf_bol(Buf* buf, size_t pos); +size_t buf_eol(Buf* buf, size_t pos); + +bool buf_findstr(Buf* buf, int dir, char* str); +char* buf_fetch(Buf* buf, bool (*isword)(Rune), size_t off); + +void buf_setln(Buf* buf, size_t line); +void buf_getln(Buf* buf, size_t* begln, size_t* endln); + +size_t buf_selbeg(Buf* buf); +size_t buf_selend(Buf* buf); +size_t buf_selsz(Buf* buf); +void buf_selln(Buf* buf); +void buf_selclr(Buf* buf, int dir); +bool buf_insel(Buf* buf, size_t off); +bool buf_inpoint(Buf* buf, size_t off); +void buf_selword(Buf* buf, bool (*isword)(Rune)); +void buf_selall(Buf* buf); +void buf_selctx(Buf* buf, bool (*isword)(Rune)); + +size_t buf_byrune(Buf* buf, size_t pos, int count); +size_t buf_moveby(Buf* buf, int bything, size_t pos, int count); +void buf_selmove(Buf* buf, bool extsel, int move, int bything); +void buf_selmoveto(Buf* buf, bool extsel, size_t off); + +/* Screen management functions + *****************************************************************************/ +typedef struct { + size_t off; /* offset of the rune in the buffer */ + size_t width; /* width of the glyph on screen */ + Rune rune; /* rune value for the cell */ +} UGlyph; + +typedef struct { + size_t off; /* offset of the first rune in the row */ + size_t len; /* number of runes displayed in the row */ + UGlyph cols[]; /* row data */ +} Row; + +typedef struct { + enum { + CURSOR = (1 << 0), + CENTER = (1 << 1), + } sync_flags; /* flags controlling how the view is synced to cursor */ + Buf buffer; /* the buffer used to populate the view */ + size_t index; /* */ + size_t width; /* width of the view in pixels */ + size_t nvisible; /* number of visible lines */ + size_t nrows; /* number of rows and columns in the view */ + Row** rows; /* array of row data structures */ +} View; + +enum { + LEFT = -1, + RIGHT = +1, + UP = -1, + DOWN = +1 +}; + +void view_init(View* view, char* file); +void view_sync(View* view); +void view_reload(View* view); +size_t view_limitrows(View* view, size_t maxrows); +void view_resize(View* view, size_t width, size_t nrows); +void view_update(View* view); +Row* view_getrow(View* view, size_t row); +void view_byrune(View* view, int move, bool extsel); +void view_byword(View* view, int move, bool extsel); +void view_byline(View* view, int move, bool extsel); +char* view_fetch(View* view, size_t row, size_t col, bool (*isword)(Rune)); +bool view_findstr(View* view, int dir, char* str); +void view_insert(View* view, Rune rune); +void view_delete(View* view, int dir, bool byword); +void view_bol(View* view, bool extsel); +void view_eol(View* view, bool extsel); +void view_bof(View* view, bool extsel); +void view_eof(View* view, bool extsel); +void view_undo(View* view); +void view_redo(View* view); +void view_paste(View* view, char* str); +void view_putstr(View* view, char* str); +void view_putraw(View* view, char* str); +char* view_getstr(View* view); +char* view_getcmd(View* view); +char* view_getword(View* view); +char* view_getctx(View* view); +void view_selctx(View* view); +void view_scroll(View* view, int move); +void view_scrollpage(View* view, int move); +void view_setln(View* view, size_t line); +size_t view_selsize(View* view); +void view_selprev(View* view); +void view_setcursor(View* view, size_t row, size_t col, bool extsel); +void view_selextend(View* view, size_t row, size_t col); +void view_selword(View* view, size_t row, size_t col); +void view_select(View* view, size_t row, size_t col); +void view_jumpto(View* view, bool extsel, size_t off); +void view_scrollto(View* view, size_t csr); +Rune view_getrune(View* view); +void view_selectall(View* view); +void view_selectobj(View* view, bool (*istype)(Rune)); + +/* Command Executions + *****************************************************************************/ +/* src/tide/job.c */ + +typedef struct Job Job; + +typedef void (*jobfn_t)(Job* job); + +struct Job { + Job *next; + int pid, fd; + void *data; + void (*writefn)(Job *job); + void (*readfn)(Job *job); +}; + +Job* job_list(void); +void job_kill(Job* job); +bool job_poll(int ms); +void job_spawn(int fd, jobfn_t readfn, jobfn_t writefn, void* data); +void job_start(char** cmd, char* data, size_t ndata, View* dest); +int job_run(char** cmd); +void job_readfd(int fd, View* view); + +/* Common Shortcuts + *****************************************************************************/ +/* src/tide/shortcuts.c */ + +void select_line(char* arg); +void join_lines(char* arg); +void delete(char* arg); +void cut(char* arg); +void paste(char* arg); +void copy(char* arg); +void del_to_bol(char* arg); +void del_to_eol(char* arg); +void del_to_bow(char* arg); +void backspace(char* arg); +void cursor_bol(char* arg); +void cursor_eol(char* arg); +void cursor_mvlr(int dir); +void cursor_mvupdn(int dir); +void cursor_home(char* arg); +void cursor_end(char* arg); +void cursor_up(char* arg); +void cursor_dn(char* arg); +void cursor_left(char* arg); +void cursor_right(char* arg); +void page_up(char* arg); +void page_dn(char* arg); +void select_prev(char* arg); +void undo(char* arg); +void redo(char* arg); +void newline(char* arg); +void highlight(char* arg); + +enum { + MouseActNone, + MouseActSel, + MouseActSelCtx, + MouseActSelWord, + MouseActCut, + MouseActPaste, + MouseActExec, + MouseActExecArg, + MouseActFetch, + MouseActScrollUp, + MouseActScrollDn, +}; + +int process_mouse(int btn, bool pressed); + +/* Command and Tag Exectuion + *****************************************************************************/ +/* src/tide/exec.c */ + +typedef struct { + char* tag; + void (*action)(char*); +} Tag; + +void exec_init(Tag* p_tags); +void exec_cmd(char* cmd); +void exec_cmdwarg(char* cmd, char* arg); +void exec_rawwarg(char* cmd, char* arg); + +/* UI Window Control + *****************************************************************************/ +/* src/tide/main.c */ + +typedef struct XFont XFont; + +typedef struct { + uint32_t attr; /* attributes applied to this cell */ + uint32_t rune; /* rune value for the cell */ +} XGlyph; + +typedef struct { + void* font; + uint32_t glyph; + short x; + short y; +} XGlyphSpec; + +enum { + MouseLeft = 1, + MouseMiddle = 2, + MouseRight = 3, + MouseWheelUp = 4, + MouseWheelDn = 5 +}; + +typedef enum { + TAGS = 0, + EDIT = 1, + NREGIONS = 2, + FOCUSED = 2 +} WinRegion; + +void win_init(void); +void win_title(char* path); +void win_font(char* font); +void win_prop_set(char* xname, char* ename, char* value); +void win_loop(void); +void win_quit(void); +void win_togglefocus(void); +View* win_view(WinRegion id); +Buf* win_buf(WinRegion id); +bool win_keymodsset(int mask); +void win_setln(int line_num); + +typedef struct { + int x, y; + int h, w; +} drawcsr; + +/* UI Drawing Routines + *****************************************************************************/ +void draw_rect(XConf* x, int color, int posx, int posy, int width, int height); +void draw_statbox(XConf* x, int status); +int draw_hrule(XConf* x, drawcsr* csr); +void draw_view(XConf* x, View* view, XftFont* font, size_t nrows, drawcsr* csr, int bg, int fg, int sel, bool csrsync); +bool draw_csr(XConf* x, View* view, int fg, size_t fheight, int posx, int posy, bool csrdrawn); +void draw_scroll(XConf* x, drawcsr* csr, View* view, int divider); + +/* Pseudo Terminal Routines + *****************************************************************************/ +bool xpty_active(void); +void xpty_togglescroll(void); +int xpty_run(View* view, char** cmd); +void xpty_send(char* cmd); +void xpty_rawsend(char* cmd); diff --git a/bin/editor/utf.h b/bin/editor/utf.h new file mode 100644 index 0000000..906f2f1 --- /dev/null +++ b/bin/editor/utf.h @@ -0,0 +1,21 @@ +/** @file */ +enum { + UTF_MAX = 6u, /* maximum number of bytes that make up a rune */ + RUNE_SELF = 0x80, /* byte values larger than this are *not* ascii */ + RUNE_ERR = 0xFFFD, /* rune value representing an error */ + RUNE_MAX = 0x10FFFF, /* Maximum decodable rune value */ + RUNE_EOF = -1, /* rune value representing end of file */ +}; + +/* Represents a unicode code point */ +typedef int32_t Rune; + +size_t utf8encode(char str[UTF_MAX], Rune rune); +bool utf8decode(Rune* rune, size_t* length, int byte); +int runewidth(unsigned col, Rune r); +bool risword(Rune r); +bool rissigil(Rune r); +bool risfile(Rune r); +bool riscmd(Rune r); +bool risblank(Rune r); +bool risbigword(Rune r); diff --git a/bin/editor/utf8.c b/bin/editor/utf8.c new file mode 100644 index 0000000..078c5a3 --- /dev/null +++ b/bin/editor/utf8.c @@ -0,0 +1,144 @@ +#include +#include "utf.h" +#include +#include + +#include "config.h" + +static const uint8_t UTF8_SeqBits[] = { 0x00, 0x80, 0xC0, 0xE0, 0xF0, 0xF8, 0xFC, 0xFE }; +static const uint8_t UTF8_SeqMask[] = { 0x00, 0xFF, 0x1F, 0x0F, 0x07, 0x03, 0x01, 0x00 }; +static const uint8_t UTF8_SeqLens[] = { 0x01, 0x00, 0x02, 0x03, 0x04, 0x05, 0x06, 0x00 }; + +static bool runevalid(Rune val) +{ + return (val <= RUNE_MAX) + && ((val & 0xFFFE) != 0xFFFE) + && ((val < 0xD800) || (val > 0xDFFF)) + && ((val < 0xFDD0) || (val > 0xFDEF)); +} + +static size_t runelen(Rune rune) +{ + size_t ret; + if(!runevalid(rune)) + { + ret = 0; + } + else if(rune <= 0x7F) + { + ret = 1; + } + else if(rune <= 0x07FF) + { + ret = 2; + } + else if(rune <= 0xFFFF) + { + ret = 3; + } + else + { + ret = 4; + } + return ret; +} + +static uint8_t utfseq(uint8_t byte) +{ + uint8_t ret = 0; + for (int i = 1; i < 8; i++) + { + if ((byte & UTF8_SeqBits[i]) == UTF8_SeqBits[i-1]) + { + ret = UTF8_SeqLens[i-1]; + break; + } + } + return ret; +} + +size_t utf8encode(char str[UTF_MAX], Rune rune) +{ + size_t len = runelen(rune); + str[0] = (len == 1 ? 0x00 : UTF8_SeqBits[len]) + | (UTF8_SeqMask[len] & (rune >> (6 * (len-1)))); + for (size_t i = 1; i < len; i++) + str[i] = 0x80u | (0x3Fu & (rune >> (6 * (len-i-1)))); + return len; +} + +bool utf8decode(Rune* rune, size_t* length, int byte) +{ + /* Handle the start of a new rune */ + if (*length == 0) { + /* If we were fed in an EOF as a start byte, handle it here */ + if (byte == EOF) { + *rune = RUNE_EOF; + } else { + /* Otherwise, decode the first byte of the rune */ + *length = utfseq(byte); + *rune = (*length == 0) ? RUNE_ERR : (byte & UTF8_SeqMask[*length]); + (*length)--; + } + /* Handle continuation bytes */ + } else if ((byte & 0xC0) == 0x80) { + /* add bits from continuation byte to rune value + * cannot overflow: 6 byte sequences contain 31 bits */ + *rune = (*rune << 6) | (byte & 0x3F); /* 10xxxxxx */ + (*length)--; + /* Sanity check the final rune value before finishing */ + if ((*length == 0) && !runevalid(*rune)) + *rune = RUNE_ERR; + /* Didn't get the continuation byte we expected */ + } else { + *rune = RUNE_ERR; + } + /* Tell the caller whether we finished or not */ + return ((*length == 0) || (*rune == RUNE_ERR)); +} + +int runewidth(unsigned col, Rune r) +{ + int width; + if (r == '\t') + { + width = (TabWidth - (col % TabWidth)); + } + else + { + width = wcwidth(r); + if (width < 0) width = 1; + } + return width; +} + +bool risword(Rune r) +{ + return (r < 127 && (isalnum(r) || r == '_' || r == '+' || r == '-')); +} + +bool rissigil(Rune r) +{ + return (r == ':' || r == '!' || r == '&' || r == '|' || r == '>' || r == '<'); +} + +bool risfile(Rune r) +{ + return (risword(r) || r == '/' || r == '.' || r == ':' || r == '-' || r == '~'); +} + +bool riscmd(Rune r) +{ + return (risword(r) || rissigil(r)); +} + +bool risblank(Rune r) +{ + return (r == ' ' || r == '\t' || r == '\n' || r == '\r'); +} + +bool risbigword(Rune r) +{ + return !risblank(r); +} + diff --git a/bin/editor/vec.h b/bin/editor/vec.h new file mode 100644 index 0000000..4cd8f03 --- /dev/null +++ b/bin/editor/vec.h @@ -0,0 +1,126 @@ +/** + @file + @brief Generic vector implementation. + @author Michael D. Lowis + @license BSD 2-clause License +*/ +#ifndef VEC_H +#define VEC_H + +#include +#include +#include +#include +#include + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunused-function" + +typedef struct { + size_t elem_count; + size_t elem_size; + size_t elem_capacity; + uint8_t* elem_buffer; +} vec_t; + +typedef int (*vec_cmpfn_t)(const void*,const void*); + +#ifndef DEFAULT_VEC_CAPACITY +#define DEFAULT_VEC_CAPACITY (size_t)8 +#endif + +static void vec_init(vec_t* vec, size_t elem_size) { + vec->elem_size = elem_size; + vec->elem_count = 0; + vec->elem_capacity = DEFAULT_VEC_CAPACITY; + vec->elem_buffer = malloc(elem_size * vec->elem_capacity); +} + +static size_t vec_size(vec_t* vec) { + return vec->elem_count; +} + +static bool vec_empty(vec_t* vec) { + return (vec->elem_count == 0); +} + +static size_t vec_capacity(vec_t* vec) { + return vec->elem_capacity; +} + +static size_t vec_next_capacity(size_t req_size) { + size_t next_power = req_size; + size_t num_bits = sizeof(size_t) * 8; + size_t bit_n; + + /* Find the next highest power of 2 */ + next_power--; + for (bit_n = 1; bit_n < num_bits; bit_n = bit_n << 1) + next_power = next_power | (next_power >> bit_n); + next_power++; + + return next_power; +} + +static void vec_reserve(vec_t* vec, size_t size) { + vec->elem_buffer = realloc(vec->elem_buffer, size * vec->elem_size); + vec->elem_capacity = size; +} + +static void vec_resize(vec_t* vec, size_t count, void* fillval) { + if (count > vec->elem_count) { + vec_reserve(vec, vec_next_capacity(count+1)); + for (; vec->elem_count < count; vec->elem_count++) + memcpy(&(vec->elem_buffer[vec->elem_count * vec->elem_size]), fillval, vec->elem_size); + } else if (count < vec->elem_count) { + vec->elem_count = count; + } +} + +static void vec_shrink_to_fit(vec_t* vec) { + vec->elem_buffer = realloc(vec->elem_buffer, vec->elem_count * vec->elem_size); + vec->elem_capacity = vec->elem_count; +} + +static void* vec_at(vec_t* vec, size_t index) { + return &(vec->elem_buffer[index * vec->elem_size]); +} + +static void vec_set(vec_t* vec, size_t index, void* data) { + memcpy(&(vec->elem_buffer[index * vec->elem_size]), data, vec->elem_size); +} + +static bool vec_insert(vec_t* vec, size_t index, size_t num_elements, ...) { + (void)vec; + (void)index; + (void)num_elements; + return false; +} + +static bool vec_erase(vec_t* vec, size_t start_idx, size_t end_idx) { + (void)vec; + (void)start_idx; + (void)end_idx; + return false; +} + +static void vec_push_back(vec_t* vec, void* data) { + vec_resize(vec, vec->elem_count+1, data); +} + +static void vec_pop_back(vec_t* vec, void* outdata) { + vec->elem_count--; + memcpy(outdata, &(vec->elem_buffer[vec->elem_count * vec->elem_size]), vec->elem_size); +} + +static void vec_clear(vec_t* vec) { + vec->elem_count = 0; +} + +static void vec_sort(vec_t* vec, int (*cmpfn)(const void*,const void*)) { + qsort(vec->elem_buffer, vec->elem_count, vec->elem_size, cmpfn); +} + +#pragma GCC diagnostic pop + +#endif /* VEC_H */ diff --git a/bin/editor/view.c b/bin/editor/view.c new file mode 100644 index 0000000..a5afaab --- /dev/null +++ b/bin/editor/view.c @@ -0,0 +1,635 @@ +#include +#include "dbc.h" +#include "utf.h" +#include + +#include "tide.h" +#include "config.h" + +#ifndef NDEBUG +static bool view_valid(View* view) +{ + return ( + (view->sync_flags <= 3u) + && (!view->nrows || view->index < view->nrows) +// && (view->width > 0) +// && (view->nvisible > 0) +// && (view->nrows > 0) +// && (view->rows != NULL) + ); +} +#endif + +/* Provided by x11.c */ +extern size_t glyph_width(View* view, int c); + +#define BUF (&(view->buffer)) +#define CSRPOS (view->buffer.selection.end) + +static void move_selection(View* view, bool extsel, int move, int bything) +{ + view->sync_flags |= CURSOR; + buf_selmove(BUF, extsel, move, bything); +} + +static void move_to(View* view, bool extsel, size_t off) +{ + view->sync_flags |= CURSOR; + buf_selmoveto(BUF, extsel, off); +} + +static bool selection_visible(View* view) +{ + bool visible = true; + if (view->rows && view->nrows) + { + size_t csr = CSRPOS; + size_t beg = view->rows[0]->off; + size_t end = view->rows[view->nrows-1]->off + + view->rows[view->nrows-1]->len; + visible = (beg <= csr && csr <= end); + } + return visible; +} + +static Sel* getsel(View* view) +{ + return &(view->buffer.selection); +} + +static void clear_rows(View* view, size_t startidx) +{ + if (view->rows) + { + /* Free and clear invalid rows now */ + for (size_t i = startidx; i < view->nrows; i++) + { + free(view->rows[i]); + view->rows[i] = NULL; + } + /* grow row array if needed */ + if (startidx > view->nrows) + { + view->rows = realloc(view->rows, startidx); + } + /* zero out newly created slots */ + for (size_t i = view->nrows; i < startidx; i++) + { + view->rows[i] = NULL; + } + view->nrows = startidx; + } +} + +void view_init(View* view, char* file) +{ + require(view != NULL); + clear_rows(view, 0); + view->sync_flags |= (CURSOR|CENTER); + view->index = 0; + view->width = 0; + view->nvisible = 0; + /* load the file and jump to the address returned from the load function */ + buf_init(BUF); + if (file) + { + buf_load(BUF, file); + } + ensure(view_valid(view)); +} + +void view_reload(View* view) +{ + require(view != NULL); + if (view->buffer.path) + { + buf_reload(BUF); + view->sync_flags |= (CURSOR|CENTER); + } + ensure(view_valid(view)); +} + +void view_sync(View* view) +{ + require(view != NULL); + view->sync_flags |= (CURSOR|CENTER); + ensure(view_valid(view)); +} + +static size_t rune_width(View* view, int c, size_t xpos, size_t width) +{ + size_t sz; + if (c == '\r') + { + sz = 0; + } + else if (c == '\n') + { + sz = (width-xpos); + } + else if (c == '\t') + { + sz = (glyph_width(view, c) - (xpos % glyph_width(view, c))); + } + else + { + sz = glyph_width(view, c); + } + return sz; +} + +size_t view_limitrows(View* view, size_t maxrows) +{ + require(view != NULL); + size_t nrows = 1, off = 0, xpos = 0; + while (nrows < maxrows && off < buf_end(&(view->buffer))) + { + Rune rune = buf_getrat(&(view->buffer), off); + xpos += rune_width(view, rune, xpos, view->width); + /* if the line is full, reset the line and increase row count */ + if (xpos > view->width) + { + xpos = 0, nrows++; + } + else + { + if (rune == '\n') + { + xpos = 0; + nrows++; + } + off = buf_byrune(&(view->buffer), off, RIGHT); + } + } + ensure(nrows >= 1); + return nrows; +} + +static size_t add_row(View* view, size_t off) +{ + /* allocate a new row */ + view->nrows++; + view->rows = realloc(view->rows, sizeof(Row*) * view->nrows); + view->rows[view->nrows-1] = calloc(1, sizeof(Row)); + view->rows[view->nrows-1]->off = off; + + /* populate the row with characters */ + for (size_t xpos = 0; xpos < view->width;) + { + int rune = buf_getrat(&(view->buffer), off); + size_t rwidth = rune_width(view, rune, xpos, view->width); + xpos += rwidth; + if (xpos <= view->width) + { + size_t len = view->rows[view->nrows-1]->len + 1; + view->rows[view->nrows-1] = realloc( + view->rows[view->nrows-1], sizeof(Row) + (len * sizeof(UGlyph))); + view->rows[view->nrows-1]->len = len; + view->rows[view->nrows-1]->cols[len-1].rune = rune; + view->rows[view->nrows-1]->cols[len-1].width = rwidth; + view->rows[view->nrows-1]->cols[len-1].off = off; + off = buf_byrune(&(view->buffer), off, RIGHT); + } + } + return off; +} + +static void resize(View* view, size_t width, size_t nrows, size_t off) +{ + bool first_line_done = false; + clear_rows(view, 0); + view->width = width; + view->nvisible = nrows; + view->index = 0; + size_t beg = off, bend = buf_end(&(view->buffer)); + off = buf_bol(&(view->buffer), off); + if (off > bend) + { + off = bend; + } + + for (size_t i = 0; nrows > 0; i++) + { + off = add_row(view, off); + Row* row = view->rows[view->nrows-1]; + first_line_done = (first_line_done || (row->cols[row->len-1].rune == '\n')); + if (first_line_done) + { + nrows--; + } + if (beg < bend && beg >= row->off && beg <= row->cols[row->len-1].off) + { + view->index = i; + } + } +} + +void view_resize(View* view, size_t width, size_t nrows) +{ + require(view != NULL); + require(width > 0); + require(nrows > 0); + if (view->width == width && view->nvisible == nrows) + { + return; + } + size_t off = (view->nrows && view->index < view->nrows ? view->rows[view->index]->off : 0); + resize(view, width, nrows, off); + ensure(view_valid(view)); +} + +void view_update(View* view) +{ + require(view != NULL); + /* refill the view contents to make sure updates are visible */ + size_t off = view->rows[view->index]->off; + clear_rows(view, view->index); + for (size_t i = 0; i < view->nvisible; i++) + { + off = add_row(view, off); + } + /* sync up the view with the cursor */ + if (view->sync_flags) + { + if (view->sync_flags & CENTER) + { + resize(view, view->width, view->nrows, CSRPOS); + view_scroll(view, UP * (view->nvisible/2)); + } + else + { + Row* lastrow = view->rows[view->nrows-1]; + size_t last_off = lastrow->cols[lastrow->len-1].off; + view_scrollto(view, CSRPOS); + if (last_off < CSRPOS) + { + view_scroll(view, UP * (view->nvisible-1)); + } + } + view->sync_flags = 0; + } + ensure(view_valid(view)); +} + +Row* view_getrow(View* view, size_t row) +{ + return (row < view->nrows ? view->rows[row] : NULL); +} + +void view_byrune(View* view, int move, bool extsel) +{ + require(view != NULL); + move_selection(view, extsel, move, BY_RUNE); + ensure(view_valid(view)); +} + +void view_byword(View* view, int move, bool extsel) +{ + require(view != NULL); + move_selection(view, extsel, move, BY_WORD); + ensure(view_valid(view)); +} + +void view_byline(View* view, int move, bool extsel) +{ + require(view != NULL); + move_selection(view, extsel, move, BY_LINE); + ensure(view_valid(view)); +} + +static size_t getoffset(View* view, size_t row, size_t col) +{ + size_t ret = 0, i = 0, y = 0, idx = view->index + row; + if (idx < view->nrows) + { + Row* selrow = view->rows[idx]; + for (; i < selrow->len; i++) + { + y += selrow->cols[i].width; + if (col < y) + { + break; + } + } + ret = selrow->cols[i].off; + } + return ret; +} + +void view_setcursor(View* view, size_t row, size_t col, bool extsel) +{ + buf_selmoveto(BUF, extsel, getoffset(view, row, col)); + ensure(view_valid(view)); +} + +void view_selword(View* view, size_t row, size_t col) +{ + if (row != SIZE_MAX && col != SIZE_MAX) + { + view_setcursor(view, row, col, false); + } + buf_selword(BUF, risbigword); + ensure(view_valid(view)); +} + +void view_selprev(View* view) +{ + if (!view_selsize(view)) + { + buf_lastins(BUF); + } + else + { + buf_selclr(BUF, RIGHT); + } + ensure(view_valid(view)); +} + +void view_select(View* view, size_t row, size_t col) +{ + view_setcursor(view, row, col, false); + buf_selctx(BUF, risword); + ensure(view_valid(view)); +} + +size_t view_selsize(View* view) +{ + return buf_selsz(BUF); +} + +char* view_fetch(View* view, size_t row, size_t col, bool (*isword)(Rune)) +{ + char* str = NULL; + size_t off = getoffset(view, row, col); + if (off != SIZE_MAX) + { + str = buf_fetch(BUF, isword, off); + } + ensure(view_valid(view)); + return str; +} + +bool view_findstr(View* view, int dir, char* str) +{ + bool found = buf_findstr(BUF, dir, str); + view->sync_flags |= (CURSOR|CENTER); + ensure(view_valid(view)); + return found; +} + +void view_insert(View* view, Rune rune) +{ + /* ignore non-printable control characters */ + if (!isspace(rune) && (rune >= 0 && rune < 0x20)) + { + return; + } + if (ExpandTabs && rune == '\t') + { + size_t off = buf_selbeg(BUF); + size_t n = (TabWidth - ((off - buf_bol(BUF, off)) % TabWidth)); + for (; n > 0; n--) buf_putc(BUF, ' '); + } + else if (CopyIndent && rune == '\n') + { + size_t off = buf_selbeg(BUF); + size_t beg = buf_bol(BUF, off), end = beg; + for (; end < buf_end(BUF) && end < off && (' ' == buf_getrat(BUF, end) || '\t' == buf_getrat(BUF, end)); end++) + { + } + char* str = buf_getsat(BUF, beg, end); + buf_putc(BUF, '\n'); + buf_puts(BUF, str); + free(str); + } + else + { + buf_putc(BUF, rune); + } + move_to(view, false, CSRPOS); + ensure(view_valid(view)); +} + +void view_delete(View* view, int dir, bool byword) +{ + if (!view_selsize(view)) + { + (byword ? view_byword : view_byrune)(view, dir, true); + } + buf_del(BUF); + move_to(view, false, CSRPOS); + ensure(view_valid(view)); +} + +void view_jumpto(View* view, bool extsel, size_t off) +{ + move_to(view, extsel, off); + ensure(view_valid(view)); +} + +void view_bol(View* view, bool extsel) +{ + /* determine whether we are jumping to start of content or line */ + Buf* buf = BUF; + unsigned bol = buf_bol(buf, CSRPOS); + unsigned boi = bol; + for (; ' ' == buf_getrat(buf, boi) || '\t' == buf_getrat(buf, boi); boi++); + unsigned pos = CSRPOS; + pos = (pos == bol || pos > boi ? boi : bol); + move_to(view, extsel, pos); + ensure(view_valid(view)); +} + +void view_eol(View* view, bool extsel) +{ + move_to(view, extsel, buf_eol(BUF, CSRPOS)); + getsel(view)->col = -1; // Peg cursor to line end + ensure(view_valid(view)); +} + +void view_bof(View* view, bool extsel) +{ + view_jumpto(view, extsel, 0); + ensure(view_valid(view)); +} + +void view_eof(View* view, bool extsel) +{ + view_jumpto(view, extsel, buf_end(BUF)); + ensure(view_valid(view)); +} + +void view_setln(View* view, size_t line) +{ + view->sync_flags |= CENTER; + if (line) + { + buf_setln(BUF, line); + buf_selln(BUF); + } + ensure(view_valid(view)); +} + +static void cursor_sync(View* view) +{ + view->sync_flags |= CURSOR; + if (!selection_visible(view)) + { + view->sync_flags |= CENTER; + } +} + +void view_undo(View* view) +{ + buf_undo(BUF); + cursor_sync(view); + ensure(view_valid(view)); +} + +void view_redo(View* view) +{ + buf_redo(BUF); + cursor_sync(view); + ensure(view_valid(view)); +} + +void view_paste(View* view, char* str) +{ + buf_logstart(BUF); + view_putstr(view, str); + buf_logstop(BUF); + view_selprev(view); + ensure(view_valid(view)); +} + +void view_putstr(View* view, char* str) +{ + buf_puts(BUF, str); + ensure(view_valid(view)); +} + +void view_putraw(View* view, char* str) +{ + void (*fn)(int) = BUF->oninsert; + BUF->oninsert = NULL; + buf_puts(BUF, str); + BUF->oninsert = fn; + ensure(view_valid(view)); +} + +char* view_getstr(View* view) +{ + return buf_gets(BUF); +} + +char* view_getcmd(View* view) +{ + if (!view_selsize(view)) + { + buf_selctx(BUF, riscmd); + } + ensure(view_valid(view)); + return view_getstr(view); +} + +void view_selctx(View* view) +{ + if (!view_selsize(view)) + { + buf_selctx(BUF, risword); + } + ensure(view_valid(view)); +} + +char* view_getctx(View* view) +{ + view_selctx(view); + ensure(view_valid(view)); + return view_getstr(view); +} + +static void scroll_up(View* view) +{ + if (view->index > 0) + { + view->index--; + } + else if (view->rows[0]->off > 0) + { + resize(view, view->width, view->nvisible, buf_byrune(BUF, view->rows[0]->off, LEFT)); + } +} + +static void scroll_dn(View* view) +{ + if (view->nrows <= 1) + { + return; + } + size_t nleft = (view->nrows - view->index); + if (nleft <= view->nvisible) + { + size_t off = view->rows[view->index+1]->off; + resize(view, view->width, view->nvisible, off); + } + else + { + view->index++; + } +} + +void view_scroll(View* view, int move) +{ + int dir = (move < 0 ? -1 : 1); + move *= dir; + for (int i = 0; i < move; i++) + { + if (dir < 0) + { + scroll_up(view); + } + else + { + scroll_dn(view); + } + } + ensure(view_valid(view)); +} + +void view_scrollpage(View* view, int move) +{ + move = (move < 0 ? -1 : 1) * view->nrows; + view_scroll(view, move); + ensure(view_valid(view)); +} + +Rune view_getrune(View* view) +{ + return buf_getc(BUF); +} + +void view_scrollto(View* view, size_t csr) +{ + Row* lastrow = view->rows[view->nrows-1]; + size_t first = view->rows[view->index]->off; + size_t last = lastrow->cols[lastrow->len-1].off; + if (csr < first || csr > last) + { + resize(view, view->width, view->nrows, csr); + } + ensure(view_valid(view)); +} + +void view_selectall(View* view) +{ + buf_selall(BUF); + view->sync_flags |= CURSOR; + ensure(view_valid(view)); +} + +void view_selectobj(View* view, bool (*istype)(Rune)) +{ + buf_selword(BUF, istype); + view->sync_flags |= CURSOR; + ensure(view_valid(view)); +} diff --git a/bin/editor/writefd.c b/bin/editor/writefd.c new file mode 100644 index 0000000..c5c2eac --- /dev/null +++ b/bin/editor/writefd.c @@ -0,0 +1,13 @@ +#include +#include "io.h" + +long writefd(int fd, char* data, long towrite) +{ + long nwrite = 0; + while (towrite && ((nwrite = write(fd, data, towrite)) > 0)) + { + data += nwrite; + towrite -= nwrite; + } + return nwrite; +} diff --git a/bin/editor/x11.c b/bin/editor/x11.c new file mode 100644 index 0000000..320a84b --- /dev/null +++ b/bin/editor/x11.c @@ -0,0 +1,257 @@ +#include +#include "x11.h" +#include "utf.h" +#include "io.h" +#include +#include +#include +#include +#include "config.h" + +struct XConf X; +static Bool Has_Error = False; +static XErrorEvent Error = {0}; + +static int onerror(Display* disp, XErrorEvent* ev) +{ + (void)disp; + Has_Error = True, Error = *ev; + return 0; +} + +int x11_init(XConf* x) +{ + int ret = -1; + signal(SIGPIPE, SIG_IGN); // Ignore the SIGPIPE signal + setlocale(LC_CTYPE, ""); + XSetLocaleModifiers(""); + /* open the X display and get basic attributes */ + if ( (x->display = XOpenDisplay(0)) ) + { + x->root = DefaultRootWindow(x->display); + XWindowAttributes wa; + XGetWindowAttributes(x->display, x->root, &wa); + x->visual = wa.visual; + x->colormap = wa.colormap; + x->screen = DefaultScreen(x->display); + x->depth = DefaultDepth(x->display, x->screen); + x->state = RUNNING; + XSetErrorHandler(onerror); + ret = 0; + } + return ret; +} + +void x11_error_clear(void) +{ + Has_Error = False; + memset(&Error, 0, sizeof(Error)); +} + +XErrorEvent* x11_error_get(void) +{ + return (Has_Error ? &Error : NULL); +} + +void x11_mkwin(XConf* x, int width, int height, int evmask) +{ + /* create the main window */ + x->width = width, x->height = height; + XSetWindowAttributes attr; + attr.background_pixel = Palette[EditBg]; + attr.bit_gravity = NorthWestGravity; + attr.backing_store = WhenMapped; + attr.event_mask = evmask + | EnterWindowMask + | ExposureMask + | VisibilityChangeMask + | StructureNotifyMask + ; + x->self = XCreateWindow( + x->display, x->root, 0, 0, x->width, x->height, 0, x->depth, InputOutput, x->visual, + CWBackPixel | CWBitGravity | CWBackingStore | CWEventMask, + &attr); + + /* register interest in the delete window message */ + Atom wmDeleteMessage = XInternAtom(x->display, "WM_DELETE_WINDOW", False); + XSetWMProtocols(x->display, x->self, &wmDeleteMessage, 1); +} + +void x11_mkdialog(XConf* x, int width, int height, int evmask) +{ + x11_mkwin(x, width, height, evmask); + Atom WindowType = XInternAtom(x->display, "_NET_WM_WINDOW_TYPE", False); + Atom DialogType = XInternAtom(x->display, "_NET_WM_WINDOW_TYPE_DIALOG", False); + XChangeProperty(x->display, x->self, WindowType, XA_ATOM, 32, PropModeReplace, (unsigned char*)&DialogType, 1); +} + +static void update_state(XConf* x, XEvent* e) +{ + if ((e->type == KeyPress) || (e->type == ButtonPress) || + (e->type == ButtonRelease) || (e->type == MotionNotify)) + { + x->now = e->xkey.time; + x->mods = e->xkey.state; + } +} + +void x11_process_events(XConf* x) +{ + int nevents; + /* reap zombie background processes */ + for (int status; waitpid(-1, &status, WNOHANG) > 0;); + /* process the entire event queue */ + while (XEventsQueued(x->display, QueuedAfterReading)) + { + telem_send("EV_READ_QUEUE(pending: %d)\n", XPending(x->display)); + XGetMotionEvents(x->display, x->self, CurrentTime, CurrentTime, &nevents); + + for (XEvent e; XPending(x->display);) + { + XNextEvent(x->display, &e); + update_state(x, &e); + if (!XFilterEvent(&e, None) && x->eventfns[e.type]) + { + telem_send("EV_HANDLE(type: %d)\n", e.type); + (x->eventfns[e.type])(x, &e); + } + else + { + telem_send("EV_IGNORE(type: %d)\n", e.type); + } + + /* log error here */ + XErrorEvent* err = x11_error_get(); + if (err) + { + char msg[8192]; + XGetErrorText(x->display, err->error_code, msg, sizeof(msg)); + telem_send("XERROR(%s)\n", msg); + x11_error_clear(); + } + } + } +} + +void x11_event_loop(XConf* x, void (*redraw)(XConf* x)) +{ + if (redraw) redraw(x); + for (XEvent e; x->state != QUITTING;) + { + XNextEvent(x->display, &e); + if (x->eventfns[e.type]) + { + x->eventfns[e.type](x, &e); + } + for (int status; waitpid(-1, &status, WNOHANG) > 0;); + if (redraw) + { + redraw(x); + } + } +} + +int x11_getptr(XConf* x, int* ptrx, int* ptry) +{ + Window root = 0, child = 0; + int winx = 0, winy = 0, mask = 0; + return XQueryPointer(x->display, x->self, &root, &child, ptrx, ptry, &winx, &winy, (unsigned int*)&mask); +} + +static uint32_t special_keys(uint32_t key) +{ + static uint32_t keymap[256] = { + /* Function keys */ + [0xBE] = KEY_F1, [0xBF] = KEY_F2, [0xC0] = KEY_F3, [0xC1] = KEY_F4, + [0xC2] = KEY_F5, [0xC3] = KEY_F6, [0xC4] = KEY_F7, [0xC5] = KEY_F8, + [0xC6] = KEY_F9, [0xC7] = KEY_F10, [0xC8] = KEY_F11, [0xC9] = KEY_F12, + /* Navigation keys */ + [0x50] = KEY_HOME, [0x51] = KEY_LEFT, [0x52] = KEY_UP, + [0x53] = KEY_RIGHT, [0x54] = KEY_DOWN, [0x55] = KEY_PGUP, + [0x56] = KEY_PGDN, [0x57] = KEY_END, + /* Control keys */ + [0x08] = '\b', [0x09] = '\t', [0x0d] = '\n', [0x0a] = '\n', + /* Miscellaneous */ + [0x63] = KEY_INSERT, [0x1B] = KEY_ESCAPE, [0xFF] = KEY_DELETE, + }; + /* lookup the key by keysym */ + key = ((key & 0xFF00) == 0xFF00 ? keymap[key & 0xFF] : key); + return (!key ? RUNE_ERR : key); +} + +uint32_t x11_getkey(XConf* x, XEvent* e) +{ + uint32_t ret; + char buf[8]; + KeySym key; + Status status; + + /* Read the key string */ + if (x->xic) + { + Xutf8LookupString(x->xic, &(e->xkey), buf, sizeof(buf), &key, &status); + } + else + { + XLookupString(&(e->xkey), buf, sizeof(buf), &key, 0); + } + + /* if it's ascii, just return it */ + if (key >= 0x20 && key <= 0x7F) + { + ret = (uint32_t)key; + } + else + { + ret = special_keys(key); + } + return ret; +} + +uint32_t x11_process_key(XConf* x, XEvent* e, KeyBinding* keys) +{ + uint32_t key = x11_getkey(x, e); + if (key != RUNE_ERR) + { + int mods = e->xkey.state & (ModCtrl|ModShift|ModAlt); + int32_t mkey = tolower(key); + for (KeyBinding* bind = keys; bind && bind->key; bind++) + { + bool match = (mkey == (int32_t)bind->key); + bool exact = (bind->mods == mods); + bool any = (bind->mods == ModAny); + bool oneplus = ((bind->mods == ModOneOrMore) && (mods & ModOneOrMore)); + if (match && (exact || oneplus || any)) + { + bind->fn(bind->arg); + key = RUNE_ERR; + break; + } + } + } + /* fallback to just inserting the rune if it doesn't fall in the private use area. + * the private use area is used to encode special keys */ + return (key < 0xE000 || key > 0xF8FF ? key : RUNE_ERR); +} + +int x11_seturgent(XConf* x, int urgent) +{ + int status = 0; + XWMHints hints = {0}, *prevhints = XGetWMHints(x->display, x->self); + if (prevhints) + { + hints = *prevhints; + XFree(prevhints); + } + if (urgent) + { + hints.flags |= XUrgencyHint; + } + else + { + hints.flags &= ~XUrgencyHint; + } + status = XSetWMHints(x->display, x->self, &hints); + telem_send("URGENT(%d %d)\n", urgent, status); + return status; +} diff --git a/bin/editor/x11.h b/bin/editor/x11.h new file mode 100644 index 0000000..4878f97 --- /dev/null +++ b/bin/editor/x11.h @@ -0,0 +1,166 @@ +/** @file */ +AUTOLIB(X11) +AUTOLIB(Xinerama) +AUTOLIB(Xft) +AUTOLIB(fontconfig) + +#include +#include +#include + +enum { + RUNNING, + SERVE_SEL, + QUITTING +}; + +typedef struct XConf { + Bool error; + int state, fd, screen, width, height, mods; + Window root; + Display* display; + Visual* visual; + Colormap colormap; + unsigned depth; + Window self; + XftDraw* xft; + XftFont *tagfont, *font; + Pixmap pixmap; + XIC xic; + XIM xim; + GC gc; + void (*eventfns[LASTEvent])(struct XConf*, XEvent*); + Time now; +} XConf; + +/* Selection identifiers */ +enum { + PRIMARY = 0, + CLIPBOARD = 1 +}; + +/* key definitions */ +enum Keys { + /* Define some runes in the private use area of unicode to represent + * special keys */ + KEY_F1 = (0xE000+0), + KEY_F2 = (0xE000+1), + KEY_F3 = (0xE000+2), + KEY_F4 = (0xE000+3), + KEY_F5 = (0xE000+4), + KEY_F6 = (0xE000+5), + KEY_F7 = (0xE000+6), + KEY_F8 = (0xE000+7), + KEY_F9 = (0xE000+8), + KEY_F10 = (0xE000+9), + KEY_F11 = (0xE000+10), + KEY_F12 = (0xE000+11), + KEY_INSERT = (0xE000+12), + KEY_DELETE = (0xE000+13), + KEY_HOME = (0xE000+14), + KEY_END = (0xE000+15), + KEY_PGUP = (0xE000+16), + KEY_PGDN = (0xE000+17), + KEY_UP = (0xE000+18), + KEY_DOWN = (0xE000+19), + KEY_RIGHT = (0xE000+20), + KEY_LEFT = (0xE000+21), + + /* ASCII Control Characters */ + KEY_CTRL_TILDE = 0x00, + KEY_CTRL_2 = 0x00, + KEY_CTRL_A = 0x01, + KEY_CTRL_B = 0x02, + KEY_CTRL_C = 0x03, + KEY_CTRL_D = 0x04, + KEY_CTRL_E = 0x05, + KEY_CTRL_F = 0x06, + KEY_CTRL_G = 0x07, + KEY_BACKSPACE = 0x08, + KEY_CTRL_H = 0x08, + KEY_TAB = 0x09, + KEY_CTRL_I = 0x09, + KEY_CTRL_J = 0x0A, + KEY_CTRL_K = 0x0B, + KEY_CTRL_L = 0x0C, + KEY_ENTER = 0x0D, + KEY_CTRL_M = 0x0D, + KEY_CTRL_N = 0x0E, + KEY_CTRL_O = 0x0F, + KEY_CTRL_P = 0x10, + KEY_CTRL_Q = 0x11, + KEY_CTRL_R = 0x12, + KEY_CTRL_S = 0x13, + KEY_CTRL_T = 0x14, + KEY_CTRL_U = 0x15, + KEY_CTRL_V = 0x16, + KEY_CTRL_W = 0x17, + KEY_CTRL_X = 0x18, + KEY_CTRL_Y = 0x19, + KEY_CTRL_Z = 0x1A, + KEY_ESCAPE = 0x1B, + KEY_CTRL_LSQ_BRACKET = 0x1B, + KEY_CTRL_3 = 0x1B, + KEY_CTRL_4 = 0x1C, + KEY_CTRL_BACKSLASH = 0x1C, + KEY_CTRL_5 = 0x1D, + KEY_CTRL_RSQ_BRACKET = 0x1D, + KEY_CTRL_6 = 0x1E, + KEY_CTRL_7 = 0x1F, + KEY_CTRL_SLASH = 0x1F, + KEY_CTRL_UNDERSCORE = 0x1F, +}; + +/* Key modifier masks */ +enum { + ModNone = 0, + ModShift = (1 << 0), + ModCapsLock = (1 << 1), + ModCtrl = (1 << 2), + ModAlt = (1 << 3), + ModNumLock = (1 << 4), + ModScrollLock = (1 << 5), + ModWindows = (1 << 6), + ModOneOrMore = (ModCtrl|ModAlt), + ModAny = -1 +}; + +typedef struct { + int mods; + uint32_t key; + void (*fn)(char*); + char* arg; +} KeyBinding; + +extern struct XConf X; + +int x11_init(XConf* x); +void x11_error_clear(void); +XErrorEvent* x11_error_get(void); +void x11_resize(XConf* x, XEvent* e); + +void x11_mkwin(XConf* x, int width, int height, int evmask); +void x11_mkdialog(XConf* x, int width, int height, int evmask); +void x11_process_events(XConf* x); +void x11_event_loop(XConf* x, void (*redraw)(XConf* x)); +int x11_getptr(XConf* x, int* ptrx, int* ptry); +uint32_t x11_getkey(XConf* x, XEvent* e); +uint32_t x11_process_key(XConf* x, XEvent* e, KeyBinding* keys); +int x11_seturgent(XConf* x, int urgent); + +void x11_centerwin(XConf* x); +void x11_init_gc(XConf* x); +void x11_show(XConf* x); +XftFont* x11_font_load(XConf* x, char* name); +void xftcolor(XConf* x, XftColor* xc, unsigned int c); +void x11_draw_rect(XConf* x, int color, int px, int py, int width, int height); +void x11_draw_glyphs(XConf* x, int color, XftFont* font, XftGlyphSpec* specs, long nspecs); +void x11_flip(XConf* x); +void x11_draw_string(XConf* x, XftFont* font, int posx, int posy, int color, char* str); + +void x11_sel_init(XConf* x); +int x11_sel_get(XConf* x, int selid, void(*cbfn)(char*)); +int x11_sel_set(XConf* x, int selid, char* str); +void x11_sel_quit(XConf* x, XEvent* e); +int x11_sel_ready(XConf* x); +void x11_sel_serve(XConf* x); diff --git a/bin/editor/x11_gc.c b/bin/editor/x11_gc.c new file mode 100644 index 0000000..d7d7a7d --- /dev/null +++ b/bin/editor/x11_gc.c @@ -0,0 +1,165 @@ +#include +#include "x11.h" +#include "io.h" +#include + +void x11_resize(XConf* x, XEvent* e) +{ + telem_send("XRESIZE(w: %d, h: %d)\n", e->xconfigure.width, e->xconfigure.height); + if (e->xconfigure.width != x->width || e->xconfigure.height != x->height) + { + x->width = e->xconfigure.width; + x->height = e->xconfigure.height; + XFreePixmap(x->display, x->pixmap); + x->pixmap = XCreatePixmap(x->display, x->self, x->width, x->height, x->depth); + XftDrawChange(x->xft, x->pixmap); + } +} + +void x11_mapnotify(XConf* x, XEvent* e) +{ + (void)x; + telem_send("XMAPNOTIFY(0x%x)\n", e->xmap.window); +} + +void x11_enternotify(XConf* x, XEvent* e) +{ + telem_send("XENTERNOTIFY(0x%x)\n", e->xmap.window); + x11_seturgent(x, 0); +} + +void x11_init_gc(XConf* x) +{ + /* set input methods */ + if ((x->xim = XOpenIM(x->display, 0, 0, 0))) + x->xic = XCreateIC(x->xim, XNInputStyle, XIMPreeditNothing|XIMStatusNothing, XNClientWindow, x->self, XNFocusWindow, x->self, NULL); + /* initialize pixmap and drawing context */ + x->pixmap = XCreatePixmap(x->display, x->self, x->width, x->height, x->depth); + x->xft = XftDrawCreate(x->display, x->pixmap, x->visual, x->colormap); + /* initialize the graphics context */ + XGCValues gcv; + gcv.foreground = WhitePixel(x->display, x->screen); + gcv.graphics_exposures = False; + x->gc = XCreateGC(x->display, x->self, GCGraphicsExposures, &gcv); + x->eventfns[ConfigureNotify] = x11_resize; + x->eventfns[MapNotify] = x11_mapnotify; + x->eventfns[EnterNotify] = x11_enternotify; +} + +void x11_centerwin(XConf* x) +{ + int ptrx = 0, ptry = 0; + (void)x11_getptr(x, &ptrx, &ptry); + int nscreens = 0; + XineramaScreenInfo* p_screens = XineramaQueryScreens(x->display, &nscreens); + for (int i = 0; i < nscreens; i++) + { + int minx = p_screens[i].x_org, + maxx = p_screens[i].x_org + p_screens[i].width, + miny = p_screens[i].y_org, + maxy = p_screens[i].y_org + p_screens[i].height; + if (minx <= ptrx && ptrx <= maxx && miny <= ptry && ptry <= maxy) + { + XMoveWindow(x->display, x->self, + minx + p_screens[i].width/2 - x->width/2, + miny + p_screens[i].height/2 - x->height/2); + break; + } + } + if (p_screens) + { + XFree(p_screens); + } +} + +void x11_show(XConf* x) +{ + /* simulate an initial resize and map the window */ + XConfigureEvent ce; + ce.type = ConfigureNotify; + ce.width = x->width; + ce.height = x->height; + XSendEvent(x->display, x->self, False, StructureNotifyMask, (XEvent *)&ce); + XMapWindow(x->display, x->self); + XSync(x->display, False); + + /* Waiting for window mapping */ + XEvent ev; + do + { + XNextEvent(x->display, &ev); + if (XFilterEvent(&ev, None)) + { + continue; + } + if (ev.type == ConfigureNotify) + { + x11_resize(x, &ev); + } + } + while (ev.type != MapNotify); +// XWarpPointer(x->display, None, x->self, 0, 0, x->width, x->height, x->width/2, x->height/2); +} + +XftFont* x11_font_load(XConf* x, char* name) +{ + XftFont* font = NULL; + if (FcInit()) + { + FcPattern* pattern = FcNameParse((FcChar8 *)name); + if (pattern) + { + /* load the base font */ + FcResult result; + FcPattern* match = XftFontMatch(x->display, x->screen, pattern, &result); + if (match) + { + font = XftFontOpenPattern(x->display, match); + } + FcPatternDestroy(pattern); + FcPatternDestroy(match); + } + } +// printf("font: '%s'\n", name); + return font; +} + +void xftcolor(XConf* x, XftColor* xc, unsigned int c) +{ + #define COLOR(c) ((c) | ((c) >> 8)) + xc->color.alpha = 0xFFFF; + xc->color.red = COLOR((c & 0x00FF0000) >> 8); + xc->color.green = COLOR((c & 0x0000FF00)); + xc->color.blue = COLOR((c & 0x000000FF) << 8); + XftColorAllocValue(x->display, x->visual, x->colormap, &(xc->color), xc); +} + +void x11_draw_rect(XConf* x, int color, int px, int py, int width, int height) +{ + XftColor clr; + xftcolor(x, &clr, color); + XftDrawRect(x->xft, &clr, px, py, width, height); + XftColorFree(x->display, x->visual, x->colormap, &clr); +} + +void x11_draw_glyphs(XConf* x, int color, XftFont* font, XftGlyphSpec* specs, long nspecs) +{ + XftColor clr; + xftcolor(x, &clr, color); + XftDrawGlyphSpec(x->xft, &clr, font, specs, nspecs); + XftColorFree(x->display, x->visual, x->colormap, &clr); +} + +void x11_flip(XConf* x) +{ + XCopyArea(x->display, x->pixmap, x->self, x->gc, 0, 0, x->width, x->height, 0, 0); + XFlush(x->display); +} + +void x11_draw_string(XConf* x, XftFont* font, int posx, int posy, int color, char* str) +{ + XftColor clr; + xftcolor(x, &clr, color); + XftDrawStringUtf8(x->xft, &clr, font, posx, posy, (const FcChar8*)str, strlen(str)); + XftColorFree(x->display, x->visual, x->colormap, &clr); +} diff --git a/bin/editor/x11_sel.c b/bin/editor/x11_sel.c new file mode 100644 index 0000000..d026750 --- /dev/null +++ b/bin/editor/x11_sel.c @@ -0,0 +1,183 @@ +#include +#include "x11.h" + +struct XSel { + char* name; + Atom atom; + char* text; + void (*callback)(char*); +}; + +static Atom SelTarget; +static struct XSel Selections[] = { + { .name = "PRIMARY" }, + { .name = "CLIPBOARD" }, +}; + +static struct XSel* selfetch(Atom atom) +{ + struct XSel* ret = NULL; + for (unsigned int i = 0; i < (sizeof(Selections) / sizeof(Selections[0])); i++) + { + if (atom == Selections[i].atom) + { + ret = &Selections[i]; + break; + } + } + return ret; +} + +static void xselclear(XConf* x, XEvent* e) +{ + (void)x; + struct XSel* sel = selfetch(e->xselectionclear.selection); + if (!sel) return; + free(sel->text); + sel->text = NULL; +} + +static void xselnotify(XConf* x, XEvent* e) +{ + /* bail if the selection cannot be converted */ + if (e->xselection.property == None) + return; + struct XSel* sel = selfetch( e->xselection.selection ); + Atom rtype; + unsigned long format = 0, nitems = 0, nleft = 0; + unsigned char* propdata = NULL; + XGetWindowProperty(x->display, x->self, sel->atom, 0, -1, False, AnyPropertyType, &rtype, + (int*)&format, &nitems, &nleft, &propdata); + if (e->xselection.target == SelTarget) + { + void(*cbfn)(char*) = sel->callback; + sel->callback = NULL; + cbfn((char*)propdata); + } + /* cleanup */ + if (propdata) XFree(propdata); +} + +static void xselrequest(XConf* x, XEvent* e) +{ + XEvent s; + struct XSel* sel = selfetch( e->xselectionrequest.selection ); + s.xselection.type = SelectionNotify; + s.xselection.property = e->xselectionrequest.property; + s.xselection.requestor = e->xselectionrequest.requestor; + s.xselection.selection = e->xselectionrequest.selection; + s.xselection.target = e->xselectionrequest.target; + s.xselection.time = e->xselectionrequest.time; + Atom target = e->xselectionrequest.target; + Atom xatargets = XInternAtom(x->display, "TARGETS", 0); + Atom xastring = XInternAtom(x->display, "STRING", 0); + if (target == xatargets) + { + /* respond with the supported type */ + XChangeProperty( + x->display, + s.xselection.requestor, + s.xselection.property, + XA_ATOM, 32, PropModeReplace, + (unsigned char*)&SelTarget, 1); + } + else if (target == SelTarget || target == xastring) + { + XChangeProperty( + x->display, + s.xselection.requestor, + s.xselection.property, + SelTarget, 8, PropModeReplace, + (unsigned char*)sel->text, strlen(sel->text)); + } + XSendEvent(x->display, s.xselection.requestor, True, 0, &s); +} + +void x11_sel_init(XConf* x) +{ + /* initialize selection atoms */ + for (unsigned int i = 0; i < (sizeof(Selections) / sizeof(Selections[0])); i++) + { + Selections[i].atom = XInternAtom(x->display, Selections[i].name, 0); + } + SelTarget = XInternAtom(x->display, "UTF8_STRING", 0); + if (SelTarget == None) + { + SelTarget = XInternAtom(x->display, "STRING", 0); + } + /* setup event handlers */ + x->eventfns[SelectionClear] = xselclear; + x->eventfns[SelectionNotify] = xselnotify; + x->eventfns[SelectionRequest] = xselrequest; +} + +int x11_sel_get(XConf* x, int selid, void(*cbfn)(char*)) +{ + int ret = 0; + struct XSel* sel = &(Selections[selid]); + if (!sel->callback) + { + Window owner = XGetSelectionOwner(x->display, sel->atom); + if (owner == x->self) + { + cbfn(sel->text); + } + else if (owner != None) + { + sel->callback = cbfn; + XConvertSelection(x->display, sel->atom, SelTarget, sel->atom, x->self, CurrentTime); + } + ret = 1; + } + return ret; +} + +int x11_sel_set(XConf* x, int selid, char* str) +{ + int ret; + struct XSel* sel = &(Selections[selid]); + if (!sel || !str || !*str) + { + free(str); + ret = 0; + } + else + { + sel->text = str; + XSetSelectionOwner(x->display, sel->atom, x->self, CurrentTime); + ret = 1; + } + return ret; +} + +void x11_sel_quit(XConf* x, XEvent* e) +{ + xselclear(x, e); + if (!Selections[PRIMARY].text && !Selections[CLIPBOARD].text) + { + x->state = QUITTING; + } +} + +int x11_sel_ready(XConf* x) +{ + (void)x; + return (Selections[PRIMARY].text || Selections[CLIPBOARD].text); +} + +void x11_sel_serve(XConf* x) +{ + X.eventfns[SelectionClear] = x11_sel_quit; + X.self = XCreateSimpleWindow(X.display, X.root, 0, 0, 1, 1, 0, 0, 0); + if (Selections[PRIMARY].text) + { + XSetSelectionOwner(x->display, Selections[PRIMARY].atom, x->self, CurrentTime); + + } + if (Selections[CLIPBOARD].text) + { + XSetSelectionOwner(x->display, Selections[CLIPBOARD].atom, x->self, CurrentTime); + } + if (fork()) exit(0); /* fork into background */ +} + diff --git a/bin/editor/xpty.c b/bin/editor/xpty.c new file mode 100644 index 0000000..5a791cd --- /dev/null +++ b/bin/editor/xpty.c @@ -0,0 +1,278 @@ +#include +#include "utf.h" +#include "io.h" +#include +#ifdef __linux__ + #include +#else + #include +#endif +#include "tide.h" + +#define BUFFERSZ 16384 + +static View* EditView = NULL; +static int Pty_Fd = -1; +static bool DoScroll = 0; +static char* PromptStr = NULL; +static char ReadBuf[BUFFERSZ+1] = {0}; +static char ArgsBuf[BUFFERSZ+1] = {0}; +static char InputBuf[BUFFERSZ+1] = {0}; +static ssize_t ArgsPos = 0; +static ssize_t InputPos = 0; +static ssize_t IgnoreCount = 0; +static enum { + READ_ECHO = 0, + READ_CHAR, + READ_ESC, + READ_OSC, + READ_OSI, +} State = READ_CHAR; + +static void read_echo(char c) +{ + (void)c; + IgnoreCount--; + if (IgnoreCount <= 0) + { + State = READ_CHAR; + } +} + +static void read_char(char c) +{ + if (c == '\033') + { + State = READ_ESC; + } + else + { + InputBuf[InputPos++] = c; + InputBuf[InputPos] = '\0'; + } +} + +static void read_esc(char c) +{ + ArgsBuf[(ArgsPos = 0)] = '\0'; + if (c == '[') + { + State = READ_OSI; + } + else if (c == ']') + { + State = READ_OSC; + } + else + { + State = READ_CHAR; + } +} + +static void read_osc(char c) +{ + if (c == '\a') + { + State = READ_CHAR; + if (ArgsBuf[0] == '7' && ArgsBuf[1] == ';') + { + chdir(&ArgsBuf[2]); + } + else if (ArgsBuf[0] == '0' && ArgsBuf[1] == ';') + { + free(PromptStr); + PromptStr = strdup(&ArgsBuf[2]); + } + } + else + { + ArgsBuf[ArgsPos++] = c; + ArgsBuf[ArgsPos] = '\0'; + } +} + +static void read_osi(char c) +{ + if (isalpha(c)) + { + State = READ_CHAR; + } +} + +static void (*Handlers[])(char c) = { + [READ_ECHO] = read_echo, + [READ_CHAR] = read_char, + [READ_ESC] = read_esc, + [READ_OSC] = read_osc, + [READ_OSI] = read_osi, +}; + +static void putb(int byte) +{ + struct termios tio = {0}; + tcgetattr(Pty_Fd, &tio); + + if ((tio.c_lflag & ICANON) == ICANON) + { + char b = byte; + write(Pty_Fd, &b, 1); + if (byte == '\n') + { + *((char*)(EditView->buffer.contents.gapstart-1)) = ' '; + EditView->buffer.point.beg = EditView->buffer.point.end; + } + else if ((tio.c_lflag & ECHO) != ECHO) + { + *((char*)(EditView->buffer.contents.gapstart-1)) = '*'; + } + else + { + read(Pty_Fd, &b, 1); + } + } + else if (byte == '\n' && (EditView->buffer.selection.end == EditView->buffer.point.end)) + { + /* get the input string and update the point */ + char* str = buf_getsat(&(EditView->buffer), EditView->buffer.point.beg, EditView->buffer.point.end); + size_t slen = strlen(str); + EditView->buffer.point.beg = EditView->buffer.point.end; + + /* write the data and read back to discard the echoed chars */ +// printf("write: '%s'\n", str); + writefd(Pty_Fd, str, slen); + State = READ_ECHO; + IgnoreCount = slen+1; + InputBuf[(InputPos = 0)] = '\0'; + + free(str); + } +} + +static void writedata(char* buf, long n) +{ + InputBuf[0] = '\0'; + InputPos = 0; +// printf("read: '%s'\n", ReadBuf); + for (long i = 0; i < n; i++) + { + Handlers[State](buf[i]); + } + view_putraw(EditView, InputBuf); + if (DoScroll) + { + EditView->sync_flags |= CURSOR; + } +} + +static void xpty_read(Job* job) +{ + long nread = read(job->fd, ReadBuf, sizeof(ReadBuf)-1); + if (nread <= 0) + { + job->readfn = NULL; + Pty_Fd = -1; + EditView->buffer.oninsert = NULL; + EditView = NULL; + } + else if (nread > 0) + { + ReadBuf[nread] = '\0'; + + /* swap the point and selection to perform the insert */ + size_t start = EditView->buffer.point.beg; + Sel sel = { + .col = EditView->buffer.selection.col, + .beg = buf_selbeg(&(EditView->buffer)), + .end = buf_selend(&(EditView->buffer)) + }; + EditView->buffer.selection.beg = EditView->buffer.point.beg; + EditView->buffer.selection.end = EditView->buffer.point.beg; + EditView->buffer.point = sel; + + /* insert the text */ + writedata(ReadBuf, nread); + + /* adjust the original selection and swap it back */ + nread = (EditView->buffer.point.end - start); + if (start <= sel.beg) + { + sel.beg += nread; + } + if (start <= sel.end) + { + sel.end += nread; + } + Sel point = EditView->buffer.selection; + EditView->buffer.selection = sel; + EditView->buffer.point = point; + } +} + +bool xpty_active(void) +{ + return (Pty_Fd >= 0); +} + +void xpty_togglescroll(void) +{ + DoScroll = !DoScroll; +} + +int xpty_run(View* view, char** cmd) +{ + (void)view; + int fd = -1; + if (Pty_Fd < 0) + { + pid_t pid = forkpty(&fd, NULL, NULL, NULL); + if (pid == 0) + { + exit(execvp(cmd[0], cmd)); + } + else if (pid < 0 ) + { + fd = -1; + } + else + { + Pty_Fd = fd; + EditView = view; + if (view_selsize(view)) + { + view_delete(view, LEFT, false); + } + view->buffer.oninsert = putb; + view->buffer.point.beg = view->buffer.selection.end; + view->buffer.point.end = view->buffer.selection.end; + job_spawn(Pty_Fd, xpty_read, NULL, NULL); + } + } + return fd; +} + +void xpty_send(char* cmd) +{ + if (EditView) + { + EditView->buffer.selection.beg = EditView->buffer.point.end; + EditView->buffer.selection.end = EditView->buffer.point.end; + if (PromptStr) + { + size_t plen = strlen(PromptStr); + if (!strncmp(PromptStr, cmd, plen)) + { + cmd += strlen(PromptStr); + } + } + view_putstr(EditView, cmd); + if (*cmd && cmd[strlen(cmd)-1] != '\n') + { + view_insert(EditView, '\n'); + } + } +} + +void xpty_rawsend(char* str) +{ + (void)write(Pty_Fd, str, strlen(str)); +} diff --git a/bin/pick.c b/bin/pick.c deleted file mode 100644 index cbfdb1c..0000000 --- a/bin/pick.c +++ /dev/null @@ -1,6 +0,0 @@ -int main(int argc, char** argv) -{ - (void)argc; - (void)argv; - return 0; -} \ No newline at end of file diff --git a/bin/pick/config.h b/bin/pick/config.h new file mode 100644 index 0000000..e1ab943 --- /dev/null +++ b/bin/pick/config.h @@ -0,0 +1,100 @@ +/** @file */ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunused-variable" + +enum { Off = 0, On = 1 }; + +enum { /* Color Names */ + Red, Purple, Orange, + EditBg, EditFg, EditSel, + TagsBg, TagsFg, TagsSel, + ScrollBg, ScrollFg, + VerBdr, HorBdr, Point, + ClrCount +}; + +#define CMD_TIDE "!tide" +#define CMD_PICKFILE "!pickfile ." +#define CMD_COMPLETE "picktag print tags" +#define CMD_FCOMPLETE "fcomplete" +#define CMD_GOTO_TAG "!picktag fetch tags" + +/* Command used to open file in editor at a given line */ +static char* EditCmd[] = { "tide", "-l", 0, 0, 0 }; + +/* The shell: Filled in with $SHELL. Used to execute commands */ +static char* ShellCmd[] = { 0, "-c", 0, 0 }; + +/* Sed command used to execute commands marked with ':' sigil */ +static char* SedCmd[] = { "sed", "-Ee", 0, 0 }; + +/* Command used to fetch some text based on a set of rules */ +static char* FetchCmd[] = { "fetch", 0, 0 }; + +/* Default tag region text in editor windows */ +static char* TagString = "Del Put Get | Font Tabs Eol | x+ w+ !st !term | Find "; + +/* List of font patterns available to the editor */ +static char* Fonts[2] = { + "Verdana:size=12", + "Liberation Mono:size=12" +}; + +#ifdef INCLUDE_DEFS +/* Integer config options */ +int WinWidth = 640; /* default window width */ +int WinHeight = 480; /* default window height */ +int ScrollWidth = 8; /* width in pixels of the scrollbar */ +int Timeout = 100; /* number of milliseconds to wait before poll() times out */ +int TabWidth = 4; /* number of spaces tab characters will be expanded to */ +int ScrollBy = 1; /* number of lines to scroll by on mouse wheel events */ +int ClickTime = 500; /* number of milliseconds after a click wehre multi-clicks will be recognized */ +int CopyIndent = On; /* whether indentation should be copied from previous line or not */ +int TrimOnSave = On; /* whether trailing whitespace should be removed on save or not */ +int ExpandTabs = On; /* whether tab characters should be expanded to spaces or not */ +int DosLineFeed = Off; /* use \r\n line endings by default */ +int Margin = 50; +#else +extern int WinWidth, WinHeight, ScrollWidth, Timeout, TabWidth, ScrollBy, + ClickTime, CopyIndent, TrimOnSave, ExpandTabs, DosLineFeed, Margin; +#endif + +static int Palette[ClrCount] = { +#if 0 /* Original Acme Colors */ + [EditBg] = 0xFFFFEA, /* Edit region background */ + [EditFg] = 0x000000, /* Edit region text */ + [EditSel] = 0xEEEE9E, /* Edit region selection */ + [TagsBg] = 0xEAFFFF, /* Tags region background */ + [TagsFg] = 0x000000, /* Tags region text */ + [TagsSel] = 0x9EEEEE, /* Tags region selection */ + [ScrollBg] = 0x99994C, /* Scroll region background */ + [ScrollFg] = 0xFFFFEA, /* Scroll region foreground */ + [VerBdr] = 0x99994C, /* Vertical border */ + [HorBdr] = 0x000000, /* Horizontal border */ + [Point] = 0xEFEFDA, /* Point background */ + [Purple] = 0x6666CC, /* Purple */ + [Red] = 0xCC0000, /* Red */ + [Orange] = 0xFF7700, /* Orange */ +#else + [EditBg] = 0xF5F5F0, /* Edit region background */ + [EditFg] = 0x222222, /* Edit region text */ + [EditSel] = 0xAACCEE, /* Edit region selection */ + + [TagsBg] = 0xF5F5F0, /* Tags region background */ + [TagsFg] = 0x222222, /* Tags region text */ + [TagsSel] = 0xAACCEE, /* Tags region selection */ + + [ScrollBg] = 0x959590, /* Scroll region background */ + [ScrollFg] = 0xF5F5F0, /* Scroll region foreground */ + + [VerBdr] = 0x959590, /* Vertical border */ + [HorBdr] = 0x222222, /* Horizontal border */ + + [Point] = 0xD5D5D0, /* Point background */ + [Purple] = 0x6666CC, /* Purple */ + [Red] = 0xCC0000, /* Red */ + [Orange] = 0xFF7700, /* Orange */ +#endif +}; + +#pragma GCC diagnostic pop diff --git a/bin/pick/io.h b/bin/pick/io.h new file mode 100644 index 0000000..d5fe389 --- /dev/null +++ b/bin/pick/io.h @@ -0,0 +1,9 @@ +/** + @file + Helper functions for reading and writing file descriptors as well as outputting telemetry data. +*/ +void telem_send(char* fmt, ...); +long writefd(int fd, char* data, long towrite); +long readfd(int fd, char* data, long toread); +char* readfile(char* path); +char* abspath(char* path); diff --git a/bin/pick/pick.c b/bin/pick/pick.c new file mode 100644 index 0000000..f6eb920 --- /dev/null +++ b/bin/pick/pick.c @@ -0,0 +1,328 @@ +#include +#include +#include +#include +#include +#include "x11.h" + +#define INCLUDE_DEFS +#include "config.h" + +static void xkeypress(XConf* x, XEvent* e); +static void xbtnpress(XConf* x, XEvent* e); +static void redraw(XConf* x); +static void xinit(XConf* x); + +typedef struct { + float score; + char* string; + size_t length; + size_t match_start; + size_t match_end; +} Choice; + +char Query[8192] = {0}; +size_t QueryIdx = 0; +vec_t Choices = {0}; +size_t ChoiceIdx = 0; +size_t Offset = 0; + +static int peekc(FILE* in) +{ + int c = fgetc(in); + ungetc(c, in); + return c; +} + +static char* rdline(FILE* fin, size_t* len) +{ + if (feof(fin) || ferror(fin)) + return NULL; + size_t size = 256; + size_t index = 0; + char* str = (char*)calloc(size,1); + while (!feof(stdin)) + { + if (index+2 >= size) + { + size = size << 1; + str = realloc(str, size); + } + char ch = fgetc(fin); + if (ch == '\r') continue; + if (ch == EOF || ch == '\n') break; + str[index++] = ch; + str[index] = '\0'; + } + if (len) *len = index; + return str; +} + +static int by_score(const void* a, const void* b) +{ + Choice* ca = ((Choice*)a); + Choice* cb = ((Choice*)b); + if (ca->score < cb->score) + return 1; + else if (ca->score > cb->score) + return -1; + else + return strcmp(ca->string, cb->string); +} + +static void load_choices(XConf* x) +{ + size_t choice_len; + char* choice_text; + bool shown = false; + Choice choice = {0}; + vec_init(&Choices, sizeof(Choice)); + while ((choice_text = rdline(stdin, &choice_len)) != NULL) + { + if (choice_len > 0) + { + choice.string = choice_text; + choice.length = strlen(choice_text); + choice.score = 1.0; + if (!shown && peekc(stdin) != EOF) + { + x11_show(x); + redraw(x); + shown = true; + } + vec_push_back(&Choices, &choice); + } + else + { + free(choice_text); + } + } +} + +static char* find_char(char* str, int ch) +{ + for (; *str; str++) + if (tolower(*str) == tolower(ch)) + return str; + return NULL; +} + +static inline bool find_match(char *string, char* query, size_t offset, char **start, char **end) +{ + char *s, *e; + /* find first match char or bail */ + s = e = find_char(&string[offset], *query); + if (s == NULL) return false; + + /* find the end of the match */ + for (query++; *query; query++) + if ((e = find_char(e+1, *query)) == NULL) + return false; + + /* if we made it this far, we found a match */ + *start = s, *end = e; + return true; +} + +static bool match(char *string, size_t offset, size_t *start, size_t *end) +{ + char *s1 = 0, *e1 = 0, *s2, *e2; + /* first check if we match at all */ + if (!find_match(string, Query, offset, &s1, &e1)) + return false; + s2 = s1, e2 = e1; // Init s2 and e2 before use below + + /* next find the longest match. If multiple, take the left most one */ + while (find_match(string, Query, ++offset, &s1, &e1) && ((e1-s1) <= (e2-s2))) + s1 = s2, e1 = e2; + + /* return the best match */ + *start = s1 - string; + *end = e1 - string; + return true; +} + +static void score(void) +{ + unsigned int nchoices = vec_size(&Choices); + for (unsigned int i = 0; i < nchoices; i++) { + Choice* choice = (Choice*)vec_at(&Choices, i); + float qlen = (float)QueryIdx; + if (match(choice->string, 0, &choice->match_start, &choice->match_end)) + { + float clen = (float)(choice->match_end - choice->match_start) + 1; + choice->score = qlen / clen / (float)(choice->length); + } + else + { + choice->match_start = 0; + choice->match_end = 0; + choice->score = 0.0f; + } + } + vec_sort(&Choices, by_score); +} + +int main(int argc, char** argv) +{ + XConf x = {0}; + if (argc >= 2) { + size_t sz = min(strlen(argv[1]), sizeof(Query)-1); + strncpy(Query, argv[1], sz); + QueryIdx = sz; + } + xinit(&x); + load_choices(&x); + score(); + if (vec_size(&Choices) > 1) + x11_event_loop(&x, redraw); + Choice* choice = (Choice*)vec_at(&Choices, ChoiceIdx); + if (vec_size(&Choices) && ChoiceIdx != SIZE_MAX) + printf("%s\n", choice->string); + return 0; +} + +static void xkeypress(XConf* x, XEvent* e) +{ + char buf[8]; + KeySym key; + Status status; + if (x->xic) + Xutf8LookupString(x->xic, &(e->xkey), buf, sizeof(buf), &key, &status); + else + XLookupString(&(e->xkey), buf, sizeof(buf), &key, 0); + if (key == XK_Return) + { + x->state = QUITTING; + } + else if (key == XK_Escape) + { + x->state = QUITTING; + ChoiceIdx = SIZE_MAX; + } + else if (key == XK_Up) + { + if (ChoiceIdx > 0) + ChoiceIdx--; + if (ChoiceIdx < Offset) + Offset--; + } + else if (key == XK_Down) + { + size_t nlines = ((x->height - x->font->height - 4) / x->font->height) - 1; + size_t maxidx = Offset + nlines; + if (ChoiceIdx+1 < vec_size(&Choices)) + ChoiceIdx++; + if (ChoiceIdx > maxidx) + Offset++; + } + else if (key == XK_BackSpace) + { + if (QueryIdx > 0) + Query[--QueryIdx] = '\0'; + score(); + } + else if (key >= 0x20 && key <= 0x7F) + { + if (QueryIdx < sizeof(Query)-1) + Query[QueryIdx++] = key; + Offset = ChoiceIdx = 0; + score(); + } +} + +static void xbtnpress(XConf* x, XEvent* e) +{ + (void)x; + if (e->xbutton.button == Button1) + { + int starty = x->font->height + 4; + e->xbutton.y = (e->xbutton.y < starty ? starty : e->xbutton.y - starty); + ChoiceIdx = Offset + (e->xbutton.y / x->font->height); + if (ChoiceIdx >= vec_size(&Choices)) + ChoiceIdx = vec_size(&Choices)-1; + } + else if (e->xbutton.button == Button2) + { + x->state = QUITTING; + } + else if (e->xbutton.button == Button3) + { + x->state = QUITTING; + ChoiceIdx = SIZE_MAX; + } + else if (e->xbutton.button == Button4) + { + if (Offset > 0) Offset--; + } + else if (e->xbutton.button == Button5) + { + if (Offset < vec_size(&Choices)) Offset++; + } +} + +static void redraw(XConf* x) +{ + /* draw the background colors and border */ + size_t fheight = x->font->height; + size_t nlines = ((x->height - x->font->height - 4) / x->font->height) - 1; + size_t scroll_height = x->height - fheight - 4; + size_t start = (size_t)((float)Offset / (float)vec_size(&Choices) * scroll_height); + size_t size = (size_t)((float)nlines / (float)vec_size(&Choices) * scroll_height); + + x11_draw_rect(x, Palette[EditBg], 0, 0, x->width, x->height); + x11_draw_rect(x, Palette[TagsBg], 0, 0, x->width, fheight + 4); + x11_draw_rect(x, Palette[HorBdr], 0, fheight + 4, x->width, 1); + x11_draw_rect(x, Palette[ScrollBg], 0, fheight + 5, ScrollWidth+1, scroll_height); + x11_draw_rect(x, Palette[ScrollFg], 0, fheight + 5 + start, ScrollWidth, (size ? size : 5)); + + /* get size of the query when printed */ + XGlyphInfo glyphinfo; + XftTextExtentsUtf8(x->display, x->font, (const FcChar8*)Query, strlen(Query), &glyphinfo); + int offset = 0; + if (glyphinfo.width >= (x->width - 4)) + offset = ((x->width - 4) - glyphinfo.width); + + /* draw the query and the cursor to the query region */ + int posx = 2 + glyphinfo.width + offset; + x11_draw_rect(x, Palette[TagsFg], posx-1, 2, 3, 3); + x11_draw_rect(x, Palette[TagsFg], posx, 2, 1, fheight); + x11_draw_rect(x, Palette[TagsFg], posx-1, 2+fheight-3, 3, 3); + x11_draw_string(x, x->font, offset + 2, fheight, Palette[TagsFg], Query); + + /* draw the scored and sorted results */ + if (vec_size(&Choices)) + { + for (int i = Offset, y = 2 * fheight + 4; ((size_t)i < vec_size(&Choices)) && ((size_t)i <= Offset+nlines); i++, y += fheight) + { + if ((size_t)i == ChoiceIdx) + x11_draw_rect(x, Palette[EditSel], ScrollWidth+3, y - x->font->ascent, x->width, fheight); + x11_draw_string(x, x->font, ScrollWidth+3, y, Palette[TagsFg], ((Choice*)vec_at(&Choices, i))->string); + } + } + else + { + x11_draw_string(x, x->font, ScrollWidth+3, 2 * fheight + 4, Palette[TagsFg], "Loading..."); + } + x11_flip(x); +} + +static void xinit(XConf* x) +{ + x11_init(x); + x11_mkdialog(x, 640, 480, 0 + | StructureNotifyMask + | KeyPressMask + | ButtonPressMask + | ExposureMask + ); + x11_init_gc(x); + x11_centerwin(x); + if (!(x->font = x11_font_load(x, Fonts[0]))) + { + perror("unable to load base font"); + exit(EXIT_FAILURE); + } + x->eventfns[KeyPress] = xkeypress; + x->eventfns[ButtonPress] = xbtnpress; +} diff --git a/bin/pick/telem.c b/bin/pick/telem.c new file mode 100644 index 0000000..913688d --- /dev/null +++ b/bin/pick/telem.c @@ -0,0 +1,94 @@ +#include +#include +#include +#include +#include +#include "io.h" + +static int TelemFd = -1; +static char TelemBuf[16384]; + +static int open_telem(char* path) +{ + static unsigned long prev = 0; + unsigned long curr = time(NULL); + if ((TelemFd < 0) && ((curr - prev) >= 1)) + { + struct stat st = {0}; + if ((stat(path, &st) >= 0) && S_ISFIFO(st.st_mode)) + { + TelemFd = open(path, O_WRONLY|O_NONBLOCK, 0); + prev = curr; + } + } + return TelemFd; +} + +static void close_telem(void) +{ + close(TelemFd); + TelemFd = -1; +} + +static char* advance(char* buf, size_t* p_len, long nwrite) +{ + if (nwrite >= 0) + { + *p_len -= nwrite; + buf = (buf + nwrite); + } + return buf; +} + +static char* add_timestamp(char* buf, size_t* p_len) +{ + time_t rawtime; + time(&rawtime); + struct tm* timeinfo = localtime(&rawtime); + long nwrite = strftime(buf, *p_len, "[%F %T] ", timeinfo); + return advance(buf, p_len, nwrite); +} + +static char* add_pid(char* buf, size_t* p_len) +{ + long nwrite = snprintf(buf, *p_len, "%s(%d) ", ARGV0, getpid()); + return advance(buf, p_len, nwrite); +} + +static char* add_message(char* buf, size_t* p_len, char* fmt, va_list args) +{ + long nwrite = vsnprintf(buf, *p_len, fmt, args); + return advance(buf, p_len, nwrite); +} + +static char* get_fifo_path(void) +{ + static char path[1024] = {0}; + if (!path[0]) + { + snprintf(path, sizeof(path)-1,"%s/tide-telem", getpwuid(getuid())->pw_dir); + } + return path; +} + +void telem_send(char* fmt, ...) +{ + char* str = TelemBuf; + size_t nleft = sizeof(TelemBuf); + int fd = open_telem(get_fifo_path()); + if (fd >= 0) + { + void (*sigfn)(int) = signal(SIGPIPE, SIG_IGN); + va_list args; + va_start(args, fmt); + str = add_timestamp(str, &nleft); + str = add_pid(str, &nleft); + str = add_message(str, &nleft, fmt, args); + va_end(args); + if (writefd(fd, TelemBuf, str - TelemBuf) < 0) + { + close_telem(); + } + signal(SIGPIPE, sigfn); + } +} diff --git a/bin/pick/utf.h b/bin/pick/utf.h new file mode 100644 index 0000000..906f2f1 --- /dev/null +++ b/bin/pick/utf.h @@ -0,0 +1,21 @@ +/** @file */ +enum { + UTF_MAX = 6u, /* maximum number of bytes that make up a rune */ + RUNE_SELF = 0x80, /* byte values larger than this are *not* ascii */ + RUNE_ERR = 0xFFFD, /* rune value representing an error */ + RUNE_MAX = 0x10FFFF, /* Maximum decodable rune value */ + RUNE_EOF = -1, /* rune value representing end of file */ +}; + +/* Represents a unicode code point */ +typedef int32_t Rune; + +size_t utf8encode(char str[UTF_MAX], Rune rune); +bool utf8decode(Rune* rune, size_t* length, int byte); +int runewidth(unsigned col, Rune r); +bool risword(Rune r); +bool rissigil(Rune r); +bool risfile(Rune r); +bool riscmd(Rune r); +bool risblank(Rune r); +bool risbigword(Rune r); diff --git a/bin/pick/utf8.c b/bin/pick/utf8.c new file mode 100644 index 0000000..078c5a3 --- /dev/null +++ b/bin/pick/utf8.c @@ -0,0 +1,144 @@ +#include +#include "utf.h" +#include +#include + +#include "config.h" + +static const uint8_t UTF8_SeqBits[] = { 0x00, 0x80, 0xC0, 0xE0, 0xF0, 0xF8, 0xFC, 0xFE }; +static const uint8_t UTF8_SeqMask[] = { 0x00, 0xFF, 0x1F, 0x0F, 0x07, 0x03, 0x01, 0x00 }; +static const uint8_t UTF8_SeqLens[] = { 0x01, 0x00, 0x02, 0x03, 0x04, 0x05, 0x06, 0x00 }; + +static bool runevalid(Rune val) +{ + return (val <= RUNE_MAX) + && ((val & 0xFFFE) != 0xFFFE) + && ((val < 0xD800) || (val > 0xDFFF)) + && ((val < 0xFDD0) || (val > 0xFDEF)); +} + +static size_t runelen(Rune rune) +{ + size_t ret; + if(!runevalid(rune)) + { + ret = 0; + } + else if(rune <= 0x7F) + { + ret = 1; + } + else if(rune <= 0x07FF) + { + ret = 2; + } + else if(rune <= 0xFFFF) + { + ret = 3; + } + else + { + ret = 4; + } + return ret; +} + +static uint8_t utfseq(uint8_t byte) +{ + uint8_t ret = 0; + for (int i = 1; i < 8; i++) + { + if ((byte & UTF8_SeqBits[i]) == UTF8_SeqBits[i-1]) + { + ret = UTF8_SeqLens[i-1]; + break; + } + } + return ret; +} + +size_t utf8encode(char str[UTF_MAX], Rune rune) +{ + size_t len = runelen(rune); + str[0] = (len == 1 ? 0x00 : UTF8_SeqBits[len]) + | (UTF8_SeqMask[len] & (rune >> (6 * (len-1)))); + for (size_t i = 1; i < len; i++) + str[i] = 0x80u | (0x3Fu & (rune >> (6 * (len-i-1)))); + return len; +} + +bool utf8decode(Rune* rune, size_t* length, int byte) +{ + /* Handle the start of a new rune */ + if (*length == 0) { + /* If we were fed in an EOF as a start byte, handle it here */ + if (byte == EOF) { + *rune = RUNE_EOF; + } else { + /* Otherwise, decode the first byte of the rune */ + *length = utfseq(byte); + *rune = (*length == 0) ? RUNE_ERR : (byte & UTF8_SeqMask[*length]); + (*length)--; + } + /* Handle continuation bytes */ + } else if ((byte & 0xC0) == 0x80) { + /* add bits from continuation byte to rune value + * cannot overflow: 6 byte sequences contain 31 bits */ + *rune = (*rune << 6) | (byte & 0x3F); /* 10xxxxxx */ + (*length)--; + /* Sanity check the final rune value before finishing */ + if ((*length == 0) && !runevalid(*rune)) + *rune = RUNE_ERR; + /* Didn't get the continuation byte we expected */ + } else { + *rune = RUNE_ERR; + } + /* Tell the caller whether we finished or not */ + return ((*length == 0) || (*rune == RUNE_ERR)); +} + +int runewidth(unsigned col, Rune r) +{ + int width; + if (r == '\t') + { + width = (TabWidth - (col % TabWidth)); + } + else + { + width = wcwidth(r); + if (width < 0) width = 1; + } + return width; +} + +bool risword(Rune r) +{ + return (r < 127 && (isalnum(r) || r == '_' || r == '+' || r == '-')); +} + +bool rissigil(Rune r) +{ + return (r == ':' || r == '!' || r == '&' || r == '|' || r == '>' || r == '<'); +} + +bool risfile(Rune r) +{ + return (risword(r) || r == '/' || r == '.' || r == ':' || r == '-' || r == '~'); +} + +bool riscmd(Rune r) +{ + return (risword(r) || rissigil(r)); +} + +bool risblank(Rune r) +{ + return (r == ' ' || r == '\t' || r == '\n' || r == '\r'); +} + +bool risbigword(Rune r) +{ + return !risblank(r); +} + diff --git a/bin/pick/writefd.c b/bin/pick/writefd.c new file mode 100644 index 0000000..c5c2eac --- /dev/null +++ b/bin/pick/writefd.c @@ -0,0 +1,13 @@ +#include +#include "io.h" + +long writefd(int fd, char* data, long towrite) +{ + long nwrite = 0; + while (towrite && ((nwrite = write(fd, data, towrite)) > 0)) + { + data += nwrite; + towrite -= nwrite; + } + return nwrite; +} diff --git a/bin/pick/x11.c b/bin/pick/x11.c new file mode 100644 index 0000000..320a84b --- /dev/null +++ b/bin/pick/x11.c @@ -0,0 +1,257 @@ +#include +#include "x11.h" +#include "utf.h" +#include "io.h" +#include +#include +#include +#include +#include "config.h" + +struct XConf X; +static Bool Has_Error = False; +static XErrorEvent Error = {0}; + +static int onerror(Display* disp, XErrorEvent* ev) +{ + (void)disp; + Has_Error = True, Error = *ev; + return 0; +} + +int x11_init(XConf* x) +{ + int ret = -1; + signal(SIGPIPE, SIG_IGN); // Ignore the SIGPIPE signal + setlocale(LC_CTYPE, ""); + XSetLocaleModifiers(""); + /* open the X display and get basic attributes */ + if ( (x->display = XOpenDisplay(0)) ) + { + x->root = DefaultRootWindow(x->display); + XWindowAttributes wa; + XGetWindowAttributes(x->display, x->root, &wa); + x->visual = wa.visual; + x->colormap = wa.colormap; + x->screen = DefaultScreen(x->display); + x->depth = DefaultDepth(x->display, x->screen); + x->state = RUNNING; + XSetErrorHandler(onerror); + ret = 0; + } + return ret; +} + +void x11_error_clear(void) +{ + Has_Error = False; + memset(&Error, 0, sizeof(Error)); +} + +XErrorEvent* x11_error_get(void) +{ + return (Has_Error ? &Error : NULL); +} + +void x11_mkwin(XConf* x, int width, int height, int evmask) +{ + /* create the main window */ + x->width = width, x->height = height; + XSetWindowAttributes attr; + attr.background_pixel = Palette[EditBg]; + attr.bit_gravity = NorthWestGravity; + attr.backing_store = WhenMapped; + attr.event_mask = evmask + | EnterWindowMask + | ExposureMask + | VisibilityChangeMask + | StructureNotifyMask + ; + x->self = XCreateWindow( + x->display, x->root, 0, 0, x->width, x->height, 0, x->depth, InputOutput, x->visual, + CWBackPixel | CWBitGravity | CWBackingStore | CWEventMask, + &attr); + + /* register interest in the delete window message */ + Atom wmDeleteMessage = XInternAtom(x->display, "WM_DELETE_WINDOW", False); + XSetWMProtocols(x->display, x->self, &wmDeleteMessage, 1); +} + +void x11_mkdialog(XConf* x, int width, int height, int evmask) +{ + x11_mkwin(x, width, height, evmask); + Atom WindowType = XInternAtom(x->display, "_NET_WM_WINDOW_TYPE", False); + Atom DialogType = XInternAtom(x->display, "_NET_WM_WINDOW_TYPE_DIALOG", False); + XChangeProperty(x->display, x->self, WindowType, XA_ATOM, 32, PropModeReplace, (unsigned char*)&DialogType, 1); +} + +static void update_state(XConf* x, XEvent* e) +{ + if ((e->type == KeyPress) || (e->type == ButtonPress) || + (e->type == ButtonRelease) || (e->type == MotionNotify)) + { + x->now = e->xkey.time; + x->mods = e->xkey.state; + } +} + +void x11_process_events(XConf* x) +{ + int nevents; + /* reap zombie background processes */ + for (int status; waitpid(-1, &status, WNOHANG) > 0;); + /* process the entire event queue */ + while (XEventsQueued(x->display, QueuedAfterReading)) + { + telem_send("EV_READ_QUEUE(pending: %d)\n", XPending(x->display)); + XGetMotionEvents(x->display, x->self, CurrentTime, CurrentTime, &nevents); + + for (XEvent e; XPending(x->display);) + { + XNextEvent(x->display, &e); + update_state(x, &e); + if (!XFilterEvent(&e, None) && x->eventfns[e.type]) + { + telem_send("EV_HANDLE(type: %d)\n", e.type); + (x->eventfns[e.type])(x, &e); + } + else + { + telem_send("EV_IGNORE(type: %d)\n", e.type); + } + + /* log error here */ + XErrorEvent* err = x11_error_get(); + if (err) + { + char msg[8192]; + XGetErrorText(x->display, err->error_code, msg, sizeof(msg)); + telem_send("XERROR(%s)\n", msg); + x11_error_clear(); + } + } + } +} + +void x11_event_loop(XConf* x, void (*redraw)(XConf* x)) +{ + if (redraw) redraw(x); + for (XEvent e; x->state != QUITTING;) + { + XNextEvent(x->display, &e); + if (x->eventfns[e.type]) + { + x->eventfns[e.type](x, &e); + } + for (int status; waitpid(-1, &status, WNOHANG) > 0;); + if (redraw) + { + redraw(x); + } + } +} + +int x11_getptr(XConf* x, int* ptrx, int* ptry) +{ + Window root = 0, child = 0; + int winx = 0, winy = 0, mask = 0; + return XQueryPointer(x->display, x->self, &root, &child, ptrx, ptry, &winx, &winy, (unsigned int*)&mask); +} + +static uint32_t special_keys(uint32_t key) +{ + static uint32_t keymap[256] = { + /* Function keys */ + [0xBE] = KEY_F1, [0xBF] = KEY_F2, [0xC0] = KEY_F3, [0xC1] = KEY_F4, + [0xC2] = KEY_F5, [0xC3] = KEY_F6, [0xC4] = KEY_F7, [0xC5] = KEY_F8, + [0xC6] = KEY_F9, [0xC7] = KEY_F10, [0xC8] = KEY_F11, [0xC9] = KEY_F12, + /* Navigation keys */ + [0x50] = KEY_HOME, [0x51] = KEY_LEFT, [0x52] = KEY_UP, + [0x53] = KEY_RIGHT, [0x54] = KEY_DOWN, [0x55] = KEY_PGUP, + [0x56] = KEY_PGDN, [0x57] = KEY_END, + /* Control keys */ + [0x08] = '\b', [0x09] = '\t', [0x0d] = '\n', [0x0a] = '\n', + /* Miscellaneous */ + [0x63] = KEY_INSERT, [0x1B] = KEY_ESCAPE, [0xFF] = KEY_DELETE, + }; + /* lookup the key by keysym */ + key = ((key & 0xFF00) == 0xFF00 ? keymap[key & 0xFF] : key); + return (!key ? RUNE_ERR : key); +} + +uint32_t x11_getkey(XConf* x, XEvent* e) +{ + uint32_t ret; + char buf[8]; + KeySym key; + Status status; + + /* Read the key string */ + if (x->xic) + { + Xutf8LookupString(x->xic, &(e->xkey), buf, sizeof(buf), &key, &status); + } + else + { + XLookupString(&(e->xkey), buf, sizeof(buf), &key, 0); + } + + /* if it's ascii, just return it */ + if (key >= 0x20 && key <= 0x7F) + { + ret = (uint32_t)key; + } + else + { + ret = special_keys(key); + } + return ret; +} + +uint32_t x11_process_key(XConf* x, XEvent* e, KeyBinding* keys) +{ + uint32_t key = x11_getkey(x, e); + if (key != RUNE_ERR) + { + int mods = e->xkey.state & (ModCtrl|ModShift|ModAlt); + int32_t mkey = tolower(key); + for (KeyBinding* bind = keys; bind && bind->key; bind++) + { + bool match = (mkey == (int32_t)bind->key); + bool exact = (bind->mods == mods); + bool any = (bind->mods == ModAny); + bool oneplus = ((bind->mods == ModOneOrMore) && (mods & ModOneOrMore)); + if (match && (exact || oneplus || any)) + { + bind->fn(bind->arg); + key = RUNE_ERR; + break; + } + } + } + /* fallback to just inserting the rune if it doesn't fall in the private use area. + * the private use area is used to encode special keys */ + return (key < 0xE000 || key > 0xF8FF ? key : RUNE_ERR); +} + +int x11_seturgent(XConf* x, int urgent) +{ + int status = 0; + XWMHints hints = {0}, *prevhints = XGetWMHints(x->display, x->self); + if (prevhints) + { + hints = *prevhints; + XFree(prevhints); + } + if (urgent) + { + hints.flags |= XUrgencyHint; + } + else + { + hints.flags &= ~XUrgencyHint; + } + status = XSetWMHints(x->display, x->self, &hints); + telem_send("URGENT(%d %d)\n", urgent, status); + return status; +} diff --git a/bin/pick/x11.h b/bin/pick/x11.h new file mode 100644 index 0000000..4878f97 --- /dev/null +++ b/bin/pick/x11.h @@ -0,0 +1,166 @@ +/** @file */ +AUTOLIB(X11) +AUTOLIB(Xinerama) +AUTOLIB(Xft) +AUTOLIB(fontconfig) + +#include +#include +#include + +enum { + RUNNING, + SERVE_SEL, + QUITTING +}; + +typedef struct XConf { + Bool error; + int state, fd, screen, width, height, mods; + Window root; + Display* display; + Visual* visual; + Colormap colormap; + unsigned depth; + Window self; + XftDraw* xft; + XftFont *tagfont, *font; + Pixmap pixmap; + XIC xic; + XIM xim; + GC gc; + void (*eventfns[LASTEvent])(struct XConf*, XEvent*); + Time now; +} XConf; + +/* Selection identifiers */ +enum { + PRIMARY = 0, + CLIPBOARD = 1 +}; + +/* key definitions */ +enum Keys { + /* Define some runes in the private use area of unicode to represent + * special keys */ + KEY_F1 = (0xE000+0), + KEY_F2 = (0xE000+1), + KEY_F3 = (0xE000+2), + KEY_F4 = (0xE000+3), + KEY_F5 = (0xE000+4), + KEY_F6 = (0xE000+5), + KEY_F7 = (0xE000+6), + KEY_F8 = (0xE000+7), + KEY_F9 = (0xE000+8), + KEY_F10 = (0xE000+9), + KEY_F11 = (0xE000+10), + KEY_F12 = (0xE000+11), + KEY_INSERT = (0xE000+12), + KEY_DELETE = (0xE000+13), + KEY_HOME = (0xE000+14), + KEY_END = (0xE000+15), + KEY_PGUP = (0xE000+16), + KEY_PGDN = (0xE000+17), + KEY_UP = (0xE000+18), + KEY_DOWN = (0xE000+19), + KEY_RIGHT = (0xE000+20), + KEY_LEFT = (0xE000+21), + + /* ASCII Control Characters */ + KEY_CTRL_TILDE = 0x00, + KEY_CTRL_2 = 0x00, + KEY_CTRL_A = 0x01, + KEY_CTRL_B = 0x02, + KEY_CTRL_C = 0x03, + KEY_CTRL_D = 0x04, + KEY_CTRL_E = 0x05, + KEY_CTRL_F = 0x06, + KEY_CTRL_G = 0x07, + KEY_BACKSPACE = 0x08, + KEY_CTRL_H = 0x08, + KEY_TAB = 0x09, + KEY_CTRL_I = 0x09, + KEY_CTRL_J = 0x0A, + KEY_CTRL_K = 0x0B, + KEY_CTRL_L = 0x0C, + KEY_ENTER = 0x0D, + KEY_CTRL_M = 0x0D, + KEY_CTRL_N = 0x0E, + KEY_CTRL_O = 0x0F, + KEY_CTRL_P = 0x10, + KEY_CTRL_Q = 0x11, + KEY_CTRL_R = 0x12, + KEY_CTRL_S = 0x13, + KEY_CTRL_T = 0x14, + KEY_CTRL_U = 0x15, + KEY_CTRL_V = 0x16, + KEY_CTRL_W = 0x17, + KEY_CTRL_X = 0x18, + KEY_CTRL_Y = 0x19, + KEY_CTRL_Z = 0x1A, + KEY_ESCAPE = 0x1B, + KEY_CTRL_LSQ_BRACKET = 0x1B, + KEY_CTRL_3 = 0x1B, + KEY_CTRL_4 = 0x1C, + KEY_CTRL_BACKSLASH = 0x1C, + KEY_CTRL_5 = 0x1D, + KEY_CTRL_RSQ_BRACKET = 0x1D, + KEY_CTRL_6 = 0x1E, + KEY_CTRL_7 = 0x1F, + KEY_CTRL_SLASH = 0x1F, + KEY_CTRL_UNDERSCORE = 0x1F, +}; + +/* Key modifier masks */ +enum { + ModNone = 0, + ModShift = (1 << 0), + ModCapsLock = (1 << 1), + ModCtrl = (1 << 2), + ModAlt = (1 << 3), + ModNumLock = (1 << 4), + ModScrollLock = (1 << 5), + ModWindows = (1 << 6), + ModOneOrMore = (ModCtrl|ModAlt), + ModAny = -1 +}; + +typedef struct { + int mods; + uint32_t key; + void (*fn)(char*); + char* arg; +} KeyBinding; + +extern struct XConf X; + +int x11_init(XConf* x); +void x11_error_clear(void); +XErrorEvent* x11_error_get(void); +void x11_resize(XConf* x, XEvent* e); + +void x11_mkwin(XConf* x, int width, int height, int evmask); +void x11_mkdialog(XConf* x, int width, int height, int evmask); +void x11_process_events(XConf* x); +void x11_event_loop(XConf* x, void (*redraw)(XConf* x)); +int x11_getptr(XConf* x, int* ptrx, int* ptry); +uint32_t x11_getkey(XConf* x, XEvent* e); +uint32_t x11_process_key(XConf* x, XEvent* e, KeyBinding* keys); +int x11_seturgent(XConf* x, int urgent); + +void x11_centerwin(XConf* x); +void x11_init_gc(XConf* x); +void x11_show(XConf* x); +XftFont* x11_font_load(XConf* x, char* name); +void xftcolor(XConf* x, XftColor* xc, unsigned int c); +void x11_draw_rect(XConf* x, int color, int px, int py, int width, int height); +void x11_draw_glyphs(XConf* x, int color, XftFont* font, XftGlyphSpec* specs, long nspecs); +void x11_flip(XConf* x); +void x11_draw_string(XConf* x, XftFont* font, int posx, int posy, int color, char* str); + +void x11_sel_init(XConf* x); +int x11_sel_get(XConf* x, int selid, void(*cbfn)(char*)); +int x11_sel_set(XConf* x, int selid, char* str); +void x11_sel_quit(XConf* x, XEvent* e); +int x11_sel_ready(XConf* x); +void x11_sel_serve(XConf* x); diff --git a/bin/pick/x11_gc.c b/bin/pick/x11_gc.c new file mode 100644 index 0000000..d7d7a7d --- /dev/null +++ b/bin/pick/x11_gc.c @@ -0,0 +1,165 @@ +#include +#include "x11.h" +#include "io.h" +#include + +void x11_resize(XConf* x, XEvent* e) +{ + telem_send("XRESIZE(w: %d, h: %d)\n", e->xconfigure.width, e->xconfigure.height); + if (e->xconfigure.width != x->width || e->xconfigure.height != x->height) + { + x->width = e->xconfigure.width; + x->height = e->xconfigure.height; + XFreePixmap(x->display, x->pixmap); + x->pixmap = XCreatePixmap(x->display, x->self, x->width, x->height, x->depth); + XftDrawChange(x->xft, x->pixmap); + } +} + +void x11_mapnotify(XConf* x, XEvent* e) +{ + (void)x; + telem_send("XMAPNOTIFY(0x%x)\n", e->xmap.window); +} + +void x11_enternotify(XConf* x, XEvent* e) +{ + telem_send("XENTERNOTIFY(0x%x)\n", e->xmap.window); + x11_seturgent(x, 0); +} + +void x11_init_gc(XConf* x) +{ + /* set input methods */ + if ((x->xim = XOpenIM(x->display, 0, 0, 0))) + x->xic = XCreateIC(x->xim, XNInputStyle, XIMPreeditNothing|XIMStatusNothing, XNClientWindow, x->self, XNFocusWindow, x->self, NULL); + /* initialize pixmap and drawing context */ + x->pixmap = XCreatePixmap(x->display, x->self, x->width, x->height, x->depth); + x->xft = XftDrawCreate(x->display, x->pixmap, x->visual, x->colormap); + /* initialize the graphics context */ + XGCValues gcv; + gcv.foreground = WhitePixel(x->display, x->screen); + gcv.graphics_exposures = False; + x->gc = XCreateGC(x->display, x->self, GCGraphicsExposures, &gcv); + x->eventfns[ConfigureNotify] = x11_resize; + x->eventfns[MapNotify] = x11_mapnotify; + x->eventfns[EnterNotify] = x11_enternotify; +} + +void x11_centerwin(XConf* x) +{ + int ptrx = 0, ptry = 0; + (void)x11_getptr(x, &ptrx, &ptry); + int nscreens = 0; + XineramaScreenInfo* p_screens = XineramaQueryScreens(x->display, &nscreens); + for (int i = 0; i < nscreens; i++) + { + int minx = p_screens[i].x_org, + maxx = p_screens[i].x_org + p_screens[i].width, + miny = p_screens[i].y_org, + maxy = p_screens[i].y_org + p_screens[i].height; + if (minx <= ptrx && ptrx <= maxx && miny <= ptry && ptry <= maxy) + { + XMoveWindow(x->display, x->self, + minx + p_screens[i].width/2 - x->width/2, + miny + p_screens[i].height/2 - x->height/2); + break; + } + } + if (p_screens) + { + XFree(p_screens); + } +} + +void x11_show(XConf* x) +{ + /* simulate an initial resize and map the window */ + XConfigureEvent ce; + ce.type = ConfigureNotify; + ce.width = x->width; + ce.height = x->height; + XSendEvent(x->display, x->self, False, StructureNotifyMask, (XEvent *)&ce); + XMapWindow(x->display, x->self); + XSync(x->display, False); + + /* Waiting for window mapping */ + XEvent ev; + do + { + XNextEvent(x->display, &ev); + if (XFilterEvent(&ev, None)) + { + continue; + } + if (ev.type == ConfigureNotify) + { + x11_resize(x, &ev); + } + } + while (ev.type != MapNotify); +// XWarpPointer(x->display, None, x->self, 0, 0, x->width, x->height, x->width/2, x->height/2); +} + +XftFont* x11_font_load(XConf* x, char* name) +{ + XftFont* font = NULL; + if (FcInit()) + { + FcPattern* pattern = FcNameParse((FcChar8 *)name); + if (pattern) + { + /* load the base font */ + FcResult result; + FcPattern* match = XftFontMatch(x->display, x->screen, pattern, &result); + if (match) + { + font = XftFontOpenPattern(x->display, match); + } + FcPatternDestroy(pattern); + FcPatternDestroy(match); + } + } +// printf("font: '%s'\n", name); + return font; +} + +void xftcolor(XConf* x, XftColor* xc, unsigned int c) +{ + #define COLOR(c) ((c) | ((c) >> 8)) + xc->color.alpha = 0xFFFF; + xc->color.red = COLOR((c & 0x00FF0000) >> 8); + xc->color.green = COLOR((c & 0x0000FF00)); + xc->color.blue = COLOR((c & 0x000000FF) << 8); + XftColorAllocValue(x->display, x->visual, x->colormap, &(xc->color), xc); +} + +void x11_draw_rect(XConf* x, int color, int px, int py, int width, int height) +{ + XftColor clr; + xftcolor(x, &clr, color); + XftDrawRect(x->xft, &clr, px, py, width, height); + XftColorFree(x->display, x->visual, x->colormap, &clr); +} + +void x11_draw_glyphs(XConf* x, int color, XftFont* font, XftGlyphSpec* specs, long nspecs) +{ + XftColor clr; + xftcolor(x, &clr, color); + XftDrawGlyphSpec(x->xft, &clr, font, specs, nspecs); + XftColorFree(x->display, x->visual, x->colormap, &clr); +} + +void x11_flip(XConf* x) +{ + XCopyArea(x->display, x->pixmap, x->self, x->gc, 0, 0, x->width, x->height, 0, 0); + XFlush(x->display); +} + +void x11_draw_string(XConf* x, XftFont* font, int posx, int posy, int color, char* str) +{ + XftColor clr; + xftcolor(x, &clr, color); + XftDrawStringUtf8(x->xft, &clr, font, posx, posy, (const FcChar8*)str, strlen(str)); + XftColorFree(x->display, x->visual, x->colormap, &clr); +} diff --git a/bin/pick/x11_sel.c b/bin/pick/x11_sel.c new file mode 100644 index 0000000..d026750 --- /dev/null +++ b/bin/pick/x11_sel.c @@ -0,0 +1,183 @@ +#include +#include "x11.h" + +struct XSel { + char* name; + Atom atom; + char* text; + void (*callback)(char*); +}; + +static Atom SelTarget; +static struct XSel Selections[] = { + { .name = "PRIMARY" }, + { .name = "CLIPBOARD" }, +}; + +static struct XSel* selfetch(Atom atom) +{ + struct XSel* ret = NULL; + for (unsigned int i = 0; i < (sizeof(Selections) / sizeof(Selections[0])); i++) + { + if (atom == Selections[i].atom) + { + ret = &Selections[i]; + break; + } + } + return ret; +} + +static void xselclear(XConf* x, XEvent* e) +{ + (void)x; + struct XSel* sel = selfetch(e->xselectionclear.selection); + if (!sel) return; + free(sel->text); + sel->text = NULL; +} + +static void xselnotify(XConf* x, XEvent* e) +{ + /* bail if the selection cannot be converted */ + if (e->xselection.property == None) + return; + struct XSel* sel = selfetch( e->xselection.selection ); + Atom rtype; + unsigned long format = 0, nitems = 0, nleft = 0; + unsigned char* propdata = NULL; + XGetWindowProperty(x->display, x->self, sel->atom, 0, -1, False, AnyPropertyType, &rtype, + (int*)&format, &nitems, &nleft, &propdata); + if (e->xselection.target == SelTarget) + { + void(*cbfn)(char*) = sel->callback; + sel->callback = NULL; + cbfn((char*)propdata); + } + /* cleanup */ + if (propdata) XFree(propdata); +} + +static void xselrequest(XConf* x, XEvent* e) +{ + XEvent s; + struct XSel* sel = selfetch( e->xselectionrequest.selection ); + s.xselection.type = SelectionNotify; + s.xselection.property = e->xselectionrequest.property; + s.xselection.requestor = e->xselectionrequest.requestor; + s.xselection.selection = e->xselectionrequest.selection; + s.xselection.target = e->xselectionrequest.target; + s.xselection.time = e->xselectionrequest.time; + Atom target = e->xselectionrequest.target; + Atom xatargets = XInternAtom(x->display, "TARGETS", 0); + Atom xastring = XInternAtom(x->display, "STRING", 0); + if (target == xatargets) + { + /* respond with the supported type */ + XChangeProperty( + x->display, + s.xselection.requestor, + s.xselection.property, + XA_ATOM, 32, PropModeReplace, + (unsigned char*)&SelTarget, 1); + } + else if (target == SelTarget || target == xastring) + { + XChangeProperty( + x->display, + s.xselection.requestor, + s.xselection.property, + SelTarget, 8, PropModeReplace, + (unsigned char*)sel->text, strlen(sel->text)); + } + XSendEvent(x->display, s.xselection.requestor, True, 0, &s); +} + +void x11_sel_init(XConf* x) +{ + /* initialize selection atoms */ + for (unsigned int i = 0; i < (sizeof(Selections) / sizeof(Selections[0])); i++) + { + Selections[i].atom = XInternAtom(x->display, Selections[i].name, 0); + } + SelTarget = XInternAtom(x->display, "UTF8_STRING", 0); + if (SelTarget == None) + { + SelTarget = XInternAtom(x->display, "STRING", 0); + } + /* setup event handlers */ + x->eventfns[SelectionClear] = xselclear; + x->eventfns[SelectionNotify] = xselnotify; + x->eventfns[SelectionRequest] = xselrequest; +} + +int x11_sel_get(XConf* x, int selid, void(*cbfn)(char*)) +{ + int ret = 0; + struct XSel* sel = &(Selections[selid]); + if (!sel->callback) + { + Window owner = XGetSelectionOwner(x->display, sel->atom); + if (owner == x->self) + { + cbfn(sel->text); + } + else if (owner != None) + { + sel->callback = cbfn; + XConvertSelection(x->display, sel->atom, SelTarget, sel->atom, x->self, CurrentTime); + } + ret = 1; + } + return ret; +} + +int x11_sel_set(XConf* x, int selid, char* str) +{ + int ret; + struct XSel* sel = &(Selections[selid]); + if (!sel || !str || !*str) + { + free(str); + ret = 0; + } + else + { + sel->text = str; + XSetSelectionOwner(x->display, sel->atom, x->self, CurrentTime); + ret = 1; + } + return ret; +} + +void x11_sel_quit(XConf* x, XEvent* e) +{ + xselclear(x, e); + if (!Selections[PRIMARY].text && !Selections[CLIPBOARD].text) + { + x->state = QUITTING; + } +} + +int x11_sel_ready(XConf* x) +{ + (void)x; + return (Selections[PRIMARY].text || Selections[CLIPBOARD].text); +} + +void x11_sel_serve(XConf* x) +{ + X.eventfns[SelectionClear] = x11_sel_quit; + X.self = XCreateSimpleWindow(X.display, X.root, 0, 0, 1, 1, 0, 0, 0); + if (Selections[PRIMARY].text) + { + XSetSelectionOwner(x->display, Selections[PRIMARY].atom, x->self, CurrentTime); + + } + if (Selections[CLIPBOARD].text) + { + XSetSelectionOwner(x->display, Selections[CLIPBOARD].atom, x->self, CurrentTime); + } + if (fork()) exit(0); /* fork into background */ +} + diff --git a/bin/rules.mk b/bin/rules.mk index 4952daf..7663b19 100644 --- a/bin/rules.mk +++ b/bin/rules.mk @@ -1,2 +1,6 @@ -$(BINDIR)/screenlock: LIBS += -lui -lX11 -lXft -lfontconfig +$(BINDIR)/edit: LIBS += -lX11 +$(BINDIR)/editor: LIBS += -lX11 -lXft -lfontconfig -lXinerama +$(BINDIR)/pick: LIBS += -lX11 -lXft -lfontconfig -lXinerama +$(BINDIR)/screenlock: LIBS += -lui -lX11 -lXft -lfontconfig -lcrypt -lXrandr +$(BINDIR)/terminal: LIBS += -lui -lX11 -lXft -lfontconfig $(BINDIR)/winmgr: LIBS += -lX11 -lXft -lfontconfig -lXinerama diff --git a/bin/screenlock.c b/bin/screenlock.c deleted file mode 100644 index 6b66458..0000000 --- a/bin/screenlock.c +++ /dev/null @@ -1,15 +0,0 @@ -#include -#include - -int main(int argc, char** argv) -{ - (void)argc; - (void)argv; - - UIWin* win = win_create("Lock Screen"); - win_show(win); - sleep(5); - win_delete(win); - - return 0; -} \ No newline at end of file diff --git a/bin/screenlock/LICENSE b/bin/screenlock/LICENSE new file mode 100644 index 0000000..2e4419b --- /dev/null +++ b/bin/screenlock/LICENSE @@ -0,0 +1,24 @@ +MIT/X Consortium License + +© 2015-2016 Markus Teich +© 2014 Dimitris Papastamos +© 2006-2014 Anselm R Garbe +© 2014-2016 Laslo Hunhold + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/bin/screenlock/arg.h b/bin/screenlock/arg.h new file mode 100644 index 0000000..0b23c53 --- /dev/null +++ b/bin/screenlock/arg.h @@ -0,0 +1,65 @@ +/* + * Copy me if you can. + * by 20h + */ + +#ifndef ARG_H__ +#define ARG_H__ + +extern char *argv0; + +/* use main(int argc, char *argv[]) */ +#define ARGBEGIN for (argv0 = *argv, argv++, argc--;\ + argv[0] && argv[0][0] == '-'\ + && argv[0][1];\ + argc--, argv++) {\ + char argc_;\ + char **argv_;\ + int brk_;\ + if (argv[0][1] == '-' && argv[0][2] == '\0') {\ + argv++;\ + argc--;\ + break;\ + }\ + for (brk_ = 0, argv[0]++, argv_ = argv;\ + argv[0][0] && !brk_;\ + argv[0]++) {\ + if (argv_ != argv)\ + break;\ + argc_ = argv[0][0];\ + switch (argc_) + +/* Handles obsolete -NUM syntax */ +#define ARGNUM case '0':\ + case '1':\ + case '2':\ + case '3':\ + case '4':\ + case '5':\ + case '6':\ + case '7':\ + case '8':\ + case '9' + +#define ARGEND }\ + } + +#define ARGC() argc_ + +#define ARGNUMF() (brk_ = 1, estrtonum(argv[0], 0, INT_MAX)) + +#define EARGF(x) ((argv[0][1] == '\0' && argv[1] == NULL)?\ + ((x), abort(), (char *)0) :\ + (brk_ = 1, (argv[0][1] != '\0')?\ + (&argv[0][1]) :\ + (argc--, argv++, argv[0]))) + +#define ARGF() ((argv[0][1] == '\0' && argv[1] == NULL)?\ + (char *)0 :\ + (brk_ = 1, (argv[0][1] != '\0')?\ + (&argv[0][1]) :\ + (argc--, argv++, argv[0]))) + +#define LNGARG() &argv[0][0] + +#endif diff --git a/bin/screenlock/config.h b/bin/screenlock/config.h new file mode 100644 index 0000000..c2a5da7 --- /dev/null +++ b/bin/screenlock/config.h @@ -0,0 +1,12 @@ +/* user and group to drop privileges to */ +static const char *user = "nobody"; +static const char *group = "nogroup"; + +static const char *colorname[NUMCOLS] = { + "black", /* after initialization */ + "#005577", /* during input */ + "black", /* wrong password */ +}; + +/* treat a cleared input like a wrong password */ +static const int failonclear = 1; diff --git a/bin/screenlock/explicit_bzero.c b/bin/screenlock/explicit_bzero.c new file mode 100644 index 0000000..4bfeed3 --- /dev/null +++ b/bin/screenlock/explicit_bzero.c @@ -0,0 +1,20 @@ +/* $OpenBSD: explicit_bzero.c,v 1.3 2014/06/21 02:34:26 matthew Exp $ */ +/* + * Public domain. + * Written by Matthew Dempsky. + */ + +#include + +__attribute__((weak)) void +__explicit_bzero_hook(void *buf, size_t len) +{ + (void)buf, (void)len; +} + +void +explicit_bzero(void *buf, size_t len) +{ + memset(buf, 0, len); + __explicit_bzero_hook(buf, len); +} diff --git a/bin/screenlock/slock.c b/bin/screenlock/slock.c new file mode 100644 index 0000000..8e00bdc --- /dev/null +++ b/bin/screenlock/slock.c @@ -0,0 +1,392 @@ +#define _DEFAULT_SOURCE +#define HAVE_SHADOW_H 1 + +#include +#include +#include +#include +#include +#include +#include + +#include +#if HAVE_SHADOW_H +#include +#endif +#include + +#include +#include +#include +#include + +enum { + INIT, + INPUT, + FAILED, + NUMCOLS +}; + +#include "config.h" + +typedef struct { + int screen; + Window root, win; + Pixmap pmap; + unsigned long colors[NUMCOLS]; +} Lock; + +static Lock **locks; +static int nscreens; +static Bool rr; +static int rrevbase; +static int rrerrbase; + +static void +die(const char *errstr, ...) +{ + va_list ap; + + va_start(ap, errstr); + vfprintf(stderr, errstr, ap); + va_end(ap); + exit(1); +} + +#ifdef __linux__ +#include +#include + +static void +dontkillme(void) +{ + FILE *f; + const char oomfile[] = "/proc/self/oom_score_adj"; + + if (!(f = fopen(oomfile, "w"))) { + if (errno == ENOENT) + return; + die("slock: fopen %s: %s\n", oomfile, strerror(errno)); + } + fprintf(f, "%d", OOM_SCORE_ADJ_MIN); + if (fclose(f)) { + if (errno == EACCES) + die("slock: unable to disable OOM killer. " + "suid or sgid set?\n"); + else + die("slock: fclose %s: %s\n", oomfile, + strerror(errno)); + } +} +#endif + +static const char * +getpw(void) +{ + const char *rval; + struct passwd *pw; + + /* Check if the current user has a password entry */ + errno = 0; + if (!(pw = getpwuid(getuid()))) { + if (errno) + die("slock: getpwuid: %s\n", strerror(errno)); + else + die("slock: cannot retrieve password entry\n"); + } + rval = pw->pw_passwd; + +#if HAVE_SHADOW_H + if (rval[0] == 'x' && rval[1] == '\0') { + struct spwd *sp; + if (!(sp = getspnam(getenv("USER")))) + die("slock: getspnam: cannot retrieve shadow entry (make sure to suid or sgid slock)\n"); + rval = sp->sp_pwdp; + } +#else + if (rval[0] == '*' && rval[1] == '\0') { +#ifdef __OpenBSD__ + if (!(pw = getpwnam_shadow(getenv("USER")))) + die("slock: getpwnam_shadow: cannot retrieve shadow entry (make sure to suid or sgid slock)\n"); + rval = pw->pw_passwd; +#else + die("slock: getpwuid: cannot retrieve shadow entry (make sure to suid or sgid slock)\n"); +#endif /* __OpenBSD__ */ + } +#endif /* HAVE_SHADOW_H */ + + return rval; +} + +static void +readpw(Display *dpy, const char *pws) +{ + char buf[32], passwd[256], *encrypted; + int num, screen, running, failure; + unsigned int len, color; + KeySym ksym; + XEvent ev; + static int oldc = INIT; + + len = 0; + running = 1; + failure = 0; + + /* As "slock" stands for "Simple X display locker", the DPMS settings + * had been removed and you can set it with "xset" or some other + * utility. This way the user can easily set a customized DPMS + * timeout. */ + while (running && !XNextEvent(dpy, &ev)) { + if (ev.type == KeyPress) { + explicit_bzero(&buf, sizeof(buf)); + num = XLookupString(&ev.xkey, buf, sizeof(buf), &ksym, 0); + if (IsKeypadKey(ksym)) { + if (ksym == XK_KP_Enter) + ksym = XK_Return; + else if (ksym >= XK_KP_0 && ksym <= XK_KP_9) + ksym = (ksym - XK_KP_0) + XK_0; + } + if (IsFunctionKey(ksym) || + IsKeypadKey(ksym) || + IsMiscFunctionKey(ksym) || + IsPFKey(ksym) || + IsPrivateKeypadKey(ksym)) + continue; + switch (ksym) { + case XK_Return: + passwd[len] = 0; + errno = 0; + if (!(encrypted = crypt(passwd, pws))) + fprintf(stderr, "slock: crypt: %s\n", strerror(errno)); + else + running = !!strcmp(encrypted, pws); + if (running) { + XBell(dpy, 100); + failure = True; + } + explicit_bzero(&passwd, sizeof(passwd)); + len = 0; + break; + case XK_Escape: + explicit_bzero(&passwd, sizeof(passwd)); + len = 0; + break; + case XK_BackSpace: + if (len) + passwd[len--] = 0; + break; + default: + if (num && !iscntrl((int)buf[0]) && (len + num < sizeof(passwd))) { + memcpy(passwd + len, buf, num); + len += num; + } + break; + } + color = len ? INPUT : (failure || failonclear ? FAILED : INIT); + if (running && oldc != (int)color) { + for (screen = 0; screen < nscreens; screen++) { + XSetWindowBackground(dpy, locks[screen]->win, locks[screen]->colors[color]); + XClearWindow(dpy, locks[screen]->win); + } + oldc = color; + } + } else if (rr && ev.type == rrevbase + RRScreenChangeNotify) { + XRRScreenChangeNotifyEvent *rre = (XRRScreenChangeNotifyEvent*)&ev; + for (screen = 0; screen < nscreens; screen++) { + if (locks[screen]->win == rre->window) { + XResizeWindow(dpy, locks[screen]->win, rre->width, rre->height); + XClearWindow(dpy, locks[screen]->win); + } + } + } else for (screen = 0; screen < nscreens; screen++) + XRaiseWindow(dpy, locks[screen]->win); + } +} + +static void +unlockscreen(Display *dpy, Lock *lock) +{ + if(dpy == NULL || lock == NULL) + return; + + XUngrabPointer(dpy, CurrentTime); + XUngrabKeyboard(dpy, CurrentTime); + XFreeColors(dpy, DefaultColormap(dpy, lock->screen), lock->colors, NUMCOLS, 0); + XFreePixmap(dpy, lock->pmap); + XDestroyWindow(dpy, lock->win); + + free(lock); +} + +static void +cleanup(Display *dpy) +{ + int s; + + for (s = 0; s < nscreens; ++s) + unlockscreen(dpy, locks[s]); + + free(locks); + XCloseDisplay(dpy); +} + +static Lock * +lockscreen(Display *dpy, int screen) +{ + char curs[] = {0, 0, 0, 0, 0, 0, 0, 0}; + int i, ptgrab, kbgrab; + Lock *lock; + XColor color, dummy; + XSetWindowAttributes wa; + Cursor invisible; + + if (dpy == NULL || screen < 0 || !(lock = malloc(sizeof(Lock)))) + return NULL; + + lock->screen = screen; + lock->root = RootWindow(dpy, lock->screen); + + for (i = 0; i < NUMCOLS; i++) { + XAllocNamedColor(dpy, DefaultColormap(dpy, lock->screen), colorname[i], &color, &dummy); + lock->colors[i] = color.pixel; + } + + /* init */ + wa.override_redirect = 1; + wa.background_pixel = lock->colors[INIT]; + lock->win = XCreateWindow(dpy, lock->root, 0, 0, DisplayWidth(dpy, lock->screen), DisplayHeight(dpy, lock->screen), + 0, DefaultDepth(dpy, lock->screen), CopyFromParent, + DefaultVisual(dpy, lock->screen), CWOverrideRedirect | CWBackPixel, &wa); + lock->pmap = XCreateBitmapFromData(dpy, lock->win, curs, 8, 8); + invisible = XCreatePixmapCursor(dpy, lock->pmap, lock->pmap, &color, &color, 0, 0); + XDefineCursor(dpy, lock->win, invisible); + + /* Try to grab mouse pointer *and* keyboard for 600ms, else fail the lock */ + for (i = 0, ptgrab = kbgrab = -1; i < 6; i++) { + if (ptgrab != GrabSuccess) { + ptgrab = XGrabPointer(dpy, lock->root, False, + ButtonPressMask | ButtonReleaseMask | + PointerMotionMask, GrabModeAsync, + GrabModeAsync, None, invisible, CurrentTime); + } + if (kbgrab != GrabSuccess) { + kbgrab = XGrabKeyboard(dpy, lock->root, True, + GrabModeAsync, GrabModeAsync, CurrentTime); + } + + /* input is grabbed: we can lock the screen */ + if (ptgrab == GrabSuccess && kbgrab == GrabSuccess) { + XMapRaised(dpy, lock->win); + if (rr) + XRRSelectInput(dpy, lock->win, RRScreenChangeNotifyMask); + + XSelectInput(dpy, lock->root, SubstructureNotifyMask); + return lock; + } + + /* retry on AlreadyGrabbed but fail on other errors */ + if ((ptgrab != AlreadyGrabbed && ptgrab != GrabSuccess) || + (kbgrab != AlreadyGrabbed && kbgrab != GrabSuccess)) + break; + + usleep(100000); + } + + /* we couldn't grab all input: fail out */ + if (ptgrab != GrabSuccess) + fprintf(stderr, "slock: unable to grab mouse pointer for screen %d\n", screen); + if (kbgrab != GrabSuccess) + fprintf(stderr, "slock: unable to grab keyboard for screen %d\n", screen); + return NULL; +} + +int +main(int argc, char **argv) { + struct passwd *pwd; + struct group *grp; + uid_t duid; + gid_t dgid; + const char *pws; + Display *dpy; + int s, nlocks; + + /* validate drop-user and -group */ + errno = 0; + if (!(pwd = getpwnam(user))) + die("slock: getpwnam %s: %s\n", user, errno ? + strerror(errno) : "user entry not found"); + duid = pwd->pw_uid; + errno = 0; + if (!(grp = getgrnam(group))) + die("slock: getgrnam %s: %s\n", group, errno ? + strerror(errno) : "group entry not found"); + dgid = grp->gr_gid; + +#ifdef __linux__ + dontkillme(); +#endif + + pws = getpw(); + if (strlen(pws) < 2) + die("slock: failed to get user password hash.\n"); + + if (!(dpy = XOpenDisplay(NULL))) + die("slock: cannot open display\n"); + + /* drop privileges */ + if (setgroups(0, NULL) < 0) + die("slock: setgroups: %s\n", strerror(errno)); + if (setgid(dgid) < 0) + die("slock: setgid: %s\n", strerror(errno)); + if (setuid(duid) < 0) + die("slock: setuid: %s\n", strerror(errno)); + + /* check for Xrandr support */ + rr = XRRQueryExtension(dpy, &rrevbase, &rrerrbase); + + /* get number of screens in display "dpy" and blank them */ + nscreens = ScreenCount(dpy); + if (!(locks = calloc(nscreens, sizeof(Lock *)))) { + XCloseDisplay(dpy); + die("slock: out of memory\n"); + } + for (nlocks = 0, s = 0; s < nscreens; s++) { + if ((locks[s] = lockscreen(dpy, s)) != NULL) + nlocks++; + else + break; + } + XSync(dpy, 0); + + /* did we manage to lock everything? */ + if (nlocks != nscreens) { + cleanup(dpy); + return 1; + } + + /* run post-lock command */ + if (argc > 0) { + switch (fork()) { + case -1: + cleanup(dpy); + die("slock: fork failed: %s\n", strerror(errno)); + break; + + case 0: + if (close(ConnectionNumber(dpy)) < 0) + die("slock: close: %s\n", strerror(errno)); + execvp(argv[0], argv); + fprintf(stderr, "slock: execvp %s: %s\n", argv[0], + strerror(errno)); + _exit(1); + } + } + + /* everything is now blank. Wait for the correct password */ + readpw(dpy, pws); + + /* password ok, unlock everything and quit */ + cleanup(dpy); + + return 0; +} diff --git a/bin/screenlock/util.h b/bin/screenlock/util.h new file mode 100644 index 0000000..6f748b8 --- /dev/null +++ b/bin/screenlock/util.h @@ -0,0 +1,2 @@ +#undef explicit_bzero +void explicit_bzero(void *, size_t); diff --git a/bin/shell.c b/bin/shell.c deleted file mode 100644 index 52fe958..0000000 --- a/bin/shell.c +++ /dev/null @@ -1,45 +0,0 @@ -#include -#include -#include - -char* read_line(FILE* f) -{ - char* line = efreadline(stdin); - size_t length = strlen(line); - if (length > 0 && line[length-1] == '\n') - { - line[--length] = '\0'; - if (length > 0 && line[length-1] == '\\') - { - line[--length] = ' '; - char* rest = read_line(f); - char* lnpart = strmcat(line, rest, 0); - free(line); - free(rest); - line = lnpart; - } - } - return line; -} - -int main(int argc, char **argv) -{ - (void)argc; - (void)argv; - - /* load config files... */ - - int running = 1; - do - { - printf("> "); - char* line = read_line(stdin); - printf("'%s'\n", line); - free(line); - } - while (running); - - /* shutdown and cleanup */ - - return 0; -} diff --git a/bin/terminal/LICENSE b/bin/terminal/LICENSE new file mode 100644 index 0000000..e61610a --- /dev/null +++ b/bin/terminal/LICENSE @@ -0,0 +1,31 @@ +MIT/X Consortium License + +© 2009-2012 Aurélien APTEL +© 2009 Anselm R Garbe +© 2012-2015 Roberto E. Vargas Caballero +© 2012-2015 Christoph Lohmann <20h at r-36 dot net> +© 2013 Eon S. Jeon +© 2013 Alexander Sedov +© 2013 Mark Edgar +© 2013 Eric Pruitt +© 2013 Michael Forney +© 2013-2014 Markus Teich +© 2014-2015 Laslo Hunhold + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/bin/terminal/arg.h b/bin/terminal/arg.h new file mode 100644 index 0000000..ba3fb3f --- /dev/null +++ b/bin/terminal/arg.h @@ -0,0 +1,48 @@ +/* + * Copy me if you can. + * by 20h + */ + +#ifndef ARG_H__ +#define ARG_H__ + +extern char *argv0; + +/* use main(int argc, char *argv[]) */ +#define ARGBEGIN for (argv0 = *argv, argv++, argc--;\ + argv[0] && argv[0][0] == '-'\ + && argv[0][1];\ + argc--, argv++) {\ + char argc_;\ + char **argv_;\ + int brk_;\ + if (argv[0][1] == '-' && argv[0][2] == '\0') {\ + argv++;\ + argc--;\ + break;\ + }\ + for (brk_ = 0, argv[0]++, argv_ = argv;\ + argv[0][0] && !brk_;\ + argv[0]++) {\ + if (argv_ != argv)\ + break;\ + argc_ = argv[0][0];\ + switch (argc_) +#define ARGEND }\ + } + +#define ARGC() argc_ + +#define EARGF(x) ((argv[0][1] == '\0' && argv[1] == NULL)?\ + ((x), abort(), (char *)0) :\ + (brk_ = 1, (argv[0][1] != '\0')?\ + (&argv[0][1]) :\ + (argc--, argv++, argv[0]))) + +#define ARGF() ((argv[0][1] == '\0' && argv[1] == NULL)?\ + (char *)0 :\ + (brk_ = 1, (argv[0][1] != '\0')?\ + (&argv[0][1]) :\ + (argc--, argv++, argv[0]))) + +#endif diff --git a/bin/terminal/config.def.h b/bin/terminal/config.def.h new file mode 100644 index 0000000..2da445e --- /dev/null +++ b/bin/terminal/config.def.h @@ -0,0 +1,460 @@ +/* See LICENSE file for copyright and license details. */ + +/* + * appearance + * + * font: see http://freedesktop.org/software/fontconfig/fontconfig-user.html + */ +static char font[] = "Liberation Mono:pixelsize=12:antialias=true:autohint=true"; +static int borderpx = 2; +#define histsize 2000 + +/* + * What program is execed by st depends of these precedence rules: + * 1: program passed with -e + * 2: utmp option + * 3: SHELL environment variable + * 4: value of shell in /etc/passwd + * 5: value of shell in config.h + */ +static char shell[] = "/bin/sh"; +static char *utmp = NULL; +static char stty_args[] = "stty raw pass8 nl -echo -iexten -cstopb 38400"; + +/* identification sequence returned in DA and DECID */ +static char vtiden[] = "\033[?6c"; + +/* Kerning / character bounding-box multipliers */ +static float cwscale = 1.0; +static float chscale = 1.0; + +/* + * word delimiter string + * + * More advanced example: " `'\"()[]{}" + */ +static char worddelimiters[] = " "; + +/* selection timeouts (in milliseconds) */ +static unsigned int doubleclicktimeout = 300; +static unsigned int tripleclicktimeout = 600; + +/* alt screens */ +static int allowaltscreen = 1; + +/* frames per second st should at maximum draw to the screen */ +static unsigned int xfps = 120; +static unsigned int actionfps = 30; + +/* + * blinking timeout (set to 0 to disable blinking) for the terminal blinking + * attribute. + */ +static unsigned int blinktimeout = 800; + +/* + * thickness of underline and bar cursors + */ +static unsigned int cursorthickness = 2; + +/* + * bell volume. It must be a value between -100 and 100. Use 0 for disabling + * it + */ +static int bellvolume = 0; + +/* default TERM value */ +static char termname[] = "st-256color"; + +/* + * spaces per tab + * + * When you are changing this value, don't forget to adapt the »it« value in + * the st.info and appropriately install the st.info in the environment where + * you use this st version. + * + * it#$tabspaces, + * + * Secondly make sure your kernel is not expanding tabs. When running `stty + * -a` »tab0« should appear. You can tell the terminal to not expand tabs by + * running following command: + * + * stty tabs + */ +static unsigned int tabspaces = 8; + +/* Terminal colors (16 first used in escape sequence) */ +static const char *colorname[] = { + /* solarized dark */ + "#073642", /* 0: black */ + "#dc322f", /* 1: red */ + "#859900", /* 2: green */ + "#b58900", /* 3: yellow */ + "#268bd2", /* 4: blue */ + "#d33682", /* 5: magenta */ + "#2aa198", /* 6: cyan */ + "#eee8d5", /* 7: white */ + "#002b36", /* 8: brblack */ + "#cb4b16", /* 9: brred */ + "#586e75", /* 10: brgreen */ + "#657b83", /* 11: bryellow */ + "#839496", /* 12: brblue */ + "#6c71c4", /* 13: brmagenta*/ + "#93a1a1", /* 14: brcyan */ + "#fdf6e3", /* 15: brwhite */ +}; + +/* Terminal colors for alternate (light) palette */ +static const char *altcolorname[] = { + /* solarized light */ + "#eee8d5", /* 0: black */ + "#dc322f", /* 1: red */ + "#859900", /* 2: green */ + "#b58900", /* 3: yellow */ + "#268bd2", /* 4: blue */ + "#d33682", /* 5: magenta */ + "#2aa198", /* 6: cyan */ + "#073642", /* 7: white */ + "#fdf6e3", /* 8: brblack */ + "#cb4b16", /* 9: brred */ + "#93a1a1", /* 10: brgreen */ + "#839496", /* 11: bryellow */ + "#657b83", /* 12: brblue */ + "#6c71c4", /* 13: brmagenta*/ + "#586e75", /* 14: brcyan */ + "#002b36", /* 15: brwhite */ +}; + +/* + * Default colors (colorname index) + * foreground, background, cursor, reverse cursor + */ +static unsigned int defaultfg = 12; +static unsigned int defaultbg = 8; +static unsigned int defaultcs = 14; +static unsigned int defaultrcs = 15; + +/* + * Default shape of cursor + * 2: Block ("█") + * 4: Underline ("_") + * 6: Bar ("|") + * 7: Snowman ("☃") + */ +static unsigned int cursorshape = 2; + +/* + * Default colour and shape of the mouse cursor + */ +static unsigned int mouseshape = XC_xterm; +static unsigned int mousefg = 7; +static unsigned int mousebg = 0; + +/* + * Colors used, when the specific fg == defaultfg. So in reverse mode this + * will reverse too. Another logic would only make the simple feature too + * complex. + */ +static unsigned int defaultitalic = 11; +static unsigned int defaultunderline = 7; + +/* + * Internal mouse shortcuts. + * Beware that overloading Button1 will disable the selection. + */ +static MouseShortcut mshortcuts[] = { + /* button mask string */ + { Button4, XK_ANY_MOD, "\031" }, + { Button5, XK_ANY_MOD, "\005" }, +}; + +/* Internal keyboard shortcuts. */ +#define MODKEY Mod1Mask + +static Shortcut shortcuts[] = { + /* mask keysym function argument */ + { XK_ANY_MOD, XK_Break, sendbreak, {.i = 0} }, + { ControlMask, XK_Print, toggleprinter, {.i = 0} }, + { ShiftMask, XK_Print, printscreen, {.i = 0} }, + { XK_ANY_MOD, XK_Print, printsel, {.i = 0} }, + { MODKEY|ShiftMask, XK_Prior, xzoom, {.f = +1} }, + { MODKEY|ShiftMask, XK_Next, xzoom, {.f = -1} }, + { MODKEY|ShiftMask, XK_Home, xzoomreset, {.f = 0} }, + { ShiftMask, XK_Insert, selpaste, {.i = 0} }, + { MODKEY|ShiftMask, XK_Insert, clippaste, {.i = 0} }, + { MODKEY|ShiftMask, XK_C, clipcopy, {.i = 0} }, + { MODKEY|ShiftMask, XK_V, clippaste, {.i = 0} }, + { MODKEY, XK_Num_Lock, numlock, {.i = 0} }, + { ShiftMask, XK_Page_Up, kscrollup, {.i = -1} }, + { ShiftMask, XK_Page_Down, kscrolldown, {.i = -1} }, + { XK_ANY_MOD, XK_F6, swapcolors, {.i = 0} }, +}; + +/* + * Special keys (change & recompile st.info accordingly) + * + * Mask value: + * * Use XK_ANY_MOD to match the key no matter modifiers state + * * Use XK_NO_MOD to match the key alone (no modifiers) + * appkey value: + * * 0: no value + * * > 0: keypad application mode enabled + * * = 2: term.numlock = 1 + * * < 0: keypad application mode disabled + * appcursor value: + * * 0: no value + * * > 0: cursor application mode enabled + * * < 0: cursor application mode disabled + * crlf value + * * 0: no value + * * > 0: crlf mode is enabled + * * < 0: crlf mode is disabled + * + * Be careful with the order of the definitions because st searches in + * this table sequentially, so any XK_ANY_MOD must be in the last + * position for a key. + */ + +/* + * If you want keys other than the X11 function keys (0xFD00 - 0xFFFF) + * to be mapped below, add them to this array. + */ +static KeySym mappedkeys[] = { -1 }; + +/* + * State bits to ignore when matching key or button events. By default, + * numlock (Mod2Mask) and keyboard layout (XK_SWITCH_MOD) are ignored. + */ +static uint ignoremod = Mod2Mask|XK_SWITCH_MOD; + +/* + * Override mouse-select while mask is active (when MODE_MOUSE is set). + * Note that if you want to use ShiftMask with selmasks, set this to an other + * modifier, set to 0 to not use it. + */ +static uint forceselmod = ShiftMask; + +/* + * This is the huge key array which defines all compatibility to the Linux + * world. Please decide about changes wisely. + */ +static Key key[] = { + /* keysym mask string appkey appcursor crlf */ + { XK_KP_Home, ShiftMask, "\033[2J", 0, -1, 0}, + { XK_KP_Home, ShiftMask, "\033[1;2H", 0, +1, 0}, + { XK_KP_Home, XK_ANY_MOD, "\033[H", 0, -1, 0}, + { XK_KP_Home, XK_ANY_MOD, "\033[1~", 0, +1, 0}, + { XK_KP_Up, XK_ANY_MOD, "\033Ox", +1, 0, 0}, + { XK_KP_Up, XK_ANY_MOD, "\033[A", 0, -1, 0}, + { XK_KP_Up, XK_ANY_MOD, "\033OA", 0, +1, 0}, + { XK_KP_Down, XK_ANY_MOD, "\033Or", +1, 0, 0}, + { XK_KP_Down, XK_ANY_MOD, "\033[B", 0, -1, 0}, + { XK_KP_Down, XK_ANY_MOD, "\033OB", 0, +1, 0}, + { XK_KP_Left, XK_ANY_MOD, "\033Ot", +1, 0, 0}, + { XK_KP_Left, XK_ANY_MOD, "\033[D", 0, -1, 0}, + { XK_KP_Left, XK_ANY_MOD, "\033OD", 0, +1, 0}, + { XK_KP_Right, XK_ANY_MOD, "\033Ov", +1, 0, 0}, + { XK_KP_Right, XK_ANY_MOD, "\033[C", 0, -1, 0}, + { XK_KP_Right, XK_ANY_MOD, "\033OC", 0, +1, 0}, + { XK_KP_Prior, ShiftMask, "\033[5;2~", 0, 0, 0}, + { XK_KP_Prior, XK_ANY_MOD, "\033[5~", 0, 0, 0}, + { XK_KP_Begin, XK_ANY_MOD, "\033[E", 0, 0, 0}, + { XK_KP_End, ControlMask, "\033[J", -1, 0, 0}, + { XK_KP_End, ControlMask, "\033[1;5F", +1, 0, 0}, + { XK_KP_End, ShiftMask, "\033[K", -1, 0, 0}, + { XK_KP_End, ShiftMask, "\033[1;2F", +1, 0, 0}, + { XK_KP_End, XK_ANY_MOD, "\033[4~", 0, 0, 0}, + { XK_KP_Next, ShiftMask, "\033[6;2~", 0, 0, 0}, + { XK_KP_Next, XK_ANY_MOD, "\033[6~", 0, 0, 0}, + { XK_KP_Insert, ShiftMask, "\033[2;2~", +1, 0, 0}, + { XK_KP_Insert, ShiftMask, "\033[4l", -1, 0, 0}, + { XK_KP_Insert, ControlMask, "\033[L", -1, 0, 0}, + { XK_KP_Insert, ControlMask, "\033[2;5~", +1, 0, 0}, + { XK_KP_Insert, XK_ANY_MOD, "\033[4h", -1, 0, 0}, + { XK_KP_Insert, XK_ANY_MOD, "\033[2~", +1, 0, 0}, + { XK_KP_Delete, ControlMask, "\033[M", -1, 0, 0}, + { XK_KP_Delete, ControlMask, "\033[3;5~", +1, 0, 0}, + { XK_KP_Delete, ShiftMask, "\033[2K", -1, 0, 0}, + { XK_KP_Delete, ShiftMask, "\033[3;2~", +1, 0, 0}, + { XK_KP_Delete, XK_ANY_MOD, "\033[P", -1, 0, 0}, + { XK_KP_Delete, XK_ANY_MOD, "\033[3~", +1, 0, 0}, + { XK_KP_Multiply, XK_ANY_MOD, "\033Oj", +2, 0, 0}, + { XK_KP_Add, XK_ANY_MOD, "\033Ok", +2, 0, 0}, + { XK_KP_Enter, XK_ANY_MOD, "\033OM", +2, 0, 0}, + { XK_KP_Enter, XK_ANY_MOD, "\r", -1, 0, -1}, + { XK_KP_Enter, XK_ANY_MOD, "\r\n", -1, 0, +1}, + { XK_KP_Subtract, XK_ANY_MOD, "\033Om", +2, 0, 0}, + { XK_KP_Decimal, XK_ANY_MOD, "\033On", +2, 0, 0}, + { XK_KP_Divide, XK_ANY_MOD, "\033Oo", +2, 0, 0}, + { XK_KP_0, XK_ANY_MOD, "\033Op", +2, 0, 0}, + { XK_KP_1, XK_ANY_MOD, "\033Oq", +2, 0, 0}, + { XK_KP_2, XK_ANY_MOD, "\033Or", +2, 0, 0}, + { XK_KP_3, XK_ANY_MOD, "\033Os", +2, 0, 0}, + { XK_KP_4, XK_ANY_MOD, "\033Ot", +2, 0, 0}, + { XK_KP_5, XK_ANY_MOD, "\033Ou", +2, 0, 0}, + { XK_KP_6, XK_ANY_MOD, "\033Ov", +2, 0, 0}, + { XK_KP_7, XK_ANY_MOD, "\033Ow", +2, 0, 0}, + { XK_KP_8, XK_ANY_MOD, "\033Ox", +2, 0, 0}, + { XK_KP_9, XK_ANY_MOD, "\033Oy", +2, 0, 0}, + { XK_Up, ShiftMask, "\033[1;2A", 0, 0, 0}, + { XK_Up, ControlMask, "\033[1;5A", 0, 0, 0}, + { XK_Up, Mod1Mask, "\033[1;3A", 0, 0, 0}, + { XK_Up, XK_ANY_MOD, "\033[A", 0, -1, 0}, + { XK_Up, XK_ANY_MOD, "\033OA", 0, +1, 0}, + { XK_Down, ShiftMask, "\033[1;2B", 0, 0, 0}, + { XK_Down, ControlMask, "\033[1;5B", 0, 0, 0}, + { XK_Down, Mod1Mask, "\033[1;3B", 0, 0, 0}, + { XK_Down, XK_ANY_MOD, "\033[B", 0, -1, 0}, + { XK_Down, XK_ANY_MOD, "\033OB", 0, +1, 0}, + { XK_Left, ShiftMask, "\033[1;2D", 0, 0, 0}, + { XK_Left, ControlMask, "\033[1;5D", 0, 0, 0}, + { XK_Left, Mod1Mask, "\033[1;3D", 0, 0, 0}, + { XK_Left, XK_ANY_MOD, "\033[D", 0, -1, 0}, + { XK_Left, XK_ANY_MOD, "\033OD", 0, +1, 0}, + { XK_Right, ShiftMask, "\033[1;2C", 0, 0, 0}, + { XK_Right, ControlMask, "\033[1;5C", 0, 0, 0}, + { XK_Right, Mod1Mask, "\033[1;3C", 0, 0, 0}, + { XK_Right, XK_ANY_MOD, "\033[C", 0, -1, 0}, + { XK_Right, XK_ANY_MOD, "\033OC", 0, +1, 0}, + { XK_ISO_Left_Tab, ShiftMask, "\033[Z", 0, 0, 0}, + { XK_Return, Mod1Mask, "\033\r", 0, 0, -1}, + { XK_Return, Mod1Mask, "\033\r\n", 0, 0, +1}, + { XK_Return, XK_ANY_MOD, "\r", 0, 0, -1}, + { XK_Return, XK_ANY_MOD, "\r\n", 0, 0, +1}, + { XK_Insert, ShiftMask, "\033[4l", -1, 0, 0}, + { XK_Insert, ShiftMask, "\033[2;2~", +1, 0, 0}, + { XK_Insert, ControlMask, "\033[L", -1, 0, 0}, + { XK_Insert, ControlMask, "\033[2;5~", +1, 0, 0}, + { XK_Insert, XK_ANY_MOD, "\033[4h", -1, 0, 0}, + { XK_Insert, XK_ANY_MOD, "\033[2~", +1, 0, 0}, + { XK_Delete, ControlMask, "\033[M", -1, 0, 0}, + { XK_Delete, ControlMask, "\033[3;5~", +1, 0, 0}, + { XK_Delete, ShiftMask, "\033[2K", -1, 0, 0}, + { XK_Delete, ShiftMask, "\033[3;2~", +1, 0, 0}, + { XK_Delete, XK_ANY_MOD, "\033[P", -1, 0, 0}, + { XK_Delete, XK_ANY_MOD, "\033[3~", +1, 0, 0}, + { XK_BackSpace, XK_NO_MOD, "\177", 0, 0, 0}, + { XK_BackSpace, Mod1Mask, "\033\177", 0, 0, 0}, + { XK_Home, ShiftMask, "\033[2J", 0, -1, 0}, + { XK_Home, ShiftMask, "\033[1;2H", 0, +1, 0}, + { XK_Home, XK_ANY_MOD, "\033[H", 0, -1, 0}, + { XK_Home, XK_ANY_MOD, "\033[1~", 0, +1, 0}, + { XK_End, ControlMask, "\033[J", -1, 0, 0}, + { XK_End, ControlMask, "\033[1;5F", +1, 0, 0}, + { XK_End, ShiftMask, "\033[K", -1, 0, 0}, + { XK_End, ShiftMask, "\033[1;2F", +1, 0, 0}, + { XK_End, XK_ANY_MOD, "\033[4~", 0, 0, 0}, + { XK_Prior, ControlMask, "\033[5;5~", 0, 0, 0}, + { XK_Prior, ShiftMask, "\033[5;2~", 0, 0, 0}, + { XK_Prior, XK_ANY_MOD, "\033[5~", 0, 0, 0}, + { XK_Next, ControlMask, "\033[6;5~", 0, 0, 0}, + { XK_Next, ShiftMask, "\033[6;2~", 0, 0, 0}, + { XK_Next, XK_ANY_MOD, "\033[6~", 0, 0, 0}, + { XK_F1, XK_NO_MOD, "\033OP" , 0, 0, 0}, + { XK_F1, /* F13 */ ShiftMask, "\033[1;2P", 0, 0, 0}, + { XK_F1, /* F25 */ ControlMask, "\033[1;5P", 0, 0, 0}, + { XK_F1, /* F37 */ Mod4Mask, "\033[1;6P", 0, 0, 0}, + { XK_F1, /* F49 */ Mod1Mask, "\033[1;3P", 0, 0, 0}, + { XK_F1, /* F61 */ Mod3Mask, "\033[1;4P", 0, 0, 0}, + { XK_F2, XK_NO_MOD, "\033OQ" , 0, 0, 0}, + { XK_F2, /* F14 */ ShiftMask, "\033[1;2Q", 0, 0, 0}, + { XK_F2, /* F26 */ ControlMask, "\033[1;5Q", 0, 0, 0}, + { XK_F2, /* F38 */ Mod4Mask, "\033[1;6Q", 0, 0, 0}, + { XK_F2, /* F50 */ Mod1Mask, "\033[1;3Q", 0, 0, 0}, + { XK_F2, /* F62 */ Mod3Mask, "\033[1;4Q", 0, 0, 0}, + { XK_F3, XK_NO_MOD, "\033OR" , 0, 0, 0}, + { XK_F3, /* F15 */ ShiftMask, "\033[1;2R", 0, 0, 0}, + { XK_F3, /* F27 */ ControlMask, "\033[1;5R", 0, 0, 0}, + { XK_F3, /* F39 */ Mod4Mask, "\033[1;6R", 0, 0, 0}, + { XK_F3, /* F51 */ Mod1Mask, "\033[1;3R", 0, 0, 0}, + { XK_F3, /* F63 */ Mod3Mask, "\033[1;4R", 0, 0, 0}, + { XK_F4, XK_NO_MOD, "\033OS" , 0, 0, 0}, + { XK_F4, /* F16 */ ShiftMask, "\033[1;2S", 0, 0, 0}, + { XK_F4, /* F28 */ ControlMask, "\033[1;5S", 0, 0, 0}, + { XK_F4, /* F40 */ Mod4Mask, "\033[1;6S", 0, 0, 0}, + { XK_F4, /* F52 */ Mod1Mask, "\033[1;3S", 0, 0, 0}, + { XK_F5, XK_NO_MOD, "\033[15~", 0, 0, 0}, + { XK_F5, /* F17 */ ShiftMask, "\033[15;2~", 0, 0, 0}, + { XK_F5, /* F29 */ ControlMask, "\033[15;5~", 0, 0, 0}, + { XK_F5, /* F41 */ Mod4Mask, "\033[15;6~", 0, 0, 0}, + { XK_F5, /* F53 */ Mod1Mask, "\033[15;3~", 0, 0, 0}, + { XK_F6, XK_NO_MOD, "\033[17~", 0, 0, 0}, + { XK_F6, /* F18 */ ShiftMask, "\033[17;2~", 0, 0, 0}, + { XK_F6, /* F30 */ ControlMask, "\033[17;5~", 0, 0, 0}, + { XK_F6, /* F42 */ Mod4Mask, "\033[17;6~", 0, 0, 0}, + { XK_F6, /* F54 */ Mod1Mask, "\033[17;3~", 0, 0, 0}, + { XK_F7, XK_NO_MOD, "\033[18~", 0, 0, 0}, + { XK_F7, /* F19 */ ShiftMask, "\033[18;2~", 0, 0, 0}, + { XK_F7, /* F31 */ ControlMask, "\033[18;5~", 0, 0, 0}, + { XK_F7, /* F43 */ Mod4Mask, "\033[18;6~", 0, 0, 0}, + { XK_F7, /* F55 */ Mod1Mask, "\033[18;3~", 0, 0, 0}, + { XK_F8, XK_NO_MOD, "\033[19~", 0, 0, 0}, + { XK_F8, /* F20 */ ShiftMask, "\033[19;2~", 0, 0, 0}, + { XK_F8, /* F32 */ ControlMask, "\033[19;5~", 0, 0, 0}, + { XK_F8, /* F44 */ Mod4Mask, "\033[19;6~", 0, 0, 0}, + { XK_F8, /* F56 */ Mod1Mask, "\033[19;3~", 0, 0, 0}, + { XK_F9, XK_NO_MOD, "\033[20~", 0, 0, 0}, + { XK_F9, /* F21 */ ShiftMask, "\033[20;2~", 0, 0, 0}, + { XK_F9, /* F33 */ ControlMask, "\033[20;5~", 0, 0, 0}, + { XK_F9, /* F45 */ Mod4Mask, "\033[20;6~", 0, 0, 0}, + { XK_F9, /* F57 */ Mod1Mask, "\033[20;3~", 0, 0, 0}, + { XK_F10, XK_NO_MOD, "\033[21~", 0, 0, 0}, + { XK_F10, /* F22 */ ShiftMask, "\033[21;2~", 0, 0, 0}, + { XK_F10, /* F34 */ ControlMask, "\033[21;5~", 0, 0, 0}, + { XK_F10, /* F46 */ Mod4Mask, "\033[21;6~", 0, 0, 0}, + { XK_F10, /* F58 */ Mod1Mask, "\033[21;3~", 0, 0, 0}, + { XK_F11, XK_NO_MOD, "\033[23~", 0, 0, 0}, + { XK_F11, /* F23 */ ShiftMask, "\033[23;2~", 0, 0, 0}, + { XK_F11, /* F35 */ ControlMask, "\033[23;5~", 0, 0, 0}, + { XK_F11, /* F47 */ Mod4Mask, "\033[23;6~", 0, 0, 0}, + { XK_F11, /* F59 */ Mod1Mask, "\033[23;3~", 0, 0, 0}, + { XK_F12, XK_NO_MOD, "\033[24~", 0, 0, 0}, + { XK_F12, /* F24 */ ShiftMask, "\033[24;2~", 0, 0, 0}, + { XK_F12, /* F36 */ ControlMask, "\033[24;5~", 0, 0, 0}, + { XK_F12, /* F48 */ Mod4Mask, "\033[24;6~", 0, 0, 0}, + { XK_F12, /* F60 */ Mod1Mask, "\033[24;3~", 0, 0, 0}, + { XK_F13, XK_NO_MOD, "\033[1;2P", 0, 0, 0}, + { XK_F14, XK_NO_MOD, "\033[1;2Q", 0, 0, 0}, + { XK_F15, XK_NO_MOD, "\033[1;2R", 0, 0, 0}, + { XK_F16, XK_NO_MOD, "\033[1;2S", 0, 0, 0}, + { XK_F17, XK_NO_MOD, "\033[15;2~", 0, 0, 0}, + { XK_F18, XK_NO_MOD, "\033[17;2~", 0, 0, 0}, + { XK_F19, XK_NO_MOD, "\033[18;2~", 0, 0, 0}, + { XK_F20, XK_NO_MOD, "\033[19;2~", 0, 0, 0}, + { XK_F21, XK_NO_MOD, "\033[20;2~", 0, 0, 0}, + { XK_F22, XK_NO_MOD, "\033[21;2~", 0, 0, 0}, + { XK_F23, XK_NO_MOD, "\033[23;2~", 0, 0, 0}, + { XK_F24, XK_NO_MOD, "\033[24;2~", 0, 0, 0}, + { XK_F25, XK_NO_MOD, "\033[1;5P", 0, 0, 0}, + { XK_F26, XK_NO_MOD, "\033[1;5Q", 0, 0, 0}, + { XK_F27, XK_NO_MOD, "\033[1;5R", 0, 0, 0}, + { XK_F28, XK_NO_MOD, "\033[1;5S", 0, 0, 0}, + { XK_F29, XK_NO_MOD, "\033[15;5~", 0, 0, 0}, + { XK_F30, XK_NO_MOD, "\033[17;5~", 0, 0, 0}, + { XK_F31, XK_NO_MOD, "\033[18;5~", 0, 0, 0}, + { XK_F32, XK_NO_MOD, "\033[19;5~", 0, 0, 0}, + { XK_F33, XK_NO_MOD, "\033[20;5~", 0, 0, 0}, + { XK_F34, XK_NO_MOD, "\033[21;5~", 0, 0, 0}, + { XK_F35, XK_NO_MOD, "\033[23;5~", 0, 0, 0}, +}; + +/* + * Selection types' masks. + * Use the same masks as usual. + * Button1Mask is always unset, to make masks match between ButtonPress. + * ButtonRelease and MotionNotify. + * If no match is found, regular selection is used. + */ +static uint selmasks[] = { + [SEL_RECTANGULAR] = Mod1Mask, +}; + +/* + * Printable characters in ASCII, used to estimate the advance width + * of single wide characters. + */ +static char ascii_printable[] = + " !\"#$%&'()*+,-./0123456789:;<=>?" + "@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_" + "`abcdefghijklmnopqrstuvwxyz{|}~"; + diff --git a/bin/terminal/config.h b/bin/terminal/config.h new file mode 100644 index 0000000..894bd30 --- /dev/null +++ b/bin/terminal/config.h @@ -0,0 +1,464 @@ +/* See LICENSE file for copyright and license details. */ + +/* + * appearance + * + * font: see http://freedesktop.org/software/fontconfig/fontconfig-user.html + */ +#ifdef __MACH__ +static char font[] = "Monaco:size=11:antialias=true:autohint=true"; +#else +static char font[] = "Liberation Mono:pixelsize=14:antialias=true:autohint=true"; +#endif +static int borderpx = 2; +#define histsize 5000 + +/* + * What program is execed by st depends of these precedence rules: + * 1: program passed with -e + * 2: utmp option + * 3: SHELL environment variable + * 4: value of shell in /etc/passwd + * 5: value of shell in config.h + */ +static char shell[] = "/bin/sh"; +static char *utmp = NULL; +static char stty_args[] = "stty raw pass8 nl -echo -iexten -cstopb 38400"; + +/* identification sequence returned in DA and DECID */ +static char vtiden[] = "\033[?6c"; + +/* Kerning / character bounding-box multipliers */ +static float cwscale = 1.0; +static float chscale = 1.0; + +/* + * word delimiter string + * + * More advanced example: " `'\"()[]{}" + */ +static char worddelimiters[] = " "; + +/* selection timeouts (in milliseconds) */ +static unsigned int doubleclicktimeout = 300; +static unsigned int tripleclicktimeout = 600; + +/* alt screens */ +static int allowaltscreen = 1; + +/* frames per second st should at maximum draw to the screen */ +static unsigned int xfps = 120; +static unsigned int actionfps = 30; + +/* + * blinking timeout (set to 0 to disable blinking) for the terminal blinking + * attribute. + */ +static unsigned int blinktimeout = 800; + +/* + * thickness of underline and bar cursors + */ +static unsigned int cursorthickness = 2; + +/* + * bell volume. It must be a value between -100 and 100. Use 0 for disabling + * it + */ +static int bellvolume = 0; + +/* default TERM value */ +static char termname[] = "st-256color"; + +/* + * spaces per tab + * + * When you are changing this value, don't forget to adapt the »it« value in + * the st.info and appropriately install the st.info in the environment where + * you use this st version. + * + * it#$tabspaces, + * + * Secondly make sure your kernel is not expanding tabs. When running `stty + * -a` »tab0« should appear. You can tell the terminal to not expand tabs by + * running following command: + * + * stty tabs + */ +static unsigned int tabspaces = 8; + +/* Terminal colors (16 first used in escape sequence) */ +static const char *colorname[] = { + /* gruvbox dark hard */ + "#1d2021", + "#cc241d", + "#98971a", + "#fb4934", + "#d79921", + "#458588", + "#b16286", + "#689d6a", + "#d5c4a1", + "#928374", + "#b8bb26", + "#fabd2f", + "#83a598", + "#d3869b", + "#8ec07c", + "#ebdbb2", +}; + + +/* Terminal colors for alternate (light) palette */ +static const char *altcolorname[] = { + /* gruvbox dark hard */ + "#1d2021", + "#cc241d", + "#98971a", + "#fb4934", + "#d79921", + "#458588", + "#b16286", + "#689d6a", + "#a89984", + "#928374", + "#b8bb26", + "#fabd2f", + "#83a598", + "#d3869b", + "#8ec07c", + "#ebdbb2", +}; + +/* + * Default colors (colorname index) + * foreground, background, cursor, reverse cursor + */ +static unsigned int defaultfg = 8; +static unsigned int defaultbg = 0; +static unsigned int defaultcs = 8; +static unsigned int defaultrcs = 0; + +/* + * Default shape of cursor + * 2: Block ("█") + * 4: Underline ("_") + * 6: Bar ("|") + * 7: Snowman ("☃") + */ +static unsigned int cursorshape = 2; + +/* + * Default colour and shape of the mouse cursor + */ +static unsigned int mouseshape = XC_xterm; +static unsigned int mousefg = 7; +static unsigned int mousebg = 0; + +/* + * Colors used, when the specific fg == defaultfg. So in reverse mode this + * will reverse too. Another logic would only make the simple feature too + * complex. + */ +static unsigned int defaultitalic = 11; +static unsigned int defaultunderline = 7; + +/* + * Internal mouse shortcuts. + * Beware that overloading Button1 will disable the selection. + */ +static MouseShortcut mshortcuts[] = { + /* button mask string */ + { Button4, XK_ANY_MOD, "\031" }, + { Button5, XK_ANY_MOD, "\005" }, +}; + +/* Internal keyboard shortcuts. */ +#define MODKEY Mod1Mask + +static Shortcut shortcuts[] = { + /* mask keysym function argument */ + { XK_ANY_MOD, XK_Break, sendbreak, {.i = 0} }, + { ControlMask, XK_Print, toggleprinter, {.i = 0} }, + { ShiftMask, XK_Print, printscreen, {.i = 0} }, + { XK_ANY_MOD, XK_Print, printsel, {.i = 0} }, + { MODKEY|ShiftMask, XK_Prior, xzoom, {.f = +1} }, + { MODKEY|ShiftMask, XK_Next, xzoom, {.f = -1} }, + { MODKEY|ShiftMask, XK_Home, xzoomreset, {.f = 0} }, + { ShiftMask, XK_Insert, selpaste, {.i = 0} }, + { MODKEY|ShiftMask, XK_Insert, clippaste, {.i = 0} }, + { MODKEY|ShiftMask, XK_C, clipcopy, {.i = 0} }, + { MODKEY|ShiftMask, XK_V, clippaste, {.i = 0} }, + { MODKEY, XK_Num_Lock, numlock, {.i = 0} }, + { ShiftMask, XK_Page_Up, kscrollup, {.i = -1} }, + { ShiftMask, XK_Page_Down, kscrolldown, {.i = -1} }, + { XK_ANY_MOD, XK_F6, swapcolors, {.i = 0} }, +}; + +/* + * Special keys (change & recompile st.info accordingly) + * + * Mask value: + * * Use XK_ANY_MOD to match the key no matter modifiers state + * * Use XK_NO_MOD to match the key alone (no modifiers) + * appkey value: + * * 0: no value + * * > 0: keypad application mode enabled + * * = 2: term.numlock = 1 + * * < 0: keypad application mode disabled + * appcursor value: + * * 0: no value + * * > 0: cursor application mode enabled + * * < 0: cursor application mode disabled + * crlf value + * * 0: no value + * * > 0: crlf mode is enabled + * * < 0: crlf mode is disabled + * + * Be careful with the order of the definitions because st searches in + * this table sequentially, so any XK_ANY_MOD must be in the last + * position for a key. + */ + +/* + * If you want keys other than the X11 function keys (0xFD00 - 0xFFFF) + * to be mapped below, add them to this array. + */ +static KeySym mappedkeys[] = { -1 }; + +/* + * State bits to ignore when matching key or button events. By default, + * numlock (Mod2Mask) and keyboard layout (XK_SWITCH_MOD) are ignored. + */ +static uint ignoremod = Mod2Mask|XK_SWITCH_MOD; + +/* + * Override mouse-select while mask is active (when MODE_MOUSE is set). + * Note that if you want to use ShiftMask with selmasks, set this to an other + * modifier, set to 0 to not use it. + */ +static uint forceselmod = ShiftMask; + +/* + * This is the huge key array which defines all compatibility to the Linux + * world. Please decide about changes wisely. + */ +static Key key[] = { + /* keysym mask string appkey appcursor crlf */ + { XK_KP_Home, ShiftMask, "\033[2J", 0, -1, 0}, + { XK_KP_Home, ShiftMask, "\033[1;2H", 0, +1, 0}, + { XK_KP_Home, XK_ANY_MOD, "\033[H", 0, -1, 0}, + { XK_KP_Home, XK_ANY_MOD, "\033[1~", 0, +1, 0}, + { XK_KP_Up, XK_ANY_MOD, "\033Ox", +1, 0, 0}, + { XK_KP_Up, XK_ANY_MOD, "\033[A", 0, -1, 0}, + { XK_KP_Up, XK_ANY_MOD, "\033OA", 0, +1, 0}, + { XK_KP_Down, XK_ANY_MOD, "\033Or", +1, 0, 0}, + { XK_KP_Down, XK_ANY_MOD, "\033[B", 0, -1, 0}, + { XK_KP_Down, XK_ANY_MOD, "\033OB", 0, +1, 0}, + { XK_KP_Left, XK_ANY_MOD, "\033Ot", +1, 0, 0}, + { XK_KP_Left, XK_ANY_MOD, "\033[D", 0, -1, 0}, + { XK_KP_Left, XK_ANY_MOD, "\033OD", 0, +1, 0}, + { XK_KP_Right, XK_ANY_MOD, "\033Ov", +1, 0, 0}, + { XK_KP_Right, XK_ANY_MOD, "\033[C", 0, -1, 0}, + { XK_KP_Right, XK_ANY_MOD, "\033OC", 0, +1, 0}, + { XK_KP_Prior, ShiftMask, "\033[5;2~", 0, 0, 0}, + { XK_KP_Prior, XK_ANY_MOD, "\033[5~", 0, 0, 0}, + { XK_KP_Begin, XK_ANY_MOD, "\033[E", 0, 0, 0}, + { XK_KP_End, ControlMask, "\033[J", -1, 0, 0}, + { XK_KP_End, ControlMask, "\033[1;5F", +1, 0, 0}, + { XK_KP_End, ShiftMask, "\033[K", -1, 0, 0}, + { XK_KP_End, ShiftMask, "\033[1;2F", +1, 0, 0}, + { XK_KP_End, XK_ANY_MOD, "\033[4~", 0, 0, 0}, + { XK_KP_Next, ShiftMask, "\033[6;2~", 0, 0, 0}, + { XK_KP_Next, XK_ANY_MOD, "\033[6~", 0, 0, 0}, + { XK_KP_Insert, ShiftMask, "\033[2;2~", +1, 0, 0}, + { XK_KP_Insert, ShiftMask, "\033[4l", -1, 0, 0}, + { XK_KP_Insert, ControlMask, "\033[L", -1, 0, 0}, + { XK_KP_Insert, ControlMask, "\033[2;5~", +1, 0, 0}, + { XK_KP_Insert, XK_ANY_MOD, "\033[4h", -1, 0, 0}, + { XK_KP_Insert, XK_ANY_MOD, "\033[2~", +1, 0, 0}, + { XK_KP_Delete, ControlMask, "\033[M", -1, 0, 0}, + { XK_KP_Delete, ControlMask, "\033[3;5~", +1, 0, 0}, + { XK_KP_Delete, ShiftMask, "\033[2K", -1, 0, 0}, + { XK_KP_Delete, ShiftMask, "\033[3;2~", +1, 0, 0}, + { XK_KP_Delete, XK_ANY_MOD, "\033[P", -1, 0, 0}, + { XK_KP_Delete, XK_ANY_MOD, "\033[3~", +1, 0, 0}, + { XK_KP_Multiply, XK_ANY_MOD, "\033Oj", +2, 0, 0}, + { XK_KP_Add, XK_ANY_MOD, "\033Ok", +2, 0, 0}, + { XK_KP_Enter, XK_ANY_MOD, "\033OM", +2, 0, 0}, + { XK_KP_Enter, XK_ANY_MOD, "\r", -1, 0, -1}, + { XK_KP_Enter, XK_ANY_MOD, "\r\n", -1, 0, +1}, + { XK_KP_Subtract, XK_ANY_MOD, "\033Om", +2, 0, 0}, + { XK_KP_Decimal, XK_ANY_MOD, "\033On", +2, 0, 0}, + { XK_KP_Divide, XK_ANY_MOD, "\033Oo", +2, 0, 0}, + { XK_KP_0, XK_ANY_MOD, "\033Op", +2, 0, 0}, + { XK_KP_1, XK_ANY_MOD, "\033Oq", +2, 0, 0}, + { XK_KP_2, XK_ANY_MOD, "\033Or", +2, 0, 0}, + { XK_KP_3, XK_ANY_MOD, "\033Os", +2, 0, 0}, + { XK_KP_4, XK_ANY_MOD, "\033Ot", +2, 0, 0}, + { XK_KP_5, XK_ANY_MOD, "\033Ou", +2, 0, 0}, + { XK_KP_6, XK_ANY_MOD, "\033Ov", +2, 0, 0}, + { XK_KP_7, XK_ANY_MOD, "\033Ow", +2, 0, 0}, + { XK_KP_8, XK_ANY_MOD, "\033Ox", +2, 0, 0}, + { XK_KP_9, XK_ANY_MOD, "\033Oy", +2, 0, 0}, + { XK_Up, ShiftMask, "\033[1;2A", 0, 0, 0}, + { XK_Up, ControlMask, "\033[1;5A", 0, 0, 0}, + { XK_Up, Mod1Mask, "\033[1;3A", 0, 0, 0}, + { XK_Up, XK_ANY_MOD, "\033[A", 0, -1, 0}, + { XK_Up, XK_ANY_MOD, "\033OA", 0, +1, 0}, + { XK_Down, ShiftMask, "\033[1;2B", 0, 0, 0}, + { XK_Down, ControlMask, "\033[1;5B", 0, 0, 0}, + { XK_Down, Mod1Mask, "\033[1;3B", 0, 0, 0}, + { XK_Down, XK_ANY_MOD, "\033[B", 0, -1, 0}, + { XK_Down, XK_ANY_MOD, "\033OB", 0, +1, 0}, + { XK_Left, ShiftMask, "\033[1;2D", 0, 0, 0}, + { XK_Left, ControlMask, "\033[1;5D", 0, 0, 0}, + { XK_Left, Mod1Mask, "\033[1;3D", 0, 0, 0}, + { XK_Left, XK_ANY_MOD, "\033[D", 0, -1, 0}, + { XK_Left, XK_ANY_MOD, "\033OD", 0, +1, 0}, + { XK_Right, ShiftMask, "\033[1;2C", 0, 0, 0}, + { XK_Right, ControlMask, "\033[1;5C", 0, 0, 0}, + { XK_Right, Mod1Mask, "\033[1;3C", 0, 0, 0}, + { XK_Right, XK_ANY_MOD, "\033[C", 0, -1, 0}, + { XK_Right, XK_ANY_MOD, "\033OC", 0, +1, 0}, + { XK_ISO_Left_Tab, ShiftMask, "\033[Z", 0, 0, 0}, + { XK_Return, Mod1Mask, "\033\r", 0, 0, -1}, + { XK_Return, Mod1Mask, "\033\r\n", 0, 0, +1}, + { XK_Return, XK_ANY_MOD, "\r", 0, 0, -1}, + { XK_Return, XK_ANY_MOD, "\r\n", 0, 0, +1}, + { XK_Insert, ShiftMask, "\033[4l", -1, 0, 0}, + { XK_Insert, ShiftMask, "\033[2;2~", +1, 0, 0}, + { XK_Insert, ControlMask, "\033[L", -1, 0, 0}, + { XK_Insert, ControlMask, "\033[2;5~", +1, 0, 0}, + { XK_Insert, XK_ANY_MOD, "\033[4h", -1, 0, 0}, + { XK_Insert, XK_ANY_MOD, "\033[2~", +1, 0, 0}, + { XK_Delete, ControlMask, "\033[M", -1, 0, 0}, + { XK_Delete, ControlMask, "\033[3;5~", +1, 0, 0}, + { XK_Delete, ShiftMask, "\033[2K", -1, 0, 0}, + { XK_Delete, ShiftMask, "\033[3;2~", +1, 0, 0}, + { XK_Delete, XK_ANY_MOD, "\033[P", -1, 0, 0}, + { XK_Delete, XK_ANY_MOD, "\033[3~", +1, 0, 0}, + { XK_BackSpace, XK_NO_MOD, "\177", 0, 0, 0}, + { XK_BackSpace, Mod1Mask, "\033\177", 0, 0, 0}, + { XK_Home, ShiftMask, "\033[2J", 0, -1, 0}, + { XK_Home, ShiftMask, "\033[1;2H", 0, +1, 0}, + { XK_Home, XK_ANY_MOD, "\033[H", 0, -1, 0}, + { XK_Home, XK_ANY_MOD, "\033[1~", 0, +1, 0}, + { XK_End, ControlMask, "\033[J", -1, 0, 0}, + { XK_End, ControlMask, "\033[1;5F", +1, 0, 0}, + { XK_End, ShiftMask, "\033[K", -1, 0, 0}, + { XK_End, ShiftMask, "\033[1;2F", +1, 0, 0}, + { XK_End, XK_ANY_MOD, "\033[4~", 0, 0, 0}, + { XK_Prior, ControlMask, "\033[5;5~", 0, 0, 0}, + { XK_Prior, ShiftMask, "\033[5;2~", 0, 0, 0}, + { XK_Prior, XK_ANY_MOD, "\033[5~", 0, 0, 0}, + { XK_Next, ControlMask, "\033[6;5~", 0, 0, 0}, + { XK_Next, ShiftMask, "\033[6;2~", 0, 0, 0}, + { XK_Next, XK_ANY_MOD, "\033[6~", 0, 0, 0}, + { XK_F1, XK_NO_MOD, "\033OP" , 0, 0, 0}, + { XK_F1, /* F13 */ ShiftMask, "\033[1;2P", 0, 0, 0}, + { XK_F1, /* F25 */ ControlMask, "\033[1;5P", 0, 0, 0}, + { XK_F1, /* F37 */ Mod4Mask, "\033[1;6P", 0, 0, 0}, + { XK_F1, /* F49 */ Mod1Mask, "\033[1;3P", 0, 0, 0}, + { XK_F1, /* F61 */ Mod3Mask, "\033[1;4P", 0, 0, 0}, + { XK_F2, XK_NO_MOD, "\033OQ" , 0, 0, 0}, + { XK_F2, /* F14 */ ShiftMask, "\033[1;2Q", 0, 0, 0}, + { XK_F2, /* F26 */ ControlMask, "\033[1;5Q", 0, 0, 0}, + { XK_F2, /* F38 */ Mod4Mask, "\033[1;6Q", 0, 0, 0}, + { XK_F2, /* F50 */ Mod1Mask, "\033[1;3Q", 0, 0, 0}, + { XK_F2, /* F62 */ Mod3Mask, "\033[1;4Q", 0, 0, 0}, + { XK_F3, XK_NO_MOD, "\033OR" , 0, 0, 0}, + { XK_F3, /* F15 */ ShiftMask, "\033[1;2R", 0, 0, 0}, + { XK_F3, /* F27 */ ControlMask, "\033[1;5R", 0, 0, 0}, + { XK_F3, /* F39 */ Mod4Mask, "\033[1;6R", 0, 0, 0}, + { XK_F3, /* F51 */ Mod1Mask, "\033[1;3R", 0, 0, 0}, + { XK_F3, /* F63 */ Mod3Mask, "\033[1;4R", 0, 0, 0}, + { XK_F4, XK_NO_MOD, "\033OS" , 0, 0, 0}, + { XK_F4, /* F16 */ ShiftMask, "\033[1;2S", 0, 0, 0}, + { XK_F4, /* F28 */ ControlMask, "\033[1;5S", 0, 0, 0}, + { XK_F4, /* F40 */ Mod4Mask, "\033[1;6S", 0, 0, 0}, + { XK_F4, /* F52 */ Mod1Mask, "\033[1;3S", 0, 0, 0}, + { XK_F5, XK_NO_MOD, "\033[15~", 0, 0, 0}, + { XK_F5, /* F17 */ ShiftMask, "\033[15;2~", 0, 0, 0}, + { XK_F5, /* F29 */ ControlMask, "\033[15;5~", 0, 0, 0}, + { XK_F5, /* F41 */ Mod4Mask, "\033[15;6~", 0, 0, 0}, + { XK_F5, /* F53 */ Mod1Mask, "\033[15;3~", 0, 0, 0}, + { XK_F6, XK_NO_MOD, "\033[17~", 0, 0, 0}, + { XK_F6, /* F18 */ ShiftMask, "\033[17;2~", 0, 0, 0}, + { XK_F6, /* F30 */ ControlMask, "\033[17;5~", 0, 0, 0}, + { XK_F6, /* F42 */ Mod4Mask, "\033[17;6~", 0, 0, 0}, + { XK_F6, /* F54 */ Mod1Mask, "\033[17;3~", 0, 0, 0}, + { XK_F7, XK_NO_MOD, "\033[18~", 0, 0, 0}, + { XK_F7, /* F19 */ ShiftMask, "\033[18;2~", 0, 0, 0}, + { XK_F7, /* F31 */ ControlMask, "\033[18;5~", 0, 0, 0}, + { XK_F7, /* F43 */ Mod4Mask, "\033[18;6~", 0, 0, 0}, + { XK_F7, /* F55 */ Mod1Mask, "\033[18;3~", 0, 0, 0}, + { XK_F8, XK_NO_MOD, "\033[19~", 0, 0, 0}, + { XK_F8, /* F20 */ ShiftMask, "\033[19;2~", 0, 0, 0}, + { XK_F8, /* F32 */ ControlMask, "\033[19;5~", 0, 0, 0}, + { XK_F8, /* F44 */ Mod4Mask, "\033[19;6~", 0, 0, 0}, + { XK_F8, /* F56 */ Mod1Mask, "\033[19;3~", 0, 0, 0}, + { XK_F9, XK_NO_MOD, "\033[20~", 0, 0, 0}, + { XK_F9, /* F21 */ ShiftMask, "\033[20;2~", 0, 0, 0}, + { XK_F9, /* F33 */ ControlMask, "\033[20;5~", 0, 0, 0}, + { XK_F9, /* F45 */ Mod4Mask, "\033[20;6~", 0, 0, 0}, + { XK_F9, /* F57 */ Mod1Mask, "\033[20;3~", 0, 0, 0}, + { XK_F10, XK_NO_MOD, "\033[21~", 0, 0, 0}, + { XK_F10, /* F22 */ ShiftMask, "\033[21;2~", 0, 0, 0}, + { XK_F10, /* F34 */ ControlMask, "\033[21;5~", 0, 0, 0}, + { XK_F10, /* F46 */ Mod4Mask, "\033[21;6~", 0, 0, 0}, + { XK_F10, /* F58 */ Mod1Mask, "\033[21;3~", 0, 0, 0}, + { XK_F11, XK_NO_MOD, "\033[23~", 0, 0, 0}, + { XK_F11, /* F23 */ ShiftMask, "\033[23;2~", 0, 0, 0}, + { XK_F11, /* F35 */ ControlMask, "\033[23;5~", 0, 0, 0}, + { XK_F11, /* F47 */ Mod4Mask, "\033[23;6~", 0, 0, 0}, + { XK_F11, /* F59 */ Mod1Mask, "\033[23;3~", 0, 0, 0}, + { XK_F12, XK_NO_MOD, "\033[24~", 0, 0, 0}, + { XK_F12, /* F24 */ ShiftMask, "\033[24;2~", 0, 0, 0}, + { XK_F12, /* F36 */ ControlMask, "\033[24;5~", 0, 0, 0}, + { XK_F12, /* F48 */ Mod4Mask, "\033[24;6~", 0, 0, 0}, + { XK_F12, /* F60 */ Mod1Mask, "\033[24;3~", 0, 0, 0}, + { XK_F13, XK_NO_MOD, "\033[1;2P", 0, 0, 0}, + { XK_F14, XK_NO_MOD, "\033[1;2Q", 0, 0, 0}, + { XK_F15, XK_NO_MOD, "\033[1;2R", 0, 0, 0}, + { XK_F16, XK_NO_MOD, "\033[1;2S", 0, 0, 0}, + { XK_F17, XK_NO_MOD, "\033[15;2~", 0, 0, 0}, + { XK_F18, XK_NO_MOD, "\033[17;2~", 0, 0, 0}, + { XK_F19, XK_NO_MOD, "\033[18;2~", 0, 0, 0}, + { XK_F20, XK_NO_MOD, "\033[19;2~", 0, 0, 0}, + { XK_F21, XK_NO_MOD, "\033[20;2~", 0, 0, 0}, + { XK_F22, XK_NO_MOD, "\033[21;2~", 0, 0, 0}, + { XK_F23, XK_NO_MOD, "\033[23;2~", 0, 0, 0}, + { XK_F24, XK_NO_MOD, "\033[24;2~", 0, 0, 0}, + { XK_F25, XK_NO_MOD, "\033[1;5P", 0, 0, 0}, + { XK_F26, XK_NO_MOD, "\033[1;5Q", 0, 0, 0}, + { XK_F27, XK_NO_MOD, "\033[1;5R", 0, 0, 0}, + { XK_F28, XK_NO_MOD, "\033[1;5S", 0, 0, 0}, + { XK_F29, XK_NO_MOD, "\033[15;5~", 0, 0, 0}, + { XK_F30, XK_NO_MOD, "\033[17;5~", 0, 0, 0}, + { XK_F31, XK_NO_MOD, "\033[18;5~", 0, 0, 0}, + { XK_F32, XK_NO_MOD, "\033[19;5~", 0, 0, 0}, + { XK_F33, XK_NO_MOD, "\033[20;5~", 0, 0, 0}, + { XK_F34, XK_NO_MOD, "\033[21;5~", 0, 0, 0}, + { XK_F35, XK_NO_MOD, "\033[23;5~", 0, 0, 0}, +}; + +/* + * Selection types' masks. + * Use the same masks as usual. + * Button1Mask is always unset, to make masks match between ButtonPress. + * ButtonRelease and MotionNotify. + * If no match is found, regular selection is used. + */ +static uint selmasks[] = { + [SEL_RECTANGULAR] = Mod1Mask, +}; + +/* + * Printable characters in ASCII, used to estimate the advance width + * of single wide characters. + */ +static char ascii_printable[] = + " !\"#$%&'()*+,-./0123456789:;<=>?" + "@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_" + "`abcdefghijklmnopqrstuvwxyz{|}~"; diff --git a/bin/terminal/st.c b/bin/terminal/st.c new file mode 100644 index 0000000..d016fec --- /dev/null +++ b/bin/terminal/st.c @@ -0,0 +1,4508 @@ +#define _DEFAULT_SOURCE +#define _XOPEN_SOURCE 700 +#define HAVE_SHADOW_H 1 + +/* See LICENSE for license details. */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "arg.h" + +char *argv0; + +#define Glyph Glyph_ +#define Font Font_ + +#if defined(__linux) + #include +#elif defined(__OpenBSD__) || defined(__NetBSD__) || defined(__APPLE__) + #include +#elif defined(__FreeBSD__) || defined(__DragonFly__) + #include +#endif + + +/* XEMBED messages */ +#define XEMBED_FOCUS_IN 4 +#define XEMBED_FOCUS_OUT 5 + +/* Arbitrary sizes */ +#define UTF_INVALID 0xFFFD +#define UTF_SIZ 4 +#define ESC_BUF_SIZ (128*UTF_SIZ) +#define ESC_ARG_SIZ 16 +#define STR_BUF_SIZ ESC_BUF_SIZ +#define STR_ARG_SIZ ESC_ARG_SIZ +#define XK_ANY_MOD UINT_MAX +#define XK_NO_MOD 0 +#define XK_SWITCH_MOD (1<<13) + +/* macros */ +#define MIN(a, b) ((a) < (b) ? (a) : (b)) +#define MAX(a, b) ((a) < (b) ? (b) : (a)) +#define LEN(a) (sizeof(a) / sizeof(a)[0]) +#define DEFAULT(a, b) (a) = (a) ? (a) : (b) +#define BETWEEN(x, a, b) ((a) <= (x) && (x) <= (b)) +#define DIVCEIL(n, d) (((n) + ((d) - 1)) / (d)) +#define ISCONTROLC0(c) (BETWEEN(c, 0, 0x1f) || (c) == '\177') +#define ISCONTROLC1(c) (BETWEEN(c, 0x80, 0x9f)) +#define ISCONTROL(c) (ISCONTROLC0(c) || ISCONTROLC1(c)) +#define ISDELIM(u) (utf8strchr(worddelimiters, u) != NULL) +#define LIMIT(x, a, b) (x) = (x) < (a) ? (a) : (x) > (b) ? (b) : (x) +#define ATTRCMP(a, b) ((a).mode != (b).mode || (a).fg != (b).fg || \ + (a).bg != (b).bg) +#define IS_SET(flag) ((term.mode & (flag)) != 0) +#define TIMEDIFF(t1, t2) ((t1.tv_sec-t2.tv_sec)*1000 + \ + (t1.tv_nsec-t2.tv_nsec)/1E6) +#define MODBIT(x, set, bit) ((set) ? ((x) |= (bit)) : ((x) &= ~(bit))) + +#define TRUECOLOR(r,g,b) (1 << 24 | (r) << 16 | (g) << 8 | (b)) +#define IS_TRUECOL(x) (1 << 24 & (x)) +#define TRUERED(x) (((x) & 0xff0000) >> 8) +#define TRUEGREEN(x) (((x) & 0xff00)) +#define TRUEBLUE(x) (((x) & 0xff) << 8) +#define TLINE(y) ((y) < term.scr ? term.hist[((y) + term.histi - term.scr \ + + histsize + 1) % histsize] : term.line[(y) - term.scr]) + + +enum glyph_attribute { + ATTR_NULL = 0, + ATTR_BOLD = 1 << 0, + ATTR_FAINT = 1 << 1, + ATTR_ITALIC = 1 << 2, + ATTR_UNDERLINE = 1 << 3, + ATTR_BLINK = 1 << 4, + ATTR_REVERSE = 1 << 5, + ATTR_INVISIBLE = 1 << 6, + ATTR_STRUCK = 1 << 7, + ATTR_WRAP = 1 << 8, + ATTR_WIDE = 1 << 9, + ATTR_WDUMMY = 1 << 10, + ATTR_BOLD_FAINT = ATTR_BOLD | ATTR_FAINT, +}; + +enum cursor_movement { + CURSOR_SAVE, + CURSOR_LOAD +}; + +enum cursor_state { + CURSOR_DEFAULT = 0, + CURSOR_WRAPNEXT = 1, + CURSOR_ORIGIN = 2 +}; + +enum term_mode { + MODE_WRAP = 1 << 0, + MODE_INSERT = 1 << 1, + MODE_APPKEYPAD = 1 << 2, + MODE_ALTSCREEN = 1 << 3, + MODE_CRLF = 1 << 4, + MODE_MOUSEBTN = 1 << 5, + MODE_MOUSEMOTION = 1 << 6, + MODE_REVERSE = 1 << 7, + MODE_KBDLOCK = 1 << 8, + MODE_HIDE = 1 << 9, + MODE_ECHO = 1 << 10, + MODE_APPCURSOR = 1 << 11, + MODE_MOUSESGR = 1 << 12, + MODE_8BIT = 1 << 13, + MODE_BLINK = 1 << 14, + MODE_FBLINK = 1 << 15, + MODE_FOCUS = 1 << 16, + MODE_MOUSEX10 = 1 << 17, + MODE_MOUSEMANY = 1 << 18, + MODE_BRCKTPASTE = 1 << 19, + MODE_PRINT = 1 << 20, + MODE_MOUSE = MODE_MOUSEBTN|MODE_MOUSEMOTION|MODE_MOUSEX10\ + |MODE_MOUSEMANY, +}; + +enum charset { + CS_GRAPHIC0, + CS_GRAPHIC1, + CS_UK, + CS_USA, + CS_MULTI, + CS_GER, + CS_FIN +}; + +enum escape_state { + ESC_START = 1, + ESC_CSI = 2, + ESC_STR = 4, /* DCS, OSC, PM, APC */ + ESC_ALTCHARSET = 8, + ESC_STR_END = 16, /* a final string was encountered */ + ESC_TEST = 32, /* Enter in test mode */ +}; + +enum window_state { + WIN_VISIBLE = 1, + WIN_FOCUSED = 2 +}; + +enum selection_mode { + SEL_IDLE = 0, + SEL_EMPTY = 1, + SEL_READY = 2 +}; + +enum selection_type { + SEL_REGULAR = 1, + SEL_RECTANGULAR = 2 +}; + +enum selection_snap { + SNAP_WORD = 1, + SNAP_LINE = 2 +}; + +typedef unsigned char uchar; +typedef unsigned int uint; +typedef unsigned long ulong; +typedef unsigned short ushort; + +typedef uint_least32_t Rune; + +typedef XftDraw *Draw; +typedef XftColor Color; + +typedef struct { + Rune u; /* character code */ + ushort mode; /* attribute flags */ + uint32_t fg; /* foreground */ + uint32_t bg; /* background */ +} Glyph; + +typedef Glyph *Line; + +typedef struct { + Glyph attr; /* current char attributes */ + int x; + int y; + char state; +} TCursor; + +/* CSI Escape sequence structs */ +/* ESC '[' [[ [] [;]] []] */ +typedef struct { + char buf[ESC_BUF_SIZ]; /* raw string */ + int len; /* raw string length */ + char priv; + int arg[ESC_ARG_SIZ]; + int narg; /* nb of args */ + char mode[2]; +} CSIEscape; + +/* STR Escape sequence structs */ +/* ESC type [[ [] [;]] ] ESC '\' */ +typedef struct { + char type; /* ESC type ... */ + char buf[STR_BUF_SIZ]; /* raw string */ + int len; /* raw string length */ + char *args[STR_ARG_SIZ]; + int narg; /* nb of args */ +} STREscape; + +/* Purely graphic info */ +typedef struct { + Display *dpy; + Colormap cmap; + Window win; + Drawable buf; + Atom xembed, wmdeletewin, netwmname, netwmpid; + XIM xim; + XIC xic; + Draw draw; + Visual *vis; + XSetWindowAttributes attrs; + int scr; + int isfixed; /* is fixed geometry? */ + int l, t; /* left and top offset */ + int gm; /* geometry mask */ + int tw, th; /* tty width and height */ + int w, h; /* window width and height */ + int ch; /* char height */ + int cw; /* char width */ + char state; /* focus, redraw, visible */ + int cursor; /* cursor style */ +} XWindow; + +typedef struct { + uint b; + uint mask; + char *s; +} MouseShortcut; + +typedef struct { + KeySym k; + uint mask; + char *s; + /* three valued logic variables: 0 indifferent, 1 on, -1 off */ + signed char appkey; /* application keypad */ + signed char appcursor; /* application cursor */ + signed char crlf; /* crlf mode */ +} Key; + +typedef struct { + int mode; + int type; + int snap; + /* + * Selection variables: + * nb – normalized coordinates of the beginning of the selection + * ne – normalized coordinates of the end of the selection + * ob – original coordinates of the beginning of the selection + * oe – original coordinates of the end of the selection + */ + struct { + int x, y; + } nb, ne, ob, oe; + + char *primary, *clipboard; + Atom xtarget; + int alt; + struct timespec tclick1; + struct timespec tclick2; +} Selection; + +typedef union { + int i; + uint ui; + float f; + const void *v; +} Arg; + +typedef struct { + uint mod; + KeySym keysym; + void (*func)(const Arg *); + const Arg arg; +} Shortcut; + +/* function definitions used in config.h */ +static void clipcopy(const Arg *); +static void clippaste(const Arg *); +static void kscrolldown(const Arg *); +static void kscrollup(const Arg *); +static void numlock(const Arg *); +static void swapcolors(const Arg *); +static void selpaste(const Arg *); +static void xzoom(const Arg *); +static void xzoomabs(const Arg *); +static void xzoomreset(const Arg *); +static void printsel(const Arg *); +static void printscreen(const Arg *) ; +static void toggleprinter(const Arg *); +static void sendbreak(const Arg *); + +/* Config.h for applying patches and the configuration. */ +#include "config.h" + +/* Internal representation of the screen */ +typedef struct { + int row; /* nb row */ + int col; /* nb col */ + Line *line; /* screen */ + Line *alt; /* alternate screen */ + Line hist[histsize]; /* history buffer */ + int histi; /* history index */ + int scr; /* scroll back */ + int *dirty; /* dirtyness of lines */ + XftGlyphFontSpec *specbuf; /* font spec buffer used for rendering */ + TCursor c; /* cursor */ + int top; /* top scroll limit */ + int bot; /* bottom scroll limit */ + int mode; /* terminal mode flags */ + int esc; /* escape state flags */ + char trantbl[4]; /* charset table translation */ + int charset; /* current charset */ + int icharset; /* selected charset for sequence */ + int numlock; /* lock numbers in keyboard */ + int *tabs; +} Term; + +/* Font structure */ +typedef struct { + int height; + int width; + int ascent; + int descent; + short lbearing; + short rbearing; + XftFont *match; + FcFontSet *set; + FcPattern *pattern; +} Font; + +/* Drawing Context */ +typedef struct { + Color col[MAX(MAX(LEN(colorname), LEN(altcolorname)), 256)]; + Font font, bfont, ifont, ibfont; + GC gc; +} DC; + +static void die(const char *, ...); +static void draw(void); +static void redraw(void); +static void drawregion(int, int, int, int); +static void execsh(void); +static void stty(void); +static void sigchld(int); +static void run(void); + +static void csidump(void); +static void csihandle(void); +static void csiparse(void); +static void csireset(void); +static int eschandle(uchar); +static void strdump(void); +static void strhandle(void); +static void strparse(void); +static void strreset(void); + +static int tattrset(int); +static void tprinter(char *, size_t); +static void tdumpsel(void); +static void tdumpline(int); +static void tdump(void); +static void tclearregion(int, int, int, int); +static void tcursor(int); +static void tdeletechar(int); +static void tdeleteline(int); +static void tinsertblank(int); +static void tinsertblankline(int); +static int tlinelen(int); +static void tmoveto(int, int); +static void tmoveato(int, int); +static void tnew(int, int); +static void tnewline(int); +static void tputtab(int); +static void tputc(Rune); +static void treset(void); +static void tresize(int, int); +static void tscrollup(int, int, int); +static void tscrolldown(int, int, int); +static void tsetattr(int *, int); +static void tsetchar(Rune, Glyph *, int, int); +static void tsetscroll(int, int); +static void tswapscreen(void); +static void tsetdirt(int, int); +static void tsetdirtattr(int); +static void tsetmode(int, int, int *, int); +static void tfulldirt(void); +static void techo(Rune); +static void tcontrolcode(uchar ); +static void tdectest(char ); +static int32_t tdefcolor(int *, int *, int); +static void tdeftran(char); +static inline int match(uint, uint); +static void ttynew(void); +static size_t ttyread(void); +static void ttyresize(void); +static void ttysend(char *, size_t); +static void ttywrite(const char *, size_t); +static void tstrsequence(uchar); + +static inline ushort sixd_to_16bit(int); +static int xmakeglyphfontspecs(XftGlyphFontSpec *, const Glyph *, int, int, int); +static void xdrawglyphfontspecs(const XftGlyphFontSpec *, Glyph, int, int, int); +static void xdrawglyph(Glyph, int, int); +static void xhints(void); +static void xclear(int, int, int, int); +static void xdrawcursor(void); +static void xinit(void); +static void xloadcols(void); +static int xsetcolorname(int, const char *); +static int xgeommasktogravity(int); +static int xloadfont(Font *, FcPattern *); +static void xloadfonts(char *, double); +static void xsettitle(char *); +static void xresettitle(void); +static void xsetpointermotion(int); +static void xseturgency(int); +static void xsetsel(char *, Time); +static void xunloadfont(Font *); +static void xunloadfonts(void); +static void xresize(int, int); + +static void expose(XEvent *); +static void visibility(XEvent *); +static void unmap(XEvent *); +static char *kmap(KeySym, uint); +static void kpress(XEvent *); +static void cmessage(XEvent *); +static void cresize(int, int); +static void resize(XEvent *); +static void focus(XEvent *); +static void brelease(XEvent *); +static void bpress(XEvent *); +static void bmotion(XEvent *); +static void propnotify(XEvent *); +static void selnotify(XEvent *); +static void selclear(XEvent *); +static void selrequest(XEvent *); + +static void selinit(void); +static void selnormalize(void); +static inline int selected(int, int); +static char *getsel(void); +static void selcopy(Time); +static void selscroll(int, int); +static void selsnap(int *, int *, int); +static int x2col(int); +static int y2row(int); +static void getbuttoninfo(XEvent *); +static void mousereport(XEvent *); + +static size_t utf8decode(char *, Rune *, size_t); +static Rune utf8decodebyte(char, size_t *); +static size_t utf8encode(Rune, char *); +static char utf8encodebyte(Rune, size_t); +static char *utf8strchr(char *s, Rune u); +static size_t utf8validate(Rune *, size_t); + +static ssize_t xwrite(int, const char *, size_t); +static void *xmalloc(size_t); +static void *xrealloc(void *, size_t); +static char *xstrdup(char *); + +static void usage(void); + +static void (*handler[LASTEvent])(XEvent *) = { + [KeyPress] = kpress, + [ClientMessage] = cmessage, + [ConfigureNotify] = resize, + [VisibilityNotify] = visibility, + [UnmapNotify] = unmap, + [Expose] = expose, + [FocusIn] = focus, + [FocusOut] = focus, + [MotionNotify] = bmotion, + [ButtonPress] = bpress, + [ButtonRelease] = brelease, +/* + * Uncomment if you want the selection to disappear when you select something + * different in another window. + */ +/* [SelectionClear] = selclear, */ + [SelectionNotify] = selnotify, +/* + * PropertyNotify is only turned on when there is some INCR transfer happening + * for the selection retrieval. + */ + [PropertyNotify] = propnotify, + [SelectionRequest] = selrequest, +}; + +/* Globals */ +static DC dc; +static XWindow xw; +static Term term; +static CSIEscape csiescseq; +static STREscape strescseq; +static int cmdfd; +static pid_t pid; +static Selection sel; +static int iofd = 1; +static char **opt_cmd = NULL; +static char *opt_class = NULL; +static char *opt_embed = NULL; +static char *opt_font = NULL; +static char *opt_io = NULL; +static char *opt_line = NULL; +static char *opt_name = NULL; +static char *opt_title = NULL; +static int oldbutton = 3; /* button event on startup: 3 = release */ + +static int usealtcolors = 0; /* 1 to use alternate palette */ + +static char *usedfont = NULL; +static double usedfontsize = 0; +static double defaultfontsize = 0; + +static uchar utfbyte[UTF_SIZ + 1] = {0x80, 0, 0xC0, 0xE0, 0xF0}; +static uchar utfmask[UTF_SIZ + 1] = {0xC0, 0x80, 0xE0, 0xF0, 0xF8}; +static Rune utfmin[UTF_SIZ + 1] = { 0, 0, 0x80, 0x800, 0x10000}; +static Rune utfmax[UTF_SIZ + 1] = {0x10FFFF, 0x7F, 0x7FF, 0xFFFF, 0x10FFFF}; + +char *cwd; +char *plumber_cmd = "tfetch"; + +/* Font Ring Cache */ +enum { + FRC_NORMAL, + FRC_ITALIC, + FRC_BOLD, + FRC_ITALICBOLD +}; + +typedef struct { + XftFont *font; + int flags; + Rune unicodep; +} Fontcache; + +/* Fontcache is an array now. A new font will be appended to the array. */ +static Fontcache frc[16]; +static int frclen = 0; + +ssize_t +xwrite(int fd, const char *s, size_t len) +{ + size_t aux = len; + ssize_t r; + + while (len > 0) { + r = write(fd, s, len); + if (r < 0) + return r; + len -= r; + s += r; + } + + return aux; +} + +void * +xmalloc(size_t len) +{ + void *p = malloc(len); + + if (!p) + die("Out of memory\n"); + + return p; +} + +void * +xrealloc(void *p, size_t len) +{ + if ((p = realloc(p, len)) == NULL) + die("Out of memory\n"); + + return p; +} + +char * +xstrdup(char *s) +{ + if ((s = strdup(s)) == NULL) + die("Out of memory\n"); + + return s; +} + +size_t +utf8decode(char *c, Rune *u, size_t clen) +{ + size_t i, j, len, type; + Rune udecoded; + + *u = UTF_INVALID; + if (!clen) + return 0; + udecoded = utf8decodebyte(c[0], &len); + if (!BETWEEN(len, 1, UTF_SIZ)) + return 1; + for (i = 1, j = 1; i < clen && j < len; ++i, ++j) { + udecoded = (udecoded << 6) | utf8decodebyte(c[i], &type); + if (type != 0) + return j; + } + if (j < len) + return 0; + *u = udecoded; + utf8validate(u, len); + + return len; +} + +Rune +utf8decodebyte(char c, size_t *i) +{ + for (*i = 0; *i < LEN(utfmask); ++(*i)) + if (((uchar)c & utfmask[*i]) == utfbyte[*i]) + return (uchar)c & ~utfmask[*i]; + + return 0; +} + +size_t +utf8encode(Rune u, char *c) +{ + size_t len, i; + + len = utf8validate(&u, 0); + if (len > UTF_SIZ) + return 0; + + for (i = len - 1; i != 0; --i) { + c[i] = utf8encodebyte(u, 0); + u >>= 6; + } + c[0] = utf8encodebyte(u, len); + + return len; +} + +char +utf8encodebyte(Rune u, size_t i) +{ + return utfbyte[i] | (u & ~utfmask[i]); +} + +char * +utf8strchr(char *s, Rune u) +{ + Rune r; + size_t i, j, len; + + len = strlen(s); + for (i = 0, j = 0; i < len; i += j) { + if (!(j = utf8decode(&s[i], &r, len - i))) + break; + if (r == u) + return &(s[i]); + } + + return NULL; +} + +size_t +utf8validate(Rune *u, size_t i) +{ + if (!BETWEEN(*u, utfmin[i], utfmax[i]) || BETWEEN(*u, 0xD800, 0xDFFF)) + *u = UTF_INVALID; + for (i = 1; *u > utfmax[i]; ++i) + ; + + return i; +} + +void +selinit(void) +{ + clock_gettime(CLOCK_MONOTONIC, &sel.tclick1); + clock_gettime(CLOCK_MONOTONIC, &sel.tclick2); + sel.mode = SEL_IDLE; + sel.snap = 0; + sel.ob.x = -1; + sel.primary = NULL; + sel.clipboard = NULL; + sel.xtarget = XInternAtom(xw.dpy, "UTF8_STRING", 0); + if (sel.xtarget == None) + sel.xtarget = XA_STRING; +} + +int +x2col(int x) +{ + x -= borderpx; + x /= xw.cw; + + return LIMIT(x, 0, term.col-1); +} + +int +y2row(int y) +{ + y -= borderpx; + y /= xw.ch; + + return LIMIT(y, 0, term.row-1); +} + +int +tlinelen(int y) +{ + int i = term.col; + + if (TLINE(y)[i - 1].mode & ATTR_WRAP) + return i; + + while (i > 0 && TLINE(y)[i - 1].u == ' ') + --i; + + return i; +} + +void +selnormalize(void) +{ + int i; + + if (sel.type == SEL_REGULAR && sel.ob.y != sel.oe.y) { + sel.nb.x = sel.ob.y < sel.oe.y ? sel.ob.x : sel.oe.x; + sel.ne.x = sel.ob.y < sel.oe.y ? sel.oe.x : sel.ob.x; + } else { + sel.nb.x = MIN(sel.ob.x, sel.oe.x); + sel.ne.x = MAX(sel.ob.x, sel.oe.x); + } + sel.nb.y = MIN(sel.ob.y, sel.oe.y); + sel.ne.y = MAX(sel.ob.y, sel.oe.y); + + selsnap(&sel.nb.x, &sel.nb.y, -1); + selsnap(&sel.ne.x, &sel.ne.y, +1); + + /* expand selection over line breaks */ + if (sel.type == SEL_RECTANGULAR) + return; + i = tlinelen(sel.nb.y); + if (i < sel.nb.x) + sel.nb.x = i; + if (tlinelen(sel.ne.y) <= sel.ne.x) + sel.ne.x = term.col - 1; +} + +int +selected(int x, int y) +{ + if (sel.mode == SEL_EMPTY) + return 0; + + if (sel.type == SEL_RECTANGULAR) + return BETWEEN(y, sel.nb.y, sel.ne.y) + && BETWEEN(x, sel.nb.x, sel.ne.x); + + return BETWEEN(y, sel.nb.y, sel.ne.y) + && (y != sel.nb.y || x >= sel.nb.x) + && (y != sel.ne.y || x <= sel.ne.x); +} + +void +selsnap(int *x, int *y, int direction) +{ + int newx, newy, xt, yt; + int delim, prevdelim; + Glyph *gp, *prevgp; + + switch (sel.snap) { + case SNAP_WORD: + /* + * Snap around if the word wraps around at the end or + * beginning of a line. + */ + prevgp = &TLINE(*y)[*x]; + prevdelim = ISDELIM(prevgp->u); + for (;;) { + newx = *x + direction; + newy = *y; + if (!BETWEEN(newx, 0, term.col - 1)) { + newy += direction; + newx = (newx + term.col) % term.col; + if (!BETWEEN(newy, 0, term.row - 1)) + break; + + if (direction > 0) + yt = *y, xt = *x; + else + yt = newy, xt = newx; + if (!(TLINE(yt)[xt].mode & ATTR_WRAP)) + break; + } + + if (newx >= tlinelen(newy)) + break; + + gp = &TLINE(newy)[newx]; + delim = ISDELIM(gp->u); + if (!(gp->mode & ATTR_WDUMMY) && (delim != prevdelim + || (delim && gp->u != prevgp->u))) + break; + + *x = newx; + *y = newy; + prevgp = gp; + prevdelim = delim; + } + break; + case SNAP_LINE: + /* + * Snap around if the the previous line or the current one + * has set ATTR_WRAP at its end. Then the whole next or + * previous line will be selected. + */ + *x = (direction < 0) ? 0 : term.col - 1; + if (direction < 0) { + for (; *y > 0; *y += direction) { + if (!(TLINE(*y-1)[term.col-1].mode + & ATTR_WRAP)) { + break; + } + } + } else if (direction > 0) { + for (; *y < term.row-1; *y += direction) { + if (!(TLINE(*y)[term.col-1].mode + & ATTR_WRAP)) { + break; + } + } + } + break; + } +} + +void +getbuttoninfo(XEvent *e) +{ + unsigned long type; + uint state = e->xbutton.state & ~(Button1Mask | forceselmod); + + sel.alt = IS_SET(MODE_ALTSCREEN); + + sel.oe.x = x2col(e->xbutton.x); + sel.oe.y = y2row(e->xbutton.y); + selnormalize(); + + sel.type = SEL_REGULAR; + for (type = 1; type < LEN(selmasks); ++type) { + if (match(selmasks[type], state)) { + sel.type = type; + break; + } + } +} + +void +mousereport(XEvent *e) +{ + int x = x2col(e->xbutton.x), y = y2row(e->xbutton.y), + button = e->xbutton.button, state = e->xbutton.state, + len; + char buf[40]; + static int ox, oy; + + /* from urxvt */ + if (e->xbutton.type == MotionNotify) { + if (x == ox && y == oy) + return; + if (!IS_SET(MODE_MOUSEMOTION) && !IS_SET(MODE_MOUSEMANY)) + return; + /* MOUSE_MOTION: no reporting if no button is pressed */ + if (IS_SET(MODE_MOUSEMOTION) && oldbutton == 3) + return; + + button = oldbutton + 32; + ox = x; + oy = y; + } else { + if (!IS_SET(MODE_MOUSESGR) && e->xbutton.type == ButtonRelease) { + button = 3; + } else { + button -= Button1; + if (button >= 3) + button += 64 - 3; + } + if (e->xbutton.type == ButtonPress) { + oldbutton = button; + ox = x; + oy = y; + } else if (e->xbutton.type == ButtonRelease) { + oldbutton = 3; + /* MODE_MOUSEX10: no button release reporting */ + if (IS_SET(MODE_MOUSEX10)) + return; + if (button == 64 || button == 65) + return; + } + } + + if (!IS_SET(MODE_MOUSEX10)) { + button += ((state & ShiftMask ) ? 4 : 0) + + ((state & Mod4Mask ) ? 8 : 0) + + ((state & ControlMask) ? 16 : 0); + } + + if (IS_SET(MODE_MOUSESGR)) { + len = snprintf(buf, sizeof(buf), "\033[<%d;%d;%d%c", + button, x+1, y+1, + e->xbutton.type == ButtonRelease ? 'm' : 'M'); + } else if (x < 223 && y < 223) { + len = snprintf(buf, sizeof(buf), "\033[M%c%c%c", + 32+button, 32+x+1, 32+y+1); + } else { + return; + } + + ttywrite(buf, len); +} + +void +bpress(XEvent *e) +{ + struct timespec now; + MouseShortcut *ms; + + if (IS_SET(MODE_MOUSE) && !(e->xbutton.state & forceselmod)) { + mousereport(e); + return; + } + + for (ms = mshortcuts; ms < mshortcuts + LEN(mshortcuts); ms++) { + if (e->xbutton.button == ms->b + && match(ms->mask, e->xbutton.state)) { + ttysend(ms->s, strlen(ms->s)); + return; + } + } + + if (e->xbutton.button == Button1) { + clock_gettime(CLOCK_MONOTONIC, &now); + + /* Clear previous selection, logically and visually. */ + selclear(NULL); + sel.mode = SEL_EMPTY; + sel.type = SEL_REGULAR; + sel.oe.x = sel.ob.x = x2col(e->xbutton.x); + sel.oe.y = sel.ob.y = y2row(e->xbutton.y); + + /* + * If the user clicks below predefined timeouts specific + * snapping behaviour is exposed. + */ + if (TIMEDIFF(now, sel.tclick2) <= tripleclicktimeout) { + sel.snap = SNAP_LINE; + } else if (TIMEDIFF(now, sel.tclick1) <= doubleclicktimeout) { + sel.snap = SNAP_WORD; + } else { + sel.snap = 0; + } + selnormalize(); + + if (sel.snap != 0) + sel.mode = SEL_READY; + tsetdirt(sel.nb.y, sel.ne.y); + sel.tclick2 = sel.tclick1; + sel.tclick1 = now; + } +} + +char * +getsel(void) +{ + char *str, *ptr; + int y, bufsize, lastx, linelen; + Glyph *gp, *last; + + if (sel.ob.x == -1) + return NULL; + + bufsize = (term.col+1) * (sel.ne.y-sel.nb.y+1) * UTF_SIZ; + ptr = str = xmalloc(bufsize); + + /* append every set & selected glyph to the selection */ + for (y = sel.nb.y; y <= sel.ne.y; y++) { + if ((linelen = tlinelen(y)) == 0) { + *ptr++ = '\n'; + continue; + } + + if (sel.type == SEL_RECTANGULAR) { + gp = &TLINE(y)[sel.nb.x]; + lastx = sel.ne.x; + } else { + gp = &TLINE(y)[sel.nb.y == y ? sel.nb.x : 0]; + lastx = (sel.ne.y == y) ? sel.ne.x : term.col-1; + } + last = &TLINE(y)[MIN(lastx, linelen-1)]; + while (last >= gp && last->u == ' ') + --last; + + for ( ; gp <= last; ++gp) { + if (gp->mode & ATTR_WDUMMY) + continue; + + ptr += utf8encode(gp->u, ptr); + } + + /* + * Copy and pasting of line endings is inconsistent + * in the inconsistent terminal and GUI world. + * The best solution seems like to produce '\n' when + * something is copied from st and convert '\n' to + * '\r', when something to be pasted is received by + * st. + * FIXME: Fix the computer world. + */ + if ((y < sel.ne.y || lastx >= linelen) && !(last->mode & ATTR_WRAP)) + *ptr++ = '\n'; + } + *ptr = 0; + return str; +} + +void +selcopy(Time t) +{ + xsetsel(getsel(), t); +} + +void +propnotify(XEvent *e) +{ + XPropertyEvent *xpev; + Atom clipboard = XInternAtom(xw.dpy, "CLIPBOARD", 0); + + xpev = &e->xproperty; + if (xpev->state == PropertyNewValue && + (xpev->atom == XA_PRIMARY || + xpev->atom == clipboard)) { + selnotify(e); + } +} + +void +selnotify(XEvent *e) +{ + ulong nitems, ofs, rem; + int format; + uchar *data, *last, *repl; + Atom type, incratom, property; + + incratom = XInternAtom(xw.dpy, "INCR", 0); + + ofs = 0; + if (e->type == SelectionNotify) { + property = e->xselection.property; + } else if(e->type == PropertyNotify) { + property = e->xproperty.atom; + } else { + return; + } + if (property == None) + return; + + do { + if (XGetWindowProperty(xw.dpy, xw.win, property, ofs, + BUFSIZ/4, False, AnyPropertyType, + &type, &format, &nitems, &rem, + &data)) { + fprintf(stderr, "Clipboard allocation failed\n"); + return; + } + + if (e->type == PropertyNotify && nitems == 0 && rem == 0) { + /* + * If there is some PropertyNotify with no data, then + * this is the signal of the selection owner that all + * data has been transferred. We won't need to receive + * PropertyNotify events anymore. + */ + MODBIT(xw.attrs.event_mask, 0, PropertyChangeMask); + XChangeWindowAttributes(xw.dpy, xw.win, CWEventMask, + &xw.attrs); + } + + if (type == incratom) { + /* + * Activate the PropertyNotify events so we receive + * when the selection owner does send us the next + * chunk of data. + */ + MODBIT(xw.attrs.event_mask, 1, PropertyChangeMask); + XChangeWindowAttributes(xw.dpy, xw.win, CWEventMask, + &xw.attrs); + + /* + * Deleting the property is the transfer start signal. + */ + XDeleteProperty(xw.dpy, xw.win, (int)property); + continue; + } + + /* + * As seen in getsel: + * Line endings are inconsistent in the terminal and GUI world + * copy and pasting. When receiving some selection data, + * replace all '\n' with '\r'. + * FIXME: Fix the computer world. + */ + repl = data; + last = data + nitems * format / 8; + while ((repl = memchr(repl, '\n', last - repl))) { + *repl++ = '\r'; + } + + if (IS_SET(MODE_BRCKTPASTE) && ofs == 0) + ttywrite("\033[200~", 6); + ttysend((char *)data, nitems * format / 8); + if (IS_SET(MODE_BRCKTPASTE) && rem == 0) + ttywrite("\033[201~", 6); + XFree(data); + /* number of 32-bit chunks returned */ + ofs += nitems * format / 32; + } while (rem > 0); + + /* + * Deleting the property again tells the selection owner to send the + * next data chunk in the property. + */ + XDeleteProperty(xw.dpy, xw.win, (int)property); +} + +void +selpaste(const Arg *dummy) +{ + (void)dummy; + XConvertSelection(xw.dpy, XA_PRIMARY, sel.xtarget, XA_PRIMARY, + xw.win, CurrentTime); +} + +void +clipcopy(const Arg *dummy) +{ + (void)dummy; + Atom clipboard; + + if (sel.clipboard != NULL) + free(sel.clipboard); + + if (sel.primary != NULL) { + sel.clipboard = xstrdup(sel.primary); + clipboard = XInternAtom(xw.dpy, "CLIPBOARD", 0); + XSetSelectionOwner(xw.dpy, clipboard, xw.win, CurrentTime); + } +} + +void +clippaste(const Arg *dummy) +{ + (void)dummy; + Atom clipboard; + + clipboard = XInternAtom(xw.dpy, "CLIPBOARD", 0); + XConvertSelection(xw.dpy, clipboard, sel.xtarget, clipboard, + xw.win, CurrentTime); +} + +void +selclear(XEvent *e) +{ + (void)e; + if (sel.ob.x == -1) + return; + sel.mode = SEL_IDLE; + sel.ob.x = -1; + tsetdirt(sel.nb.y, sel.ne.y); +} + +void +selrequest(XEvent *e) +{ + (void)e; + XSelectionRequestEvent *xsre; + XSelectionEvent xev; + Atom xa_targets, string, clipboard; + char *seltext; + + xsre = (XSelectionRequestEvent *) e; + xev.type = SelectionNotify; + xev.requestor = xsre->requestor; + xev.selection = xsre->selection; + xev.target = xsre->target; + xev.time = xsre->time; + if (xsre->property == None) + xsre->property = xsre->target; + + /* reject */ + xev.property = None; + + xa_targets = XInternAtom(xw.dpy, "TARGETS", 0); + if (xsre->target == xa_targets) { + /* respond with the supported type */ + string = sel.xtarget; + XChangeProperty(xsre->display, xsre->requestor, xsre->property, + XA_ATOM, 32, PropModeReplace, + (uchar *) &string, 1); + xev.property = xsre->property; + } else if (xsre->target == sel.xtarget || xsre->target == XA_STRING) { + /* + * xith XA_STRING non ascii characters may be incorrect in the + * requestor. It is not our problem, use utf8. + */ + clipboard = XInternAtom(xw.dpy, "CLIPBOARD", 0); + if (xsre->selection == XA_PRIMARY) { + seltext = sel.primary; + } else if (xsre->selection == clipboard) { + seltext = sel.clipboard; + } else { + fprintf(stderr, + "Unhandled clipboard selection 0x%lx\n", + xsre->selection); + return; + } + if (seltext != NULL) { + XChangeProperty(xsre->display, xsre->requestor, + xsre->property, xsre->target, + 8, PropModeReplace, + (uchar *)seltext, strlen(seltext)); + xev.property = xsre->property; + } + } + + /* all done, send a notification to the listener */ + if (!XSendEvent(xsre->display, xsre->requestor, 1, 0, (XEvent *) &xev)) + fprintf(stderr, "Error sending SelectionNotify event\n"); +} + +void +xsetsel(char *str, Time t) +{ + free(sel.primary); + sel.primary = str; + + XSetSelectionOwner(xw.dpy, XA_PRIMARY, xw.win, t); + if (XGetSelectionOwner(xw.dpy, XA_PRIMARY) != xw.win) + selclear(0); + + clipcopy(NULL); +} + +void +brelease(XEvent *e) +{ + if (IS_SET(MODE_MOUSE) && !(e->xbutton.state & forceselmod)) { + mousereport(e); + return; + } + + if (e->xbutton.button == Button2) { + selpaste(NULL); + } else if (e->xbutton.button == Button3 && !fork()) { + exit(execvp("tfetch", (char*[]){ "tfetch", sel.primary, 0 })); + } else if (e->xbutton.button == Button1) { + if (sel.mode == SEL_READY) { + getbuttoninfo(e); + selcopy(e->xbutton.time); + } else { + selclear(NULL); + } + sel.mode = SEL_IDLE; + tsetdirt(sel.nb.y, sel.ne.y); + } +} + +void +bmotion(XEvent *e) +{ + int oldey, oldex, oldsby, oldsey; + + if (IS_SET(MODE_MOUSE) && !(e->xbutton.state & forceselmod)) { + mousereport(e); + return; + } + + if (!sel.mode) + return; + + sel.mode = SEL_READY; + oldey = sel.oe.y; + oldex = sel.oe.x; + oldsby = sel.nb.y; + oldsey = sel.ne.y; + getbuttoninfo(e); + + if (oldey != sel.oe.y || oldex != sel.oe.x) + tsetdirt(MIN(sel.nb.y, oldsby), MAX(sel.ne.y, oldsey)); +} + +void +die(const char *errstr, ...) +{ + va_list ap; + + va_start(ap, errstr); + vfprintf(stderr, errstr, ap); + va_end(ap); + exit(1); +} + +void +execsh(void) +{ + char **args, *sh, *prog; + const struct passwd *pw; + char buf[sizeof(long) * 8 + 1]; + + errno = 0; + if ((pw = getpwuid(getuid())) == NULL) { + if (errno) + die("getpwuid:%s\n", strerror(errno)); + else + die("who are you?\n"); + } + + if ((sh = getenv("SHELL")) == NULL) + sh = (pw->pw_shell[0]) ? pw->pw_shell : shell; + + if (opt_cmd) + prog = opt_cmd[0]; + else if (utmp) + prog = utmp; + else + prog = sh; + args = (opt_cmd) ? opt_cmd : (char *[]) {prog, NULL}; + + snprintf(buf, sizeof(buf), "%lu", xw.win); + + unsetenv("COLUMNS"); + unsetenv("LINES"); + unsetenv("TERMCAP"); + setenv("LOGNAME", pw->pw_name, 1); + setenv("USER", pw->pw_name, 1); + setenv("SHELL", sh, 1); + setenv("HOME", pw->pw_dir, 1); + setenv("TERM", termname, 1); + setenv("WINDOWID", buf, 1); + + signal(SIGCHLD, SIG_DFL); + signal(SIGHUP, SIG_DFL); + signal(SIGINT, SIG_DFL); + signal(SIGQUIT, SIG_DFL); + signal(SIGTERM, SIG_DFL); + signal(SIGALRM, SIG_DFL); + + execvp(prog, args); + _exit(1); +} + +void +sigchld(int a) +{ + (void)a; + + int stat; + pid_t p; + + if ((p = waitpid(pid, &stat, WNOHANG)) < 0) + die("Waiting for pid %hd failed: %s\n", pid, strerror(errno)); + + if (pid != p) + return; + + if (!WIFEXITED(stat) || WEXITSTATUS(stat)) + die("child finished with error '%d'\n", stat); + exit(0); +} + + +void +stty(void) +{ + char cmd[_POSIX_ARG_MAX], **p, *q, *s; + size_t n, siz; + + if ((n = strlen(stty_args)) > sizeof(cmd)-1) + die("incorrect stty parameters\n"); + memcpy(cmd, stty_args, n); + q = cmd + n; + siz = sizeof(cmd) - n; + for (p = opt_cmd; p && (s = *p); ++p) { + if ((n = strlen(s)) > siz-1) + die("stty parameter length too long\n"); + *q++ = ' '; + memcpy(q, s, n); + q += n; + siz -= n + 1; + } + *q = '\0'; + if (system(cmd) != 0) + perror("Couldn't call stty"); +} + +void +ttynew(void) +{ + int m, s; + struct winsize w = {term.row, term.col, 0, 0}; + + if (opt_io) { + term.mode |= MODE_PRINT; + iofd = (!strcmp(opt_io, "-")) ? + 1 : open(opt_io, O_WRONLY | O_CREAT, 0666); + if (iofd < 0) { + fprintf(stderr, "Error opening %s:%s\n", + opt_io, strerror(errno)); + } + } + + if (opt_line) { + if ((cmdfd = open(opt_line, O_RDWR)) < 0) + die("open line failed: %s\n", strerror(errno)); + dup2(cmdfd, 0); + stty(); + return; + } + + /* seems to work fine on linux, openbsd and freebsd */ + if (openpty(&m, &s, NULL, NULL, &w) < 0) + die("openpty failed: %s\n", strerror(errno)); + + switch (pid = fork()) { + case -1: + die("fork failed\n"); + break; + case 0: + close(iofd); + setsid(); /* create a new process group */ + dup2(s, 0); + dup2(s, 1); + dup2(s, 2); + if (ioctl(s, TIOCSCTTY, NULL) < 0) + die("ioctl TIOCSCTTY failed: %s\n", strerror(errno)); + close(s); + close(m); + execsh(); + break; + default: + close(s); + cmdfd = m; + signal(SIGCHLD, sigchld); + break; + } +} + +size_t +ttyread(void) +{ + static char buf[BUFSIZ]; + static int buflen = 0; + char *ptr; + int charsize; /* size of utf8 char in bytes */ + Rune unicodep; + int ret; + + /* append read bytes to unprocessed bytes */ + if ((ret = read(cmdfd, buf+buflen, LEN(buf)-buflen)) < 0) + die("Couldn't read from shell: %s\n", strerror(errno)); + + /* process every complete utf8 char */ + buflen += ret; + ptr = buf; + while ((charsize = utf8decode(ptr, &unicodep, buflen))) { + tputc(unicodep); + ptr += charsize; + buflen -= charsize; + } + + /* keep any uncomplete utf8 char for the next call */ + memmove(buf, ptr, buflen); + + if (term.scr > 0 && term.scr < histsize-1) + term.scr++; + + return ret; +} + +void +ttywrite(const char *s, size_t n) +{ + fd_set wfd, rfd; + ssize_t r; + size_t lim = 256; + Arg arg = (Arg){ .i = term.scr }; + + kscrolldown(&arg); + + /* + * Remember that we are using a pty, which might be a modem line. + * Writing too much will clog the line. That's why we are doing this + * dance. + * FIXME: Migrate the world to Plan 9. + */ + while (n > 0) { + FD_ZERO(&wfd); + FD_ZERO(&rfd); + FD_SET(cmdfd, &wfd); + FD_SET(cmdfd, &rfd); + + /* Check if we can write. */ + if (pselect(cmdfd+1, &rfd, &wfd, NULL, NULL, NULL) < 0) { + if (errno == EINTR) + continue; + die("select failed: %s\n", strerror(errno)); + } + if (FD_ISSET(cmdfd, &wfd)) { + /* + * Only write the bytes written by ttywrite() or the + * default of 256. This seems to be a reasonable value + * for a serial line. Bigger values might clog the I/O. + */ + if ((r = write(cmdfd, s, (n < lim)? n : lim)) < 0) + goto write_error; + if (r < (ssize_t)n) { + /* + * We weren't able to write out everything. + * This means the buffer is getting full + * again. Empty it. + */ + if (n < lim) + lim = ttyread(); + n -= r; + s += r; + } else { + /* All bytes have been written. */ + break; + } + } + if (FD_ISSET(cmdfd, &rfd)) + lim = ttyread(); + } + return; + +write_error: + die("write error on tty: %s\n", strerror(errno)); +} + +void +ttysend(char *s, size_t n) +{ + int len; + Rune u; + + ttywrite(s, n); + if (IS_SET(MODE_ECHO)) + while ((len = utf8decode(s, &u, n)) > 0) { + techo(u); + n -= len; + s += len; + } +} + +void +ttyresize(void) +{ + struct winsize w; + + w.ws_row = term.row; + w.ws_col = term.col; + w.ws_xpixel = xw.tw; + w.ws_ypixel = xw.th; + if (ioctl(cmdfd, TIOCSWINSZ, &w) < 0) + fprintf(stderr, "Couldn't set window size: %s\n", strerror(errno)); +} + +int +tattrset(int attr) +{ + int i, j; + + for (i = 0; i < term.row-1; i++) { + for (j = 0; j < term.col-1; j++) { + if (term.line[i][j].mode & attr) + return 1; + } + } + + return 0; +} + +void +tsetdirt(int top, int bot) +{ + int i; + + LIMIT(top, 0, term.row-1); + LIMIT(bot, 0, term.row-1); + + for (i = top; i <= bot; i++) + term.dirty[i] = 1; +} + +void +tsetdirtattr(int attr) +{ + int i, j; + + for (i = 0; i < term.row-1; i++) { + for (j = 0; j < term.col-1; j++) { + if (term.line[i][j].mode & attr) { + tsetdirt(i, i); + break; + } + } + } +} + +void +tfulldirt(void) +{ + tsetdirt(0, term.row-1); +} + +void +tcursor(int mode) +{ + static TCursor c[2]; + int alt = IS_SET(MODE_ALTSCREEN); + + if (mode == CURSOR_SAVE) { + c[alt] = term.c; + } else if (mode == CURSOR_LOAD) { + term.c = c[alt]; + tmoveto(c[alt].x, c[alt].y); + } +} + +void +treset(void) +{ + uint i; + + term.c = (TCursor){{ + .mode = ATTR_NULL, + .fg = defaultfg, + .bg = defaultbg + }, .x = 0, .y = 0, .state = CURSOR_DEFAULT}; + + memset(term.tabs, 0, term.col * sizeof(*term.tabs)); + for (i = tabspaces; i < term.col; i += tabspaces) + term.tabs[i] = 1; + term.top = 0; + term.bot = term.row - 1; + term.mode = MODE_WRAP; + memset(term.trantbl, CS_USA, sizeof(term.trantbl)); + term.charset = 0; + + for (i = 0; i < 2; i++) { + tmoveto(0, 0); + tcursor(CURSOR_SAVE); + tclearregion(0, 0, term.col-1, term.row-1); + tswapscreen(); + } +} + +void +tnew(int col, int row) +{ + term = (Term){ .c = { .attr = { .fg = defaultfg, .bg = defaultbg } } }; + tresize(col, row); + term.numlock = 1; + + treset(); +} + +void +tswapscreen(void) +{ + Line *tmp = term.line; + + term.line = term.alt; + term.alt = tmp; + term.mode ^= MODE_ALTSCREEN; + tfulldirt(); +} + +void +kscrolldown(const Arg* a) +{ + int n = a->i; + + if (n < 0) + n = term.row + n; + + if (n > term.scr) + n = term.scr; + + if (term.scr > 0) { + term.scr -= n; + selscroll(0, -n); + tfulldirt(); + } +} + +void +kscrollup(const Arg* a) +{ + int n = a->i; + + if (n < 0) + n = term.row + n; + + if (term.scr <= histsize - n) { + term.scr += n; + selscroll(0, n); + tfulldirt(); + } +} + +void +tscrolldown(int orig, int n, int copyhist) +{ + int i; + Line temp; + + LIMIT(n, 0, term.bot-orig+1); + + if (copyhist) { + term.histi = (term.histi - 1 + histsize) % histsize; + temp = term.hist[term.histi]; + term.hist[term.histi] = term.line[term.bot]; + term.line[term.bot] = temp; + } + + tsetdirt(orig, term.bot-n); + tclearregion(0, term.bot-n+1, term.col-1, term.bot); + + for (i = term.bot; i >= orig+n; i--) { + temp = term.line[i]; + term.line[i] = term.line[i-n]; + term.line[i-n] = temp; + } + + selscroll(orig, n); +} + +void +tscrollup(int orig, int n, int copyhist) +{ + int i; + Line temp; + + LIMIT(n, 0, term.bot-orig+1); + + if (copyhist) { + term.histi = (term.histi + 1) % histsize; + temp = term.hist[term.histi]; + term.hist[term.histi] = term.line[orig]; + term.line[orig] = temp; + } + + tclearregion(0, orig, term.col-1, orig+n-1); + tsetdirt(orig+n, term.bot); + + for (i = orig; i <= term.bot-n; i++) { + temp = term.line[i]; + term.line[i] = term.line[i+n]; + term.line[i+n] = temp; + } + + selscroll(orig, -n); +} + +void +selscroll(int orig, int n) +{ + if (sel.ob.x == -1) + return; + + if (BETWEEN(sel.ob.y, orig, term.bot) || BETWEEN(sel.oe.y, orig, term.bot)) { + if ((sel.ob.y += n) > term.bot || (sel.oe.y += n) < term.top) { + selclear(NULL); + return; + } + if (sel.type == SEL_RECTANGULAR) { + if (sel.ob.y < term.top) + sel.ob.y = term.top; + if (sel.oe.y > term.bot) + sel.oe.y = term.bot; + } else { + if (sel.ob.y < term.top) { + sel.ob.y = term.top; + sel.ob.x = 0; + } + if (sel.oe.y > term.bot) { + sel.oe.y = term.bot; + sel.oe.x = term.col; + } + } + selnormalize(); + } +} + +void +tnewline(int first_col) +{ + int y = term.c.y; + + if (y == term.bot) { + tscrollup(term.top, 1, 1); + } else { + y++; + } + tmoveto(first_col ? 0 : term.c.x, y); +} + +void +csiparse(void) +{ + char *p = csiescseq.buf, *np; + long int v; + + csiescseq.narg = 0; + if (*p == '?') { + csiescseq.priv = 1; + p++; + } + + csiescseq.buf[csiescseq.len] = '\0'; + while (p < csiescseq.buf+csiescseq.len) { + np = NULL; + v = strtol(p, &np, 10); + if (np == p) + v = 0; + if (v == LONG_MAX || v == LONG_MIN) + v = -1; + csiescseq.arg[csiescseq.narg++] = v; + p = np; + if (*p != ';' || csiescseq.narg == ESC_ARG_SIZ) + break; + p++; + } + csiescseq.mode[0] = *p++; + csiescseq.mode[1] = (p < csiescseq.buf+csiescseq.len) ? *p : '\0'; +} + +/* for absolute user moves, when decom is set */ +void +tmoveato(int x, int y) +{ + tmoveto(x, y + ((term.c.state & CURSOR_ORIGIN) ? term.top: 0)); +} + +void +tmoveto(int x, int y) +{ + int miny, maxy; + + if (term.c.state & CURSOR_ORIGIN) { + miny = term.top; + maxy = term.bot; + } else { + miny = 0; + maxy = term.row - 1; + } + term.c.state &= ~CURSOR_WRAPNEXT; + term.c.x = LIMIT(x, 0, term.col-1); + term.c.y = LIMIT(y, miny, maxy); +} + +void +tsetchar(Rune u, Glyph *attr, int x, int y) +{ + static char *vt100_0[62] = { /* 0x41 - 0x7e */ + "↑", "↓", "→", "←", "█", "▚", "☃", /* A - G */ + 0, 0, 0, 0, 0, 0, 0, 0, /* H - O */ + 0, 0, 0, 0, 0, 0, 0, 0, /* P - W */ + 0, 0, 0, 0, 0, 0, 0, " ", /* X - _ */ + "◆", "▒", "␉", "␌", "␍", "␊", "°", "±", /* ` - g */ + "␤", "␋", "┘", "┐", "┌", "└", "┼", "⎺", /* h - o */ + "⎻", "─", "⎼", "⎽", "├", "┤", "┴", "┬", /* p - w */ + "│", "≤", "≥", "π", "≠", "£", "·", /* x - ~ */ + }; + + /* + * The table is proudly stolen from rxvt. + */ + if (term.trantbl[term.charset] == CS_GRAPHIC0 && + BETWEEN(u, 0x41, 0x7e) && vt100_0[u - 0x41]) + utf8decode(vt100_0[u - 0x41], &u, UTF_SIZ); + + if (term.line[y][x].mode & ATTR_WIDE) { + if (x+1 < term.col) { + term.line[y][x+1].u = ' '; + term.line[y][x+1].mode &= ~ATTR_WDUMMY; + } + } else if (term.line[y][x].mode & ATTR_WDUMMY) { + term.line[y][x-1].u = ' '; + term.line[y][x-1].mode &= ~ATTR_WIDE; + } + + term.dirty[y] = 1; + term.line[y][x] = *attr; + term.line[y][x].u = u; +} + +void +tclearregion(int x1, int y1, int x2, int y2) +{ + int x, y, temp; + Glyph *gp; + + if (x1 > x2) + temp = x1, x1 = x2, x2 = temp; + if (y1 > y2) + temp = y1, y1 = y2, y2 = temp; + + LIMIT(x1, 0, term.col-1); + LIMIT(x2, 0, term.col-1); + LIMIT(y1, 0, term.row-1); + LIMIT(y2, 0, term.row-1); + + for (y = y1; y <= y2; y++) { + term.dirty[y] = 1; + for (x = x1; x <= x2; x++) { + gp = &term.line[y][x]; + if (selected(x, y)) + selclear(NULL); + gp->fg = term.c.attr.fg; + gp->bg = term.c.attr.bg; + gp->mode = 0; + gp->u = ' '; + } + } +} + +void +tdeletechar(int n) +{ + int dst, src, size; + Glyph *line; + + LIMIT(n, 0, term.col - term.c.x); + + dst = term.c.x; + src = term.c.x + n; + size = term.col - src; + line = term.line[term.c.y]; + + memmove(&line[dst], &line[src], size * sizeof(Glyph)); + tclearregion(term.col-n, term.c.y, term.col-1, term.c.y); +} + +void +tinsertblank(int n) +{ + int dst, src, size; + Glyph *line; + + LIMIT(n, 0, term.col - term.c.x); + + dst = term.c.x + n; + src = term.c.x; + size = term.col - dst; + line = term.line[term.c.y]; + + memmove(&line[dst], &line[src], size * sizeof(Glyph)); + tclearregion(src, term.c.y, dst - 1, term.c.y); +} + +void +tinsertblankline(int n) +{ + if (BETWEEN(term.c.y, term.top, term.bot)) + tscrolldown(term.c.y, n, 0); +} + +void +tdeleteline(int n) +{ + if (BETWEEN(term.c.y, term.top, term.bot)) + tscrollup(term.c.y, n, 0); +} + +int32_t +tdefcolor(int *attr, int *npar, int l) +{ + int32_t idx = -1; + uint r, g, b; + + 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); + break; + } + r = attr[*npar + 2]; + g = attr[*npar + 3]; + 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); + 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); + break; + } + *npar += 2; + if (!BETWEEN(attr[*npar], 0, 255)) + fprintf(stderr, "erresc: bad fgcolor %d\n", attr[*npar]); + else + idx = attr[*npar]; + break; + case 0: /* implemented defined (only foreground) */ + case 1: /* transparent */ + 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]); + break; + } + + return idx; +} + +void +tsetattr(int *attr, int l) +{ + int i; + int32_t idx; + + for (i = 0; i < l; i++) { + switch (attr[i]) { + case 0: + term.c.attr.mode &= ~( + ATTR_BOLD | + ATTR_FAINT | + ATTR_ITALIC | + ATTR_UNDERLINE | + ATTR_BLINK | + ATTR_REVERSE | + ATTR_INVISIBLE | + ATTR_STRUCK ); + term.c.attr.fg = defaultfg; + term.c.attr.bg = defaultbg; + break; + case 1: + term.c.attr.mode |= ATTR_BOLD; + break; + case 2: + term.c.attr.mode |= ATTR_FAINT; + break; + case 3: + term.c.attr.mode |= ATTR_ITALIC; + break; + case 4: + term.c.attr.mode |= ATTR_UNDERLINE; + break; + case 5: /* slow blink */ + /* FALLTHROUGH */ + case 6: /* rapid blink */ + term.c.attr.mode |= ATTR_BLINK; + break; + case 7: + term.c.attr.mode |= ATTR_REVERSE; + break; + case 8: + term.c.attr.mode |= ATTR_INVISIBLE; + break; + case 9: + term.c.attr.mode |= ATTR_STRUCK; + break; + case 22: + term.c.attr.mode &= ~(ATTR_BOLD | ATTR_FAINT); + break; + case 23: + term.c.attr.mode &= ~ATTR_ITALIC; + break; + case 24: + term.c.attr.mode &= ~ATTR_UNDERLINE; + break; + case 25: + term.c.attr.mode &= ~ATTR_BLINK; + break; + case 27: + term.c.attr.mode &= ~ATTR_REVERSE; + break; + case 28: + term.c.attr.mode &= ~ATTR_INVISIBLE; + break; + case 29: + term.c.attr.mode &= ~ATTR_STRUCK; + break; + case 38: + if ((idx = tdefcolor(attr, &i, l)) >= 0) + term.c.attr.fg = idx; + break; + case 39: + term.c.attr.fg = defaultfg; + break; + case 48: + if ((idx = tdefcolor(attr, &i, l)) >= 0) + term.c.attr.bg = idx; + break; + case 49: + term.c.attr.bg = defaultbg; + break; + default: + if (BETWEEN(attr[i], 30, 37)) { + term.c.attr.fg = attr[i] - 30; + } else if (BETWEEN(attr[i], 40, 47)) { + term.c.attr.bg = attr[i] - 40; + } else if (BETWEEN(attr[i], 90, 97)) { + term.c.attr.fg = attr[i] - 90 + 8; + } else if (BETWEEN(attr[i], 100, 107)) { + term.c.attr.bg = attr[i] - 100 + 8; + } else { + fprintf(stderr, + "erresc(default): gfx attr %d unknown\n", + attr[i]), csidump(); + } + break; + } + } +} + +void +tsetscroll(int t, int b) +{ + int temp; + + LIMIT(t, 0, term.row-1); + LIMIT(b, 0, term.row-1); + if (t > b) { + temp = t; + t = b; + b = temp; + } + term.top = t; + term.bot = b; +} + +void +tsetmode(int priv, int set, int *args, int narg) +{ + int *lim, mode; + int alt; + + for (lim = args + narg; args < lim; ++args) { + if (priv) { + switch (*args) { + case 1: /* DECCKM -- Cursor key */ + MODBIT(term.mode, set, MODE_APPCURSOR); + break; + case 5: /* DECSCNM -- Reverse video */ + mode = term.mode; + MODBIT(term.mode, set, MODE_REVERSE); + if (mode != term.mode) + redraw(); + break; + case 6: /* DECOM -- Origin */ + MODBIT(term.c.state, set, CURSOR_ORIGIN); + tmoveato(0, 0); + break; + case 7: /* DECAWM -- Auto wrap */ + MODBIT(term.mode, set, MODE_WRAP); + break; + case 0: /* Error (IGNORED) */ + case 2: /* DECANM -- ANSI/VT52 (IGNORED) */ + case 3: /* DECCOLM -- Column (IGNORED) */ + case 4: /* DECSCLM -- Scroll (IGNORED) */ + case 8: /* DECARM -- Auto repeat (IGNORED) */ + case 18: /* DECPFF -- Printer feed (IGNORED) */ + case 19: /* DECPEX -- Printer extent (IGNORED) */ + case 42: /* DECNRCM -- National characters (IGNORED) */ + case 12: /* att610 -- Start blinking cursor (IGNORED) */ + break; + case 25: /* DECTCEM -- Text Cursor Enable Mode */ + MODBIT(term.mode, !set, MODE_HIDE); + break; + case 9: /* X10 mouse compatibility mode */ + xsetpointermotion(0); + MODBIT(term.mode, 0, MODE_MOUSE); + MODBIT(term.mode, set, MODE_MOUSEX10); + break; + case 1000: /* 1000: report button press */ + xsetpointermotion(0); + MODBIT(term.mode, 0, MODE_MOUSE); + MODBIT(term.mode, set, MODE_MOUSEBTN); + break; + case 1002: /* 1002: report motion on button press */ + xsetpointermotion(0); + MODBIT(term.mode, 0, MODE_MOUSE); + MODBIT(term.mode, set, MODE_MOUSEMOTION); + break; + case 1003: /* 1003: enable all mouse motions */ + xsetpointermotion(set); + MODBIT(term.mode, 0, MODE_MOUSE); + MODBIT(term.mode, set, MODE_MOUSEMANY); + break; + case 1004: /* 1004: send focus events to tty */ + MODBIT(term.mode, set, MODE_FOCUS); + break; + case 1006: /* 1006: extended reporting mode */ + MODBIT(term.mode, set, MODE_MOUSESGR); + break; + case 1034: + MODBIT(term.mode, set, MODE_8BIT); + break; + case 1049: /* swap screen & set/restore cursor as xterm */ + if (!allowaltscreen) + break; + tcursor((set) ? CURSOR_SAVE : CURSOR_LOAD); + /* FALLTHROUGH */ + case 47: /* swap screen */ + case 1047: + if (!allowaltscreen) + break; + alt = IS_SET(MODE_ALTSCREEN); + if (alt) { + tclearregion(0, 0, term.col-1, + term.row-1); + } + if (set ^ alt) /* set is always 1 or 0 */ + tswapscreen(); + if (*args != 1049) + break; + /* FALLTHROUGH */ + case 1048: + tcursor((set) ? CURSOR_SAVE : CURSOR_LOAD); + break; + case 2004: /* 2004: bracketed paste mode */ + MODBIT(term.mode, set, MODE_BRCKTPASTE); + break; + /* Not implemented mouse modes. See comments there. */ + case 1001: /* mouse highlight mode; can hang the + terminal by design when implemented. */ + case 1005: /* UTF-8 mouse mode; will confuse + applications not supporting UTF-8 + and luit. */ + case 1015: /* urxvt mangled mouse mode; incompatible + and can be mistaken for other control + codes. */ + default: + fprintf(stderr, + "erresc: unknown private set/reset mode %d\n", + *args); + break; + } + } else { + switch (*args) { + case 0: /* Error (IGNORED) */ + break; + case 2: /* KAM -- keyboard action */ + MODBIT(term.mode, set, MODE_KBDLOCK); + break; + case 4: /* IRM -- Insertion-replacement */ + MODBIT(term.mode, set, MODE_INSERT); + break; + case 12: /* SRM -- Send/Receive */ + MODBIT(term.mode, !set, MODE_ECHO); + break; + case 20: /* LNM -- Linefeed/new line */ + MODBIT(term.mode, set, MODE_CRLF); + break; + default: + fprintf(stderr, + "erresc: unknown set/reset mode %d\n", + *args); + break; + } + } + } +} + +void +csihandle(void) +{ + char buf[40]; + int len; + + switch (csiescseq.mode[0]) { + default: + unknown: + fprintf(stderr, "erresc: unknown csi "); + csidump(); + /* die(""); */ + break; + case '@': /* ICH -- Insert blank char */ + DEFAULT(csiescseq.arg[0], 1); + tinsertblank(csiescseq.arg[0]); + break; + case 'A': /* CUU -- Cursor Up */ + DEFAULT(csiescseq.arg[0], 1); + tmoveto(term.c.x, term.c.y-csiescseq.arg[0]); + break; + case 'B': /* CUD -- Cursor Down */ + case 'e': /* VPR --Cursor Down */ + DEFAULT(csiescseq.arg[0], 1); + tmoveto(term.c.x, term.c.y+csiescseq.arg[0]); + break; + case 'i': /* MC -- Media Copy */ + switch (csiescseq.arg[0]) { + case 0: + tdump(); + break; + case 1: + tdumpline(term.c.y); + break; + case 2: + tdumpsel(); + break; + case 4: + term.mode &= ~MODE_PRINT; + break; + case 5: + term.mode |= MODE_PRINT; + break; + } + break; + case 'c': /* DA -- Device Attributes */ + if (csiescseq.arg[0] == 0) + ttywrite(vtiden, sizeof(vtiden) - 1); + break; + case 'C': /* CUF -- Cursor Forward */ + case 'a': /* HPR -- Cursor Forward */ + DEFAULT(csiescseq.arg[0], 1); + tmoveto(term.c.x+csiescseq.arg[0], term.c.y); + break; + case 'D': /* CUB -- Cursor Backward */ + DEFAULT(csiescseq.arg[0], 1); + tmoveto(term.c.x-csiescseq.arg[0], term.c.y); + break; + case 'E': /* CNL -- Cursor Down and first col */ + DEFAULT(csiescseq.arg[0], 1); + tmoveto(0, term.c.y+csiescseq.arg[0]); + break; + case 'F': /* CPL -- Cursor Up and first col */ + DEFAULT(csiescseq.arg[0], 1); + tmoveto(0, term.c.y-csiescseq.arg[0]); + break; + case 'g': /* TBC -- Tabulation clear */ + switch (csiescseq.arg[0]) { + case 0: /* clear current tab stop */ + term.tabs[term.c.x] = 0; + break; + case 3: /* clear all the tabs */ + memset(term.tabs, 0, term.col * sizeof(*term.tabs)); + break; + default: + goto unknown; + } + break; + case 'G': /* CHA -- Move to */ + case '`': /* HPA */ + DEFAULT(csiescseq.arg[0], 1); + tmoveto(csiescseq.arg[0]-1, term.c.y); + break; + case 'H': /* CUP -- Move to */ + case 'f': /* HVP */ + DEFAULT(csiescseq.arg[0], 1); + DEFAULT(csiescseq.arg[1], 1); + tmoveato(csiescseq.arg[1]-1, csiescseq.arg[0]-1); + break; + case 'I': /* CHT -- Cursor Forward Tabulation tab stops */ + DEFAULT(csiescseq.arg[0], 1); + tputtab(csiescseq.arg[0]); + break; + case 'J': /* ED -- Clear screen */ + selclear(NULL); + switch (csiescseq.arg[0]) { + case 0: /* below */ + tclearregion(term.c.x, term.c.y, term.col-1, term.c.y); + if (term.c.y < term.row-1) { + tclearregion(0, term.c.y+1, term.col-1, + term.row-1); + } + break; + case 1: /* above */ + if (term.c.y > 1) + tclearregion(0, 0, term.col-1, term.c.y-1); + tclearregion(0, term.c.y, term.c.x, term.c.y); + break; + case 2: /* all */ + tclearregion(0, 0, term.col-1, term.row-1); + break; + default: + goto unknown; + } + break; + case 'K': /* EL -- Clear line */ + switch (csiescseq.arg[0]) { + case 0: /* right */ + tclearregion(term.c.x, term.c.y, term.col-1, + term.c.y); + break; + case 1: /* left */ + tclearregion(0, term.c.y, term.c.x, term.c.y); + break; + case 2: /* all */ + tclearregion(0, term.c.y, term.col-1, term.c.y); + break; + } + break; + case 'S': /* SU -- Scroll line up */ + DEFAULT(csiescseq.arg[0], 1); + tscrollup(term.top, csiescseq.arg[0], 0); + break; + case 'T': /* SD -- Scroll line down */ + DEFAULT(csiescseq.arg[0], 1); + tscrolldown(term.top, csiescseq.arg[0], 0); + break; + case 'L': /* IL -- Insert blank lines */ + DEFAULT(csiescseq.arg[0], 1); + tinsertblankline(csiescseq.arg[0]); + break; + case 'l': /* RM -- Reset Mode */ + tsetmode(csiescseq.priv, 0, csiescseq.arg, csiescseq.narg); + break; + case 'M': /* DL -- Delete lines */ + DEFAULT(csiescseq.arg[0], 1); + tdeleteline(csiescseq.arg[0]); + break; + case 'X': /* ECH -- Erase char */ + DEFAULT(csiescseq.arg[0], 1); + tclearregion(term.c.x, term.c.y, + term.c.x + csiescseq.arg[0] - 1, term.c.y); + break; + case 'P': /* DCH -- Delete char */ + DEFAULT(csiescseq.arg[0], 1); + tdeletechar(csiescseq.arg[0]); + break; + case 'Z': /* CBT -- Cursor Backward Tabulation tab stops */ + DEFAULT(csiescseq.arg[0], 1); + tputtab(-csiescseq.arg[0]); + break; + case 'd': /* VPA -- Move to */ + DEFAULT(csiescseq.arg[0], 1); + tmoveato(term.c.x, csiescseq.arg[0]-1); + break; + case 'h': /* SM -- Set terminal mode */ + tsetmode(csiescseq.priv, 1, csiescseq.arg, csiescseq.narg); + break; + case 'm': /* SGR -- Terminal attribute (color) */ + tsetattr(csiescseq.arg, csiescseq.narg); + 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); + ttywrite(buf, len); + } + break; + case 'r': /* DECSTBM -- Set Scrolling Region */ + if (csiescseq.priv) { + goto unknown; + } else { + DEFAULT(csiescseq.arg[0], 1); + DEFAULT(csiescseq.arg[1], term.row); + tsetscroll(csiescseq.arg[0]-1, csiescseq.arg[1]-1); + tmoveato(0, 0); + } + break; + case 's': /* DECSC -- Save cursor position (ANSI.SYS) */ + tcursor(CURSOR_SAVE); + break; + case 'u': /* DECRC -- Restore cursor position (ANSI.SYS) */ + tcursor(CURSOR_LOAD); + break; + case ' ': + switch (csiescseq.mode[1]) { + case 'q': /* DECSCUSR -- Set Cursor Style */ + DEFAULT(csiescseq.arg[0], 1); + if (!BETWEEN(csiescseq.arg[0], 0, 6)) { + goto unknown; + } + xw.cursor = csiescseq.arg[0]; + break; + default: + goto unknown; + } + break; + } +} + +void +csidump(void) +{ + int i; + uint c; + + printf("ESC["); + for (i = 0; i < csiescseq.len; i++) { + c = csiescseq.buf[i] & 0xff; + if (isprint(c)) { + putchar(c); + } else if (c == '\n') { + printf("(\\n)"); + } else if (c == '\r') { + printf("(\\r)"); + } else if (c == 0x1b) { + printf("(\\e)"); + } else { + printf("(%02x)", c); + } + } + putchar('\n'); +} + +void +csireset(void) +{ + memset(&csiescseq, 0, sizeof(csiescseq)); +} + +void +strhandle(void) +{ + char *p = NULL; + int j, narg, par; + + term.esc &= ~(ESC_STR_END|ESC_STR); + strparse(); + par = (narg = strescseq.narg) ? atoi(strescseq.args[0]) : 0; + + switch (strescseq.type) { + case ']': /* OSC -- Operating System Command */ + switch (par) { + case 7: + if (narg > 1 && strescseq.args[1]) + chdir(strescseq.args[1]); + return; + case 0: + case 1: + case 2: + if (narg > 1) + xsettitle(strescseq.args[1]); + return; + case 4: /* color set */ + if (narg < 3) + break; + p = strescseq.args[2]; + /* FALLTHROUGH */ + case 104: /* color reset, here p = NULL */ + j = (narg > 1) ? atoi(strescseq.args[1]) : -1; + if (xsetcolorname(j, p)) { + fprintf(stderr, "erresc: invalid color %s\n", p); + } else { + /* + * TODO if defaultbg color is changed, borders + * are dirty + */ + redraw(); + } + return; + } + break; + case 'k': /* old title set compatibility */ + xsettitle(strescseq.args[0]); + return; + case 'P': /* DCS -- Device Control String */ + case '_': /* APC -- Application Program Command */ + case '^': /* PM -- Privacy Message */ + return; + } + + fprintf(stderr, "erresc: unknown str "); + strdump(); +} + +void +strparse(void) +{ + int c; + char *p = strescseq.buf; + + strescseq.narg = 0; + strescseq.buf[strescseq.len] = '\0'; + + if (*p == '\0') + return; + + while (strescseq.narg < STR_ARG_SIZ) { + strescseq.args[strescseq.narg++] = p; + while ((c = *p) != ';' && c != '\0') + ++p; + if (c == '\0') + return; + *p++ = '\0'; + } +} + +void +strdump(void) +{ + int i; + uint c; + + printf("ESC%c", strescseq.type); + for (i = 0; i < strescseq.len; i++) { + c = strescseq.buf[i] & 0xff; + if (c == '\0') { + return; + } else if (isprint(c)) { + putchar(c); + } else if (c == '\n') { + printf("(\\n)"); + } else if (c == '\r') { + printf("(\\r)"); + } else if (c == 0x1b) { + printf("(\\e)"); + } else { + printf("(%02x)", c); + } + } + printf("ESC\\\n"); +} + +void +strreset(void) +{ + memset(&strescseq, 0, sizeof(strescseq)); +} + +void +sendbreak(const Arg *arg) +{ + if (tcsendbreak(cmdfd, 0)) + perror("Error sending break"); +} + +void +tprinter(char *s, size_t len) +{ + if (iofd != -1 && xwrite(iofd, s, len) < 0) { + fprintf(stderr, "Error writing in %s:%s\n", + opt_io, strerror(errno)); + close(iofd); + iofd = -1; + } +} + +void +toggleprinter(const Arg *arg) +{ + term.mode ^= MODE_PRINT; +} + +void +printscreen(const Arg *arg) +{ + tdump(); +} + +void +printsel(const Arg *arg) +{ + tdumpsel(); +} + +void +tdumpsel(void) +{ + char *ptr; + + if ((ptr = getsel())) { + tprinter(ptr, strlen(ptr)); + free(ptr); + } +} + +void +tdumpline(int n) +{ + char buf[UTF_SIZ]; + Glyph *bp, *end; + + bp = &term.line[n][0]; + end = &bp[MIN(tlinelen(n), term.col) - 1]; + if (bp != end || bp->u != ' ') { + for ( ;bp <= end; ++bp) + tprinter(buf, utf8encode(bp->u, buf)); + } + tprinter("\n", 1); +} + +void +tdump(void) +{ + int i; + + for (i = 0; i < term.row; ++i) + tdumpline(i); +} + +void +tputtab(int n) +{ + uint x = term.c.x; + + if (n > 0) { + while (x < term.col && n--) + for (++x; x < term.col && !term.tabs[x]; ++x) + /* nothing */ ; + } else if (n < 0) { + while (x > 0 && n++) + for (--x; x > 0 && !term.tabs[x]; --x) + /* nothing */ ; + } + term.c.x = LIMIT(x, 0, term.col-1); +} + +void +techo(Rune u) +{ + if (ISCONTROL(u)) { /* control code */ + if (u & 0x80) { + u &= 0x7f; + tputc('^'); + tputc('['); + } else if (u != '\n' && u != '\r' && u != '\t') { + u ^= 0x40; + tputc('^'); + } + } + tputc(u); +} + +void +tdeftran(char ascii) +{ + static char cs[] = "0B"; + static int vcs[] = {CS_GRAPHIC0, CS_USA}; + char *p; + + if ((p = strchr(cs, ascii)) == NULL) { + fprintf(stderr, "esc unhandled charset: ESC ( %c\n", ascii); + } else { + term.trantbl[term.icharset] = vcs[p - cs]; + } +} + +void +tdectest(char c) +{ + int x, y; + + if (c == '8') { /* DEC screen alignment test. */ + for (x = 0; x < term.col; ++x) { + for (y = 0; y < term.row; ++y) + tsetchar('E', &term.c.attr, x, y); + } + } +} + +void +tstrsequence(uchar c) +{ + switch (c) { + case 0x90: /* DCS -- Device Control String */ + c = 'P'; + break; + case 0x9f: /* APC -- Application Program Command */ + c = '_'; + break; + case 0x9e: /* PM -- Privacy Message */ + c = '^'; + break; + case 0x9d: /* OSC -- Operating System Command */ + c = ']'; + break; + } + strreset(); + strescseq.type = c; + term.esc |= ESC_STR; +} + +void +tcontrolcode(uchar ascii) +{ + switch (ascii) { + case '\t': /* HT */ + tputtab(1); + return; + case '\b': /* BS */ + tmoveto(term.c.x-1, term.c.y); + return; + case '\r': /* CR */ + tmoveto(0, term.c.y); + return; + case '\f': /* LF */ + case '\v': /* VT */ + case '\n': /* LF */ + /* go to first col if the mode is set */ + tnewline(IS_SET(MODE_CRLF)); + return; + case '\a': /* BEL */ + if (term.esc & ESC_STR_END) { + /* backwards compatibility to xterm */ + strhandle(); + } else { + if (!(xw.state & WIN_FOCUSED)) + xseturgency(1); + if (bellvolume) + XkbBell(xw.dpy, xw.win, bellvolume, (Atom)NULL); + } + break; + case '\033': /* ESC */ + csireset(); + term.esc &= ~(ESC_CSI|ESC_ALTCHARSET|ESC_TEST); + term.esc |= ESC_START; + return; + case '\016': /* SO (LS1 -- Locking shift 1) */ + case '\017': /* SI (LS0 -- Locking shift 0) */ + term.charset = 1 - (ascii - '\016'); + return; + case '\032': /* SUB */ + tsetchar('?', &term.c.attr, term.c.x, term.c.y); + /* FALLTHRU */ + case '\030': /* CAN */ + csireset(); + break; + case '\005': /* ENQ (IGNORED) */ + case '\000': /* NUL (IGNORED) */ + case '\021': /* XON (IGNORED) */ + case '\023': /* XOFF (IGNORED) */ + case 0177: /* DEL (IGNORED) */ + return; + case 0x80: /* TODO: PAD */ + case 0x81: /* TODO: HOP */ + case 0x82: /* TODO: BPH */ + case 0x83: /* TODO: NBH */ + case 0x84: /* TODO: IND */ + break; + case 0x85: /* NEL -- Next line */ + tnewline(1); /* always go to first col */ + break; + case 0x86: /* TODO: SSA */ + case 0x87: /* TODO: ESA */ + break; + case 0x88: /* HTS -- Horizontal tab stop */ + term.tabs[term.c.x] = 1; + break; + case 0x89: /* TODO: HTJ */ + case 0x8a: /* TODO: VTS */ + case 0x8b: /* TODO: PLD */ + case 0x8c: /* TODO: PLU */ + case 0x8d: /* TODO: RI */ + case 0x8e: /* TODO: SS2 */ + case 0x8f: /* TODO: SS3 */ + case 0x91: /* TODO: PU1 */ + case 0x92: /* TODO: PU2 */ + case 0x93: /* TODO: STS */ + case 0x94: /* TODO: CCH */ + case 0x95: /* TODO: MW */ + case 0x96: /* TODO: SPA */ + case 0x97: /* TODO: EPA */ + case 0x98: /* TODO: SOS */ + case 0x99: /* TODO: SGCI */ + break; + case 0x9a: /* DECID -- Identify Terminal */ + ttywrite(vtiden, sizeof(vtiden) - 1); + break; + case 0x9b: /* TODO: CSI */ + case 0x9c: /* TODO: ST */ + break; + case 0x90: /* DCS -- Device Control String */ + case 0x9d: /* OSC -- Operating System Command */ + case 0x9e: /* PM -- Privacy Message */ + case 0x9f: /* APC -- Application Program Command */ + tstrsequence(ascii); + return; + } + /* only CAN, SUB, \a and C1 chars interrupt a sequence */ + term.esc &= ~(ESC_STR_END|ESC_STR); +} + +/* + * returns 1 when the sequence is finished and it hasn't to read + * more characters for this sequence, otherwise 0 + */ +int +eschandle(uchar ascii) +{ + switch (ascii) { + case '[': + term.esc |= ESC_CSI; + return 0; + case '#': + term.esc |= ESC_TEST; + return 0; + case 'P': /* DCS -- Device Control String */ + case '_': /* APC -- Application Program Command */ + case '^': /* PM -- Privacy Message */ + case ']': /* OSC -- Operating System Command */ + case 'k': /* old title set compatibility */ + tstrsequence(ascii); + return 0; + case 'n': /* LS2 -- Locking shift 2 */ + case 'o': /* LS3 -- Locking shift 3 */ + term.charset = 2 + (ascii - 'n'); + break; + case '(': /* GZD4 -- set primary charset G0 */ + case ')': /* G1D4 -- set secondary charset G1 */ + case '*': /* G2D4 -- set tertiary charset G2 */ + case '+': /* G3D4 -- set quaternary charset G3 */ + term.icharset = ascii - '('; + term.esc |= ESC_ALTCHARSET; + return 0; + case 'D': /* IND -- Linefeed */ + if (term.c.y == term.bot) { + tscrollup(term.top, 1, 1); + } else { + tmoveto(term.c.x, term.c.y+1); + } + break; + case 'E': /* NEL -- Next line */ + tnewline(1); /* always go to first col */ + break; + case 'H': /* HTS -- Horizontal tab stop */ + term.tabs[term.c.x] = 1; + break; + case 'M': /* RI -- Reverse index */ + if (term.c.y == term.top) { + tscrolldown(term.top, 1, 1); + } else { + tmoveto(term.c.x, term.c.y-1); + } + break; + case 'Z': /* DECID -- Identify Terminal */ + ttywrite(vtiden, sizeof(vtiden) - 1); + break; + case 'c': /* RIS -- Reset to inital state */ + treset(); + xresettitle(); + xloadcols(); + break; + case '=': /* DECPAM -- Application keypad */ + term.mode |= MODE_APPKEYPAD; + break; + case '>': /* DECPNM -- Normal keypad */ + term.mode &= ~MODE_APPKEYPAD; + break; + case '7': /* DECSC -- Save Cursor */ + tcursor(CURSOR_SAVE); + break; + case '8': /* DECRC -- Restore Cursor */ + tcursor(CURSOR_LOAD); + break; + case '\\': /* ST -- String Terminator */ + if (term.esc & ESC_STR_END) + strhandle(); + break; + default: + fprintf(stderr, "erresc: unknown sequence ESC 0x%02X '%c'\n", + (uchar) ascii, isprint(ascii)? ascii:'.'); + break; + } + return 1; +} + +void +tputc(Rune u) +{ + char c[UTF_SIZ]; + int control; + int width, len; + Glyph *gp; + + control = ISCONTROL(u); + len = utf8encode(u, c); + if (!control && (width = wcwidth(u)) == -1) { + memcpy(c, "\357\277\275", 4); /* UTF_INVALID */ + width = 1; + } + + if (IS_SET(MODE_PRINT)) + tprinter(c, len); + + /* + * STR sequence must be checked before anything else + * because it uses all following characters until it + * receives a ESC, a SUB, a ST or any other C1 control + * character. + */ + if (term.esc & ESC_STR) { + if (u == '\a' || u == 030 || u == 032 || u == 033 || + ISCONTROLC1(u)) { + term.esc &= ~(ESC_START|ESC_STR); + term.esc |= ESC_STR_END; + } else if (strescseq.len + len < sizeof(strescseq.buf) - 1) { + memmove(&strescseq.buf[strescseq.len], c, len); + strescseq.len += len; + return; + } else { + /* + * Here is a bug in terminals. If the user never sends + * some code to stop the str or esc command, then st + * will stop responding. But this is better than + * silently failing with unknown characters. At least + * then users will report back. + * + * In the case users ever get fixed, here is the code: + */ + /* + * term.esc = 0; + * strhandle(); + */ + return; + } + } + + /* + * Actions of control codes must be performed as soon they arrive + * because they can be embedded inside a control sequence, and + * they must not cause conflicts with sequences. + */ + if (control) { + tcontrolcode(u); + /* + * control codes are not shown ever + */ + return; + } else if (term.esc & ESC_START) { + if (term.esc & ESC_CSI) { + csiescseq.buf[csiescseq.len++] = u; + if (BETWEEN(u, 0x40, 0x7E) + || csiescseq.len >= \ + sizeof(csiescseq.buf)-1) { + term.esc = 0; + csiparse(); + csihandle(); + } + return; + } else if (term.esc & ESC_ALTCHARSET) { + tdeftran(u); + } else if (term.esc & ESC_TEST) { + tdectest(u); + } else { + if (!eschandle(u)) + return; + /* sequence already finished */ + } + term.esc = 0; + /* + * All characters which form part of a sequence are not + * printed + */ + return; + } + if (sel.ob.x != -1 && BETWEEN(term.c.y, sel.ob.y, sel.oe.y)) + selclear(NULL); + + gp = &term.line[term.c.y][term.c.x]; + if (IS_SET(MODE_WRAP) && (term.c.state & CURSOR_WRAPNEXT)) { + gp->mode |= ATTR_WRAP; + tnewline(1); + gp = &term.line[term.c.y][term.c.x]; + } + + if (IS_SET(MODE_INSERT) && term.c.x+width < term.col) + memmove(gp+width, gp, (term.col - term.c.x - width) * sizeof(Glyph)); + + if (term.c.x+width > term.col) { + tnewline(1); + gp = &term.line[term.c.y][term.c.x]; + } + + tsetchar(u, &term.c.attr, term.c.x, term.c.y); + + if (width == 2) { + gp->mode |= ATTR_WIDE; + if (term.c.x+1 < term.col) { + gp[1].u = '\0'; + gp[1].mode = ATTR_WDUMMY; + } + } + if (term.c.x+width < term.col) { + tmoveto(term.c.x+width, term.c.y); + } else { + term.c.state |= CURSOR_WRAPNEXT; + } +} + +void +tresize(int col, int row) +{ + int i, j; + int minrow = MIN(row, term.row); + int mincol = MIN(col, term.col); + int *bp; + TCursor c; + + if (col < 1 || row < 1) { + fprintf(stderr, + "tresize: error resizing to %dx%d\n", col, row); + return; + } + + /* + * slide screen to keep cursor where we expect it - + * tscrollup would work here, but we can optimize to + * memmove because we're freeing the earlier lines + */ + for (i = 0; i <= term.c.y - row; i++) { + free(term.line[i]); + free(term.alt[i]); + } + /* ensure that both src and dst are not NULL */ + if (i > 0) { + memmove(term.line, term.line + i, row * sizeof(Line)); + memmove(term.alt, term.alt + i, row * sizeof(Line)); + } + for (i += row; i < term.row; i++) { + free(term.line[i]); + free(term.alt[i]); + } + + /* resize to new width */ + term.specbuf = xrealloc(term.specbuf, col * sizeof(XftGlyphFontSpec)); + + /* resize to new height */ + term.line = xrealloc(term.line, row * sizeof(Line)); + term.alt = xrealloc(term.alt, row * sizeof(Line)); + term.dirty = xrealloc(term.dirty, row * sizeof(*term.dirty)); + term.tabs = xrealloc(term.tabs, col * sizeof(*term.tabs)); + + for (i = 0; i < histsize; i++) { + term.hist[i] = xrealloc(term.hist[i], col * sizeof(Glyph)); + for (j = mincol; j < col; j++) { + term.hist[i][j] = term.c.attr; + term.hist[i][j].u = ' '; + } + } + + /* resize each row to new width, zero-pad if needed */ + for (i = 0; i < minrow; i++) { + term.line[i] = xrealloc(term.line[i], col * sizeof(Glyph)); + term.alt[i] = xrealloc(term.alt[i], col * sizeof(Glyph)); + } + + /* allocate any new rows */ + for (/* i == minrow */; i < row; i++) { + term.line[i] = xmalloc(col * sizeof(Glyph)); + term.alt[i] = xmalloc(col * sizeof(Glyph)); + } + if (col > term.col) { + bp = term.tabs + term.col; + + memset(bp, 0, sizeof(*term.tabs) * (col - term.col)); + while (--bp > term.tabs && !*bp) + /* nothing */ ; + for (bp += tabspaces; bp < term.tabs + col; bp += tabspaces) + *bp = 1; + } + /* update terminal size */ + term.col = col; + term.row = row; + /* reset scrolling region */ + tsetscroll(0, row-1); + /* make use of the LIMIT in tmoveto */ + tmoveto(term.c.x, term.c.y); + /* Clearing both screens (it makes dirty all lines) */ + c = term.c; + for (i = 0; i < 2; i++) { + if (mincol < col && 0 < minrow) { + tclearregion(mincol, 0, col - 1, minrow - 1); + } + if (0 < col && minrow < row) { + tclearregion(0, minrow, col - 1, row - 1); + } + tswapscreen(); + tcursor(CURSOR_LOAD); + } + term.c = c; +} + +void +xresize(int col, int row) +{ + xw.tw = MAX(1, col * xw.cw); + xw.th = MAX(1, row * xw.ch); + + XFreePixmap(xw.dpy, xw.buf); + xw.buf = XCreatePixmap(xw.dpy, xw.win, xw.w, xw.h, + DefaultDepth(xw.dpy, xw.scr)); + XftDrawChange(xw.draw, xw.buf); + xclear(0, 0, xw.w, xw.h); +} + +ushort +sixd_to_16bit(int x) +{ + return x == 0 ? 0 : 0x3737 + 0x2828 * x; +} + +int +xloadcolor(int i, const char *name, Color *ncolor) +{ + XRenderColor color = { .alpha = 0xffff }; + + if (!name) { + if (BETWEEN(i, 16, 255)) { /* 256 color */ + if (i < 6*6*6+16) { /* same colors as xterm */ + color.red = sixd_to_16bit( ((i-16)/36)%6 ); + color.green = sixd_to_16bit( ((i-16)/6) %6 ); + color.blue = sixd_to_16bit( ((i-16)/1) %6 ); + } else { /* greyscale */ + color.red = 0x0808 + 0x0a0a * (i - (6*6*6+16)); + color.green = color.blue = color.red; + } + return XftColorAllocValue(xw.dpy, xw.vis, + xw.cmap, &color, ncolor); + } else + name = colorname[i]; + } + + return XftColorAllocName(xw.dpy, xw.vis, xw.cmap, name, ncolor); +} + +void +xloadcols(void) +{ + int i; + static int loaded; + Color *cp; + + if (loaded) { + for (cp = dc.col; cp < &dc.col[LEN(dc.col)]; ++cp) + XftColorFree(xw.dpy, xw.vis, xw.cmap, cp); + } + + for (i = 0; i < LEN(dc.col); i++) + if (!xloadcolor(i, NULL, &dc.col[i])) { + if (colorname[i]) + die("Could not allocate color '%s'\n", colorname[i]); + else + die("Could not allocate color %d\n", i); + } + loaded = 1; +} + +int +xsetcolorname(int x, const char *name) +{ + Color ncolor; + + if (!BETWEEN(x, 0, LEN(dc.col))) + return 1; + + + if (!xloadcolor(x, name, &ncolor)) + return 1; + + XftColorFree(xw.dpy, xw.vis, xw.cmap, &dc.col[x]); + dc.col[x] = ncolor; + + return 0; +} + +/* + * Absolute coordinates. + */ +void +xclear(int x1, int y1, int x2, int y2) +{ + XftDrawRect(xw.draw, + &dc.col[IS_SET(MODE_REVERSE)? defaultfg : defaultbg], + x1, y1, x2-x1, y2-y1); +} + +void +xhints(void) +{ + XClassHint class = {opt_name ? opt_name : termname, + opt_class ? opt_class : termname}; + XWMHints wm = {.flags = InputHint, .input = 1}; + XSizeHints *sizeh = NULL; + + sizeh = XAllocSizeHints(); + + sizeh->flags = PSize | PResizeInc | PBaseSize; + sizeh->height = xw.h; + sizeh->width = xw.w; + sizeh->height_inc = xw.ch; + sizeh->width_inc = xw.cw; + sizeh->base_height = 2 * borderpx; + sizeh->base_width = 2 * borderpx; + if (xw.isfixed) { + sizeh->flags |= PMaxSize | PMinSize; + sizeh->min_width = sizeh->max_width = xw.w; + sizeh->min_height = sizeh->max_height = xw.h; + } + if (xw.gm & (XValue|YValue)) { + sizeh->flags |= USPosition | PWinGravity; + sizeh->x = xw.l; + sizeh->y = xw.t; + sizeh->win_gravity = xgeommasktogravity(xw.gm); + } + + XSetWMProperties(xw.dpy, xw.win, NULL, NULL, NULL, 0, sizeh, &wm, + &class); + XFree(sizeh); +} + +int +xgeommasktogravity(int mask) +{ + switch (mask & (XNegative|YNegative)) { + case 0: + return NorthWestGravity; + case XNegative: + return NorthEastGravity; + case YNegative: + return SouthWestGravity; + } + + return SouthEastGravity; +} + +int +xloadfont(Font *f, FcPattern *pattern) +{ + FcPattern *match; + FcResult result; + XGlyphInfo extents; + + match = XftFontMatch(xw.dpy, xw.scr, pattern, &result); + if (!match) + return 1; + + if (!(f->match = XftFontOpenPattern(xw.dpy, match))) { + FcPatternDestroy(match); + return 1; + } + + XftTextExtentsUtf8(xw.dpy, f->match, + (const FcChar8 *) ascii_printable, + strlen(ascii_printable), &extents); + + f->set = NULL; + f->pattern = FcPatternDuplicate(pattern); + + f->ascent = f->match->ascent; + f->descent = f->match->descent; + f->lbearing = 0; + f->rbearing = f->match->max_advance_width; + + f->height = f->ascent + f->descent; + f->width = DIVCEIL(extents.xOff, strlen(ascii_printable)); + + return 0; +} + +void +xloadfonts(char *fontstr, double fontsize) +{ + FcPattern *pattern; + double fontval; + float ceilf(float); + + if (fontstr[0] == '-') { + pattern = XftXlfdParse(fontstr, False, False); + } else { + pattern = FcNameParse((FcChar8 *)fontstr); + } + + if (!pattern) + die("st: can't open font %s\n", fontstr); + + if (fontsize > 1) { + FcPatternDel(pattern, FC_PIXEL_SIZE); + FcPatternDel(pattern, FC_SIZE); + FcPatternAddDouble(pattern, FC_PIXEL_SIZE, (double)fontsize); + usedfontsize = fontsize; + } else { + if (FcPatternGetDouble(pattern, FC_PIXEL_SIZE, 0, &fontval) == + FcResultMatch) { + usedfontsize = fontval; + } else if (FcPatternGetDouble(pattern, FC_SIZE, 0, &fontval) == + FcResultMatch) { + usedfontsize = -1; + } else { + /* + * Default font size is 12, if none given. This is to + * have a known usedfontsize value. + */ + FcPatternAddDouble(pattern, FC_PIXEL_SIZE, 12); + usedfontsize = 12; + } + defaultfontsize = usedfontsize; + } + + if (xloadfont(&dc.font, pattern)) + die("st: can't open font %s\n", fontstr); + + if (usedfontsize < 0) { + FcPatternGetDouble(dc.font.match->pattern, + FC_PIXEL_SIZE, 0, &fontval); + usedfontsize = fontval; + if (fontsize == 0) + defaultfontsize = fontval; + } + + /* Setting character width and height. */ + xw.cw = ceilf(dc.font.width * cwscale); + xw.ch = ceilf(dc.font.height * chscale); + + FcPatternDel(pattern, FC_SLANT); + FcPatternAddInteger(pattern, FC_SLANT, FC_SLANT_ITALIC); + if (xloadfont(&dc.ifont, pattern)) + die("st: can't open font %s\n", fontstr); + + FcPatternDel(pattern, FC_WEIGHT); + FcPatternAddInteger(pattern, FC_WEIGHT, FC_WEIGHT_BOLD); + if (xloadfont(&dc.ibfont, pattern)) + die("st: can't open font %s\n", fontstr); + + FcPatternDel(pattern, FC_SLANT); + FcPatternAddInteger(pattern, FC_SLANT, FC_SLANT_ROMAN); + if (xloadfont(&dc.bfont, pattern)) + die("st: can't open font %s\n", fontstr); + + FcPatternDestroy(pattern); +} + +void +xunloadfont(Font *f) +{ + XftFontClose(xw.dpy, f->match); + FcPatternDestroy(f->pattern); + if (f->set) + FcFontSetDestroy(f->set); +} + +void +xunloadfonts(void) +{ + /* Free the loaded fonts in the font cache. */ + while (frclen > 0) + XftFontClose(xw.dpy, frc[--frclen].font); + + xunloadfont(&dc.font); + xunloadfont(&dc.bfont); + xunloadfont(&dc.ifont); + xunloadfont(&dc.ibfont); +} + +void +xzoom(const Arg *arg) +{ + Arg larg; + + larg.f = usedfontsize + arg->f; + xzoomabs(&larg); +} + +void +xzoomabs(const Arg *arg) +{ + xunloadfonts(); + xloadfonts(usedfont, arg->f); + cresize(0, 0); + ttyresize(); + redraw(); + xhints(); +} + +void +xzoomreset(const Arg *arg) +{ + Arg larg; + + if (defaultfontsize > 0) { + larg.f = defaultfontsize; + xzoomabs(&larg); + } +} + +void +xinit(void) +{ + XGCValues gcvalues; + Cursor cursor; + Window parent; + pid_t thispid = getpid(); + XColor xmousefg, xmousebg; + + if (!(xw.dpy = XOpenDisplay(NULL))) + die("Can't open display\n"); + xw.scr = XDefaultScreen(xw.dpy); + xw.vis = XDefaultVisual(xw.dpy, xw.scr); + + /* font */ + if (!FcInit()) + die("Could not init fontconfig.\n"); + + usedfont = (opt_font == NULL)? font : opt_font; + xloadfonts(usedfont, 0); + + /* colors */ + xw.cmap = XDefaultColormap(xw.dpy, xw.scr); + xloadcols(); + + /* adjust fixed window geometry */ + xw.w = 2 * borderpx + term.col * xw.cw; + xw.h = 2 * borderpx + term.row * xw.ch; + if (xw.gm & XNegative) + xw.l += DisplayWidth(xw.dpy, xw.scr) - xw.w - 2; + if (xw.gm & YNegative) + xw.t += DisplayHeight(xw.dpy, xw.scr) - xw.h - 2; + + /* Events */ + xw.attrs.background_pixel = dc.col[defaultbg].pixel; + xw.attrs.border_pixel = dc.col[defaultbg].pixel; + xw.attrs.bit_gravity = NorthWestGravity; + xw.attrs.event_mask = FocusChangeMask | KeyPressMask + | ExposureMask | VisibilityChangeMask | StructureNotifyMask + | ButtonMotionMask | ButtonPressMask | ButtonReleaseMask; + xw.attrs.colormap = xw.cmap; + + if (!(opt_embed && (parent = strtol(opt_embed, NULL, 0)))) + parent = XRootWindow(xw.dpy, xw.scr); + xw.win = XCreateWindow(xw.dpy, parent, xw.l, xw.t, + xw.w, xw.h, 0, XDefaultDepth(xw.dpy, xw.scr), InputOutput, + xw.vis, CWBackPixel | CWBorderPixel | CWBitGravity + | CWEventMask | CWColormap, &xw.attrs); + + memset(&gcvalues, 0, sizeof(gcvalues)); + gcvalues.graphics_exposures = False; + dc.gc = XCreateGC(xw.dpy, parent, GCGraphicsExposures, + &gcvalues); + xw.buf = XCreatePixmap(xw.dpy, xw.win, xw.w, xw.h, + DefaultDepth(xw.dpy, xw.scr)); + XSetForeground(xw.dpy, dc.gc, dc.col[defaultbg].pixel); + XFillRectangle(xw.dpy, xw.buf, dc.gc, 0, 0, xw.w, xw.h); + + /* Xft rendering context */ + xw.draw = XftDrawCreate(xw.dpy, xw.buf, xw.vis, xw.cmap); + + /* input methods */ + if ((xw.xim = XOpenIM(xw.dpy, NULL, NULL, NULL)) == NULL) { + XSetLocaleModifiers("@im=local"); + if ((xw.xim = XOpenIM(xw.dpy, NULL, NULL, NULL)) == NULL) { + XSetLocaleModifiers("@im="); + if ((xw.xim = XOpenIM(xw.dpy, + NULL, NULL, NULL)) == NULL) { + die("XOpenIM failed. Could not open input" + " device.\n"); + } + } + } + xw.xic = XCreateIC(xw.xim, XNInputStyle, XIMPreeditNothing + | XIMStatusNothing, XNClientWindow, xw.win, + XNFocusWindow, xw.win, NULL); + if (xw.xic == NULL) + die("XCreateIC failed. Could not obtain input method.\n"); + + /* white cursor, black outline */ + cursor = XCreateFontCursor(xw.dpy, mouseshape); + XDefineCursor(xw.dpy, xw.win, cursor); + + if (XParseColor(xw.dpy, xw.cmap, colorname[mousefg], &xmousefg) == 0) { + xmousefg.red = 0xffff; + xmousefg.green = 0xffff; + xmousefg.blue = 0xffff; + } + + if (XParseColor(xw.dpy, xw.cmap, colorname[mousebg], &xmousebg) == 0) { + xmousebg.red = 0x0000; + xmousebg.green = 0x0000; + xmousebg.blue = 0x0000; + } + + XRecolorCursor(xw.dpy, cursor, &xmousefg, &xmousebg); + + xw.xembed = XInternAtom(xw.dpy, "_XEMBED", False); + xw.wmdeletewin = XInternAtom(xw.dpy, "WM_DELETE_WINDOW", False); + xw.netwmname = XInternAtom(xw.dpy, "_NET_WM_NAME", False); + XSetWMProtocols(xw.dpy, xw.win, &xw.wmdeletewin, 1); + + xw.netwmpid = XInternAtom(xw.dpy, "_NET_WM_PID", False); + XChangeProperty(xw.dpy, xw.win, xw.netwmpid, XA_CARDINAL, 32, + PropModeReplace, (uchar *)&thispid, 1); + + xresettitle(); + XMapWindow(xw.dpy, xw.win); + xhints(); + XSync(xw.dpy, False); +} + +int +xmakeglyphfontspecs(XftGlyphFontSpec *specs, const Glyph *glyphs, int len, int x, int y) +{ + float winx = borderpx + x * xw.cw, winy = borderpx + y * xw.ch, xp, yp; + ushort mode, prevmode = USHRT_MAX; + Font *font = &dc.font; + int frcflags = FRC_NORMAL; + float runewidth = xw.cw; + Rune rune; + FT_UInt glyphidx; + FcResult fcres; + FcPattern *fcpattern, *fontpattern; + FcFontSet *fcsets[] = { NULL }; + FcCharSet *fccharset; + int i, f, numspecs = 0; + + for (i = 0, xp = winx, yp = winy + font->ascent; i < len; ++i) { + /* Fetch rune and mode for current glyph. */ + rune = glyphs[i].u; + mode = glyphs[i].mode; + + /* Skip dummy wide-character spacing. */ + if (mode == ATTR_WDUMMY) + continue; + + /* Determine font for glyph if different from previous glyph. */ + if (prevmode != mode) { + prevmode = mode; + font = &dc.font; + frcflags = FRC_NORMAL; + runewidth = xw.cw * ((mode & ATTR_WIDE) ? 2.0f : 1.0f); + if ((mode & ATTR_ITALIC) && (mode & ATTR_BOLD)) { + font = &dc.ibfont; + frcflags = FRC_ITALICBOLD; + } else if (mode & ATTR_ITALIC) { + font = &dc.ifont; + frcflags = FRC_ITALIC; + } else if (mode & ATTR_BOLD) { + font = &dc.bfont; + frcflags = FRC_BOLD; + } + yp = winy + font->ascent; + } + + /* Lookup character index with default font. */ + glyphidx = XftCharIndex(xw.dpy, font->match, rune); + if (glyphidx) { + specs[numspecs].font = font->match; + specs[numspecs].glyph = glyphidx; + specs[numspecs].x = (short)xp; + specs[numspecs].y = (short)yp; + xp += runewidth; + numspecs++; + continue; + } + + /* Fallback on font cache, search the font cache for match. */ + for (f = 0; f < frclen; f++) { + glyphidx = XftCharIndex(xw.dpy, frc[f].font, rune); + /* Everything correct. */ + if (glyphidx && frc[f].flags == frcflags) + break; + /* We got a default font for a not found glyph. */ + if (!glyphidx && frc[f].flags == frcflags + && frc[f].unicodep == rune) { + break; + } + } + + /* Nothing was found. Use fontconfig to find matching font. */ + if (f >= frclen) { + if (!font->set) + font->set = FcFontSort(0, font->pattern, + 1, 0, &fcres); + fcsets[0] = font->set; + + /* + * Nothing was found in the cache. Now use + * some dozen of Fontconfig calls to get the + * font for one single character. + * + * Xft and fontconfig are design failures. + */ + fcpattern = FcPatternDuplicate(font->pattern); + fccharset = FcCharSetCreate(); + + FcCharSetAddChar(fccharset, rune); + FcPatternAddCharSet(fcpattern, FC_CHARSET, + fccharset); + FcPatternAddBool(fcpattern, FC_SCALABLE, 1); + + FcConfigSubstitute(0, fcpattern, + FcMatchPattern); + FcDefaultSubstitute(fcpattern); + + fontpattern = FcFontSetMatch(0, fcsets, 1, + fcpattern, &fcres); + + /* + * Overwrite or create the new cache entry. + */ + if (frclen >= LEN(frc)) { + frclen = LEN(frc) - 1; + XftFontClose(xw.dpy, frc[frclen].font); + frc[frclen].unicodep = 0; + } + + frc[frclen].font = XftFontOpenPattern(xw.dpy, + fontpattern); + frc[frclen].flags = frcflags; + frc[frclen].unicodep = rune; + + glyphidx = XftCharIndex(xw.dpy, frc[frclen].font, rune); + + f = frclen; + frclen++; + + FcPatternDestroy(fcpattern); + FcCharSetDestroy(fccharset); + } + + specs[numspecs].font = frc[f].font; + specs[numspecs].glyph = glyphidx; + specs[numspecs].x = (short)xp; + specs[numspecs].y = (short)yp; + xp += runewidth; + numspecs++; + } + + return numspecs; +} + +void +xdrawglyphfontspecs(const XftGlyphFontSpec *specs, Glyph base, int len, int x, int y) +{ + int charlen = len * ((base.mode & ATTR_WIDE) ? 2 : 1); + int winx = borderpx + x * xw.cw, winy = borderpx + y * xw.ch, + width = charlen * xw.cw; + Color *fg, *bg, *temp, revfg, revbg, truefg, truebg; + XRenderColor colfg, colbg; + XRectangle r; + + /* Determine foreground and background colors based on mode. */ + if (base.fg == defaultfg) { + if (base.mode & ATTR_ITALIC) + base.fg = defaultitalic; + else if ((base.mode & ATTR_ITALIC) && (base.mode & ATTR_BOLD)) + base.fg = defaultitalic; + else if (base.mode & ATTR_UNDERLINE) + base.fg = defaultunderline; + } + + if (IS_TRUECOL(base.fg)) { + colfg.alpha = 0xffff; + colfg.red = TRUERED(base.fg); + colfg.green = TRUEGREEN(base.fg); + colfg.blue = TRUEBLUE(base.fg); + XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colfg, &truefg); + fg = &truefg; + } else { + fg = &dc.col[base.fg]; + } + + if (IS_TRUECOL(base.bg)) { + colbg.alpha = 0xffff; + colbg.green = TRUEGREEN(base.bg); + colbg.red = TRUERED(base.bg); + colbg.blue = TRUEBLUE(base.bg); + XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colbg, &truebg); + bg = &truebg; + } else { + bg = &dc.col[base.bg]; + } + + /* Change basic system colors [0-7] to bright system colors [8-15] */ + if ((base.mode & ATTR_BOLD_FAINT) == ATTR_BOLD && BETWEEN(base.fg, 0, 7)) + fg = &dc.col[base.fg]; + + if (IS_SET(MODE_REVERSE)) { + if (fg == &dc.col[defaultfg]) { + fg = &dc.col[defaultbg]; + } else { + colfg.red = ~fg->color.red; + colfg.green = ~fg->color.green; + colfg.blue = ~fg->color.blue; + colfg.alpha = fg->color.alpha; + XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colfg, + &revfg); + fg = &revfg; + } + + if (bg == &dc.col[defaultbg]) { + bg = &dc.col[defaultfg]; + } else { + colbg.red = ~bg->color.red; + colbg.green = ~bg->color.green; + colbg.blue = ~bg->color.blue; + colbg.alpha = bg->color.alpha; + XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colbg, + &revbg); + bg = &revbg; + } + } + + if (base.mode & ATTR_REVERSE) { + temp = fg; + fg = bg; + bg = temp; + } + + if ((base.mode & ATTR_BOLD_FAINT) == ATTR_FAINT) { + colfg.red = fg->color.red / 2; + colfg.green = fg->color.green / 2; + colfg.blue = fg->color.blue / 2; + XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colfg, &revfg); + fg = &revfg; + } + + if (base.mode & ATTR_BLINK && term.mode & MODE_BLINK) + fg = bg; + + if (base.mode & ATTR_INVISIBLE) + fg = bg; + + /* Intelligent cleaning up of the borders. */ + if (x == 0) { + xclear(0, (y == 0)? 0 : winy, borderpx, + winy + xw.ch + ((y >= term.row-1)? xw.h : 0)); + } + if (x + charlen >= term.col) { + xclear(winx + width, (y == 0)? 0 : winy, xw.w, + ((y >= term.row-1)? xw.h : (winy + xw.ch))); + } + if (y == 0) + xclear(winx, 0, winx + width, borderpx); + if (y == term.row-1) + xclear(winx, winy + xw.ch, winx + width, xw.h); + + /* Clean up the region we want to draw to. */ + XftDrawRect(xw.draw, bg, winx, winy, width, xw.ch); + + /* Set the clip region because Xft is sometimes dirty. */ + r.x = 0; + r.y = 0; + r.height = xw.ch; + r.width = width; + XftDrawSetClipRectangles(xw.draw, winx, winy, &r, 1); + + /* Render the glyphs. */ + XftDrawGlyphFontSpec(xw.draw, fg, specs, len); + + /* Render underline and strikethrough. */ + if (base.mode & ATTR_UNDERLINE) { + XftDrawRect(xw.draw, fg, winx, winy + dc.font.ascent + 1, + width, 1); + } + + if (base.mode & ATTR_STRUCK) { + XftDrawRect(xw.draw, fg, winx, winy + 2 * dc.font.ascent / 3, + width, 1); + } + + /* Reset clip to none. */ + XftDrawSetClip(xw.draw, 0); +} + +void +xdrawglyph(Glyph g, int x, int y) +{ + int numspecs; + XftGlyphFontSpec spec; + + numspecs = xmakeglyphfontspecs(&spec, &g, 1, x, y); + xdrawglyphfontspecs(&spec, g, numspecs, x, y); +} + +void +xdrawcursor(void) +{ + static int oldx = 0, oldy = 0; + int curx; + Glyph g = {' ', ATTR_NULL, defaultbg, defaultcs}, og; + int ena_sel = sel.ob.x != -1 && sel.alt == IS_SET(MODE_ALTSCREEN); + Color drawcol; + + LIMIT(oldx, 0, term.col-1); + LIMIT(oldy, 0, term.row-1); + + curx = term.c.x; + + /* adjust position if in dummy */ + if (term.line[oldy][oldx].mode & ATTR_WDUMMY) + oldx--; + if (term.line[term.c.y][curx].mode & ATTR_WDUMMY) + curx--; + + /* remove the old cursor */ + og = term.line[oldy][oldx]; + if (ena_sel && selected(oldx, oldy)) + og.mode ^= ATTR_REVERSE; + xdrawglyph(og, oldx, oldy); + + g.u = term.line[term.c.y][term.c.x].u; + + /* + * Select the right color for the right mode. + */ + if (IS_SET(MODE_REVERSE)) { + g.mode |= ATTR_REVERSE; + g.bg = defaultfg; + if (ena_sel && selected(term.c.x, term.c.y)) { + drawcol = dc.col[defaultcs]; + g.fg = defaultrcs; + } else { + drawcol = dc.col[defaultrcs]; + g.fg = defaultcs; + } + } else { + if (ena_sel && selected(term.c.x, term.c.y)) { + drawcol = dc.col[defaultrcs]; + g.fg = defaultfg; + g.bg = defaultrcs; + } else { + drawcol = dc.col[defaultcs]; + } + } + + if (IS_SET(MODE_HIDE)) + return; + + /* draw the new one */ + if (xw.state & WIN_FOCUSED) { + switch (xw.cursor) { + case 7: /* st extension: snowman */ + utf8decode("☃", &g.u, UTF_SIZ); + /* FALLTHRU */ + case 0: /* Blinking Block */ + case 1: /* Blinking Block (Default) */ + case 2: /* Steady Block */ + g.mode |= term.line[term.c.y][curx].mode & ATTR_WIDE; + xdrawglyph(g, term.c.x, term.c.y); + break; + case 3: /* Blinking Underline */ + case 4: /* Steady Underline */ + XftDrawRect(xw.draw, &drawcol, + borderpx + curx * xw.cw, + borderpx + (term.c.y + 1) * xw.ch - \ + cursorthickness, + xw.cw, cursorthickness); + break; + case 5: /* Blinking bar */ + case 6: /* Steady bar */ + XftDrawRect(xw.draw, &drawcol, + borderpx + curx * xw.cw, + borderpx + term.c.y * xw.ch, + cursorthickness, xw.ch); + break; + } + } else { + XftDrawRect(xw.draw, &drawcol, + borderpx + curx * xw.cw, + borderpx + term.c.y * xw.ch, + xw.cw - 1, 1); + XftDrawRect(xw.draw, &drawcol, + borderpx + curx * xw.cw, + borderpx + term.c.y * xw.ch, + 1, xw.ch - 1); + XftDrawRect(xw.draw, &drawcol, + borderpx + (curx + 1) * xw.cw - 1, + borderpx + term.c.y * xw.ch, + 1, xw.ch - 1); + XftDrawRect(xw.draw, &drawcol, + borderpx + curx * xw.cw, + borderpx + (term.c.y + 1) * xw.ch - 1, + xw.cw, 1); + } + oldx = curx, oldy = term.c.y; +} + + +void +xsettitle(char *p) +{ + XTextProperty prop; + + Xutf8TextListToTextProperty(xw.dpy, &p, 1, XUTF8StringStyle, + &prop); + XSetWMName(xw.dpy, xw.win, &prop); + XSetTextProperty(xw.dpy, xw.win, &prop, xw.netwmname); + XFree(prop.value); +} + +void +xresettitle(void) +{ + xsettitle(opt_title ? opt_title : "st"); +} + +void +redraw(void) +{ + tfulldirt(); + draw(); +} + +void +draw(void) +{ + drawregion(0, 0, term.col, term.row); + XCopyArea(xw.dpy, xw.buf, xw.win, dc.gc, 0, 0, xw.w, + xw.h, 0, 0); + XSetForeground(xw.dpy, dc.gc, + dc.col[IS_SET(MODE_REVERSE)? + defaultfg : defaultbg].pixel); +} + +void +drawregion(int x1, int y1, int x2, int y2) +{ + int i, x, y, ox, numspecs; + Glyph base, new; + XftGlyphFontSpec *specs; + int ena_sel = sel.ob.x != -1 && sel.alt == IS_SET(MODE_ALTSCREEN); + + if (!(xw.state & WIN_VISIBLE)) + return; + + for (y = y1; y < y2; y++) { + if (!term.dirty[y]) + continue; + + term.dirty[y] = 0; + + specs = term.specbuf; + numspecs = xmakeglyphfontspecs(specs, &TLINE(y)[x1], x2 - x1, x1, y); + + i = ox = 0; + for (x = x1; x < x2 && i < numspecs; x++) { + new = TLINE(y)[x]; + if (new.mode == ATTR_WDUMMY) + continue; + if (ena_sel && selected(x, y)) + new.mode ^= ATTR_REVERSE; + if (i > 0 && ATTRCMP(base, new)) { + xdrawglyphfontspecs(specs, base, i, ox, y); + specs += i; + numspecs -= i; + i = 0; + } + if (i == 0) { + ox = x; + base = new; + } + i++; + } + if (i > 0) + xdrawglyphfontspecs(specs, base, i, ox, y); + } + if (term.scr == 0) + xdrawcursor(); +} + +void +expose(XEvent *ev) +{ + redraw(); +} + +void +visibility(XEvent *ev) +{ + XVisibilityEvent *e = &ev->xvisibility; + + MODBIT(xw.state, e->state != VisibilityFullyObscured, WIN_VISIBLE); +} + +void +unmap(XEvent *ev) +{ + xw.state &= ~WIN_VISIBLE; +} + +void +xsetpointermotion(int set) +{ + MODBIT(xw.attrs.event_mask, set, PointerMotionMask); + XChangeWindowAttributes(xw.dpy, xw.win, CWEventMask, &xw.attrs); +} + +void +xseturgency(int add) +{ + XWMHints *h = XGetWMHints(xw.dpy, xw.win); + + MODBIT(h->flags, add, XUrgencyHint); + XSetWMHints(xw.dpy, xw.win, h); + XFree(h); +} + +void +focus(XEvent *ev) +{ + XFocusChangeEvent *e = &ev->xfocus; + + if (e->mode == NotifyGrab) + return; + + if (ev->type == FocusIn) { + XSetICFocus(xw.xic); + xw.state |= WIN_FOCUSED; + xseturgency(0); + if (IS_SET(MODE_FOCUS)) + ttywrite("\033[I", 3); + } else { + XUnsetICFocus(xw.xic); + xw.state &= ~WIN_FOCUSED; + if (IS_SET(MODE_FOCUS)) + ttywrite("\033[O", 3); + } +} + +int +match(uint mask, uint state) +{ + return mask == XK_ANY_MOD || mask == (state & ~ignoremod); +} + +void +numlock(const Arg *dummy) +{ + term.numlock ^= 1; +} + +void +swapcolors(const Arg *dummy) +{ + (void)dummy; + + usealtcolors = !usealtcolors; + xloadcols(); + redraw(); +} + +char* +kmap(KeySym k, uint state) +{ + Key *kp; + int i; + + /* Check for mapped keys out of X11 function keys. */ + for (i = 0; i < LEN(mappedkeys); i++) { + if (mappedkeys[i] == k) + break; + } + if (i == LEN(mappedkeys)) { + if ((k & 0xFFFF) < 0xFD00) + return NULL; + } + + for (kp = key; kp < key + LEN(key); kp++) { + if (kp->k != k) + continue; + + if (!match(kp->mask, state)) + continue; + + if (IS_SET(MODE_APPKEYPAD) ? kp->appkey < 0 : kp->appkey > 0) + continue; + if (term.numlock && kp->appkey == 2) + continue; + + if (IS_SET(MODE_APPCURSOR) ? kp->appcursor < 0 : kp->appcursor > 0) + continue; + + if (IS_SET(MODE_CRLF) ? kp->crlf < 0 : kp->crlf > 0) + continue; + + return kp->s; + } + + return NULL; +} + +void +kpress(XEvent *ev) +{ + XKeyEvent *e = &ev->xkey; + KeySym ksym; + char buf[32], *customkey; + int len; + Rune c; + Status status; + Shortcut *bp; + + if (IS_SET(MODE_KBDLOCK)) + return; + + len = XmbLookupString(xw.xic, e, buf, sizeof buf, &ksym, &status); + /* 1. shortcuts */ + for (bp = shortcuts; bp < shortcuts + LEN(shortcuts); bp++) { + if (ksym == bp->keysym && match(bp->mod, e->state)) { + bp->func(&(bp->arg)); + return; + } + } + + /* 2. custom keys from config.h */ + if ((customkey = kmap(ksym, e->state))) { + ttysend(customkey, strlen(customkey)); + return; + } + + /* 3. composed string from input method */ + if (len == 0) + return; + if (len == 1 && e->state & Mod1Mask) { + if (IS_SET(MODE_8BIT)) { + if (*buf < 0177) { + c = *buf | 0x80; + len = utf8encode(c, buf); + } + } else { + buf[1] = buf[0]; + buf[0] = '\033'; + len = 2; + } + } + ttysend(buf, len); +} + + +void +cmessage(XEvent *e) +{ + /* + * See xembed specs + * http://standards.freedesktop.org/xembed-spec/xembed-spec-latest.html + */ + if (e->xclient.message_type == xw.xembed && e->xclient.format == 32) { + if (e->xclient.data.l[1] == XEMBED_FOCUS_IN) { + xw.state |= WIN_FOCUSED; + xseturgency(0); + } else if (e->xclient.data.l[1] == XEMBED_FOCUS_OUT) { + xw.state &= ~WIN_FOCUSED; + } + } else if (e->xclient.data.l[0] == xw.wmdeletewin) { + /* Send SIGHUP to shell */ + kill(pid, SIGHUP); + exit(0); + } +} + +void +cresize(int width, int height) +{ + int col, row; + + if (width != 0) + xw.w = width; + if (height != 0) + xw.h = height; + + col = (xw.w - 2 * borderpx) / xw.cw; + row = (xw.h - 2 * borderpx) / xw.ch; + + tresize(col, row); + xresize(col, row); +} + +void +resize(XEvent *e) +{ + if (e->xconfigure.width == xw.w && e->xconfigure.height == xw.h) + return; + + cresize(e->xconfigure.width, e->xconfigure.height); + ttyresize(); +} + +void +run(void) +{ + XEvent ev; + int w = xw.w, h = xw.h; + fd_set rfd; + int xfd = XConnectionNumber(xw.dpy), xev, blinkset = 0, dodraw = 0; + struct timespec drawtimeout, *tv = NULL, now, last, lastblink; + long deltatime; + + /* Waiting for window mapping */ + do { + XNextEvent(xw.dpy, &ev); + /* + * This XFilterEvent call is required because of XOpenIM. It + * does filter out the key event and some client message for + * the input method too. + */ + if (XFilterEvent(&ev, None)) + continue; + if (ev.type == ConfigureNotify) { + w = ev.xconfigure.width; + h = ev.xconfigure.height; + } + } while (ev.type != MapNotify); + + cresize(w, h); + ttynew(); + ttyresize(); + + clock_gettime(CLOCK_MONOTONIC, &last); + lastblink = last; + + for (xev = actionfps;;) { + FD_ZERO(&rfd); + FD_SET(cmdfd, &rfd); + FD_SET(xfd, &rfd); + + if (pselect(MAX(xfd, cmdfd)+1, &rfd, NULL, NULL, tv, NULL) < 0) { + if (errno == EINTR) + continue; + die("select failed: %s\n", strerror(errno)); + } + if (FD_ISSET(cmdfd, &rfd)) { + ttyread(); + if (blinktimeout) { + blinkset = tattrset(ATTR_BLINK); + if (!blinkset) + MODBIT(term.mode, 0, MODE_BLINK); + } + } + + if (FD_ISSET(xfd, &rfd)) + xev = actionfps; + + clock_gettime(CLOCK_MONOTONIC, &now); + drawtimeout.tv_sec = 0; + drawtimeout.tv_nsec = (1000 * 1E6)/ xfps; + tv = &drawtimeout; + + dodraw = 0; + if (blinktimeout && TIMEDIFF(now, lastblink) > blinktimeout) { + tsetdirtattr(ATTR_BLINK); + term.mode ^= MODE_BLINK; + lastblink = now; + dodraw = 1; + } + deltatime = TIMEDIFF(now, last); + if (deltatime > 1000 / (xev ? xfps : actionfps)) { + dodraw = 1; + last = now; + } + + if (dodraw) { + while (XPending(xw.dpy)) { + XNextEvent(xw.dpy, &ev); + if (XFilterEvent(&ev, None)) + continue; + if (handler[ev.type]) + (handler[ev.type])(&ev); + } + + draw(); + XFlush(xw.dpy); + + if (xev && !FD_ISSET(xfd, &rfd)) + xev--; + if (!FD_ISSET(cmdfd, &rfd) && !FD_ISSET(xfd, &rfd)) { + if (blinkset) { + if (TIMEDIFF(now, lastblink) \ + > blinktimeout) { + drawtimeout.tv_nsec = 1000; + } else { + drawtimeout.tv_nsec = (1E6 * \ + (blinktimeout - \ + TIMEDIFF(now, + lastblink))); + } + drawtimeout.tv_sec = \ + drawtimeout.tv_nsec / 1E9; + drawtimeout.tv_nsec %= (long)1E9; + } else { + tv = NULL; + } + } + } + } +} + +void +usage(void) +{ + die("usage: %s [-aiv] [-c class] [-f font] [-g geometry]" + " [-n name] [-o file]\n" + " [-T title] [-t title] [-w windowid]" + " [[-e] command [args ...]]\n" + " %s [-aiv] [-c class] [-f font] [-g geometry]" + " [-n name] [-o file]\n" + " [-T title] [-t title] [-w windowid] -l line" + " [stty_args ...]\n", argv0, argv0); +} + +int +main(int argc, char *argv[]) +{ + uint cols = 80, rows = 24; + + xw.l = xw.t = 0; + xw.isfixed = False; + xw.cursor = cursorshape; + + ARGBEGIN { + case 'a': + allowaltscreen = 0; + break; + case 'c': + opt_class = EARGF(usage()); + break; + case 'e': + if (argc > 0) + --argc, ++argv; + goto run; + case 'f': + opt_font = EARGF(usage()); + break; + case 'g': + xw.gm = XParseGeometry(EARGF(usage()), + &xw.l, &xw.t, &cols, &rows); + break; + case 'i': + xw.isfixed = 1; + break; + case 'o': + opt_io = EARGF(usage()); + break; + case 'l': + opt_line = EARGF(usage()); + break; + case 'n': + opt_name = EARGF(usage()); + break; + case 't': + case 'T': + opt_title = EARGF(usage()); + break; + case 'w': + opt_embed = EARGF(usage()); + break; + default: + usage(); + } ARGEND; + +run: + if (argc > 0) { + /* eat all remaining arguments */ + opt_cmd = argv; + if (!opt_title && !opt_line) + opt_title = basename(xstrdup(argv[0])); + } + setlocale(LC_CTYPE, ""); + XSetLocaleModifiers(""); + tnew(MAX(cols, 1), MAX(rows, 1)); + xinit(); + selinit(); + run(); + + return 0; +} + diff --git a/config.mk b/config.mk index 02e0cd8..109d8ea 100644 --- a/config.mk +++ b/config.mk @@ -3,7 +3,7 @@ OUTDIR = build BINDIR = $(OUTDIR)/bin CC = cc -CFLAGS = -O2 --std=c99 -pedantic -Wall -Wextra -Werror +CFLAGS = -O2 --std=c99 -pedantic -Wall -Wextra -Werror -Wno-unused-result -Wno-sign-compare -Wno-type-limits -Wno-unused-parameter CPPFLAGS = -Iinc/ -I/usr/include/freetype2 AR = ar diff --git a/inc/liba.h b/inc/liba.h index b8e4e73..de49b27 100644 --- a/inc/liba.h +++ b/inc/liba.h @@ -76,6 +76,26 @@ int Net_Accept(int fd); int Net_Dial(char* dialstr); void Net_Serve(char* dialstr, void (*on_client)(int cfd)); +/* + Message Bus Interface +*/ +typedef struct MBusConnection MBusConnection; +typedef struct MBusMessage MBusMessage; +typedef uint16_t MBusIdentifier; +typedef uint16_t MBusTopic; + +MBusConnection* MBus_Connect(char* dialstr); +void MBus_Disconnect(MBusConnection* conn); +void MBus_Subscribe(MBusConnection* conn, MBusTopic topic); +void MBus_Unsubscribe(MBusConnection* conn, MBusTopic topic); +void MBus_MsgSend(MBusConnection* conn, MBusMessage* msg); + +MBusMessage* MBus_CreateMessage(MBusIdentifier dest); +void MBus_PutBool(MBusMessage* msg, bool val); +void MBus_PutInt(MBusMessage* msg, int64_t val); +void MBus_PutFloat(MBusMessage* msg, double val); +void MBus_PutString(MBusMessage* msg, double val); + /* Basic Runtime Facilities */ diff --git a/inc/stdc.h b/inc/stdc.h new file mode 100644 index 0000000..68e0e36 --- /dev/null +++ b/inc/stdc.h @@ -0,0 +1,171 @@ +/** + @file + @brief Collection of useful C types and functions. + @author Michael D. Lowis + @license BSD 2-clause License +*/ +#define _POSIX_C_SOURCE 200809L +#define _XOPEN_SOURCE 700 +#define AUTOLIB(n) \ + int __autolib_##n __attribute__ ((weak)); +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunused-function" + +static char* strmcat(char* first, ...) { + va_list args; + /* calculate the length of the final string */ + size_t len = strlen(first); + va_start(args, first); + for (char* s = NULL; (s = va_arg(args, char*));) + len += strlen(s); + va_end(args); + /* allocate the final string and copy the args into it */ + char *str = malloc(len+1), *curr = str; + while (first && *first) *(curr++) = *(first++); + va_start(args, first); + for (char* s = NULL; (s = va_arg(args, char*));) + while (s && *s) *(curr++) = *(s++); + va_end(args); + /* null terminate and return */ + *curr = '\0'; + return str; +} + +/* Option Parsing + * + * This following macros implement a simple POSIX-style option parsing strategy. + * They are heavily influenced and inspired by the arg.h file from suckless.org + * (http://git.suckless.org/libsl/tree/arg.h). That file is in turn inspired by + * the corresponding macros defined in plan9. + * + * The interface assumes that the main function will have the following + * prototype: + * + * int main(int argc, char** argv); + * + * An example usage of the interface would look something like the follwoing: + * + * char* ARGV0; + * int main(int argc, char** argv) { + * OPTBEGIN { + * case 'a': printf("Simple option\n"); break; + * case 'b': printf("Option with arg: %s\n", OPTARG()); break; + * default: printf("Unknown option!\n"); + * } OPTEND; + * return 0; + * } + */ + +/* This variable contains the value of argv[0] so that it can be referenced + * again once the option parsing is done. This variable must be defined by the + * program. + * + * NOTE: Ensure that you define this variable with external linkage (i.e. not + * static) */ +extern char* ARGV0; + +/* This is a helper function used by the following macros to parse the next + * option from the command line. */ +static inline char* _getopt_(int* p_argc, char*** p_argv) { + if (!(*p_argv)[0][1] && !(*p_argv)[1]) { + return (char*)0; + } else if ((*p_argv)[0][1]) { + return &(*p_argv)[0][1]; + } else { + *p_argv = *p_argv + 1; + *p_argc = *p_argc - 1; + return (*p_argv)[0]; + } +} + +/* This macro is almost identical to the ARGBEGIN macro from suckless.org. If + * it ain't broke, don't fix it. */ +#define OPTBEGIN \ + for ( \ + ARGV0 = *argv, argc--, argv++; \ + argv[0] && argv[0][1] && argv[0][0] == '-'; \ + argc--, argv++ \ + ) { \ + int brk_; char argc_ , **argv_, *optarg_; \ + if (argv[0][1] == '-' && !argv[0][2]) { \ + (void)optarg_; argv++, argc--; break; \ + } \ + for (brk_=0, argv[0]++, argv_=argv; argv[0][0] && !brk_; argv[0]++) { \ + if (argv_ != argv) break; \ + argc_ = argv[0][0]; \ + switch (argc_) + +/* Terminate the option parsing. */ +#define OPTEND }} + +/* Get the current option character */ +#define OPTC() (argc_) + +/* Get an argument from the command line and return it as a string. If no + * argument is available, this macro returns NULL */ +#define OPTARG() \ + (optarg_ = _getopt_(&argc,&argv), brk_ = (optarg_!=0), optarg_) + +/* Get an argument from the command line and return it as a string. If no + * argument is available, this macro executes the provided code. If that code + * returns, then abort is called. */ +#define EOPTARG(code) \ + (optarg_ = _getopt_(&argc,&argv), \ + (!optarg_ ? ((code), abort(), (char*)0) : (brk_ = 1, optarg_))) + +/* Helper macro to recognize number options */ +#define OPTNUM \ + case '0': \ + case '1': \ + case '2': \ + case '3': \ + case '4': \ + case '5': \ + case '6': \ + case '7': \ + case '8': \ + case '9' + +/* Helper macro to recognize "long" options ala GNU style. */ +#define OPTLONG \ + case '-' + +/* Miscellaneous + *****************************************************************************/ +#ifndef nelem + #define nelem(x) \ + (sizeof(x)/sizeof((x)[0])) +#endif + +#ifndef container_of + #define container_of(obj, type, member) \ + ((type*)((uintptr_t)obj - offsetof(type, member))) +#endif + +#ifndef min + #define min(x,y) \ + ((x) < (y) ? (x) : (y)) +#endif + +#ifndef max + #define max(x,y) \ + ((x) > (y) ? (x) : (y)) +#endif + +#pragma GCC diagnostic pop diff --git a/inc/vec.h b/inc/vec.h new file mode 100644 index 0000000..4cd8f03 --- /dev/null +++ b/inc/vec.h @@ -0,0 +1,126 @@ +/** + @file + @brief Generic vector implementation. + @author Michael D. Lowis + @license BSD 2-clause License +*/ +#ifndef VEC_H +#define VEC_H + +#include +#include +#include +#include +#include + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunused-function" + +typedef struct { + size_t elem_count; + size_t elem_size; + size_t elem_capacity; + uint8_t* elem_buffer; +} vec_t; + +typedef int (*vec_cmpfn_t)(const void*,const void*); + +#ifndef DEFAULT_VEC_CAPACITY +#define DEFAULT_VEC_CAPACITY (size_t)8 +#endif + +static void vec_init(vec_t* vec, size_t elem_size) { + vec->elem_size = elem_size; + vec->elem_count = 0; + vec->elem_capacity = DEFAULT_VEC_CAPACITY; + vec->elem_buffer = malloc(elem_size * vec->elem_capacity); +} + +static size_t vec_size(vec_t* vec) { + return vec->elem_count; +} + +static bool vec_empty(vec_t* vec) { + return (vec->elem_count == 0); +} + +static size_t vec_capacity(vec_t* vec) { + return vec->elem_capacity; +} + +static size_t vec_next_capacity(size_t req_size) { + size_t next_power = req_size; + size_t num_bits = sizeof(size_t) * 8; + size_t bit_n; + + /* Find the next highest power of 2 */ + next_power--; + for (bit_n = 1; bit_n < num_bits; bit_n = bit_n << 1) + next_power = next_power | (next_power >> bit_n); + next_power++; + + return next_power; +} + +static void vec_reserve(vec_t* vec, size_t size) { + vec->elem_buffer = realloc(vec->elem_buffer, size * vec->elem_size); + vec->elem_capacity = size; +} + +static void vec_resize(vec_t* vec, size_t count, void* fillval) { + if (count > vec->elem_count) { + vec_reserve(vec, vec_next_capacity(count+1)); + for (; vec->elem_count < count; vec->elem_count++) + memcpy(&(vec->elem_buffer[vec->elem_count * vec->elem_size]), fillval, vec->elem_size); + } else if (count < vec->elem_count) { + vec->elem_count = count; + } +} + +static void vec_shrink_to_fit(vec_t* vec) { + vec->elem_buffer = realloc(vec->elem_buffer, vec->elem_count * vec->elem_size); + vec->elem_capacity = vec->elem_count; +} + +static void* vec_at(vec_t* vec, size_t index) { + return &(vec->elem_buffer[index * vec->elem_size]); +} + +static void vec_set(vec_t* vec, size_t index, void* data) { + memcpy(&(vec->elem_buffer[index * vec->elem_size]), data, vec->elem_size); +} + +static bool vec_insert(vec_t* vec, size_t index, size_t num_elements, ...) { + (void)vec; + (void)index; + (void)num_elements; + return false; +} + +static bool vec_erase(vec_t* vec, size_t start_idx, size_t end_idx) { + (void)vec; + (void)start_idx; + (void)end_idx; + return false; +} + +static void vec_push_back(vec_t* vec, void* data) { + vec_resize(vec, vec->elem_count+1, data); +} + +static void vec_pop_back(vec_t* vec, void* outdata) { + vec->elem_count--; + memcpy(outdata, &(vec->elem_buffer[vec->elem_count * vec->elem_size]), vec->elem_size); +} + +static void vec_clear(vec_t* vec) { + vec->elem_count = 0; +} + +static void vec_sort(vec_t* vec, int (*cmpfn)(const void*,const void*)) { + qsort(vec->elem_buffer, vec->elem_count, vec->elem_size, cmpfn); +} + +#pragma GCC diagnostic pop + +#endif /* VEC_H */ diff --git a/lib/MBus.c b/lib/MBus.c new file mode 100644 index 0000000..3187c6e --- /dev/null +++ b/lib/MBus.c @@ -0,0 +1,17 @@ +#include + +struct MBusConnection { + int fd; +}; + +MBusConnection* MBus_Connect(char* dialstr) +{ + (void)dialstr; + return NULL; +} + +void MBus_Disconnect(MBusConnection* conn) +{ + (void)conn; +} +