+void
+strreset(void) {
+ memset(&strescseq, 0, sizeof(strescseq));
+}
+
+void
+tputtab(bool forward) {
+ uint x = term.c.x;
+
+ if(forward) {
+ if(x == term.col)
+ return;
+ for(++x; x < term.col && !term.tabs[x]; ++x)
+ /* nothing */ ;
+ } else {
+ if(x == 0)
+ return;
+ for(--x; x > 0 && !term.tabs[x]; --x)
+ /* nothing */ ;
+ }
+ tmoveto(x, term.c.y);
+}
+
+void
+techo(char *buf, int len) {
+ for(; len > 0; buf++, len--) {
+ char c = *buf;
+
+ if(c == '\033') { /* escape */
+ tputc("^", 1);
+ tputc("[", 1);
+ } else if(c < '\x20') { /* control code */
+ if(c != '\n' && c != '\r' && c != '\t') {
+ c |= '\x40';
+ tputc("^", 1);
+ }
+ tputc(&c, 1);
+ } else {
+ break;
+ }
+ }
+ if(len)
+ tputc(buf, len);
+}
+
+void
+tputc(char *c, int len) {
+ uchar ascii = *c;
+ bool control = ascii < '\x20' || ascii == 0177;
+
+ if(iofd != -1) {
+ if(xwrite(iofd, c, len) < 0) {
+ fprintf(stderr, "Error writing in %s:%s\n",
+ opt_io, strerror(errno));
+ close(iofd);
+ iofd = -1;
+ }
+ }
+
+ /*
+ * STR sequences must be checked before anything else
+ * because it can use some control codes as part of the sequence.
+ */
+ if(term.esc & ESC_STR) {
+ switch(ascii) {
+ case '\033':
+ term.esc = ESC_START | ESC_STR_END;
+ break;
+ case '\a': /* backwards compatibility to xterm */
+ term.esc = 0;
+ strhandle();
+ break;
+ default:
+ if(strescseq.len + len < sizeof(strescseq.buf) - 1) {
+ memmove(&strescseq.buf[strescseq.len], c, len);
+ strescseq.len += len;
+ } 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) {
+ 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(!(xw.state & WIN_FOCUSED))
+ xseturgency(1);
+ return;
+ case '\033': /* ESC */
+ csireset();
+ term.esc = ESC_START;
+ return;
+ case '\016': /* SO */
+ case '\017': /* SI */
+ /*
+ * Different charsets are hard to handle. Applications
+ * should use the right alt charset escapes for the
+ * only reason they still exist: line drawing. The
+ * rest is incompatible history st should not support.
+ */
+ return;
+ case '\032': /* SUB */
+ case '\030': /* CAN */
+ csireset();
+ return;
+ case '\005': /* ENQ (IGNORED) */
+ case '\000': /* NUL (IGNORED) */
+ case '\021': /* XON (IGNORED) */
+ case '\023': /* XOFF (IGNORED) */
+ case 0177: /* DEL (IGNORED) */
+ return;
+ }
+ } else if(term.esc & ESC_START) {
+ if(term.esc & ESC_CSI) {
+ csiescseq.buf[csiescseq.len++] = ascii;
+ if(BETWEEN(ascii, 0x40, 0x7E)
+ || csiescseq.len >= \
+ sizeof(csiescseq.buf)-1) {
+ term.esc = 0;
+ csiparse();
+ csihandle();
+ }
+ } else if(term.esc & ESC_STR_END) {
+ term.esc = 0;
+ if(ascii == '\\')
+ strhandle();
+ } else if(term.esc & ESC_ALTCHARSET) {
+ switch(ascii) {
+ case '0': /* Line drawing set */
+ term.c.attr.mode |= ATTR_GFX;
+ break;
+ case 'B': /* USASCII */
+ term.c.attr.mode &= ~ATTR_GFX;
+ break;
+ case 'A': /* UK (IGNORED) */
+ case '<': /* multinational charset (IGNORED) */
+ case '5': /* Finnish (IGNORED) */
+ case 'C': /* Finnish (IGNORED) */
+ case 'K': /* German (IGNORED) */
+ break;
+ default:
+ fprintf(stderr, "esc unhandled charset: ESC ( %c\n", ascii);
+ }
+ term.esc = 0;
+ } else if(term.esc & ESC_TEST) {
+ if(ascii == '8') { /* DEC screen alignment test. */
+ char E[UTF_SIZ] = "E";
+ int x, y;
+
+ for(x = 0; x < term.col; ++x) {
+ for(y = 0; y < term.row; ++y)
+ tsetchar(E, &term.c.attr, x, y);
+ }
+ }
+ term.esc = 0;
+ } else {
+ switch(ascii) {
+ case '[':
+ term.esc |= ESC_CSI;
+ break;
+ case '#':
+ term.esc |= ESC_TEST;
+ break;
+ 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 */
+ strreset();
+ strescseq.type = ascii;
+ term.esc |= ESC_STR;
+ break;
+ case '(': /* set primary charset G0 */
+ term.esc |= ESC_ALTCHARSET;
+ break;
+ case ')': /* set secondary charset G1 (IGNORED) */
+ case '*': /* set tertiary charset G2 (IGNORED) */
+ case '+': /* set quaternary charset G3 (IGNORED) */
+ term.esc = 0;
+ break;
+ case 'D': /* IND -- Linefeed */
+ if(term.c.y == term.bot) {
+ tscrollup(term.top, 1);
+ } else {
+ tmoveto(term.c.x, term.c.y+1);
+ }
+ term.esc = 0;
+ break;
+ case 'E': /* NEL -- Next line */
+ tnewline(1); /* always go to first col */
+ term.esc = 0;
+ break;
+ case 'H': /* HTS -- Horizontal tab stop */
+ term.tabs[term.c.x] = 1;
+ term.esc = 0;
+ break;
+ case 'M': /* RI -- Reverse index */
+ if(term.c.y == term.top) {
+ tscrolldown(term.top, 1);
+ } else {
+ tmoveto(term.c.x, term.c.y-1);
+ }
+ term.esc = 0;
+ break;
+ case 'Z': /* DECID -- Identify Terminal */
+ ttywrite(VT102ID, sizeof(VT102ID) - 1);
+ term.esc = 0;
+ break;
+ case 'c': /* RIS -- Reset to inital state */
+ treset();
+ term.esc = 0;
+ xresettitle();
+ xloadcols();
+ break;
+ case '=': /* DECPAM -- Application keypad */
+ term.mode |= MODE_APPKEYPAD;
+ term.esc = 0;
+ break;
+ case '>': /* DECPNM -- Normal keypad */
+ term.mode &= ~MODE_APPKEYPAD;
+ term.esc = 0;
+ break;
+ case '7': /* DECSC -- Save Cursor */
+ tcursor(CURSOR_SAVE);
+ term.esc = 0;
+ break;
+ case '8': /* DECRC -- Restore Cursor */
+ tcursor(CURSOR_LOAD);
+ term.esc = 0;
+ break;
+ case '\\': /* ST -- Stop */
+ term.esc = 0;
+ break;
+ default:
+ fprintf(stderr, "erresc: unknown sequence ESC 0x%02X '%c'\n",
+ (uchar) ascii, isprint(ascii)? ascii:'.');
+ term.esc = 0;
+ }
+ }
+ /*
+ * All characters which form part of a sequence are not
+ * printed
+ */
+ return;
+ }
+ /*
+ * Display control codes only if we are in graphic mode
+ */
+ if(control && !(term.c.attr.mode & ATTR_GFX))
+ return;
+ if(sel.ob.x != -1 && BETWEEN(term.c.y, sel.ob.y, sel.oe.y))
+ selclear(NULL);
+ if(IS_SET(MODE_WRAP) && (term.c.state & CURSOR_WRAPNEXT)) {
+ term.line[term.c.y][term.c.x].mode |= ATTR_WRAP;
+ tnewline(1);
+ }
+
+ if(IS_SET(MODE_INSERT) && term.c.x+1 < term.col) {
+ memmove(&term.line[term.c.y][term.c.x+1],
+ &term.line[term.c.y][term.c.x],
+ (term.col - term.c.x - 1) * sizeof(Glyph));
+ }
+
+ tsetchar(c, &term.c.attr, term.c.x, term.c.y);
+ if(term.c.x+1 < term.col) {
+ tmoveto(term.c.x+1, term.c.y);
+ } else {
+ term.c.state |= CURSOR_WRAPNEXT;
+ }
+}
+
+int
+tresize(int col, int row) {
+ int i;
+ int minrow = MIN(row, term.row);
+ int mincol = MIN(col, term.col);
+ int slide = term.c.y - row + 1;
+ bool *bp;
+ Line *orig;
+
+ if(col < 1 || row < 1)
+ return 0;
+
+ /* free unneeded rows */
+ i = 0;
+ if(slide > 0) {
+ /*
+ * 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 < slide; i++) {
+ free(term.line[i]);
+ free(term.alt[i]);
+ }
+ memmove(term.line, term.line + slide, row * sizeof(Line));
+ memmove(term.alt, term.alt + slide, row * sizeof(Line));
+ }
+ for(i += row; i < term.row; i++) {
+ free(term.line[i]);
+ free(term.alt[i]);
+ }
+
+ /* 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));
+
+ /* resize each row to new width, zero-pad if needed */
+ for(i = 0; i < minrow; i++) {
+ term.dirty[i] = 1;
+ 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.dirty[i] = 1;
+ term.line[i] = xcalloc(col, sizeof(Glyph));
+ term.alt [i] = xcalloc(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 */
+ orig = term.line;
+ do {
+ 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();
+ } while(orig != term.line);
+
+ return (slide > 0);
+}
+
+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);
+}
+
+static inline ushort
+sixd_to_16bit(int x) {
+ return x == 0 ? 0 : 0x3737 + 0x2828 * x;
+}
+
+void
+xloadcols(void) {
+ int i, r, g, b;
+ XRenderColor color = { .alpha = 0xffff };
+ static bool loaded;
+ Colour *cp;
+
+ if(loaded) {
+ for (cp = dc.col; cp < dc.col + LEN(dc.col); ++cp)
+ XftColorFree(xw.dpy, xw.vis, xw.cmap, cp);
+ }
+
+ /* load colors [0-15] colors and [256-LEN(colorname)[ (config.h) */
+ for(i = 0; i < LEN(colorname); i++) {
+ if(!colorname[i])
+ continue;
+ if(!XftColorAllocName(xw.dpy, xw.vis, xw.cmap, colorname[i], &dc.col[i])) {
+ die("Could not allocate color '%s'\n", colorname[i]);
+ }
+ }