st

Kris's build of st
git clone git clone https://git.krisyotam.com/krisyotam/st.git
Log | Files | Refs | README | LICENSE

st.c (87325B)


      1 /* See LICENSE for license details. */
      2 #include <ctype.h>
      3 #include <errno.h>
      4 #include <fcntl.h>
      5 #include <limits.h>
      6 #include <pwd.h>
      7 #include <stdarg.h>
      8 #include <stdio.h>
      9 #include <stdlib.h>
     10 #include <string.h>
     11 #include <signal.h>
     12 #include <sys/ioctl.h>
     13 #include <sys/select.h>
     14 #include <sys/types.h>
     15 #include <sys/wait.h>
     16 #include <termios.h>
     17 #include <time.h>
     18 #include <unistd.h>
     19 #include <wchar.h>
     20 
     21 #include "st.h"
     22 #include "win.h"
     23 
     24 #if KEYBOARDSELECT_PATCH
     25 #include <X11/keysym.h>
     26 #include <X11/X.h>
     27 #endif // KEYBOARDSELECT_PATCH
     28 
     29 #if SIXEL_PATCH
     30 #include "sixel.h"
     31 #endif // SIXEL_PATCH
     32 
     33 #if   defined(__linux)
     34  #include <pty.h>
     35 #elif defined(__OpenBSD__) || defined(__NetBSD__) || defined(__APPLE__)
     36  #include <util.h>
     37 #elif defined(__FreeBSD__) || defined(__DragonFly__)
     38  #include <libutil.h>
     39 #endif
     40 
     41 /* Arbitrary sizes */
     42 #define UTF_INVALID   0xFFFD
     43 #define UTF_SIZ       4
     44 #define ESC_BUF_SIZ   (128*UTF_SIZ)
     45 #define ESC_ARG_SIZ   16
     46 #if UNDERCURL_PATCH
     47 #define CAR_PER_ARG   4
     48 #endif // UNDERCURL_PATCH
     49 #define STR_BUF_SIZ   ESC_BUF_SIZ
     50 #define STR_ARG_SIZ   ESC_ARG_SIZ
     51 #define STR_TERM_ST   "\033\\"
     52 #define STR_TERM_BEL  "\007"
     53 
     54 /* macros */
     55 #define IS_SET(flag)    ((term.mode & (flag)) != 0)
     56 #define ISCONTROLC0(c)  (BETWEEN(c, 0, 0x1f) || (c) == 0x7f)
     57 #define ISCONTROLC1(c)  (BETWEEN(c, 0x80, 0x9f))
     58 #define ISCONTROL(c)    (ISCONTROLC0(c) || ISCONTROLC1(c))
     59 #define ISDELIM(u)      (u && wcschr(worddelimiters, u))
     60 
     61 enum term_mode {
     62 	MODE_WRAP         = 1 << 0,
     63 	MODE_INSERT       = 1 << 1,
     64 	MODE_ALTSCREEN    = 1 << 2,
     65 	MODE_CRLF         = 1 << 3,
     66 	MODE_ECHO         = 1 << 4,
     67 	MODE_PRINT        = 1 << 5,
     68 	MODE_UTF8         = 1 << 6,
     69 	#if SIXEL_PATCH
     70 	MODE_SIXEL        = 1 << 7,
     71 	MODE_SIXEL_CUR_RT = 1 << 8,
     72 	MODE_SIXEL_SDM    = 1 << 9
     73 	#endif // SIXEL_PATCH
     74 };
     75 
     76 #if REFLOW_PATCH
     77 enum scroll_mode {
     78 	SCROLL_RESIZE = -1,
     79 	SCROLL_NOSAVEHIST = 0,
     80 	SCROLL_SAVEHIST = 1
     81 };
     82 #endif // REFLOW_PATCH
     83 
     84 enum cursor_movement {
     85 	CURSOR_SAVE,
     86 	CURSOR_LOAD
     87 };
     88 
     89 enum cursor_state {
     90 	CURSOR_DEFAULT  = 0,
     91 	CURSOR_WRAPNEXT = 1,
     92 	CURSOR_ORIGIN   = 2
     93 };
     94 
     95 enum charset {
     96 	CS_GRAPHIC0,
     97 	CS_GRAPHIC1,
     98 	CS_UK,
     99 	CS_USA,
    100 	CS_MULTI,
    101 	CS_GER,
    102 	CS_FIN
    103 };
    104 
    105 enum escape_state {
    106 	ESC_START      = 1,
    107 	ESC_CSI        = 2,
    108 	ESC_STR        = 4,  /* DCS, OSC, PM, APC */
    109 	ESC_ALTCHARSET = 8,
    110 	ESC_STR_END    = 16, /* a final string was encountered */
    111 	ESC_TEST       = 32, /* Enter in test mode */
    112 	ESC_UTF8       = 64,
    113 	#if SIXEL_PATCH
    114 	ESC_DCS        =128,
    115 	#endif // SIXEL_PATCH
    116 };
    117 
    118 typedef struct {
    119 	int mode;
    120 	int type;
    121 	int snap;
    122 	/*
    123 	 * Selection variables:
    124 	 * nb – normalized coordinates of the beginning of the selection
    125 	 * ne – normalized coordinates of the end of the selection
    126 	 * ob – original coordinates of the beginning of the selection
    127 	 * oe – original coordinates of the end of the selection
    128 	 */
    129 	struct {
    130 		int x, y;
    131 	} nb, ne, ob, oe;
    132 
    133 	int alt;
    134 } Selection;
    135 
    136 /* CSI Escape sequence structs */
    137 /* ESC '[' [[ [<priv>] <arg> [;]] <mode> [<mode>]] */
    138 typedef struct {
    139 	char buf[ESC_BUF_SIZ]; /* raw string */
    140 	size_t len;            /* raw string length */
    141 	char priv;
    142 	int arg[ESC_ARG_SIZ];
    143 	int narg;              /* nb of args */
    144 	char mode[2];
    145 	#if UNDERCURL_PATCH
    146 	int carg[ESC_ARG_SIZ][CAR_PER_ARG]; /* colon args */
    147 	#endif // UNDERCURL_PATCH
    148 } CSIEscape;
    149 
    150 /* STR Escape sequence structs */
    151 /* ESC type [[ [<priv>] <arg> [;]] <mode>] ESC '\' */
    152 typedef struct {
    153 	char type;             /* ESC type ... */
    154 	char *buf;             /* allocated raw string */
    155 	size_t siz;            /* allocation size */
    156 	size_t len;            /* raw string length */
    157 	char *args[STR_ARG_SIZ];
    158 	int narg;              /* nb of args */
    159     char *term;            /* terminator: ST or BEL */
    160 } STREscape;
    161 
    162 static void execsh(char *, char **);
    163 static void stty(char **);
    164 static void sigchld(int);
    165 static void ttywriteraw(const char *, size_t);
    166 
    167 static void csidump(void);
    168 static void csihandle(void);
    169 #if SIXEL_PATCH
    170 static void dcshandle(void);
    171 #endif // SIXEL_PATCH
    172 #if UNDERCURL_PATCH
    173 static void readcolonargs(char **, int, int[][CAR_PER_ARG]);
    174 #endif // UNDERCURL_PATCH
    175 static void csiparse(void);
    176 static void csireset(void);
    177 static void osc_color_response(int, int, int);
    178 static int eschandle(uchar);
    179 static void strdump(void);
    180 static void strhandle(void);
    181 static void strparse(void);
    182 static void strreset(void);
    183 
    184 static void tprinter(char *, size_t);
    185 static void tdumpsel(void);
    186 static void tdumpline(int);
    187 static void tdump(void);
    188 #if !REFLOW_PATCH
    189 static void tclearregion(int, int, int, int);
    190 #endif // REFLOW_PATCH
    191 static void tcursor(int);
    192 static void tresetcursor(void);
    193 #if !REFLOW_PATCH
    194 static void tdeletechar(int);
    195 #endif // REFLOW_PATCH
    196 #if SIXEL_PATCH
    197 static void tdeleteimages(void);
    198 #endif // SIXEL_PATCH
    199 static void tdeleteline(int);
    200 static void tinsertblank(int);
    201 static void tinsertblankline(int);
    202 #if !REFLOW_PATCH
    203 static int tlinelen(int);
    204 #endif // REFLOW_PATCH
    205 static void tmoveto(int, int);
    206 static void tmoveato(int, int);
    207 static void tnewline(int);
    208 static void tputtab(int);
    209 static void tputc(Rune);
    210 static void treset(void);
    211 #if !REFLOW_PATCH
    212 #if SCROLLBACK_PATCH
    213 static void tscrollup(int, int, int);
    214 #else
    215 static void tscrollup(int, int);
    216 #endif // SCROLLBACK_PATCH
    217 #endif // REFLOW_PATCH
    218 static void tscrolldown(int, int);
    219 static void tsetattr(const int *, int);
    220 static void tsetchar(Rune, const Glyph *, int, int);
    221 static void tsetdirt(int, int);
    222 static void tsetscroll(int, int);
    223 #if SIXEL_PATCH
    224 static inline void tsetsixelattr(Line line, int x1, int x2);
    225 #endif // SIXEL_PATCH
    226 static void tswapscreen(void);
    227 static void tsetmode(int, int, const int *, int);
    228 static int twrite(const char *, int, int);
    229 static void tcontrolcode(uchar );
    230 static void tdectest(char );
    231 static void tdefutf8(char);
    232 static int32_t tdefcolor(const int *, int *, int);
    233 static void tdeftran(char);
    234 static void tstrsequence(uchar);
    235 static void selnormalize(void);
    236 #if !REFLOW_PATCH
    237 static void selscroll(int, int);
    238 #endif // REFLOW_PATCH
    239 static void selsnap(int *, int *, int);
    240 
    241 static size_t utf8decode(const char *, Rune *, size_t);
    242 static inline Rune utf8decodebyte(char, size_t *);
    243 static inline char utf8encodebyte(Rune, size_t);
    244 static inline size_t utf8validate(Rune *, size_t);
    245 
    246 static char *base64dec(const char *);
    247 static char base64dec_getc(const char **);
    248 
    249 static ssize_t xwrite(int, const char *, size_t);
    250 
    251 /* Globals */
    252 static Selection sel;
    253 static CSIEscape csiescseq;
    254 static STREscape strescseq;
    255 static int iofd = 1;
    256 static int cmdfd;
    257 #if EXTERNALPIPEIN_PATCH && EXTERNALPIPE_PATCH
    258 static int csdfd;
    259 #endif // EXTERNALPIPEIN_PATCH
    260 static pid_t pid;
    261 #if SIXEL_PATCH
    262 sixel_state_t sixel_st;
    263 #endif // SIXEL_PATCH
    264 
    265 static const uchar utfbyte[UTF_SIZ + 1] = {0x80,    0, 0xC0, 0xE0, 0xF0};
    266 static const uchar utfmask[UTF_SIZ + 1] = {0xC0, 0x80, 0xE0, 0xF0, 0xF8};
    267 static const Rune utfmin[UTF_SIZ + 1] = {       0,    0,  0x80,  0x800,  0x10000};
    268 static const Rune utfmax[UTF_SIZ + 1] = {0x10FFFF, 0x7F, 0x7FF, 0xFFFF, 0x10FFFF};
    269 
    270 #include "patch/st_include.h"
    271 
    272 ssize_t
    273 xwrite(int fd, const char *s, size_t len)
    274 {
    275 	size_t aux = len;
    276 	ssize_t r;
    277 
    278 	while (len > 0) {
    279 		r = write(fd, s, len);
    280 		if (r < 0)
    281 			return r;
    282 		len -= r;
    283 		s += r;
    284 	}
    285 
    286 	return aux;
    287 }
    288 
    289 void *
    290 xmalloc(size_t len)
    291 {
    292 	void *p;
    293 
    294 	if (!(p = malloc(len)))
    295 		die("malloc: %s\n", strerror(errno));
    296 
    297 	return p;
    298 }
    299 
    300 void *
    301 xrealloc(void *p, size_t len)
    302 {
    303 	if ((p = realloc(p, len)) == NULL)
    304 		die("realloc: %s\n", strerror(errno));
    305 
    306 	return p;
    307 }
    308 
    309 char *
    310 xstrdup(const char *s)
    311 {
    312 	char *p;
    313 	if ((p = strdup(s)) == NULL)
    314 		die("strdup: %s\n", strerror(errno));
    315 
    316 	return p;
    317 }
    318 
    319 size_t
    320 utf8decode(const char *c, Rune *u, size_t clen)
    321 {
    322 	size_t i, len;
    323 	Rune udecoded;
    324 
    325 	*u = UTF_INVALID;
    326 	if (!clen)
    327 		return 0;
    328 	udecoded = utf8decodebyte(c[0], &len);
    329 	if (!BETWEEN(len, 2, UTF_SIZ)) {
    330 		*u = (len == 1) ? udecoded : UTF_INVALID;
    331 		return 1;
    332 	}
    333 	clen = MIN(clen, len);
    334 	for (i = 1; i < clen; ++i) {
    335 		if ((c[i] & 0xC0) != 0x80)
    336 			return i;
    337 		udecoded = (udecoded << 6) | (c[i] & 0x3F);
    338 	}
    339 	if (i < len)
    340 		return 0;
    341 	*u = (!BETWEEN(udecoded, utfmin[len], utfmax[len]) || BETWEEN(udecoded, 0xD800, 0xDFFF))
    342 	        ? UTF_INVALID : udecoded;
    343 
    344 	return len;
    345 }
    346 
    347 Rune
    348 utf8decodebyte(char c, size_t *i)
    349 {
    350 	for (*i = 0; *i < LEN(utfmask); ++(*i))
    351 		if (((uchar)c & utfmask[*i]) == utfbyte[*i])
    352 			return (uchar)c & ~utfmask[*i];
    353 
    354 	return 0;
    355 }
    356 
    357 size_t
    358 utf8encode(Rune u, char *c)
    359 {
    360 	size_t len, i;
    361 
    362 	len = utf8validate(&u, 0);
    363 	if (len > UTF_SIZ)
    364 		return 0;
    365 
    366 	for (i = len - 1; i != 0; --i) {
    367 		c[i] = utf8encodebyte(u, 0);
    368 		u >>= 6;
    369 	}
    370 	c[0] = utf8encodebyte(u, len);
    371 
    372 	return len;
    373 }
    374 
    375 char
    376 utf8encodebyte(Rune u, size_t i)
    377 {
    378 	return utfbyte[i] | (u & ~utfmask[i]);
    379 }
    380 
    381 size_t
    382 utf8validate(Rune *u, size_t i)
    383 {
    384 	if (!BETWEEN(*u, utfmin[i], utfmax[i]) || BETWEEN(*u, 0xD800, 0xDFFF))
    385 		*u = UTF_INVALID;
    386 	for (i = 1; *u > utfmax[i]; ++i)
    387 		;
    388 
    389 	return i;
    390 }
    391 
    392 char
    393 base64dec_getc(const char **src)
    394 {
    395 	while (**src && !isprint((unsigned char)**src))
    396 		(*src)++;
    397 	return **src ? *((*src)++) : '=';  /* emulate padding if string ends */
    398 }
    399 
    400 char *
    401 base64dec(const char *src)
    402 {
    403 	size_t in_len = strlen(src);
    404 	char *result, *dst;
    405 	static const char base64_digits[256] = {
    406 		[43] = 62, 0, 0, 0, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61,
    407 		0, 0, 0, -1, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12,
    408 		13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 0, 0, 0, 0,
    409 		0, 0, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39,
    410 		40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51
    411 	};
    412 
    413 	if (in_len % 4)
    414 		in_len += 4 - (in_len % 4);
    415 	result = dst = xmalloc(in_len / 4 * 3 + 1);
    416 	while (*src) {
    417 		int a = base64_digits[(unsigned char) base64dec_getc(&src)];
    418 		int b = base64_digits[(unsigned char) base64dec_getc(&src)];
    419 		int c = base64_digits[(unsigned char) base64dec_getc(&src)];
    420 		int d = base64_digits[(unsigned char) base64dec_getc(&src)];
    421 
    422 		/* invalid input. 'a' can be -1, e.g. if src is "\n" (c-str) */
    423 		if (a == -1 || b == -1)
    424 			break;
    425 
    426 		*dst++ = (a << 2) | ((b & 0x30) >> 4);
    427 		if (c == -1)
    428 			break;
    429 		*dst++ = ((b & 0x0f) << 4) | ((c & 0x3c) >> 2);
    430 		if (d == -1)
    431 			break;
    432 		*dst++ = ((c & 0x03) << 6) | d;
    433 	}
    434 	*dst = '\0';
    435 	return result;
    436 }
    437 
    438 void
    439 selinit(void)
    440 {
    441 	sel.mode = SEL_IDLE;
    442 	sel.snap = 0;
    443 	sel.ob.x = -1;
    444 }
    445 
    446 #if !REFLOW_PATCH
    447 int
    448 tlinelen(int y)
    449 {
    450 	int i = term.col;
    451 
    452 	#if SCROLLBACK_PATCH
    453 	if (TLINE(y)[i - 1].mode & ATTR_WRAP)
    454 		return i;
    455 
    456 	while (i > 0 && TLINE(y)[i - 1].u == ' ')
    457  		--i;
    458 	#else
    459 	if (term.line[y][i - 1].mode & ATTR_WRAP)
    460 		return i;
    461 
    462 	while (i > 0 && term.line[y][i - 1].u == ' ')
    463 		--i;
    464 	#endif // SCROLLBACK_PATCH
    465 
    466 	return i;
    467 }
    468 #endif // REFLOW_PATCH
    469 
    470 void
    471 selstart(int col, int row, int snap)
    472 {
    473 	selclear();
    474 	sel.mode = SEL_EMPTY;
    475 	sel.type = SEL_REGULAR;
    476 	sel.alt = IS_SET(MODE_ALTSCREEN);
    477 	sel.snap = snap;
    478 	sel.oe.x = sel.ob.x = col;
    479 	sel.oe.y = sel.ob.y = row;
    480 	selnormalize();
    481 
    482 	if (sel.snap != 0)
    483 		sel.mode = SEL_READY;
    484 	tsetdirt(sel.nb.y, sel.ne.y);
    485 }
    486 
    487 void
    488 selextend(int col, int row, int type, int done)
    489 {
    490 	int oldey, oldex, oldsby, oldsey, oldtype;
    491 
    492 	if (sel.mode == SEL_IDLE)
    493 		return;
    494 	if (done && sel.mode == SEL_EMPTY) {
    495 		selclear();
    496 		return;
    497 	}
    498 
    499 	oldey = sel.oe.y;
    500 	oldex = sel.oe.x;
    501 	oldsby = sel.nb.y;
    502 	oldsey = sel.ne.y;
    503 	oldtype = sel.type;
    504 
    505 	sel.oe.x = col;
    506 	sel.oe.y = row;
    507 	sel.type = type;
    508 	selnormalize();
    509 
    510 	if (oldey != sel.oe.y || oldex != sel.oe.x || oldtype != sel.type || sel.mode == SEL_EMPTY)
    511 		tsetdirt(MIN(sel.nb.y, oldsby), MAX(sel.ne.y, oldsey));
    512 
    513 	sel.mode = done ? SEL_IDLE : SEL_READY;
    514 }
    515 
    516 void
    517 selnormalize(void)
    518 {
    519 	int i;
    520 
    521 	if (sel.type == SEL_REGULAR && sel.ob.y != sel.oe.y) {
    522 		sel.nb.x = sel.ob.y < sel.oe.y ? sel.ob.x : sel.oe.x;
    523 		sel.ne.x = sel.ob.y < sel.oe.y ? sel.oe.x : sel.ob.x;
    524 	} else {
    525 		sel.nb.x = MIN(sel.ob.x, sel.oe.x);
    526 		sel.ne.x = MAX(sel.ob.x, sel.oe.x);
    527 	}
    528 	sel.nb.y = MIN(sel.ob.y, sel.oe.y);
    529 	sel.ne.y = MAX(sel.ob.y, sel.oe.y);
    530 
    531 	selsnap(&sel.nb.x, &sel.nb.y, -1);
    532 	selsnap(&sel.ne.x, &sel.ne.y, +1);
    533 
    534 	/* expand selection over line breaks */
    535 	if (sel.type == SEL_RECTANGULAR)
    536 		return;
    537 
    538 	#if REFLOW_PATCH
    539 	i = tlinelen(TLINE(sel.nb.y));
    540 	if (sel.nb.x > i)
    541 		sel.nb.x = i;
    542 	if (sel.ne.x >= tlinelen(TLINE(sel.ne.y)))
    543 		sel.ne.x = term.col - 1;
    544 	#else
    545 	i = tlinelen(sel.nb.y);
    546 	if (i < sel.nb.x)
    547 		sel.nb.x = i;
    548 	if (tlinelen(sel.ne.y) <= sel.ne.x)
    549 		sel.ne.x = term.col - 1;
    550 	#endif // REFLOW_PATCH
    551 }
    552 
    553 #if !REFLOW_PATCH
    554 int
    555 selected(int x, int y)
    556 {
    557 	if (sel.mode == SEL_EMPTY || sel.ob.x == -1 ||
    558 			sel.alt != IS_SET(MODE_ALTSCREEN))
    559 		return 0;
    560 
    561 	if (sel.type == SEL_RECTANGULAR)
    562 		return BETWEEN(y, sel.nb.y, sel.ne.y)
    563 		    && BETWEEN(x, sel.nb.x, sel.ne.x);
    564 
    565 	return BETWEEN(y, sel.nb.y, sel.ne.y)
    566 	    && (y != sel.nb.y || x >= sel.nb.x)
    567 	    && (y != sel.ne.y || x <= sel.ne.x);
    568 }
    569 #endif // REFLOW_PATCH
    570 
    571 #if !REFLOW_PATCH
    572 void
    573 selsnap(int *x, int *y, int direction)
    574 {
    575 	int newx, newy, xt, yt;
    576 	int delim, prevdelim;
    577 	const Glyph *gp, *prevgp;
    578 
    579 	switch (sel.snap) {
    580 	case SNAP_WORD:
    581 		/*
    582 		 * Snap around if the word wraps around at the end or
    583 		 * beginning of a line.
    584 		 */
    585 		#if SCROLLBACK_PATCH
    586 		prevgp = &TLINE(*y)[*x];
    587 		#else
    588 		prevgp = &term.line[*y][*x];
    589 		#endif // SCROLLBACK_PATCH
    590 		prevdelim = ISDELIM(prevgp->u);
    591 		for (;;) {
    592 			newx = *x + direction;
    593 			newy = *y;
    594 			if (!BETWEEN(newx, 0, term.col - 1)) {
    595 				newy += direction;
    596 				newx = (newx + term.col) % term.col;
    597 				if (!BETWEEN(newy, 0, term.row - 1))
    598 					break;
    599 
    600 				if (direction > 0)
    601 					yt = *y, xt = *x;
    602 				else
    603 					yt = newy, xt = newx;
    604 				#if SCROLLBACK_PATCH
    605 				if (!(TLINE(yt)[xt].mode & ATTR_WRAP))
    606 				#else
    607 				if (!(term.line[yt][xt].mode & ATTR_WRAP))
    608 				#endif // SCROLLBACK_PATCH
    609 					break;
    610 			}
    611 
    612 			if (newx >= tlinelen(newy))
    613 				break;
    614 
    615 			#if SCROLLBACK_PATCH
    616 			gp = &TLINE(newy)[newx];
    617 			#else
    618 			gp = &term.line[newy][newx];
    619 			#endif // SCROLLBACK_PATCH
    620 			delim = ISDELIM(gp->u);
    621 			if (!(gp->mode & ATTR_WDUMMY) && (delim != prevdelim
    622 					|| (delim && gp->u != prevgp->u)))
    623 				break;
    624 
    625 			*x = newx;
    626 			*y = newy;
    627 			prevgp = gp;
    628 			prevdelim = delim;
    629 		}
    630 		break;
    631 	case SNAP_LINE:
    632 		/*
    633 		 * Snap around if the the previous line or the current one
    634 		 * has set ATTR_WRAP at its end. Then the whole next or
    635 		 * previous line will be selected.
    636 		 */
    637 		*x = (direction < 0) ? 0 : term.col - 1;
    638 		if (direction < 0) {
    639 			for (; *y > 0; *y += direction) {
    640 				#if SCROLLBACK_PATCH
    641 				if (!(TLINE(*y-1)[term.col-1].mode & ATTR_WRAP))
    642 				#else
    643 				if (!(term.line[*y-1][term.col-1].mode & ATTR_WRAP))
    644 				#endif // SCROLLBACK_PATCH
    645 				{
    646 					break;
    647 				}
    648 			}
    649 		} else if (direction > 0) {
    650 			for (; *y < term.row-1; *y += direction) {
    651 				#if SCROLLBACK_PATCH
    652 				if (!(TLINE(*y)[term.col-1].mode & ATTR_WRAP))
    653 				#else
    654 				if (!(term.line[*y][term.col-1].mode & ATTR_WRAP))
    655 				#endif // SCROLLBACK_PATCH
    656 				{
    657 					break;
    658 				}
    659 			}
    660 		}
    661 		break;
    662 	}
    663 }
    664 #endif // REFLOW_PATCH
    665 
    666 #if !REFLOW_PATCH
    667 char *
    668 getsel(void)
    669 {
    670 	char *str, *ptr;
    671 	int y, bufsize, lastx, linelen;
    672 	const Glyph *gp, *last;
    673 
    674 	if (sel.ob.x == -1)
    675 		return NULL;
    676 
    677 	bufsize = (term.col+1) * (sel.ne.y-sel.nb.y+1) * UTF_SIZ;
    678 	ptr = str = xmalloc(bufsize);
    679 
    680 	/* append every set & selected glyph to the selection */
    681 	for (y = sel.nb.y; y <= sel.ne.y; y++)
    682 	{
    683 		if ((linelen = tlinelen(y)) == 0) {
    684 			*ptr++ = '\n';
    685 			continue;
    686 		}
    687 
    688 		if (sel.type == SEL_RECTANGULAR) {
    689 			#if SCROLLBACK_PATCH
    690 			gp = &TLINE(y)[sel.nb.x];
    691 			#else
    692 			gp = &term.line[y][sel.nb.x];
    693 			#endif // SCROLLBACK_PATCH
    694 			lastx = sel.ne.x;
    695 		} else {
    696 			#if SCROLLBACK_PATCH
    697 			gp = &TLINE(y)[sel.nb.y == y ? sel.nb.x : 0];
    698 			#else
    699 			gp = &term.line[y][sel.nb.y == y ? sel.nb.x : 0];
    700 			#endif // SCROLLBACK_PATCH
    701 			lastx = (sel.ne.y == y) ? sel.ne.x : term.col-1;
    702 		}
    703 
    704 		#if SCROLLBACK_PATCH
    705 		last = &TLINE(y)[MIN(lastx, linelen-1)];
    706 		#else
    707 		last = &term.line[y][MIN(lastx, linelen-1)];
    708 		#endif // SCROLLBACK_PATCH
    709 		while (last >= gp && last->u == ' ')
    710 			--last;
    711 
    712 		for ( ; gp <= last; ++gp) {
    713 			if (gp->mode & ATTR_WDUMMY)
    714 				continue;
    715 
    716 			ptr += utf8encode(gp->u, ptr);
    717 		}
    718 
    719 		/*
    720 		 * Copy and pasting of line endings is inconsistent
    721 		 * in the inconsistent terminal and GUI world.
    722 		 * The best solution seems like to produce '\n' when
    723 		 * something is copied from st and convert '\n' to
    724 		 * '\r', when something to be pasted is received by
    725 		 * st.
    726 		 * FIXME: Fix the computer world.
    727 		 */
    728 		if ((y < sel.ne.y || lastx >= linelen)
    729 		    && (!(last->mode & ATTR_WRAP) || sel.type == SEL_RECTANGULAR))
    730 			*ptr++ = '\n';
    731 	}
    732 	*ptr = 0;
    733 	return str;
    734 }
    735 #endif // REFLOW_PATCH
    736 
    737 void
    738 selclear(void)
    739 {
    740 	if (sel.ob.x == -1)
    741 		return;
    742 	selremove();
    743 	tsetdirt(sel.nb.y, sel.ne.y);
    744 }
    745 
    746 void
    747 selremove(void)
    748 {
    749 	sel.mode = SEL_IDLE;
    750 	sel.ob.x = -1;
    751 }
    752 
    753 void
    754 die(const char *errstr, ...)
    755 {
    756 	va_list ap;
    757 
    758 	va_start(ap, errstr);
    759 	vfprintf(stderr, errstr, ap);
    760 	va_end(ap);
    761 	exit(1);
    762 }
    763 
    764 void
    765 execsh(char *cmd, char **args)
    766 {
    767 	char *sh, *prog, *arg;
    768 	const struct passwd *pw;
    769 
    770 	errno = 0;
    771 	if ((pw = getpwuid(getuid())) == NULL) {
    772 		if (errno)
    773 			die("getpwuid: %s\n", strerror(errno));
    774 		else
    775 			die("who are you?\n");
    776 	}
    777 
    778 	if ((sh = getenv("SHELL")) == NULL)
    779 		sh = (pw->pw_shell[0]) ? pw->pw_shell : cmd;
    780 
    781 	if (args) {
    782 		prog = args[0];
    783 		arg = NULL;
    784 	} else if (scroll) {
    785 		prog = scroll;
    786 		arg = utmp ? utmp : sh;
    787 	} else if (utmp) {
    788 		prog = utmp;
    789 		arg = NULL;
    790 	} else {
    791 		prog = sh;
    792 		arg = NULL;
    793 	}
    794 	DEFAULT(args, ((char *[]) {prog, arg, NULL}));
    795 
    796 	unsetenv("COLUMNS");
    797 	unsetenv("LINES");
    798 	unsetenv("TERMCAP");
    799 	setenv("LOGNAME", pw->pw_name, 1);
    800 	setenv("USER", pw->pw_name, 1);
    801 	setenv("SHELL", sh, 1);
    802 	setenv("HOME", pw->pw_dir, 1);
    803 	setenv("TERM", termname, 1);
    804 	setenv("COLORTERM", "truecolor", 1);
    805 
    806 	signal(SIGCHLD, SIG_DFL);
    807 	signal(SIGHUP, SIG_DFL);
    808 	signal(SIGINT, SIG_DFL);
    809 	signal(SIGQUIT, SIG_DFL);
    810 	signal(SIGTERM, SIG_DFL);
    811 	signal(SIGALRM, SIG_DFL);
    812 
    813 	execvp(prog, args);
    814 	_exit(1);
    815 }
    816 
    817 void
    818 sigchld(int a)
    819 {
    820 	int stat;
    821 	pid_t p;
    822 
    823 	while ((p = waitpid(-1, &stat, WNOHANG)) > 0) {
    824 		if (p == pid) {
    825 			#if EXTERNALPIPEIN_PATCH && EXTERNALPIPE_PATCH
    826 			close(csdfd);
    827 			#endif // EXTERNALPIPEIN_PATCH
    828 
    829 			if (WIFEXITED(stat) && WEXITSTATUS(stat))
    830 				die("child exited with status %d\n", WEXITSTATUS(stat));
    831 			else if (WIFSIGNALED(stat))
    832 				die("child terminated due to signal %d\n", WTERMSIG(stat));
    833 			_exit(0);
    834 		}
    835 	}
    836 }
    837 
    838 void
    839 stty(char **args)
    840 {
    841 	char cmd[_POSIX_ARG_MAX], **p, *q, *s;
    842 	size_t n, siz;
    843 
    844 	if ((n = strlen(stty_args)) > sizeof(cmd)-1)
    845 		die("incorrect stty parameters\n");
    846 	memcpy(cmd, stty_args, n);
    847 	q = cmd + n;
    848 	siz = sizeof(cmd) - n;
    849 	for (p = args; p && (s = *p); ++p) {
    850 		if ((n = strlen(s)) > siz-1)
    851 			die("stty parameter length too long\n");
    852 		*q++ = ' ';
    853 		memcpy(q, s, n);
    854 		q += n;
    855 		siz -= n + 1;
    856 	}
    857 	*q = '\0';
    858 	if (system(cmd) != 0)
    859 		perror("Couldn't call stty");
    860 }
    861 
    862 int
    863 ttynew(const char *line, char *cmd, const char *out, char **args)
    864 {
    865 	int m, s;
    866 	struct sigaction sa;
    867 
    868 	if (out) {
    869 		term.mode |= MODE_PRINT;
    870 		iofd = (!strcmp(out, "-")) ?
    871 			  1 : open(out, O_WRONLY | O_CREAT, 0666);
    872 		if (iofd < 0) {
    873 			fprintf(stderr, "Error opening %s:%s\n",
    874 				out, strerror(errno));
    875 		}
    876 	}
    877 
    878 	if (line) {
    879 		if ((cmdfd = open(line, O_RDWR)) < 0)
    880 			die("open line '%s' failed: %s\n",
    881 			    line, strerror(errno));
    882 		dup2(cmdfd, 0);
    883 		stty(args);
    884 		return cmdfd;
    885 	}
    886 
    887 	/* seems to work fine on linux, openbsd and freebsd */
    888 	if (openpty(&m, &s, NULL, NULL, NULL) < 0)
    889 		die("openpty failed: %s\n", strerror(errno));
    890 
    891 	switch (pid = fork()) {
    892 	case -1:
    893 		die("fork failed: %s\n", strerror(errno));
    894 		break;
    895 	case 0:
    896 		close(iofd);
    897 		close(m);
    898 		setsid(); /* create a new process group */
    899 		dup2(s, 0);
    900 		dup2(s, 1);
    901 		dup2(s, 2);
    902 		if (ioctl(s, TIOCSCTTY, NULL) < 0)
    903 			die("ioctl TIOCSCTTY failed: %s\n", strerror(errno));
    904 		if (s > 2)
    905 				close(s);
    906 #ifdef __OpenBSD__
    907 		if (pledge("stdio getpw proc exec", NULL) == -1)
    908 			die("pledge\n");
    909 #endif
    910 		execsh(cmd, args);
    911 		break;
    912 	default:
    913 #ifdef __OpenBSD__
    914 		#if RIGHTCLICKTOPLUMB_PATCH || OPENCOPIED_PATCH
    915 		if (pledge("stdio rpath tty proc ps exec", NULL) == -1)
    916 		#else
    917 		if (pledge("stdio rpath tty proc", NULL) == -1)
    918 		#endif // RIGHTCLICKTOPLUMB_PATCH
    919 			die("pledge\n");
    920 #endif
    921 		#if EXTERNALPIPEIN_PATCH && EXTERNALPIPE_PATCH
    922 		csdfd = s;
    923 		cmdfd = m;
    924 		#else
    925 		close(s);
    926 		cmdfd = m;
    927 		#endif // EXTERNALPIPEIN_PATCH
    928 		memset(&sa, 0, sizeof(sa));
    929 		sigemptyset(&sa.sa_mask);
    930 		sa.sa_handler = sigchld;
    931 		sigaction(SIGCHLD, &sa, NULL);
    932 		break;
    933 	}
    934 	return cmdfd;
    935 }
    936 
    937 size_t
    938 ttyread(void)
    939 {
    940 	static char buf[BUFSIZ];
    941 	static int buflen = 0;
    942 	int ret, written;
    943 
    944 	/* append read bytes to unprocessed bytes */
    945 	#if SYNC_PATCH
    946 	ret = twrite_aborted ? 1 : read(cmdfd, buf+buflen, LEN(buf)-buflen);
    947 	#else
    948 	ret = read(cmdfd, buf+buflen, LEN(buf)-buflen);
    949 	#endif // SYNC_PATCH
    950 
    951 	switch (ret) {
    952 	case 0:
    953 		exit(0);
    954 	case -1:
    955 		die("couldn't read from shell: %s\n", strerror(errno));
    956 	default:
    957 		#if SYNC_PATCH
    958 		buflen += twrite_aborted ? 0 : ret;
    959 		#else
    960 		buflen += ret;
    961 		#endif // SYNC_PATCH
    962 		written = twrite(buf, buflen, 0);
    963 		buflen -= written;
    964 		/* keep any incomplete UTF-8 byte sequence for the next call */
    965 		if (buflen > 0)
    966 			memmove(buf, buf + written, buflen);
    967 		return ret;
    968 	}
    969 }
    970 
    971 void
    972 ttywrite(const char *s, size_t n, int may_echo)
    973 {
    974 	const char *next;
    975 	#if REFLOW_PATCH || SCROLLBACK_PATCH
    976 	kscrolldown(&((Arg){ .i = term.scr }));
    977 	#endif // SCROLLBACK_PATCH
    978 
    979 	if (may_echo && IS_SET(MODE_ECHO))
    980 		twrite(s, n, 1);
    981 
    982 	if (!IS_SET(MODE_CRLF)) {
    983 		ttywriteraw(s, n);
    984 		return;
    985 	}
    986 
    987 	/* This is similar to how the kernel handles ONLCR for ttys */
    988 	while (n > 0) {
    989 		if (*s == '\r') {
    990 			next = s + 1;
    991 			ttywriteraw("\r\n", 2);
    992 		} else {
    993 			next = memchr(s, '\r', n);
    994 			DEFAULT(next, s + n);
    995 			ttywriteraw(s, next - s);
    996 		}
    997 		n -= next - s;
    998 		s = next;
    999 	}
   1000 }
   1001 
   1002 void
   1003 ttywriteraw(const char *s, size_t n)
   1004 {
   1005 	fd_set wfd, rfd;
   1006 	ssize_t r;
   1007 	size_t lim = 256;
   1008 
   1009 	/*
   1010 	 * Remember that we are using a pty, which might be a modem line.
   1011 	 * Writing too much will clog the line. That's why we are doing this
   1012 	 * dance.
   1013 	 * FIXME: Migrate the world to Plan 9.
   1014 	 */
   1015 	while (n > 0) {
   1016 		FD_ZERO(&wfd);
   1017 		FD_ZERO(&rfd);
   1018 		FD_SET(cmdfd, &wfd);
   1019 		FD_SET(cmdfd, &rfd);
   1020 
   1021 		/* Check if we can write. */
   1022 		if (pselect(cmdfd+1, &rfd, &wfd, NULL, NULL, NULL) < 0) {
   1023 			if (errno == EINTR)
   1024 				continue;
   1025 			die("select failed: %s\n", strerror(errno));
   1026 		}
   1027 		if (FD_ISSET(cmdfd, &wfd)) {
   1028 			/*
   1029 			 * Only write the bytes written by ttywrite() or the
   1030 			 * default of 256. This seems to be a reasonable value
   1031 			 * for a serial line. Bigger values might clog the I/O.
   1032 			 */
   1033 			if ((r = write(cmdfd, s, (n < lim)? n : lim)) < 0)
   1034 				goto write_error;
   1035 			if (r < n) {
   1036 				/*
   1037 				 * We weren't able to write out everything.
   1038 				 * This means the buffer is getting full
   1039 				 * again. Empty it.
   1040 				 */
   1041 				if (n < lim)
   1042 					lim = ttyread();
   1043 				n -= r;
   1044 				s += r;
   1045 			} else {
   1046 				/* All bytes have been written. */
   1047 				break;
   1048 			}
   1049 		}
   1050 		if (FD_ISSET(cmdfd, &rfd))
   1051 			lim = ttyread();
   1052 	}
   1053 	return;
   1054 
   1055 write_error:
   1056 	die("write error on tty: %s\n", strerror(errno));
   1057 }
   1058 
   1059 void
   1060 ttyresize(int tw, int th)
   1061 {
   1062 	struct winsize w;
   1063 
   1064 	w.ws_row = term.row;
   1065 	w.ws_col = term.col;
   1066 	w.ws_xpixel = tw;
   1067 	w.ws_ypixel = th;
   1068 	if (ioctl(cmdfd, TIOCSWINSZ, &w) < 0)
   1069 		fprintf(stderr, "Couldn't set window size: %s\n", strerror(errno));
   1070 }
   1071 
   1072 void
   1073 ttyhangup(void)
   1074 {
   1075 	/* Send SIGHUP to shell */
   1076 	kill(pid, SIGHUP);
   1077 }
   1078 
   1079 int
   1080 tattrset(int attr)
   1081 {
   1082 	int i, j;
   1083 
   1084 	for (i = 0; i < term.row-1; i++) {
   1085 		for (j = 0; j < term.col-1; j++) {
   1086 			if (term.line[i][j].mode & attr)
   1087 				return 1;
   1088 		}
   1089 	}
   1090 
   1091 	return 0;
   1092 }
   1093 
   1094 int
   1095 tisaltscr(void)
   1096 {
   1097 	return IS_SET(MODE_ALTSCREEN);
   1098 }
   1099 
   1100 void
   1101 tsetdirt(int top, int bot)
   1102 {
   1103 	int i;
   1104 
   1105 	LIMIT(top, 0, term.row-1);
   1106 	LIMIT(bot, 0, term.row-1);
   1107 
   1108 	for (i = top; i <= bot; i++)
   1109 		term.dirty[i] = 1;
   1110 }
   1111 
   1112 void
   1113 tsetdirtattr(int attr)
   1114 {
   1115 	int i, j;
   1116 
   1117 	for (i = 0; i < term.row-1; i++) {
   1118 		for (j = 0; j < term.col-1; j++) {
   1119 			if (term.line[i][j].mode & attr) {
   1120 				#if REFLOW_PATCH
   1121 				term.dirty[i] = 1;
   1122 				#else
   1123 				tsetdirt(i, i);
   1124 				#endif // REFLOW_PATCH
   1125 				break;
   1126 			}
   1127 		}
   1128 	}
   1129 }
   1130 
   1131 #if SIXEL_PATCH
   1132 void
   1133 tsetsixelattr(Line line, int x1, int x2)
   1134 {
   1135 	for (; x1 <= x2; x1++)
   1136 		line[x1].mode |= ATTR_SIXEL;
   1137 }
   1138 #endif // SIXEL_PATCH
   1139 
   1140 void
   1141 tfulldirt(void)
   1142 {
   1143 	#if SYNC_PATCH
   1144 	tsync_end();
   1145 	#endif // SYNC_PATCH
   1146 	#if REFLOW_PATCH
   1147 	for (int i = 0; i < term.row; i++)
   1148 		term.dirty[i] = 1;
   1149 	#else
   1150 	tsetdirt(0, term.row-1);
   1151 	#endif // REFLOW_PATCH
   1152 }
   1153 
   1154 void
   1155 tcursor(int mode)
   1156 {
   1157 	static TCursor c[2];
   1158 	int alt = IS_SET(MODE_ALTSCREEN);
   1159 
   1160 	if (mode == CURSOR_SAVE) {
   1161 		c[alt] = term.c;
   1162 	} else if (mode == CURSOR_LOAD) {
   1163 		term.c = c[alt];
   1164 		tmoveto(c[alt].x, c[alt].y);
   1165 	}
   1166 }
   1167 
   1168 void
   1169 tresetcursor(void)
   1170 {
   1171 	term.c = (TCursor){ { .mode = ATTR_NULL, .fg = defaultfg, .bg = defaultbg },
   1172 	                    .x = 0, .y = 0, .state = CURSOR_DEFAULT };
   1173 }
   1174 
   1175 void
   1176 treset(void)
   1177 {
   1178 	uint i;
   1179 	#if REFLOW_PATCH
   1180 	int x, y;
   1181 	#endif // REFLOW_PATCH
   1182 
   1183 	tresetcursor();
   1184 
   1185 	memset(term.tabs, 0, term.col * sizeof(*term.tabs));
   1186 	for (i = tabspaces; i < term.col; i += tabspaces)
   1187 		term.tabs[i] = 1;
   1188 	term.top = 0;
   1189 	term.bot = term.row - 1;
   1190 	term.mode = MODE_WRAP|MODE_UTF8;
   1191 	memset(term.trantbl, CS_USA, sizeof(term.trantbl));
   1192 	term.charset = 0;
   1193 	#if REFLOW_PATCH
   1194 	term.histf = 0;
   1195 	term.histi = 0;
   1196 	term.scr = 0;
   1197 	selremove();
   1198 	#endif // REFLOW_PATCH
   1199 
   1200 	for (i = 0; i < 2; i++) {
   1201 		#if REFLOW_PATCH
   1202 		tcursor(CURSOR_SAVE); /* reset saved cursor */
   1203 		for (y = 0; y < term.row; y++)
   1204 			for (x = 0; x < term.col; x++)
   1205 				tclearglyph(&term.line[y][x], 0);
   1206 		#else
   1207 		tmoveto(0, 0);
   1208 		tcursor(CURSOR_SAVE);
   1209 		#if COLUMNS_PATCH
   1210 		tclearregion(0, 0, term.maxcol-1, term.row-1);
   1211 		#else
   1212 		tclearregion(0, 0, term.col-1, term.row-1);
   1213 		#endif // COLUMNS_PATCH
   1214 		#endif // REFLOW_PATCH
   1215 		#if SIXEL_PATCH
   1216 		tdeleteimages();
   1217 		#endif // SIXEL_PATCH
   1218 		tswapscreen();
   1219 	}
   1220 	#if REFLOW_PATCH
   1221 	tfulldirt();
   1222 	#endif // REFLOW_PATCH
   1223 }
   1224 
   1225 #if !REFLOW_PATCH
   1226 void
   1227 tnew(int col, int row)
   1228 {
   1229 	term = (Term){ .c = { .attr = { .fg = defaultfg, .bg = defaultbg } } };
   1230 	tresize(col, row);
   1231 	treset();
   1232 }
   1233 #endif // REFLOW_PATCH
   1234 
   1235 #if !REFLOW_PATCH
   1236 void
   1237 tswapscreen(void)
   1238 {
   1239 	Line *tmp = term.line;
   1240 	#if SIXEL_PATCH
   1241 	ImageList *im = term.images;
   1242 	#endif // SIXEL_PATCH
   1243 
   1244 	term.line = term.alt;
   1245 	term.alt = tmp;
   1246 	#if SIXEL_PATCH
   1247 	term.images = term.images_alt;
   1248 	term.images_alt = im;
   1249 	#endif // SIXEL_PATCH
   1250 	term.mode ^= MODE_ALTSCREEN;
   1251 	tfulldirt();
   1252 }
   1253 #endif // REFLOW_PATCH
   1254 
   1255 #if !REFLOW_PATCH
   1256 void
   1257 tscrolldown(int orig, int n)
   1258 {
   1259 	#if OPENURLONCLICK_PATCH
   1260 	restoremousecursor();
   1261 	#endif //OPENURLONCLICK_PATCH
   1262 
   1263 	int i;
   1264 	Line temp;
   1265 	#if SIXEL_PATCH
   1266 	int bot = term.bot;
   1267 	#if SCROLLBACK_PATCH
   1268 	int scr = IS_SET(MODE_ALTSCREEN) ? 0 : term.scr;
   1269 	#else
   1270 	int scr = 0;
   1271 	#endif // SCROLLBACK_PATCH
   1272 	int itop = orig + scr, ibot = bot + scr;
   1273 	ImageList *im, *next;
   1274 	#endif // SIXEL_PATCH
   1275 
   1276 	LIMIT(n, 0, term.bot-orig+1);
   1277 
   1278 	tsetdirt(orig, term.bot-n);
   1279 	#if COLUMNS_PATCH
   1280 	tclearregion(0, term.bot-n+1, term.maxcol-1, term.bot);
   1281 	#else
   1282 	tclearregion(0, term.bot-n+1, term.col-1, term.bot);
   1283 	#endif // COLUMNS_PATCH
   1284 
   1285 	for (i = term.bot; i >= orig+n; i--) {
   1286 		temp = term.line[i];
   1287 		term.line[i] = term.line[i-n];
   1288 		term.line[i-n] = temp;
   1289 	}
   1290 
   1291 	#if SIXEL_PATCH
   1292 	/* move images, if they are inside the scrolling region */
   1293 	for (im = term.images; im; im = next) {
   1294 		next = im->next;
   1295 		if (im->y >= itop && im->y <= ibot) {
   1296 			im->y += n;
   1297 			if (im->y > ibot)
   1298 				delete_image(im);
   1299 		}
   1300 	}
   1301 	#endif // SIXEL_PATCH
   1302 
   1303 	#if SCROLLBACK_PATCH
   1304 	if (term.scr == 0)
   1305 		selscroll(orig, n);
   1306 	#else
   1307 	selscroll(orig, n);
   1308 	#endif // SCROLLBACK_PATCH
   1309 }
   1310 #endif // REFLOW_PATCH
   1311 
   1312 #if !REFLOW_PATCH
   1313 void
   1314 #if SCROLLBACK_PATCH
   1315 tscrollup(int orig, int n, int copyhist)
   1316 #else
   1317 tscrollup(int orig, int n)
   1318 #endif // SCROLLBACK_PATCH
   1319 {
   1320 	#if OPENURLONCLICK_PATCH
   1321 	restoremousecursor();
   1322 	#endif //OPENURLONCLICK_PATCH
   1323 
   1324 	int i;
   1325 	Line temp;
   1326 	#if SIXEL_PATCH
   1327 	int bot = term.bot;
   1328 	#if SCROLLBACK_PATCH
   1329 	int scr = IS_SET(MODE_ALTSCREEN) ? 0 : term.scr;
   1330 	#else
   1331 	int scr = 0;
   1332 	#endif // SCROLLBACK_PATCH
   1333 	int itop = orig + scr, ibot = bot + scr;
   1334 	ImageList *im, *next;
   1335 	#endif // SIXEL_PATCH
   1336 
   1337 	LIMIT(n, 0, term.bot-orig+1);
   1338 
   1339 	#if SCROLLBACK_PATCH
   1340 	if (copyhist && !IS_SET(MODE_ALTSCREEN)) {
   1341 		for (i = 0; i < n; i++) {
   1342 			term.histi = (term.histi + 1) % HISTSIZE;
   1343 			temp = term.hist[term.histi];
   1344 			term.hist[term.histi] = term.line[orig+i];
   1345 			term.line[orig+i] = temp;
   1346 		}
   1347 		term.histn = MIN(term.histn + n, HISTSIZE);
   1348 
   1349 		if (term.scr > 0 && term.scr < HISTSIZE)
   1350 			term.scr = MIN(term.scr + n, HISTSIZE-1);
   1351 	}
   1352 	#endif // SCROLLBACK_PATCH
   1353 
   1354 	#if COLUMNS_PATCH
   1355 	tclearregion(0, orig, term.maxcol-1, orig+n-1);
   1356 	#else
   1357 	tclearregion(0, orig, term.col-1, orig+n-1);
   1358 	#endif // COLUMNS_PATCH
   1359 	tsetdirt(orig+n, term.bot);
   1360 
   1361 	for (i = orig; i <= term.bot-n; i++) {
   1362 		temp = term.line[i];
   1363 		term.line[i] = term.line[i+n];
   1364 		term.line[i+n] = temp;
   1365 	}
   1366 
   1367 	#if SIXEL_PATCH
   1368 	#if SCROLLBACK_PATCH
   1369 	if (IS_SET(MODE_ALTSCREEN) || !copyhist || orig != 0) {
   1370 		/* move images, if they are inside the scrolling region */
   1371 		for (im = term.images; im; im = next) {
   1372 			next = im->next;
   1373 			if (im->y >= itop && im->y <= ibot) {
   1374 				im->y -= n;
   1375 				if (im->y < itop)
   1376 					delete_image(im);
   1377 			}
   1378 		}
   1379 	} else {
   1380 		/* move images, if they are inside the scrolling region or scrollback */
   1381 		for (im = term.images; im; im = next) {
   1382 			next = im->next;
   1383 			im->y -= scr;
   1384 			if (im->y < 0) {
   1385 				im->y -= n;
   1386 			} else if (im->y >= orig && im->y <= bot) {
   1387 				im->y -= n;
   1388 				if (im->y < orig)
   1389 					im->y -= orig; // move to scrollback
   1390 			}
   1391 			if (im->y < -HISTSIZE)
   1392 				delete_image(im);
   1393 			else
   1394 				im->y += term.scr;
   1395 		}
   1396 	}
   1397 	#else
   1398 	/* move images, if they are inside the scrolling region */
   1399 	for (im = term.images; im; im = next) {
   1400 		next = im->next;
   1401 		if (im->y >= itop && im->y <= ibot) {
   1402 			im->y -= n;
   1403 			if (im->y < itop)
   1404 				delete_image(im);
   1405 		}
   1406 	}
   1407 	#endif // SCROLLBACK_PATCH
   1408 	#endif // SIXEL_PATCH
   1409 
   1410 	#if SCROLLBACK_PATCH
   1411 	if (term.scr == 0)
   1412 		selscroll(orig, -n);
   1413 	#else
   1414 	selscroll(orig, -n);
   1415 	#endif // SCROLLBACK_PATCH
   1416 }
   1417 #endif // REFLOW_PATCH
   1418 
   1419 #if !REFLOW_PATCH
   1420 void
   1421 selscroll(int orig, int n)
   1422 {
   1423 	if (sel.ob.x == -1 || sel.alt != IS_SET(MODE_ALTSCREEN))
   1424 		return;
   1425 
   1426 	if (BETWEEN(sel.nb.y, orig, term.bot) != BETWEEN(sel.ne.y, orig, term.bot)) {
   1427 		selclear();
   1428 	} else if (BETWEEN(sel.nb.y, orig, term.bot)) {
   1429 		sel.ob.y += n;
   1430 		sel.oe.y += n;
   1431 		if (sel.ob.y < term.top || sel.ob.y > term.bot ||
   1432 		    sel.oe.y < term.top || sel.oe.y > term.bot) {
   1433 			selclear();
   1434 		} else {
   1435 			selnormalize();
   1436 		}
   1437 	}
   1438 }
   1439 #endif // REFLOW_PATCH
   1440 
   1441 void
   1442 tnewline(int first_col)
   1443 {
   1444 	int y = term.c.y;
   1445 
   1446 	if (y == term.bot) {
   1447 		#if REFLOW_PATCH
   1448 		tscrollup(term.top, term.bot, 1, SCROLL_SAVEHIST);
   1449 		#elif SCROLLBACK_PATCH
   1450 		tscrollup(term.top, 1, 1);
   1451 		#else
   1452 		tscrollup(term.top, 1);
   1453 		#endif // SCROLLBACK_PATCH
   1454 	} else {
   1455 		y++;
   1456 	}
   1457 	tmoveto(first_col ? 0 : term.c.x, y);
   1458 }
   1459 
   1460 #if UNDERCURL_PATCH
   1461 void
   1462 readcolonargs(char **p, int cursor, int params[][CAR_PER_ARG])
   1463 {
   1464 	int i = 0;
   1465 	for (; i < CAR_PER_ARG; i++)
   1466 		params[cursor][i] = -1;
   1467 
   1468 	if (**p != ':')
   1469 		return;
   1470 
   1471 	char *np = NULL;
   1472 	i = 0;
   1473 
   1474 	while (**p == ':' && i < CAR_PER_ARG) {
   1475 		while (**p == ':')
   1476 			(*p)++;
   1477 		params[cursor][i] = strtol(*p, &np, 10);
   1478 		*p = np;
   1479 		i++;
   1480 	}
   1481 }
   1482 #endif // UNDERCURL_PATCH
   1483 
   1484 void
   1485 csiparse(void)
   1486 {
   1487 	char *p = csiescseq.buf, *np;
   1488 	long int v;
   1489 	int sep = ';'; /* colon or semi-colon, but not both */
   1490 
   1491 	csiescseq.narg = 0;
   1492 	if (*p == '?') {
   1493 		csiescseq.priv = 1;
   1494 		p++;
   1495 	}
   1496 
   1497 	csiescseq.buf[csiescseq.len] = '\0';
   1498 	while (p < csiescseq.buf+csiescseq.len) {
   1499 		np = NULL;
   1500 		v = strtol(p, &np, 10);
   1501 		if (np == p)
   1502 			v = 0;
   1503 		if (v == LONG_MAX || v == LONG_MIN)
   1504 			v = -1;
   1505 		csiescseq.arg[csiescseq.narg++] = v;
   1506 		p = np;
   1507 		#if UNDERCURL_PATCH
   1508 		readcolonargs(&p, csiescseq.narg-1, csiescseq.carg);
   1509 		#endif // UNDERCURL_PATCH
   1510 		if (sep == ';' && *p == ':')
   1511 			sep = ':'; /* allow override to colon once */
   1512 		if (*p != sep || csiescseq.narg == ESC_ARG_SIZ)
   1513 			break;
   1514 		p++;
   1515 	}
   1516 	csiescseq.mode[0] = *p++;
   1517 	csiescseq.mode[1] = (p < csiescseq.buf+csiescseq.len) ? *p : '\0';
   1518 }
   1519 
   1520 /* for absolute user moves, when decom is set */
   1521 void
   1522 tmoveato(int x, int y)
   1523 {
   1524 	tmoveto(x, y + ((term.c.state & CURSOR_ORIGIN) ? term.top: 0));
   1525 }
   1526 
   1527 void
   1528 tmoveto(int x, int y)
   1529 {
   1530 	int miny, maxy;
   1531 
   1532 	if (term.c.state & CURSOR_ORIGIN) {
   1533 		miny = term.top;
   1534 		maxy = term.bot;
   1535 	} else {
   1536 		miny = 0;
   1537 		maxy = term.row - 1;
   1538 	}
   1539 	term.c.state &= ~CURSOR_WRAPNEXT;
   1540 	term.c.x = LIMIT(x, 0, term.col-1);
   1541 	term.c.y = LIMIT(y, miny, maxy);
   1542 }
   1543 
   1544 void
   1545 tsetchar(Rune u, const Glyph *attr, int x, int y)
   1546 {
   1547 	static const char *vt100_0[62] = { /* 0x41 - 0x7e */
   1548 		"↑", "↓", "→", "←", "█", "▚", "☃", /* A - G */
   1549 		0, 0, 0, 0, 0, 0, 0, 0, /* H - O */
   1550 		0, 0, 0, 0, 0, 0, 0, 0, /* P - W */
   1551 		0, 0, 0, 0, 0, 0, 0, " ", /* X - _ */
   1552 		"◆", "▒", "␉", "␌", "␍", "␊", "°", "±", /* ` - g */
   1553 		"␤", "␋", "┘", "┐", "┌", "└", "┼", "⎺", /* h - o */
   1554 		"⎻", "─", "⎼", "⎽", "├", "┤", "┴", "┬", /* p - w */
   1555 		"│", "≤", "≥", "π", "≠", "£", "·", /* x - ~ */
   1556 	};
   1557 
   1558 	/*
   1559 	 * The table is proudly stolen from rxvt.
   1560 	 */
   1561 	if (term.trantbl[term.charset] == CS_GRAPHIC0 &&
   1562 	   BETWEEN(u, 0x41, 0x7e) && vt100_0[u - 0x41])
   1563 		utf8decode(vt100_0[u - 0x41], &u, UTF_SIZ);
   1564 
   1565 	if (term.line[y][x].mode & ATTR_WIDE) {
   1566 		if (x+1 < term.col) {
   1567 			term.line[y][x+1].u = ' ';
   1568 			term.line[y][x+1].mode &= ~ATTR_WDUMMY;
   1569 		}
   1570 	} else if (term.line[y][x].mode & ATTR_WDUMMY) {
   1571 		term.line[y][x-1].u = ' ';
   1572 		term.line[y][x-1].mode &= ~ATTR_WIDE;
   1573 	}
   1574 
   1575 	term.dirty[y] = 1;
   1576 	term.line[y][x] = *attr;
   1577 	term.line[y][x].u = u;
   1578 	#if REFLOW_PATCH
   1579 	term.line[y][x].mode |= ATTR_SET;
   1580 	#endif // REFLOW_PATCH
   1581 
   1582 	#if BOXDRAW_PATCH
   1583 	if (isboxdraw(u))
   1584 		term.line[y][x].mode |= ATTR_BOXDRAW;
   1585 	#endif // BOXDRAW_PATCH
   1586 }
   1587 
   1588 #if !REFLOW_PATCH
   1589 void
   1590 tclearregion(int x1, int y1, int x2, int y2)
   1591 {
   1592 	int x, y, temp;
   1593 	Glyph *gp;
   1594 
   1595 	if (x1 > x2)
   1596 		temp = x1, x1 = x2, x2 = temp;
   1597 	if (y1 > y2)
   1598 		temp = y1, y1 = y2, y2 = temp;
   1599 
   1600 	#if COLUMNS_PATCH
   1601 	LIMIT(x1, 0, term.maxcol-1);
   1602 	LIMIT(x2, 0, term.maxcol-1);
   1603 	#else
   1604 	LIMIT(x1, 0, term.col-1);
   1605 	LIMIT(x2, 0, term.col-1);
   1606 	#endif // COLUMNS_PATCH
   1607 	LIMIT(y1, 0, term.row-1);
   1608 	LIMIT(y2, 0, term.row-1);
   1609 
   1610 	for (y = y1; y <= y2; y++) {
   1611 		term.dirty[y] = 1;
   1612 		for (x = x1; x <= x2; x++) {
   1613 			gp = &term.line[y][x];
   1614 			if (selected(x, y))
   1615 				selclear();
   1616 			gp->fg = term.c.attr.fg;
   1617 			gp->bg = term.c.attr.bg;
   1618 			gp->mode = 0;
   1619 			gp->u = ' ';
   1620 		}
   1621 	}
   1622 }
   1623 #endif // REFLOW_PATCH
   1624 
   1625 #if !REFLOW_PATCH
   1626 void
   1627 tdeletechar(int n)
   1628 {
   1629 	int dst, src, size;
   1630 	Glyph *line;
   1631 
   1632 	LIMIT(n, 0, term.col - term.c.x);
   1633 
   1634 	dst = term.c.x;
   1635 	src = term.c.x + n;
   1636 	size = term.col - src;
   1637 	line = term.line[term.c.y];
   1638 
   1639 	memmove(&line[dst], &line[src], size * sizeof(Glyph));
   1640 	tclearregion(term.col-n, term.c.y, term.col-1, term.c.y);
   1641 }
   1642 #endif // REFLOW_PATCH
   1643 
   1644 #if !REFLOW_PATCH
   1645 void
   1646 tinsertblank(int n)
   1647 {
   1648 	int dst, src, size;
   1649 	Glyph *line;
   1650 
   1651 	LIMIT(n, 0, term.col - term.c.x);
   1652 
   1653 	dst = term.c.x + n;
   1654 	src = term.c.x;
   1655 	size = term.col - dst;
   1656 	line = term.line[term.c.y];
   1657 
   1658 	memmove(&line[dst], &line[src], size * sizeof(Glyph));
   1659 	tclearregion(src, term.c.y, dst - 1, term.c.y);
   1660 }
   1661 #endif // REFLOW_PATCH
   1662 
   1663 void
   1664 tinsertblankline(int n)
   1665 {
   1666 	if (BETWEEN(term.c.y, term.top, term.bot))
   1667 		tscrolldown(term.c.y, n);
   1668 }
   1669 
   1670 #if SIXEL_PATCH
   1671 void
   1672 tdeleteimages(void)
   1673 {
   1674 	ImageList *im, *next;
   1675 
   1676 	for (im = term.images; im; im = next) {
   1677 		next = im->next;
   1678 		delete_image(im);
   1679 	}
   1680 }
   1681 #endif // SIXEL_PATCH
   1682 
   1683 void
   1684 tdeleteline(int n)
   1685 {
   1686 	if (BETWEEN(term.c.y, term.top, term.bot)) {
   1687 		#if REFLOW_PATCH
   1688 		tscrollup(term.c.y, term.bot, n, SCROLL_NOSAVEHIST);
   1689 		#elif SCROLLBACK_PATCH
   1690 		tscrollup(term.c.y, n, 0);
   1691 		#else
   1692 		tscrollup(term.c.y, n);
   1693 		#endif // SCROLLBACK_PATCH
   1694 	}
   1695 }
   1696 
   1697 int32_t
   1698 tdefcolor(const int *attr, int *npar, int l)
   1699 {
   1700 	int32_t idx = -1;
   1701 	uint r, g, b;
   1702 
   1703 	switch (attr[*npar + 1]) {
   1704 	case 2: /* direct color in RGB space */
   1705 		if (*npar + 4 >= l) {
   1706 			fprintf(stderr,
   1707 				"erresc(38): Incorrect number of parameters (%d)\n",
   1708 				*npar);
   1709 			break;
   1710 		}
   1711 		r = attr[*npar + 2];
   1712 		g = attr[*npar + 3];
   1713 		b = attr[*npar + 4];
   1714 		*npar += 4;
   1715 		if (!BETWEEN(r, 0, 255) || !BETWEEN(g, 0, 255) || !BETWEEN(b, 0, 255))
   1716 			fprintf(stderr, "erresc: bad rgb color (%u,%u,%u)\n",
   1717 				r, g, b);
   1718 		else
   1719 			idx = TRUECOLOR(r, g, b);
   1720 		break;
   1721 	case 5: /* indexed color */
   1722 		if (*npar + 2 >= l) {
   1723 			fprintf(stderr,
   1724 				"erresc(38): Incorrect number of parameters (%d)\n",
   1725 				*npar);
   1726 			break;
   1727 		}
   1728 		*npar += 2;
   1729 		if (!BETWEEN(attr[*npar], 0, 255))
   1730 			fprintf(stderr, "erresc: bad fgcolor %d\n", attr[*npar]);
   1731 		else
   1732 			idx = attr[*npar];
   1733 		break;
   1734 	case 0: /* implemented defined (only foreground) */
   1735 	case 1: /* transparent */
   1736 	case 3: /* direct color in CMY space */
   1737 	case 4: /* direct color in CMYK space */
   1738 	default:
   1739 		fprintf(stderr,
   1740 		        "erresc(38): gfx attr %d unknown\n", attr[*npar]);
   1741 		break;
   1742 	}
   1743 
   1744 	return idx;
   1745 }
   1746 
   1747 void
   1748 tsetattr(const int *attr, int l)
   1749 {
   1750 	int i;
   1751 	int32_t idx;
   1752 
   1753 	for (i = 0; i < l; i++) {
   1754 		switch (attr[i]) {
   1755 		case 0:
   1756 			term.c.attr.mode &= ~(
   1757 				ATTR_BOLD       |
   1758 				ATTR_FAINT      |
   1759 				ATTR_ITALIC     |
   1760 				ATTR_UNDERLINE  |
   1761 				ATTR_BLINK      |
   1762 				ATTR_REVERSE    |
   1763 				ATTR_INVISIBLE  |
   1764 				ATTR_STRUCK     );
   1765 			term.c.attr.fg = defaultfg;
   1766 			term.c.attr.bg = defaultbg;
   1767 			#if UNDERCURL_PATCH
   1768 			term.c.attr.ustyle = -1;
   1769 			term.c.attr.ucolor[0] = -1;
   1770 			term.c.attr.ucolor[1] = -1;
   1771 			term.c.attr.ucolor[2] = -1;
   1772 			#endif // UNDERCURL_PATCH
   1773 			break;
   1774 		case 1:
   1775 			term.c.attr.mode |= ATTR_BOLD;
   1776 			break;
   1777 		case 2:
   1778 			term.c.attr.mode |= ATTR_FAINT;
   1779 			break;
   1780 		case 3:
   1781 			term.c.attr.mode |= ATTR_ITALIC;
   1782 			break;
   1783 		case 4:
   1784 			#if UNDERCURL_PATCH
   1785 			term.c.attr.ustyle = csiescseq.carg[i][0];
   1786 
   1787 			if (term.c.attr.ustyle != 0)
   1788 				term.c.attr.mode |= ATTR_UNDERLINE;
   1789 			else
   1790 				term.c.attr.mode &= ~ATTR_UNDERLINE;
   1791 
   1792 			term.c.attr.mode ^= ATTR_DIRTYUNDERLINE;
   1793 			#else
   1794 			term.c.attr.mode |= ATTR_UNDERLINE;
   1795 			#endif // UNDERCURL_PATCH
   1796 			break;
   1797 		case 5: /* slow blink */
   1798 			/* FALLTHROUGH */
   1799 		case 6: /* rapid blink */
   1800 			term.c.attr.mode |= ATTR_BLINK;
   1801 			break;
   1802 		case 7:
   1803 			term.c.attr.mode |= ATTR_REVERSE;
   1804 			break;
   1805 		case 8:
   1806 			term.c.attr.mode |= ATTR_INVISIBLE;
   1807 			break;
   1808 		case 9:
   1809 			term.c.attr.mode |= ATTR_STRUCK;
   1810 			break;
   1811 		case 22:
   1812 			term.c.attr.mode &= ~(ATTR_BOLD | ATTR_FAINT);
   1813 			break;
   1814 		case 23:
   1815 			term.c.attr.mode &= ~ATTR_ITALIC;
   1816 			break;
   1817 		case 24:
   1818 			term.c.attr.mode &= ~ATTR_UNDERLINE;
   1819 			break;
   1820 		case 25:
   1821 			term.c.attr.mode &= ~ATTR_BLINK;
   1822 			break;
   1823 		case 27:
   1824 			term.c.attr.mode &= ~ATTR_REVERSE;
   1825 			break;
   1826 		case 28:
   1827 			term.c.attr.mode &= ~ATTR_INVISIBLE;
   1828 			break;
   1829 		case 29:
   1830 			term.c.attr.mode &= ~ATTR_STRUCK;
   1831 			break;
   1832 		case 38:
   1833 			if ((idx = tdefcolor(attr, &i, l)) >= 0)
   1834 				#if MONOCHROME_PATCH
   1835 				term.c.attr.fg = defaultfg;
   1836 				#else
   1837 				term.c.attr.fg = idx;
   1838 				#endif // MONOCHROME_PATCH
   1839 			break;
   1840 		case 39: /* set foreground color to default */
   1841 			term.c.attr.fg = defaultfg;
   1842 			break;
   1843 		case 48:
   1844 			if ((idx = tdefcolor(attr, &i, l)) >= 0)
   1845 				#if MONOCHROME_PATCH
   1846 				term.c.attr.bg = 0;
   1847 				#else
   1848 				term.c.attr.bg = idx;
   1849 				#endif // MONOCHROME_PATCH
   1850 			break;
   1851 		case 49: /* set background color to default */
   1852 			term.c.attr.bg = defaultbg;
   1853 			break;
   1854 		#if UNDERCURL_PATCH
   1855 		case 58:
   1856 			term.c.attr.ucolor[0] = csiescseq.carg[i][1];
   1857 			term.c.attr.ucolor[1] = csiescseq.carg[i][2];
   1858 			term.c.attr.ucolor[2] = csiescseq.carg[i][3];
   1859 			term.c.attr.mode ^= ATTR_DIRTYUNDERLINE;
   1860 			break;
   1861 		case 59:
   1862 			term.c.attr.ucolor[0] = -1;
   1863 			term.c.attr.ucolor[1] = -1;
   1864 			term.c.attr.ucolor[2] = -1;
   1865 			term.c.attr.mode ^= ATTR_DIRTYUNDERLINE;
   1866 			break;
   1867 		#else
   1868 		case 58:
   1869 			/* This starts a sequence to change the color of
   1870 			 * "underline" pixels. We don't support that and
   1871 			 * instead eat up a following "5;n" or "2;r;g;b". */
   1872 			tdefcolor(attr, &i, l);
   1873 			break;
   1874 		#endif // UNDERCURL_PATCH
   1875 		default:
   1876 			if (BETWEEN(attr[i], 30, 37)) {
   1877 				#if MONOCHROME_PATCH
   1878 				term.c.attr.fg = defaultfg;
   1879 				#else
   1880 				term.c.attr.fg = attr[i] - 30;
   1881 				#endif // MONOCHROME_PATCH
   1882 			} else if (BETWEEN(attr[i], 40, 47)) {
   1883 				#if MONOCHROME_PATCH
   1884 				term.c.attr.bg = 0;
   1885 				#else
   1886 				term.c.attr.bg = attr[i] - 40;
   1887 				#endif // MONOCHROME_PATCH
   1888 			} else if (BETWEEN(attr[i], 90, 97)) {
   1889 				#if MONOCHROME_PATCH
   1890 				term.c.attr.fg = defaultfg;
   1891 				#else
   1892 				term.c.attr.fg = attr[i] - 90 + 8;
   1893 				#endif // MONOCHROME_PATCH
   1894 			} else if (BETWEEN(attr[i], 100, 107)) {
   1895 				#if MONOCHROME_PATCH
   1896 				term.c.attr.bg = 0;
   1897 				#else
   1898 				term.c.attr.bg = attr[i] - 100 + 8;
   1899 				#endif // MONOCHROME_PATCH
   1900 			} else {
   1901 				fprintf(stderr,
   1902 					"erresc(default): gfx attr %d unknown\n",
   1903 					attr[i]);
   1904 				csidump();
   1905 			}
   1906 			break;
   1907 		}
   1908 	}
   1909 }
   1910 
   1911 void
   1912 tsetscroll(int t, int b)
   1913 {
   1914 	int temp;
   1915 
   1916 	LIMIT(t, 0, term.row-1);
   1917 	LIMIT(b, 0, term.row-1);
   1918 	if (t > b) {
   1919 		temp = t;
   1920 		t = b;
   1921 		b = temp;
   1922 	}
   1923 	term.top = t;
   1924 	term.bot = b;
   1925 }
   1926 
   1927 void
   1928 tsetmode(int priv, int set, const int *args, int narg)
   1929 {
   1930 	int alt;
   1931 	const int *lim;
   1932 
   1933 	for (lim = args + narg; args < lim; ++args) {
   1934 		if (priv) {
   1935 			switch (*args) {
   1936 			case 1: /* DECCKM -- Cursor key */
   1937 				xsetmode(set, MODE_APPCURSOR);
   1938 				break;
   1939 			case 5: /* DECSCNM -- Reverse video */
   1940 				xsetmode(set, MODE_REVERSE);
   1941 				break;
   1942 			case 6: /* DECOM -- Origin */
   1943 				MODBIT(term.c.state, set, CURSOR_ORIGIN);
   1944 				tmoveato(0, 0);
   1945 				break;
   1946 			case 7: /* DECAWM -- Auto wrap */
   1947 				MODBIT(term.mode, set, MODE_WRAP);
   1948 				break;
   1949 			case 0:  /* Error (IGNORED) */
   1950 			case 2:  /* DECANM -- ANSI/VT52 (IGNORED) */
   1951 			case 3:  /* DECCOLM -- Column  (IGNORED) */
   1952 			case 4:  /* DECSCLM -- Scroll (IGNORED) */
   1953 			case 8:  /* DECARM -- Auto repeat (IGNORED) */
   1954 			case 18: /* DECPFF -- Printer feed (IGNORED) */
   1955 			case 19: /* DECPEX -- Printer extent (IGNORED) */
   1956 			case 42: /* DECNRCM -- National characters (IGNORED) */
   1957 			case 12: /* att610 -- Start blinking cursor (IGNORED) */
   1958 				break;
   1959 			case 25: /* DECTCEM -- Text Cursor Enable Mode */
   1960 				xsetmode(!set, MODE_HIDE);
   1961 				break;
   1962 			case 9:    /* X10 mouse compatibility mode */
   1963 				xsetpointermotion(0);
   1964 				xsetmode(0, MODE_MOUSE);
   1965 				xsetmode(set, MODE_MOUSEX10);
   1966 				break;
   1967 			case 1000: /* 1000: report button press */
   1968 				xsetpointermotion(0);
   1969 				xsetmode(0, MODE_MOUSE);
   1970 				xsetmode(set, MODE_MOUSEBTN);
   1971 				break;
   1972 			case 1002: /* 1002: report motion on button press */
   1973 				xsetpointermotion(0);
   1974 				xsetmode(0, MODE_MOUSE);
   1975 				xsetmode(set, MODE_MOUSEMOTION);
   1976 				break;
   1977 			case 1003: /* 1003: enable all mouse motions */
   1978 				xsetpointermotion(set);
   1979 				xsetmode(0, MODE_MOUSE);
   1980 				xsetmode(set, MODE_MOUSEMANY);
   1981 				break;
   1982 			case 1004: /* 1004: send focus events to tty */
   1983 				xsetmode(set, MODE_FOCUS);
   1984 				break;
   1985 			case 1006: /* 1006: extended reporting mode */
   1986 				xsetmode(set, MODE_MOUSESGR);
   1987 				break;
   1988 			case 1034: /* 1034: enable 8-bit mode for keyboard input */
   1989 				xsetmode(set, MODE_8BIT);
   1990 				break;
   1991 			case 1049: /* swap screen & set/restore cursor as xterm */
   1992 				if (!allowaltscreen)
   1993 					break;
   1994 				tcursor((set) ? CURSOR_SAVE : CURSOR_LOAD);
   1995 				/* FALLTHROUGH */
   1996 			case 47: /* swap screen buffer */
   1997 			case 1047: /* swap screen buffer */
   1998 				if (!allowaltscreen)
   1999 					break;
   2000 				#if REFLOW_PATCH
   2001 				if (set)
   2002 					tloadaltscreen(*args != 47, *args == 1049);
   2003 				else
   2004 					tloaddefscreen(*args != 47, *args == 1049);
   2005 				break;
   2006 				#else
   2007 				alt = IS_SET(MODE_ALTSCREEN);
   2008 				if (alt) {
   2009 					#if COLUMNS_PATCH
   2010 					tclearregion(0, 0, term.maxcol-1, term.row-1);
   2011 					#else
   2012 					tclearregion(0, 0, term.col-1, term.row-1);
   2013 					#endif // COLUMNS_PATCH
   2014 				}
   2015 				if (set ^ alt) /* set is always 1 or 0 */
   2016 					tswapscreen();
   2017 				if (*args != 1049)
   2018 					break;
   2019 				/* FALLTHROUGH */
   2020 				#endif // REFLOW_PATCH
   2021 			case 1048: /* save/restore cursor (like DECSC/DECRC) */
   2022 				#if REFLOW_PATCH
   2023 				if (!allowaltscreen)
   2024 					break;
   2025 				#endif // REFLOW_PATCH
   2026 				tcursor((set) ? CURSOR_SAVE : CURSOR_LOAD);
   2027 				break;
   2028 			case 2004: /* 2004: bracketed paste mode */
   2029 				xsetmode(set, MODE_BRCKTPASTE);
   2030 				break;
   2031 			/* Not implemented mouse modes. See comments there. */
   2032 			case 1001: /* mouse highlight mode; can hang the
   2033 				      terminal by design when implemented. */
   2034 			case 1005: /* UTF-8 mouse mode; will confuse
   2035 				      applications not supporting UTF-8
   2036 				      and luit. */
   2037 			case 1015: /* urxvt mangled mouse mode; incompatible
   2038 				      and can be mistaken for other control
   2039 				      codes. */
   2040 				break;
   2041 			#if SIXEL_PATCH
   2042 			case 80: /* DECSDM -- Sixel Display Mode */
   2043 				MODBIT(term.mode, set, MODE_SIXEL_SDM);
   2044 				break;
   2045 			case 8452: /* sixel scrolling leaves cursor to right of graphic */
   2046 				MODBIT(term.mode, set, MODE_SIXEL_CUR_RT);
   2047 				break;
   2048 			#endif // SIXEL_PATCH
   2049 			#if SYNC_PATCH
   2050 			case 2026:
   2051 				if (set) {
   2052 					tsync_begin();
   2053 				} else {
   2054 					tsync_end();
   2055 				}
   2056 				break;
   2057 			#endif // SYNC_PATCH
   2058 			default:
   2059 				fprintf(stderr,
   2060 					"erresc: unknown private set/reset mode %d\n",
   2061 					*args);
   2062 				break;
   2063 			}
   2064 		} else {
   2065 			switch (*args) {
   2066 			case 0:  /* Error (IGNORED) */
   2067 				break;
   2068 			case 2:
   2069 				xsetmode(set, MODE_KBDLOCK);
   2070 				break;
   2071 			case 4:  /* IRM -- Insertion-replacement */
   2072 				MODBIT(term.mode, set, MODE_INSERT);
   2073 				break;
   2074 			case 12: /* SRM -- Send/Receive */
   2075 				MODBIT(term.mode, !set, MODE_ECHO);
   2076 				break;
   2077 			case 20: /* LNM -- Linefeed/new line */
   2078 				MODBIT(term.mode, set, MODE_CRLF);
   2079 				break;
   2080 			default:
   2081 				fprintf(stderr,
   2082 					"erresc: unknown set/reset mode %d\n",
   2083 					*args);
   2084 				break;
   2085 			}
   2086 		}
   2087 	}
   2088 }
   2089 
   2090 void
   2091 csihandle(void)
   2092 {
   2093 	char buffer[40];
   2094 	int n = 0, len;
   2095 	#if SIXEL_PATCH
   2096 	ImageList *im, *next;
   2097 	int pi, pa;
   2098 	#endif // SIXEL_PATCH
   2099 	#if REFLOW_PATCH
   2100 	int x;
   2101 	#endif // REFLOW_PATCH
   2102 	#if COLUMNS_PATCH
   2103 	int maxcol = term.maxcol;
   2104 	#else
   2105 	int maxcol = term.col;
   2106 	#endif // COLUMNS_PATCH
   2107 
   2108 	switch (csiescseq.mode[0]) {
   2109 	default:
   2110 	unknown:
   2111 		fprintf(stderr, "erresc: unknown csi ");
   2112 		csidump();
   2113 		/* die(""); */
   2114 		break;
   2115 	case '@': /* ICH -- Insert <n> blank char */
   2116 		DEFAULT(csiescseq.arg[0], 1);
   2117 		tinsertblank(csiescseq.arg[0]);
   2118 		break;
   2119 	case 'A': /* CUU -- Cursor <n> Up */
   2120 		DEFAULT(csiescseq.arg[0], 1);
   2121 		tmoveto(term.c.x, term.c.y-csiescseq.arg[0]);
   2122 		break;
   2123 	case 'B': /* CUD -- Cursor <n> Down */
   2124 	case 'e': /* VPR --Cursor <n> Down */
   2125 		DEFAULT(csiescseq.arg[0], 1);
   2126 		tmoveto(term.c.x, term.c.y+csiescseq.arg[0]);
   2127 		break;
   2128 	case 'i': /* MC -- Media Copy */
   2129 		switch (csiescseq.arg[0]) {
   2130 		case 0:
   2131 			tdump();
   2132 			break;
   2133 		case 1:
   2134 			tdumpline(term.c.y);
   2135 			break;
   2136 		case 2:
   2137 			tdumpsel();
   2138 			break;
   2139 		case 4:
   2140 			term.mode &= ~MODE_PRINT;
   2141 			break;
   2142 		case 5:
   2143 			term.mode |= MODE_PRINT;
   2144 			break;
   2145 		}
   2146 		break;
   2147 	case 'c': /* DA -- Device Attributes */
   2148 		if (csiescseq.arg[0] == 0)
   2149 			ttywrite(vtiden, strlen(vtiden), 0);
   2150 		break;
   2151 	case 'b': /* REP -- if last char is printable print it <n> more times */
   2152 		LIMIT(csiescseq.arg[0], 1, 65535);
   2153 		if (term.lastc)
   2154 			while (csiescseq.arg[0]-- > 0)
   2155 				tputc(term.lastc);
   2156 		break;
   2157 	case 'C': /* CUF -- Cursor <n> Forward */
   2158 	case 'a': /* HPR -- Cursor <n> Forward */
   2159 		DEFAULT(csiescseq.arg[0], 1);
   2160 		tmoveto(term.c.x+csiescseq.arg[0], term.c.y);
   2161 		break;
   2162 	case 'D': /* CUB -- Cursor <n> Backward */
   2163 		DEFAULT(csiescseq.arg[0], 1);
   2164 		tmoveto(term.c.x-csiescseq.arg[0], term.c.y);
   2165 		break;
   2166 	case 'E': /* CNL -- Cursor <n> Down and first col */
   2167 		DEFAULT(csiescseq.arg[0], 1);
   2168 		tmoveto(0, term.c.y+csiescseq.arg[0]);
   2169 		break;
   2170 	case 'F': /* CPL -- Cursor <n> Up and first col */
   2171 		DEFAULT(csiescseq.arg[0], 1);
   2172 		tmoveto(0, term.c.y-csiescseq.arg[0]);
   2173 		break;
   2174 	case 'g': /* TBC -- Tabulation clear */
   2175 		switch (csiescseq.arg[0]) {
   2176 		case 0: /* clear current tab stop */
   2177 			term.tabs[term.c.x] = 0;
   2178 			break;
   2179 		case 3: /* clear all the tabs */
   2180 			memset(term.tabs, 0, term.col * sizeof(*term.tabs));
   2181 			break;
   2182 		default:
   2183 			goto unknown;
   2184 		}
   2185 		break;
   2186 	case 'G': /* CHA -- Move to <col> */
   2187 	case '`': /* HPA */
   2188 		DEFAULT(csiescseq.arg[0], 1);
   2189 		tmoveto(csiescseq.arg[0]-1, term.c.y);
   2190 		break;
   2191 	case 'H': /* CUP -- Move to <row> <col> */
   2192 	case 'f': /* HVP */
   2193 		DEFAULT(csiescseq.arg[0], 1);
   2194 		DEFAULT(csiescseq.arg[1], 1);
   2195 		tmoveato(csiescseq.arg[1]-1, csiescseq.arg[0]-1);
   2196 		break;
   2197 	case 'I': /* CHT -- Cursor Forward Tabulation <n> tab stops */
   2198 		DEFAULT(csiescseq.arg[0], 1);
   2199 		tputtab(csiescseq.arg[0]);
   2200 		break;
   2201 	case 'J': /* ED -- Clear screen */
   2202 		switch (csiescseq.arg[0]) {
   2203 		case 0: /* below */
   2204 			#if REFLOW_PATCH
   2205 			tclearregion(term.c.x, term.c.y, term.col-1, term.c.y, 1);
   2206 			if (term.c.y < term.row-1)
   2207 				tclearregion(0, term.c.y+1, term.col-1, term.row-1, 1);
   2208 			#else
   2209 			tclearregion(term.c.x, term.c.y, maxcol-1, term.c.y);
   2210 			if (term.c.y < term.row-1)
   2211 				tclearregion(0, term.c.y+1, maxcol-1, term.row-1);
   2212 			#endif // REFLOW_PATCH
   2213 			break;
   2214 		case 1: /* above */
   2215 			#if REFLOW_PATCH
   2216 			if (term.c.y > 0)
   2217 				tclearregion(0, 0, term.col-1, term.c.y-1, 1);
   2218 			tclearregion(0, term.c.y, term.c.x, term.c.y, 1);
   2219 			#else
   2220 			if (term.c.y > 0)
   2221 				tclearregion(0, 0, maxcol-1, term.c.y-1);
   2222 			tclearregion(0, term.c.y, term.c.x, term.c.y);
   2223 			#endif // REFLOW_PATCH
   2224 			break;
   2225 		case 2: /* screen */
   2226 			#if REFLOW_PATCH
   2227 			if (IS_SET(MODE_ALTSCREEN)) {
   2228 				tclearregion(0, 0, term.col-1, term.row-1, 1);
   2229 				#if SIXEL_PATCH
   2230 				tdeleteimages();
   2231 				#endif // SIXEL_PATCH
   2232 				break;
   2233 			}
   2234 			/* vte does this:
   2235 			tscrollup(0, term.row-1, term.row, SCROLL_SAVEHIST); */
   2236 			/* alacritty does this: */
   2237 			for (n = term.row-1; n >= 0 && tlinelen(term.line[n]) == 0; n--)
   2238 				;
   2239 			#if SIXEL_PATCH
   2240 			for (im = term.images; im; im = im->next)
   2241 				n = MAX(im->y - term.scr, n);
   2242 			#endif // SIXEL_PATCH
   2243 			if (n >= 0)
   2244 				tscrollup(0, term.row-1, n+1, SCROLL_SAVEHIST);
   2245 			tscrollup(0, term.row-1, term.row-n-1, SCROLL_NOSAVEHIST);
   2246 			break;
   2247 			#else // !REFLOW_PATCH
   2248 			#if SCROLLBACK_PATCH
   2249 			if (!IS_SET(MODE_ALTSCREEN)) {
   2250 				#if SCROLLBACK_PATCH
   2251 				kscrolldown(&((Arg){ .i = term.scr }));
   2252 				#endif
   2253 				int n, m, bot = term.bot;
   2254 				term.bot = term.row-1;
   2255 				for (n = term.row-1; n >= 0; n--) {
   2256 					for (m = 0; m < maxcol && term.line[n][m].u == ' ' && !term.line[n][m].mode; m++);
   2257 					if (m < maxcol) {
   2258 						#if SCROLLBACK_PATCH
   2259 						tscrollup(0, n+1, 1);
   2260 						#else
   2261 						tscrollup(0, n+1);
   2262 						#endif
   2263 						break;
   2264 					}
   2265 				}
   2266 				if (n < term.row-1)
   2267 					tclearregion(0, 0, maxcol-1, term.row-n-2);
   2268 				term.bot = bot;
   2269 				break;
   2270 			}
   2271 			#endif // SCROLLBACK_PATCH
   2272 
   2273 			tclearregion(0, 0, maxcol-1, term.row-1);
   2274 			#if SIXEL_PATCH
   2275 			tdeleteimages();
   2276 			#endif // SIXEL_PATCH
   2277 			#endif // REFLOW_PTCH
   2278 			break;
   2279 		case 3: /* scrollback */
   2280 			#if REFLOW_PATCH
   2281 			if (IS_SET(MODE_ALTSCREEN))
   2282 				break;
   2283 			kscrolldown(&((Arg){ .i = term.scr }));
   2284 			term.scr = 0;
   2285 			term.histi = 0;
   2286 			term.histf = 0;
   2287 			#if SIXEL_PATCH
   2288 			for (im = term.images; im; im = next) {
   2289 				next = im->next;
   2290 				if (im->y < 0)
   2291 					delete_image(im);
   2292 			}
   2293 			#endif // SIXEL_PATCH
   2294 			break;
   2295 			#else // !REFLOW_PATCH
   2296 			#if SCROLLBACK_PATCH
   2297 			if (!IS_SET(MODE_ALTSCREEN)) {
   2298 				term.scr = 0;
   2299 				term.histi = 0;
   2300 				term.histn = 0;
   2301 				Glyph g=(Glyph){.bg=term.c.attr.bg, .fg=term.c.attr.fg, .u=' ', .mode=0};
   2302 				for (int i = 0; i < HISTSIZE; i++) {
   2303 					for (int j = 0; j < maxcol; j++)
   2304 						term.hist[i][j] = g;
   2305 				}
   2306 			}
   2307 			#endif // SCROLLBACK_PATCH
   2308 			#if SIXEL_PATCH
   2309 			for (im = term.images; im; im = next) {
   2310 				next = im->next;
   2311 				if (im->y < 0)
   2312 					delete_image(im);
   2313 			}
   2314 			#endif // SIXEL_PATCH
   2315 			break;
   2316 			#endif // REFLOW_PATCH
   2317 		#if SIXEL_PATCH
   2318 		case 6: /* sixels */
   2319 			tdeleteimages();
   2320 			tfulldirt();
   2321 			break;
   2322 		#endif // SIXEL_PATCH
   2323 		default:
   2324 			goto unknown;
   2325 		}
   2326 		break;
   2327 	case 'K': /* EL -- Clear line */
   2328 		switch (csiescseq.arg[0]) {
   2329 		#if REFLOW_PATCH
   2330 		case 0: /* right */
   2331 			tclearregion(term.c.x, term.c.y, term.col-1, term.c.y, 1);
   2332 			break;
   2333 		case 1: /* left */
   2334 			tclearregion(0, term.c.y, term.c.x, term.c.y, 1);
   2335 			break;
   2336 		case 2: /* all */
   2337 			tclearregion(0, term.c.y, term.col-1, term.c.y, 1);
   2338 			break;
   2339 		}
   2340 		#else
   2341 		case 0: /* right */
   2342 			tclearregion(term.c.x, term.c.y, maxcol-1, term.c.y);
   2343 			break;
   2344 		case 1: /* left */
   2345 			tclearregion(0, term.c.y, term.c.x, term.c.y);
   2346 			break;
   2347 		case 2: /* all */
   2348 			tclearregion(0, term.c.y, maxcol-1, term.c.y);
   2349 			break;
   2350 		}
   2351 		#endif // REFLOW_PATCH
   2352 		break;
   2353 	case 'S': /* SU -- Scroll <n> line up ; XTSMGRAPHICS */
   2354 		if (csiescseq.priv) {
   2355 			#if SIXEL_PATCH
   2356 			if (csiescseq.narg > 1) {
   2357 				/* XTSMGRAPHICS */
   2358 				pi = csiescseq.arg[0];
   2359 				pa = csiescseq.arg[1];
   2360 				if (pi == 1 && (pa == 1 || pa == 2 || pa == 4)) {
   2361 					/* number of sixel color registers */
   2362 					/* (read, reset and read the maximum value give the same response) */
   2363 					n = snprintf(buffer, sizeof buffer, "\033[?1;0;%dS", DECSIXEL_PALETTE_MAX);
   2364 					ttywrite(buffer, n, 1);
   2365 					break;
   2366 				} else if (pi == 2 && (pa == 1 || pa == 2 || pa == 4)) {
   2367 					/* sixel graphics geometry (in pixels) */
   2368 					/* (read, reset and read the maximum value give the same response) */
   2369 					n = snprintf(buffer, sizeof buffer, "\033[?2;0;%d;%dS",
   2370 					             MIN(term.col * win.cw, DECSIXEL_WIDTH_MAX),
   2371 					             MIN(term.row * win.ch, DECSIXEL_HEIGHT_MAX));
   2372 					ttywrite(buffer, n, 1);
   2373 					break;
   2374 				}
   2375 				/* the number of color registers and sixel geometry can't be changed */
   2376 				n = snprintf(buffer, sizeof buffer, "\033[?%d;3;0S", pi); /* failure */
   2377 				ttywrite(buffer, n, 1);
   2378 			}
   2379 			#endif // SIXEL_PATCH
   2380 			goto unknown;
   2381 		}
   2382 		DEFAULT(csiescseq.arg[0], 1);
   2383 		#if REFLOW_PATCH
   2384 		/* xterm, urxvt, alacritty save this in history */
   2385 		tscrollup(term.top, term.bot, csiescseq.arg[0], SCROLL_SAVEHIST);
   2386 		#elif SIXEL_PATCH && SCROLLBACK_PATCH
   2387 		tscrollup(term.top, csiescseq.arg[0], 1);
   2388 		#elif SCROLLBACK_PATCH
   2389 		tscrollup(term.top, csiescseq.arg[0], 0);
   2390 		#else
   2391 		tscrollup(term.top, csiescseq.arg[0]);
   2392 		#endif // SCROLLBACK_PATCH
   2393 		break;
   2394 	case 'T': /* SD -- Scroll <n> line down */
   2395 		DEFAULT(csiescseq.arg[0], 1);
   2396 		tscrolldown(term.top, csiescseq.arg[0]);
   2397 		break;
   2398 	case 'L': /* IL -- Insert <n> blank lines */
   2399 		DEFAULT(csiescseq.arg[0], 1);
   2400 		tinsertblankline(csiescseq.arg[0]);
   2401 		break;
   2402 	case 'l': /* RM -- Reset Mode */
   2403 		tsetmode(csiescseq.priv, 0, csiescseq.arg, csiescseq.narg);
   2404 		break;
   2405 	case 'M': /* DL -- Delete <n> lines */
   2406 		DEFAULT(csiescseq.arg[0], 1);
   2407 		tdeleteline(csiescseq.arg[0]);
   2408 		break;
   2409 	case 'X': /* ECH -- Erase <n> char */
   2410 		#if REFLOW_PATCH
   2411 		if (csiescseq.arg[0] < 0)
   2412 			return;
   2413 		DEFAULT(csiescseq.arg[0], 1);
   2414 		x = MIN(term.c.x + csiescseq.arg[0], term.col) - 1;
   2415 		tclearregion(term.c.x, term.c.y, x, term.c.y, 1);
   2416 		#else
   2417 		DEFAULT(csiescseq.arg[0], 1);
   2418 		tclearregion(term.c.x, term.c.y,
   2419 				term.c.x + csiescseq.arg[0] - 1, term.c.y);
   2420 		#endif // REFLOW_PATCH
   2421 		break;
   2422 	case 'P': /* DCH -- Delete <n> char */
   2423 		DEFAULT(csiescseq.arg[0], 1);
   2424 		tdeletechar(csiescseq.arg[0]);
   2425 		break;
   2426 	case 'Z': /* CBT -- Cursor Backward Tabulation <n> tab stops */
   2427 		DEFAULT(csiescseq.arg[0], 1);
   2428 		tputtab(-csiescseq.arg[0]);
   2429 		break;
   2430 	case 'd': /* VPA -- Move to <row> */
   2431 		DEFAULT(csiescseq.arg[0], 1);
   2432 		tmoveato(term.c.x, csiescseq.arg[0]-1);
   2433 		break;
   2434 	case 'h': /* SM -- Set terminal mode */
   2435 		tsetmode(csiescseq.priv, 1, csiescseq.arg, csiescseq.narg);
   2436 		break;
   2437 	case 'm': /* SGR -- Terminal attribute (color) */
   2438 		tsetattr(csiescseq.arg, csiescseq.narg);
   2439 		break;
   2440 	case 'n': /* DSR -- Device Status Report */
   2441 		switch (csiescseq.arg[0]) {
   2442 		case 5: /* Status Report "OK" `0n` */
   2443 			ttywrite("\033[0n", sizeof("\033[0n") - 1, 0);
   2444 			break;
   2445 		case 6: /* Report Cursor Position (CPR) "<row>;<column>R" */
   2446 			len = snprintf(buffer, sizeof(buffer), "\033[%i;%iR",
   2447 			               term.c.y+1, term.c.x+1);
   2448 			ttywrite(buffer, len, 0);
   2449 			break;
   2450 		default:
   2451 			goto unknown;
   2452 		}
   2453 		break;
   2454 	#if SYNC_PATCH || SIXEL_PATCH
   2455 	case '$': /* DECRQM -- DEC Request Mode (private) */
   2456 		if (csiescseq.mode[1] == 'p' && csiescseq.priv) {
   2457 			switch (csiescseq.arg[0]) {
   2458 			#if SIXEL_PATCH
   2459 			case 80:
   2460 				/* Sixel Display Mode  */
   2461 				ttywrite(IS_SET(MODE_SIXEL_SDM) ? "\033[?80;1$y"
   2462 				                                : "\033[?80;2$y", 9, 0);
   2463 				break;
   2464 			case 8452:
   2465 				/* Sixel scrolling leaves cursor to right of graphic */
   2466 				ttywrite(IS_SET(MODE_SIXEL_CUR_RT) ? "\033[?8452;1$y"
   2467 				                                   : "\033[?8452;2$y", 11, 0);
   2468 				break;
   2469 			#endif // SIXEL_PATCH
   2470 			#if SYNC_PATCH
   2471 			case 2026:
   2472 				/* https://gist.github.com/christianparpart/d8a62cc1ab659194337d73e399004036 */
   2473 				ttywrite(su ? "\033[?2026;1$y" : "\033[?2026;2$y", 11, 0);
   2474 				break;
   2475 			#endif // SYNC_PATCH
   2476 			default:
   2477 				goto unknown;
   2478 			}
   2479 			break;
   2480 		}
   2481 		goto unknown;
   2482 	#endif // SYNC_PATCH | SIXEL_PATCH
   2483 	case 'r': /* DECSTBM -- Set Scrolling Region */
   2484 		if (csiescseq.priv) {
   2485 			goto unknown;
   2486 		} else {
   2487 			DEFAULT(csiescseq.arg[0], 1);
   2488 			DEFAULT(csiescseq.arg[1], term.row);
   2489 			tsetscroll(csiescseq.arg[0]-1, csiescseq.arg[1]-1);
   2490 			tmoveato(0, 0);
   2491 		}
   2492 		break;
   2493 	case 's': /* DECSC -- Save cursor position (ANSI.SYS) */
   2494 		tcursor(CURSOR_SAVE);
   2495 		break;
   2496 	#if CSI_22_23_PATCH | SIXEL_PATCH
   2497 	case 't': /* title stack operations ; XTWINOPS */
   2498 		switch (csiescseq.arg[0]) {
   2499 		#if SIXEL_PATCH
   2500 		case 14: /* text area size in pixels */
   2501 			if (csiescseq.narg > 1)
   2502 				goto unknown;
   2503 			n = snprintf(buffer, sizeof buffer, "\033[4;%d;%dt",
   2504 			             term.row * win.ch, term.col * win.cw);
   2505 			ttywrite(buffer, n, 1);
   2506 			break;
   2507 		case 16: /* character cell size in pixels */
   2508 			n = snprintf(buffer, sizeof buffer, "\033[6;%d;%dt", win.ch, win.cw);
   2509 			ttywrite(buffer, n, 1);
   2510 			break;
   2511 		case 18: /* size of the text area in characters */
   2512 			n = snprintf(buffer, sizeof buffer, "\033[8;%d;%dt", term.row, term.col);
   2513 			ttywrite(buffer, n, 1);
   2514 			break;
   2515 		#endif // SIXEL_PATCH
   2516 		#if CSI_22_23_PATCH
   2517 		case 22: /* pust current title on stack */
   2518 			switch (csiescseq.arg[1]) {
   2519 			case 0:
   2520 			case 1:
   2521 			case 2:
   2522 				xpushtitle();
   2523 				break;
   2524 			default:
   2525 				goto unknown;
   2526 			}
   2527 			break;
   2528 		case 23: /* pop last title from stack */
   2529 			switch (csiescseq.arg[1]) {
   2530 			case 0:
   2531 			case 1:
   2532 			case 2:
   2533 				xsettitle(NULL, 1);
   2534 				break;
   2535 			default:
   2536 				goto unknown;
   2537 			}
   2538 			break;
   2539 		#endif // CSI_22_23_PATCH
   2540 		default:
   2541 			goto unknown;
   2542 		}
   2543 		break;
   2544 	#endif // CSI_22_23_PATCH | SIXEL_PATCH
   2545 	case 'u': /* DECRC -- Restore cursor position (ANSI.SYS) */
   2546 		if (csiescseq.priv) {
   2547 			goto unknown;
   2548 		} else {
   2549 			tcursor(CURSOR_LOAD);
   2550 		}
   2551 		break;
   2552 	case ' ':
   2553 		switch (csiescseq.mode[1]) {
   2554 		case 'q': /* DECSCUSR -- Set Cursor Style */
   2555 			if (xsetcursor(csiescseq.arg[0]))
   2556 				goto unknown;
   2557 			break;
   2558 		default:
   2559 			goto unknown;
   2560 		}
   2561 		break;
   2562 	}
   2563 }
   2564 
   2565 void
   2566 csidump(void)
   2567 {
   2568 	size_t i;
   2569 	uint c;
   2570 
   2571 	fprintf(stderr, "ESC[");
   2572 	for (i = 0; i < csiescseq.len; i++) {
   2573 		c = csiescseq.buf[i] & 0xff;
   2574 		if (isprint(c)) {
   2575 			putc(c, stderr);
   2576 		} else if (c == '\n') {
   2577 			fprintf(stderr, "(\\n)");
   2578 		} else if (c == '\r') {
   2579 			fprintf(stderr, "(\\r)");
   2580 		} else if (c == 0x1b) {
   2581 			fprintf(stderr, "(\\e)");
   2582 		} else {
   2583 			fprintf(stderr, "(%02x)", c);
   2584 		}
   2585 	}
   2586 	putc('\n', stderr);
   2587 }
   2588 
   2589 void
   2590 csireset(void)
   2591 {
   2592 	memset(&csiescseq, 0, sizeof(csiescseq));
   2593 }
   2594 
   2595 void
   2596 osc_color_response(int num, int index, int is_osc4)
   2597 {
   2598 	int n;
   2599 	char buf[32];
   2600 	unsigned char r, g, b;
   2601 
   2602 	if (xgetcolor(is_osc4 ? num : index, &r, &g, &b)) {
   2603 		fprintf(stderr, "erresc: failed to fetch %s color %d\n",
   2604 		        is_osc4 ? "osc4" : "osc",
   2605 		        is_osc4 ? num : index);
   2606 		return;
   2607 	}
   2608 
   2609 	n = snprintf(buf, sizeof buf, "\033]%s%d;rgb:%02x%02x/%02x%02x/%02x%02x%s",
   2610 	             is_osc4 ? "4;" : "", num, r, r, g, g, b, b, strescseq.term);
   2611 	if (n < 0 || n >= sizeof(buf)) {
   2612 		fprintf(stderr, "error: %s while printing %s response\n",
   2613 		        n < 0 ? "snprintf failed" : "truncation occurred",
   2614 		        is_osc4 ? "osc4" : "osc");
   2615 	} else {
   2616 		ttywrite(buf, n, 1);
   2617 	}
   2618 }
   2619 
   2620 void
   2621 strhandle(void)
   2622 {
   2623 	char *p = NULL, *dec;
   2624 	int j, narg, par;
   2625 	const struct { int idx; char *str; } osc_table[] = {
   2626 		{ defaultfg, "foreground" },
   2627 		{ defaultbg, "background" },
   2628 		{ defaultcs, "cursor" }
   2629 	};
   2630 	#if SIXEL_PATCH
   2631 	ImageList *im, *newimages, *next, *tail = NULL;
   2632 	int i, x1, y1, x2, y2, y, numimages;
   2633 	int cx, cy;
   2634 	Line line;
   2635 	#if SCROLLBACK_PATCH || REFLOW_PATCH
   2636 	int scr = IS_SET(MODE_ALTSCREEN) ? 0 : term.scr;
   2637 	#else
   2638 	int scr = 0;
   2639 	#endif // SCROLLBACK_PATCH
   2640 	#endif // SIXEL_PATCH
   2641 
   2642 	term.esc &= ~(ESC_STR_END|ESC_STR);
   2643 	strparse();
   2644 	par = (narg = strescseq.narg) ? atoi(strescseq.args[0]) : 0;
   2645 
   2646 	switch (strescseq.type) {
   2647 	case ']': /* OSC -- Operating System Command */
   2648 		switch (par) {
   2649 		case 0:
   2650 			if (narg > 1) {
   2651 				#if CSI_22_23_PATCH
   2652 				xsettitle(strescseq.args[1], 0);
   2653 				#else
   2654 				xsettitle(strescseq.args[1]);
   2655 				#endif // CSI_22_23_PATCH
   2656 				xseticontitle(strescseq.args[1]);
   2657 			}
   2658 			return;
   2659 		case 1:
   2660 			if (narg > 1)
   2661 				xseticontitle(strescseq.args[1]);
   2662 			return;
   2663 		case 2:
   2664 			if (narg > 1)
   2665 				#if CSI_22_23_PATCH
   2666 				xsettitle(strescseq.args[1], 0);
   2667 				#else
   2668 				xsettitle(strescseq.args[1]);
   2669 				#endif // CSI_22_23_PATCH
   2670 			return;
   2671 		case 52: /* manipulate selection data */
   2672 			if (narg > 2 && allowwindowops) {
   2673 				dec = base64dec(strescseq.args[2]);
   2674 				if (dec) {
   2675 					xsetsel(dec);
   2676 					xclipcopy();
   2677 				} else {
   2678 					fprintf(stderr, "erresc: invalid base64\n");
   2679 				}
   2680 			}
   2681 			return;
   2682 		#if OSC7_PATCH
   2683 		case 7:
   2684 			osc7parsecwd((const char *)strescseq.args[1]);
   2685 			return;
   2686 		#endif // OSC7_PATCH
   2687 		case 8: /* Clear Hyperlinks */
   2688 			return;
   2689 		case 10: /* set dynamic VT100 text foreground color */
   2690 		case 11: /* set dynamic VT100 text background color */
   2691 		case 12: /* set dynamic text cursor color */
   2692 			if (narg < 2)
   2693 				break;
   2694 			p = strescseq.args[1];
   2695 			if ((j = par - 10) < 0 || j >= LEN(osc_table))
   2696 				break; /* shouldn't be possible */
   2697 
   2698 			if (!strcmp(p, "?")) {
   2699 				osc_color_response(par, osc_table[j].idx, 0);
   2700 			} else if (xsetcolorname(osc_table[j].idx, p)) {
   2701 				fprintf(stderr, "erresc: invalid %s color: %s\n",
   2702 				        osc_table[j].str, p);
   2703 			} else {
   2704 				tfulldirt();
   2705 			}
   2706 			return;
   2707 		case 4: /* color set */
   2708 			if (narg < 3)
   2709 				break;
   2710 			p = strescseq.args[2];
   2711 			/* FALLTHROUGH */
   2712 		case 104: /* color reset */
   2713 			j = (narg > 1) ? atoi(strescseq.args[1]) : -1;
   2714 
   2715 			if (p && !strcmp(p, "?")) {
   2716 				osc_color_response(j, 0, 1);
   2717 			} else if (xsetcolorname(j, p)) {
   2718 				if (par == 104 && narg <= 1) {
   2719 					xloadcols();
   2720 					return; /* color reset without parameter */
   2721 				}
   2722 				fprintf(stderr, "erresc: invalid color j=%d, p=%s\n",
   2723 				        j, p ? p : "(null)");
   2724 			} else {
   2725 				/*
   2726 				 * TODO if defaultbg color is changed, borders
   2727 				 * are dirty
   2728 				 */
   2729 				tfulldirt();
   2730 			}
   2731 			return;
   2732 		case 110: /* reset dynamic VT100 text foreground color */
   2733 		case 111: /* reset dynamic VT100 text background color */
   2734 		case 112: /* reset dynamic text cursor color */
   2735 			if (narg != 1)
   2736 				break;
   2737 			if ((j = par - 110) < 0 || j >= LEN(osc_table))
   2738 				break; /* shouldn't be possible */
   2739 			if (xsetcolorname(osc_table[j].idx, NULL)) {
   2740 				fprintf(stderr, "erresc: %s color not found\n", osc_table[j].str);
   2741 			} else {
   2742 				tfulldirt();
   2743 			}
   2744 			return;
   2745 		#if OSC133_PATCH
   2746 		case 133:
   2747 			if (narg < 2)
   2748 				break;
   2749 			switch (*strescseq.args[1]) {
   2750 			case 'A':
   2751 				term.c.attr.mode |= ATTR_FTCS_PROMPT;
   2752 				break;
   2753 			/* We don't handle these arguments yet */
   2754 			case 'B':
   2755 			case 'C':
   2756 			case 'D':
   2757 				break;
   2758 			default:
   2759 				fprintf(stderr, "erresc: unknown OSC 133 argument: %c\n", *strescseq.args[1]);
   2760 				break;
   2761 			}
   2762 			return;
   2763 		#endif // OSC133_PATCH
   2764 		}
   2765 		break;
   2766 	case 'k': /* old title set compatibility */
   2767 		#if CSI_22_23_PATCH
   2768 		xsettitle(strescseq.args[0], 0);
   2769 		#else
   2770 		xsettitle(strescseq.args[0]);
   2771 		#endif // CSI_22_23_PATCH
   2772 		return;
   2773 	case 'P': /* DCS -- Device Control String */
   2774 		#if SIXEL_PATCH
   2775 		if (IS_SET(MODE_SIXEL)) {
   2776 			term.mode &= ~MODE_SIXEL;
   2777 			if (!sixel_st.image.data) {
   2778 				sixel_parser_deinit(&sixel_st);
   2779 				return;
   2780 			}
   2781 			cx = IS_SET(MODE_SIXEL_SDM) ? 0 : term.c.x;
   2782 			cy = IS_SET(MODE_SIXEL_SDM) ? 0 : term.c.y;
   2783 			if ((numimages = sixel_parser_finalize(&sixel_st, &newimages,
   2784 					cx, cy + scr, win.cw, win.ch)) <= 0) {
   2785 				sixel_parser_deinit(&sixel_st);
   2786 				perror("sixel_parser_finalize() failed");
   2787 				return;
   2788 			}
   2789 			sixel_parser_deinit(&sixel_st);
   2790 			x1 = newimages->x;
   2791 			y1 = newimages->y;
   2792 			x2 = x1 + newimages->cols;
   2793 			y2 = y1 + numimages;
   2794 			/* Delete the old images that are covered by the new image(s). We also need
   2795 			 * to check if they have already been deleted before adding the new ones. */
   2796 			if (term.images) {
   2797 				char transparent[numimages];
   2798 				for (i = 0, im = newimages; im; im = im->next, i++) {
   2799 					transparent[i] = im->transparent;
   2800 				}
   2801 				for (im = term.images; im; im = next) {
   2802 					next = im->next;
   2803 					if (im->y >= y1 && im->y < y2) {
   2804 						y = im->y - scr;
   2805 						if (y >= 0 && y < term.row && term.dirty[y]) {
   2806 							line = term.line[y];
   2807 							j = MIN(im->x + im->cols, term.col);
   2808 							for (i = im->x; i < j; i++) {
   2809 								if (line[i].mode & ATTR_SIXEL)
   2810 									break;
   2811 							}
   2812 							if (i == j) {
   2813 								delete_image(im);
   2814 								continue;
   2815 							}
   2816 						}
   2817 						if (im->x >= x1 && im->x + im->cols <= x2 && !transparent[im->y - y1]) {
   2818 							delete_image(im);
   2819 							continue;
   2820 						}
   2821 					}
   2822 					tail = im;
   2823 				}
   2824 			}
   2825 			if (tail) {
   2826 				tail->next = newimages;
   2827 				newimages->prev = tail;
   2828 			} else {
   2829 				term.images = newimages;
   2830 			}
   2831 			#if COLUMNS_PATCH && !REFLOW_PATCH
   2832 			x2 = MIN(x2, term.maxcol) - 1;
   2833 			#else
   2834 			x2 = MIN(x2, term.col) - 1;
   2835 			#endif // COLUMNS_PATCH
   2836 			if (IS_SET(MODE_SIXEL_SDM)) {
   2837 				/* Sixel display mode: put the sixel in the upper left corner of
   2838 				 * the screen, disable scrolling (the sixel will be truncated if
   2839 				 * it is too long) and do not change the cursor position. */
   2840 				for (i = 0, im = newimages; im; im = next, i++) {
   2841 					next = im->next;
   2842 					if (i >= term.row) {
   2843 						delete_image(im);
   2844 						continue;
   2845 					}
   2846 					im->y = i + scr;
   2847 					tsetsixelattr(term.line[i], x1, x2);
   2848 					term.dirty[MIN(im->y, term.row-1)] = 1;
   2849 				}
   2850 			} else {
   2851 				for (i = 0, im = newimages; im; im = next, i++) {
   2852 					next = im->next;
   2853 					#if SCROLLBACK_PATCH || REFLOW_PATCH
   2854 					scr = IS_SET(MODE_ALTSCREEN) ? 0 : term.scr;
   2855 					#endif // SCROLLBACK_PATCH
   2856 					im->y = term.c.y + scr;
   2857 					tsetsixelattr(term.line[term.c.y], x1, x2);
   2858 					term.dirty[MIN(im->y, term.row-1)] = 1;
   2859 					if (i < numimages-1) {
   2860 						im->next = NULL;
   2861 						tnewline(0);
   2862 						im->next = next;
   2863 					}
   2864 				}
   2865 				/* if mode 8452 is set, sixel scrolling leaves cursor to right of graphic */
   2866 				if (IS_SET(MODE_SIXEL_CUR_RT))
   2867 					term.c.x = MIN(term.c.x + newimages->cols, term.col-1);
   2868 			}
   2869 		}
   2870 		#endif // SIXEL_PATCH
   2871 		#if SYNC_PATCH
   2872 		/* https://gitlab.com/gnachman/iterm2/-/wikis/synchronized-updates-spec */
   2873 		if (strstr(strescseq.buf, "=1s") == strescseq.buf)
   2874 			tsync_begin();  /* BSU */
   2875 		else if (strstr(strescseq.buf, "=2s") == strescseq.buf)
   2876 			tsync_end();  /* ESU */
   2877 		#endif // SYNC_PATCH
   2878 		#if SIXEL_PATCH || SYNC_PATCH
   2879 		return;
   2880 		#endif // SIXEL_PATCH | SYNC_PATCH
   2881 	case '_': /* APC -- Application Program Command */
   2882 	case '^': /* PM -- Privacy Message */
   2883 		return;
   2884 	}
   2885 
   2886 	fprintf(stderr, "erresc: unknown str ");
   2887 	strdump();
   2888 }
   2889 
   2890 void
   2891 strparse(void)
   2892 {
   2893 	int c;
   2894 	char *p = strescseq.buf;
   2895 
   2896 	strescseq.narg = 0;
   2897 	strescseq.buf[strescseq.len] = '\0';
   2898 
   2899 	if (*p == '\0')
   2900 		return;
   2901 
   2902 	/* preserve semicolons in window titles, icon names and OSC 7 sequences */
   2903 	if (strescseq.type == ']' && (
   2904 		p[0] <= '2'
   2905 	#if OSC7_PATCH
   2906 		|| p[0] == '7'
   2907 	#endif // OSC7_PATCH
   2908 	) && p[1] == ';') {
   2909 		strescseq.args[strescseq.narg++] = p;
   2910 		strescseq.args[strescseq.narg++] = p + 2;
   2911 		p[1] = '\0';
   2912 		return;
   2913 	}
   2914 
   2915 	while (strescseq.narg < STR_ARG_SIZ) {
   2916 		strescseq.args[strescseq.narg++] = p;
   2917 		while ((c = *p) != ';' && c != '\0')
   2918 			++p;
   2919 		if (c == '\0')
   2920 			return;
   2921 		*p++ = '\0';
   2922 	}
   2923 }
   2924 
   2925 void
   2926 strdump(void)
   2927 {
   2928 	size_t i;
   2929 	uint c;
   2930 
   2931 	fprintf(stderr, "ESC%c", strescseq.type);
   2932 	for (i = 0; i < strescseq.len; i++) {
   2933 		c = strescseq.buf[i] & 0xff;
   2934 		if (c == '\0') {
   2935 			putc('\n', stderr);
   2936 			return;
   2937 		} else if (isprint(c)) {
   2938 			putc(c, stderr);
   2939 		} else if (c == '\n') {
   2940 			fprintf(stderr, "(\\n)");
   2941 		} else if (c == '\r') {
   2942 			fprintf(stderr, "(\\r)");
   2943 		} else if (c == 0x1b) {
   2944 			fprintf(stderr, "(\\e)");
   2945 		} else {
   2946 			fprintf(stderr, "(%02x)", c);
   2947 		}
   2948 	}
   2949 	fprintf(stderr, (strescseq.term[0] == 0x1b) ? "ESC\\\n" : "BEL\n");
   2950 }
   2951 
   2952 void
   2953 strreset(void)
   2954 {
   2955 	strescseq = (STREscape){
   2956 		.buf = xrealloc(strescseq.buf, STR_BUF_SIZ),
   2957 		.siz = STR_BUF_SIZ,
   2958 	};
   2959 }
   2960 
   2961 void
   2962 sendbreak(const Arg *arg)
   2963 {
   2964 	if (tcsendbreak(cmdfd, 0))
   2965 		perror("Error sending break");
   2966 }
   2967 
   2968 void
   2969 tprinter(char *s, size_t len)
   2970 {
   2971 	if (iofd != -1 && xwrite(iofd, s, len) < 0) {
   2972 		perror("Error writing to output file");
   2973 		close(iofd);
   2974 		iofd = -1;
   2975 	}
   2976 }
   2977 
   2978 void
   2979 toggleprinter(const Arg *arg)
   2980 {
   2981 	term.mode ^= MODE_PRINT;
   2982 }
   2983 
   2984 void
   2985 printscreen(const Arg *arg)
   2986 {
   2987 	tdump();
   2988 }
   2989 
   2990 void
   2991 printsel(const Arg *arg)
   2992 {
   2993 	tdumpsel();
   2994 }
   2995 
   2996 void
   2997 tdumpsel(void)
   2998 {
   2999 	char *ptr;
   3000 
   3001 	if ((ptr = getsel())) {
   3002 		tprinter(ptr, strlen(ptr));
   3003 		free(ptr);
   3004 	}
   3005 }
   3006 
   3007 #if !REFLOW_PATCH
   3008 void
   3009 tdumpline(int n)
   3010 {
   3011 	char buf[UTF_SIZ];
   3012 	const Glyph *bp, *end;
   3013 
   3014 	bp = &term.line[n][0];
   3015 	end = &bp[MIN(tlinelen(n), term.col) - 1];
   3016 	if (bp != end || bp->u != ' ') {
   3017 		for ( ; bp <= end; ++bp)
   3018 			tprinter(buf, utf8encode(bp->u, buf));
   3019 	}
   3020 	tprinter("\n", 1);
   3021 }
   3022 #endif // REFLOW_PATCH
   3023 
   3024 void
   3025 tdump(void)
   3026 {
   3027 	int i;
   3028 
   3029 	for (i = 0; i < term.row; ++i)
   3030 		tdumpline(i);
   3031 }
   3032 
   3033 void
   3034 tputtab(int n)
   3035 {
   3036 	uint x = term.c.x;
   3037 
   3038 	if (n > 0) {
   3039 		while (x < term.col && n--)
   3040 			for (++x; x < term.col && !term.tabs[x]; ++x)
   3041 				/* nothing */ ;
   3042 	} else if (n < 0) {
   3043 		while (x > 0 && n++)
   3044 			for (--x; x > 0 && !term.tabs[x]; --x)
   3045 				/* nothing */ ;
   3046 	}
   3047 	term.c.x = LIMIT(x, 0, term.col-1);
   3048 }
   3049 
   3050 void
   3051 tdefutf8(char ascii)
   3052 {
   3053 	if (ascii == 'G')
   3054 		term.mode |= MODE_UTF8;
   3055 	else if (ascii == '@')
   3056 		term.mode &= ~MODE_UTF8;
   3057 }
   3058 
   3059 void
   3060 tdeftran(char ascii)
   3061 {
   3062 	static char cs[] = "0B";
   3063 	static int vcs[] = {CS_GRAPHIC0, CS_USA};
   3064 	char *p;
   3065 
   3066 	if ((p = strchr(cs, ascii)) == NULL) {
   3067 		fprintf(stderr, "esc unhandled charset: ESC ( %c\n", ascii);
   3068 	} else {
   3069 		term.trantbl[term.icharset] = vcs[p - cs];
   3070 	}
   3071 }
   3072 
   3073 void
   3074 tdectest(char c)
   3075 {
   3076 	int x, y;
   3077 
   3078 	if (c == '8') { /* DEC screen alignment test. */
   3079 		for (x = 0; x < term.col; ++x) {
   3080 			for (y = 0; y < term.row; ++y)
   3081 				tsetchar('E', &term.c.attr, x, y);
   3082 		}
   3083 	}
   3084 }
   3085 
   3086 void
   3087 tstrsequence(uchar c)
   3088 {
   3089 	#if SIXEL_PATCH
   3090 	strreset();
   3091 	#endif // SIXEL_PATCH
   3092 
   3093 	switch (c) {
   3094 	case 0x90:   /* DCS -- Device Control String */
   3095 		c = 'P';
   3096 		#if SIXEL_PATCH
   3097 		term.esc |= ESC_DCS;
   3098 		#endif // SIXEL_PATCH
   3099 		break;
   3100 	case 0x9f:   /* APC -- Application Program Command */
   3101 		c = '_';
   3102 		break;
   3103 	case 0x9e:   /* PM -- Privacy Message */
   3104 		c = '^';
   3105 		break;
   3106 	case 0x9d:   /* OSC -- Operating System Command */
   3107 		c = ']';
   3108 		break;
   3109 	}
   3110 	#if !SIXEL_PATCH
   3111 	strreset();
   3112 	#endif // SIXEL_PATCH
   3113 	strescseq.type = c;
   3114 	term.esc |= ESC_STR;
   3115 }
   3116 
   3117 void
   3118 tcontrolcode(uchar ascii)
   3119 {
   3120 	switch (ascii) {
   3121 	case '\t':   /* HT */
   3122 		tputtab(1);
   3123 		return;
   3124 	case '\b':   /* BS */
   3125 		tmoveto(term.c.x-1, term.c.y);
   3126 		return;
   3127 	case '\r':   /* CR */
   3128 		tmoveto(0, term.c.y);
   3129 		return;
   3130 	case '\f':   /* LF */
   3131 	case '\v':   /* VT */
   3132 	case '\n':   /* LF */
   3133 		/* go to first col if the mode is set */
   3134 		tnewline(IS_SET(MODE_CRLF));
   3135 		return;
   3136 	case '\a':   /* BEL */
   3137 		if (term.esc & ESC_STR_END) {
   3138 			/* backwards compatibility to xterm */
   3139 			strescseq.term = STR_TERM_BEL;
   3140 			strhandle();
   3141 		} else {
   3142 			xbell();
   3143 		}
   3144 		break;
   3145 	case '\033': /* ESC */
   3146 		csireset();
   3147 		term.esc &= ~(ESC_CSI|ESC_ALTCHARSET|ESC_TEST);
   3148 		term.esc |= ESC_START;
   3149 		return;
   3150 	case '\016': /* SO (LS1 -- Locking shift 1) */
   3151 	case '\017': /* SI (LS0 -- Locking shift 0) */
   3152 		term.charset = 1 - (ascii - '\016');
   3153 		return;
   3154 	case '\032': /* SUB */
   3155 		tsetchar('?', &term.c.attr, term.c.x, term.c.y);
   3156 		/* FALLTHROUGH */
   3157 	case '\030': /* CAN */
   3158 		csireset();
   3159 		break;
   3160 	case '\005': /* ENQ (IGNORED) */
   3161 	case '\000': /* NUL (IGNORED) */
   3162 	case '\021': /* XON (IGNORED) */
   3163 	case '\023': /* XOFF (IGNORED) */
   3164 	case 0177:   /* DEL (IGNORED) */
   3165 		return;
   3166 	case 0x80:   /* TODO: PAD */
   3167 	case 0x81:   /* TODO: HOP */
   3168 	case 0x82:   /* TODO: BPH */
   3169 	case 0x83:   /* TODO: NBH */
   3170 	case 0x84:   /* TODO: IND */
   3171 		break;
   3172 	case 0x85:   /* NEL -- Next line */
   3173 		tnewline(1); /* always go to first col */
   3174 		break;
   3175 	case 0x86:   /* TODO: SSA */
   3176 	case 0x87:   /* TODO: ESA */
   3177 		break;
   3178 	case 0x88:   /* HTS -- Horizontal tab stop */
   3179 		term.tabs[term.c.x] = 1;
   3180 		break;
   3181 	case 0x89:   /* TODO: HTJ */
   3182 	case 0x8a:   /* TODO: VTS */
   3183 	case 0x8b:   /* TODO: PLD */
   3184 	case 0x8c:   /* TODO: PLU */
   3185 	case 0x8d:   /* TODO: RI */
   3186 	case 0x8e:   /* TODO: SS2 */
   3187 	case 0x8f:   /* TODO: SS3 */
   3188 	case 0x91:   /* TODO: PU1 */
   3189 	case 0x92:   /* TODO: PU2 */
   3190 	case 0x93:   /* TODO: STS */
   3191 	case 0x94:   /* TODO: CCH */
   3192 	case 0x95:   /* TODO: MW */
   3193 	case 0x96:   /* TODO: SPA */
   3194 	case 0x97:   /* TODO: EPA */
   3195 	case 0x98:   /* TODO: SOS */
   3196 	case 0x99:   /* TODO: SGCI */
   3197 		break;
   3198 	case 0x9a:   /* DECID -- Identify Terminal */
   3199 		ttywrite(vtiden, strlen(vtiden), 0);
   3200 		break;
   3201 	case 0x9b:   /* TODO: CSI */
   3202 	case 0x9c:   /* TODO: ST */
   3203 		break;
   3204 	case 0x90:   /* DCS -- Device Control String */
   3205 	case 0x9d:   /* OSC -- Operating System Command */
   3206 	case 0x9e:   /* PM -- Privacy Message */
   3207 	case 0x9f:   /* APC -- Application Program Command */
   3208 		tstrsequence(ascii);
   3209 		return;
   3210 	}
   3211 	/* only CAN, SUB, \a and C1 chars interrupt a sequence */
   3212 	term.esc &= ~(ESC_STR_END|ESC_STR);
   3213 }
   3214 
   3215 #if SIXEL_PATCH
   3216 void
   3217 dcshandle(void)
   3218 {
   3219 	int bgcolor, transparent;
   3220 	unsigned char r, g, b, a = 255;
   3221 
   3222 	switch (csiescseq.mode[0]) {
   3223 	default:
   3224 	unknown:
   3225 		fprintf(stderr, "erresc: unknown csi ");
   3226 		csidump();
   3227 		/* die(""); */
   3228 		break;
   3229 	#if SYNC_PATCH
   3230 	case '=':
   3231 		/* https://gitlab.com/gnachman/iterm2/-/wikis/synchronized-updates-spec */
   3232 		if (csiescseq.buf[2] == 's' && csiescseq.buf[1] == '1')
   3233 			tsync_begin();  /* BSU */
   3234 		else if (csiescseq.buf[2] == 's' && csiescseq.buf[1] == '2')
   3235 			tsync_end();  /* ESU */
   3236 		else
   3237 			goto unknown;
   3238 		break;
   3239 	#endif // SYNC_PATCH
   3240 	case 'q': /* DECSIXEL */
   3241 		transparent = (csiescseq.narg >= 2 && csiescseq.arg[1] == 1);
   3242 		if (IS_TRUECOL(term.c.attr.bg)) {
   3243 			r = term.c.attr.bg >> 16 & 255;
   3244 			g = term.c.attr.bg >> 8 & 255;
   3245 			b = term.c.attr.bg >> 0 & 255;
   3246 		} else {
   3247 			xgetcolor(term.c.attr.bg, &r, &g, &b);
   3248 			if (term.c.attr.bg == defaultbg)
   3249 				a = dc.col[defaultbg].pixel >> 24 & 255;
   3250 		}
   3251 		bgcolor = a << 24 | r << 16 | g << 8 | b;
   3252 		if (sixel_parser_init(&sixel_st, transparent, (255 << 24), bgcolor, 1, win.cw, win.ch) != 0)
   3253 			perror("sixel_parser_init() failed");
   3254 		term.mode |= MODE_SIXEL;
   3255 		break;
   3256 	}
   3257 }
   3258 #endif // SIXEL_PATCH
   3259 
   3260 /*
   3261  * returns 1 when the sequence is finished and it hasn't to read
   3262  * more characters for this sequence, otherwise 0
   3263  */
   3264 int
   3265 eschandle(uchar ascii)
   3266 {
   3267 	switch (ascii) {
   3268 	case '[':
   3269 		term.esc |= ESC_CSI;
   3270 		return 0;
   3271 	case '#':
   3272 		term.esc |= ESC_TEST;
   3273 		return 0;
   3274 	case '%':
   3275 		term.esc |= ESC_UTF8;
   3276 		return 0;
   3277 	case 'P': /* DCS -- Device Control String */
   3278 		#if SIXEL_PATCH
   3279 		term.esc |= ESC_DCS;
   3280 		#endif // SIXEL_PATCH
   3281 	case '_': /* APC -- Application Program Command */
   3282 	case '^': /* PM -- Privacy Message */
   3283 	case ']': /* OSC -- Operating System Command */
   3284 	case 'k': /* old title set compatibility */
   3285 		tstrsequence(ascii);
   3286 		return 0;
   3287 	case 'n': /* LS2 -- Locking shift 2 */
   3288 	case 'o': /* LS3 -- Locking shift 3 */
   3289 		term.charset = 2 + (ascii - 'n');
   3290 		break;
   3291 	case '(': /* GZD4 -- set primary charset G0 */
   3292 	case ')': /* G1D4 -- set secondary charset G1 */
   3293 	case '*': /* G2D4 -- set tertiary charset G2 */
   3294 	case '+': /* G3D4 -- set quaternary charset G3 */
   3295 		term.icharset = ascii - '(';
   3296 		term.esc |= ESC_ALTCHARSET;
   3297 		return 0;
   3298 	case 'D': /* IND -- Linefeed */
   3299 		if (term.c.y == term.bot) {
   3300 			#if REFLOW_PATCH
   3301 			tscrollup(term.top, term.bot, 1, SCROLL_SAVEHIST);
   3302 			#elif SCROLLBACK_PATCH
   3303 			tscrollup(term.top, 1, 1);
   3304 			#else
   3305 			tscrollup(term.top, 1);
   3306 			#endif // SCROLLBACK_PATCH
   3307 		} else {
   3308 			tmoveto(term.c.x, term.c.y+1);
   3309 		}
   3310 		break;
   3311 	case 'E': /* NEL -- Next line */
   3312 		tnewline(1); /* always go to first col */
   3313 		break;
   3314 	case 'H': /* HTS -- Horizontal tab stop */
   3315 		term.tabs[term.c.x] = 1;
   3316 		break;
   3317 	case 'M': /* RI -- Reverse index */
   3318 		if (term.c.y == term.top) {
   3319 			tscrolldown(term.top, 1);
   3320 		} else {
   3321 			tmoveto(term.c.x, term.c.y-1);
   3322 		}
   3323 		break;
   3324 	case 'Z': /* DECID -- Identify Terminal */
   3325 		ttywrite(vtiden, strlen(vtiden), 0);
   3326 		break;
   3327 	case 'c': /* RIS -- Reset to initial state */
   3328 		treset();
   3329 		#if CSI_22_23_PATCH
   3330 		xfreetitlestack();
   3331 		#endif // CSI_22_23_PATCH
   3332 		resettitle();
   3333 		xloadcols();
   3334 		xsetmode(0, MODE_HIDE);
   3335 		#if SCROLLBACK_PATCH && !REFLOW_PATCH
   3336 		if (!IS_SET(MODE_ALTSCREEN)) {
   3337 			term.scr = 0;
   3338 			term.histi = 0;
   3339 			term.histn = 0;
   3340 		}
   3341 		#endif // SCROLLBACK_PATCH
   3342 		break;
   3343 	case '=': /* DECPAM -- Application keypad */
   3344 		xsetmode(1, MODE_APPKEYPAD);
   3345 		break;
   3346 	case '>': /* DECPNM -- Normal keypad */
   3347 		xsetmode(0, MODE_APPKEYPAD);
   3348 		break;
   3349 	case '7': /* DECSC -- Save Cursor */
   3350 		tcursor(CURSOR_SAVE);
   3351 		break;
   3352 	case '8': /* DECRC -- Restore Cursor */
   3353 		tcursor(CURSOR_LOAD);
   3354 		break;
   3355 	case '\\': /* ST -- String Terminator */
   3356 		if (term.esc & ESC_STR_END) {
   3357 			strescseq.term = STR_TERM_ST;
   3358 			strhandle();
   3359 		}
   3360 		break;
   3361 	default:
   3362 		fprintf(stderr, "erresc: unknown sequence ESC 0x%02X '%c'\n",
   3363 			(uchar) ascii, isprint(ascii)? ascii:'.');
   3364 		break;
   3365 	}
   3366 	return 1;
   3367 }
   3368 
   3369 void
   3370 tputc(Rune u)
   3371 {
   3372 	char c[UTF_SIZ];
   3373 	int control;
   3374 	int width, len;
   3375 	Glyph *gp;
   3376 
   3377 	control = ISCONTROL(u);
   3378 	if (u < 127 || !IS_SET(MODE_UTF8))
   3379 	{
   3380 		c[0] = u;
   3381 		width = len = 1;
   3382 	} else {
   3383 		len = utf8encode(u, c);
   3384 		if (!control && (width = wcwidth(u)) == -1)
   3385 			width = 1;
   3386 	}
   3387 
   3388 	if (IS_SET(MODE_PRINT))
   3389 		tprinter(c, len);
   3390 
   3391 	/*
   3392 	 * STR sequence must be checked before anything else
   3393 	 * because it uses all following characters until it
   3394 	 * receives a ESC, a SUB, a ST or any other C1 control
   3395 	 * character.
   3396 	 */
   3397 	if (term.esc & ESC_STR) {
   3398 		if (u == '\a' || u == 030 || u == 032 || u == 033 ||
   3399 		   ISCONTROLC1(u)) {
   3400 			#if SIXEL_PATCH
   3401 			term.esc &= ~(ESC_START|ESC_STR|ESC_DCS);
   3402 			#else
   3403 			term.esc &= ~(ESC_START|ESC_STR);
   3404 			#endif // SIXEL_PATCH
   3405 			term.esc |= ESC_STR_END;
   3406 			goto check_control_code;
   3407 		}
   3408 
   3409 		#if SIXEL_PATCH
   3410 		if (term.esc & ESC_DCS)
   3411 			goto check_control_code;
   3412 		#endif // SIXEL_PATCH
   3413 
   3414 		if (strescseq.len+len >= strescseq.siz) {
   3415 			/*
   3416 			 * Here is a bug in terminals. If the user never sends
   3417 			 * some code to stop the str or esc command, then st
   3418 			 * will stop responding. But this is better than
   3419 			 * silently failing with unknown characters. At least
   3420 			 * then users will report back.
   3421 			 *
   3422 			 * In the case users ever get fixed, here is the code:
   3423 			 */
   3424 			/*
   3425 			 * term.esc = 0;
   3426 			 * strhandle();
   3427 			 */
   3428 			if (strescseq.siz > (SIZE_MAX - UTF_SIZ) / 2)
   3429 				return;
   3430 			strescseq.siz *= 2;
   3431 			strescseq.buf = xrealloc(strescseq.buf, strescseq.siz);
   3432 		}
   3433 
   3434 		memmove(&strescseq.buf[strescseq.len], c, len);
   3435 		strescseq.len += len;
   3436 		return;
   3437 	}
   3438 
   3439 check_control_code:
   3440 	/*
   3441 	 * Actions of control codes must be performed as soon they arrive
   3442 	 * because they can be embedded inside a control sequence, and
   3443 	 * they must not cause conflicts with sequences.
   3444 	 */
   3445 	if (control) {
   3446 		/* in UTF-8 mode ignore handling C1 control characters */
   3447 		if (IS_SET(MODE_UTF8) && ISCONTROLC1(u))
   3448 			return;
   3449 		tcontrolcode(u);
   3450 		/*
   3451 		 * control codes are not shown ever
   3452 		 */
   3453 		if (!term.esc)
   3454 			term.lastc = 0;
   3455 		return;
   3456 	} else if (term.esc & ESC_START) {
   3457 		if (term.esc & ESC_CSI) {
   3458 			csiescseq.buf[csiescseq.len++] = u;
   3459 			if (BETWEEN(u, 0x40, 0x7E)
   3460 					|| csiescseq.len >= \
   3461 					sizeof(csiescseq.buf)-1) {
   3462 				term.esc = 0;
   3463 				csiparse();
   3464 				csihandle();
   3465 			}
   3466 			return;
   3467 		#if SIXEL_PATCH
   3468 		} else if (term.esc & ESC_DCS) {
   3469 			csiescseq.buf[csiescseq.len++] = u;
   3470 			if (BETWEEN(u, 0x40, 0x7E)
   3471 					|| csiescseq.len >= \
   3472 					sizeof(csiescseq.buf)-1) {
   3473 				csiparse();
   3474 				dcshandle();
   3475 			}
   3476 			return;
   3477 		#endif // SIXEL_PATCH
   3478 		} else if (term.esc & ESC_UTF8) {
   3479 			tdefutf8(u);
   3480 		} else if (term.esc & ESC_ALTCHARSET) {
   3481 			tdeftran(u);
   3482 		} else if (term.esc & ESC_TEST) {
   3483 			tdectest(u);
   3484 		} else {
   3485 			if (!eschandle(u))
   3486 				return;
   3487 			/* sequence already finished */
   3488 		}
   3489 		term.esc = 0;
   3490 		/*
   3491 		 * All characters which form part of a sequence are not
   3492 		 * printed
   3493 		 */
   3494 		return;
   3495 	}
   3496 
   3497 	#if REFLOW_PATCH
   3498 	/* selected() takes relative coordinates */
   3499 	if (selected(term.c.x, term.c.y + term.scr))
   3500 		selclear();
   3501 	#else
   3502 	if (selected(term.c.x, term.c.y))
   3503 		selclear();
   3504 	#endif // REFLOW_PATCH
   3505 
   3506 	gp = &term.line[term.c.y][term.c.x];
   3507 	if (IS_SET(MODE_WRAP) && (term.c.state & CURSOR_WRAPNEXT)) {
   3508 		gp->mode |= ATTR_WRAP;
   3509 		tnewline(1);
   3510 		gp = &term.line[term.c.y][term.c.x];
   3511 	}
   3512 
   3513 	if (IS_SET(MODE_INSERT) && term.c.x+width < term.col) {
   3514 		memmove(gp+width, gp, (term.col - term.c.x - width) * sizeof(Glyph));
   3515 		gp->mode &= ~ATTR_WIDE;
   3516 	}
   3517 
   3518 	if (term.c.x+width > term.col) {
   3519 		if (IS_SET(MODE_WRAP))
   3520 			tnewline(1);
   3521 		else
   3522 			tmoveto(term.col - width, term.c.y);
   3523 		gp = &term.line[term.c.y][term.c.x];
   3524 	}
   3525 
   3526 	tsetchar(u, &term.c.attr, term.c.x, term.c.y);
   3527 	#if OSC133_PATCH
   3528 	term.c.attr.mode &= ~ATTR_FTCS_PROMPT;
   3529 	#endif // OSC133_PATCH
   3530 	term.lastc = u;
   3531 
   3532 	if (width == 2) {
   3533 		gp->mode |= ATTR_WIDE;
   3534 		if (term.c.x+1 < term.col) {
   3535 			if (gp[1].mode == ATTR_WIDE && term.c.x+2 < term.col) {
   3536 				gp[2].u = ' ';
   3537 				gp[2].mode &= ~ATTR_WDUMMY;
   3538 			}
   3539 			gp[1].u = '\0';
   3540 			gp[1].mode = ATTR_WDUMMY;
   3541 		}
   3542 	}
   3543 	if (term.c.x+width < term.col) {
   3544 		tmoveto(term.c.x+width, term.c.y);
   3545 	} else {
   3546 		#if REFLOW_PATCH
   3547 		term.wrapcwidth[IS_SET(MODE_ALTSCREEN)] = width;
   3548 		#endif // REFLOW_PATCH
   3549 		term.c.state |= CURSOR_WRAPNEXT;
   3550 	}
   3551 }
   3552 
   3553 int
   3554 twrite(const char *buf, int buflen, int show_ctrl)
   3555 {
   3556 	int charsize;
   3557 	Rune u;
   3558 	int n;
   3559 
   3560 	#if SYNC_PATCH
   3561 	int su0 = su;
   3562 	twrite_aborted = 0;
   3563 	#endif // SYNC_PATCH
   3564 
   3565 	for (n = 0; n < buflen; n += charsize) {
   3566 		#if SIXEL_PATCH
   3567 		if (IS_SET(MODE_SIXEL) && sixel_st.state != PS_ESC) {
   3568 			charsize = sixel_parser_parse(&sixel_st, (const unsigned char*)buf + n, buflen - n);
   3569 			continue;
   3570 		} else if (IS_SET(MODE_UTF8))
   3571 		#else
   3572 		if (IS_SET(MODE_UTF8))
   3573 		#endif // SIXEL_PATCH
   3574 		{
   3575 			/* process a complete utf8 char */
   3576 			charsize = utf8decode(buf + n, &u, buflen - n);
   3577 			if (charsize == 0)
   3578 				break;
   3579 		} else {
   3580 			u = buf[n] & 0xFF;
   3581 			charsize = 1;
   3582 		}
   3583 		#if SYNC_PATCH
   3584 		if (su0 && !su) {
   3585 			twrite_aborted = 1;
   3586 			break;  // ESU - allow rendering before a new BSU
   3587 		}
   3588 		#endif // SYNC_PATCH
   3589 		if (show_ctrl && ISCONTROL(u)) {
   3590 			if (u & 0x80) {
   3591 				u &= 0x7f;
   3592 				tputc('^');
   3593 				tputc('[');
   3594 			} else if (u != '\n' && u != '\r' && u != '\t') {
   3595 				u ^= 0x40;
   3596 				tputc('^');
   3597 			}
   3598 		}
   3599 		tputc(u);
   3600 	}
   3601 	return n;
   3602 }
   3603 
   3604 #if !REFLOW_PATCH
   3605 void
   3606 tresize(int col, int row)
   3607 {
   3608 	int i, j;
   3609 	#if COLUMNS_PATCH
   3610 	int tmp = col;
   3611 	int minrow, mincol;
   3612 
   3613 	if (!term.maxcol)
   3614 		term.maxcol = term.col;
   3615 	col = MAX(col, term.maxcol);
   3616 	minrow = MIN(row, term.row);
   3617 	mincol = MIN(col, term.maxcol);
   3618 	#else
   3619 	int minrow = MIN(row, term.row);
   3620 	int mincol = MIN(col, term.col);
   3621 	#endif // COLUMNS_PATCH
   3622 	int *bp;
   3623 	#if SIXEL_PATCH
   3624 	int x2;
   3625 	Line line;
   3626 	ImageList *im, *next;
   3627 	#endif // SIXEL_PATCH
   3628 
   3629 	#if KEYBOARDSELECT_PATCH
   3630 	if ( row < term.row  || col < term.col )
   3631 		toggle_winmode(trt_kbdselect(XK_Escape, NULL, 0));
   3632 	#endif // KEYBOARDSELECT_PATCH
   3633 
   3634 	if (col < 1 || row < 1) {
   3635 		fprintf(stderr,
   3636 		        "tresize: error resizing to %dx%d\n", col, row);
   3637 		return;
   3638 	}
   3639 
   3640 	/* scroll both screens independently */
   3641 	if (row < term.row) {
   3642 		tcursor(CURSOR_SAVE);
   3643 		tsetscroll(0, term.row - 1);
   3644 		for (i = 0; i < 2; i++) {
   3645 			if (term.c.y >= row) {
   3646 				#if SCROLLBACK_PATCH
   3647 				tscrollup(0, term.c.y - row + 1, !IS_SET(MODE_ALTSCREEN));
   3648 				#else
   3649 				tscrollup(0, term.c.y - row + 1);
   3650 				#endif // SCROLLBACK_PATCH
   3651 			}
   3652 			for (j = row; j < term.row; j++)
   3653 				free(term.line[j]);
   3654 			tswapscreen();
   3655 			tcursor(CURSOR_LOAD);
   3656 		}
   3657 	}
   3658 
   3659 	/* resize to new height */
   3660 	term.line = xrealloc(term.line, row * sizeof(Line));
   3661 	term.alt  = xrealloc(term.alt,  row * sizeof(Line));
   3662 	term.dirty = xrealloc(term.dirty, row * sizeof(*term.dirty));
   3663 	term.tabs = xrealloc(term.tabs, col * sizeof(*term.tabs));
   3664 
   3665 	#if SCROLLBACK_PATCH
   3666 	Glyph gc=(Glyph){.bg=term.c.attr.bg, .fg=term.c.attr.fg, .u=' ', .mode=0};
   3667 	for (i = 0; i < HISTSIZE; i++) {
   3668 		term.hist[i] = xrealloc(term.hist[i], col * sizeof(Glyph));
   3669 		for (j = mincol; j < col; j++)
   3670 			term.hist[i][j] = gc;
   3671 	}
   3672 	#endif // SCROLLBACK_PATCH
   3673 
   3674 	/* resize each row to new width, zero-pad if needed */
   3675 	for (i = 0; i < minrow; i++) {
   3676 		term.line[i] = xrealloc(term.line[i], col * sizeof(Glyph));
   3677 		term.alt[i]  = xrealloc(term.alt[i],  col * sizeof(Glyph));
   3678 	}
   3679 
   3680 	/* allocate any new rows */
   3681 	for (/* i = minrow */; i < row; i++) {
   3682 		term.line[i] = xmalloc(col * sizeof(Glyph));
   3683 		term.alt[i] = xmalloc(col * sizeof(Glyph));
   3684 	}
   3685 	#if COLUMNS_PATCH
   3686 	if (col > term.maxcol)
   3687 	#else
   3688 	if (col > term.col)
   3689 	#endif // COLUMNS_PATCH
   3690 	{
   3691 		#if COLUMNS_PATCH
   3692 		bp = term.tabs + term.maxcol;
   3693 		memset(bp, 0, sizeof(*term.tabs) * (col - term.maxcol));
   3694 		#else
   3695 		bp = term.tabs + term.col;
   3696 		memset(bp, 0, sizeof(*term.tabs) * (col - term.col));
   3697 		#endif // COLUMNS_PATCH
   3698 
   3699 		while (--bp > term.tabs && !*bp)
   3700 			/* nothing */ ;
   3701 		for (bp += tabspaces; bp < term.tabs + col; bp += tabspaces)
   3702 			*bp = 1;
   3703 	}
   3704 
   3705 	/* update terminal size */
   3706 	#if COLUMNS_PATCH
   3707 	term.col = tmp;
   3708 	term.maxcol = col;
   3709 	#else
   3710 	term.col = col;
   3711 	#endif // COLUMNS_PATCH
   3712 	term.row = row;
   3713 
   3714 	/* reset scrolling region */
   3715 	tsetscroll(0, row-1);
   3716 	/* Clearing both screens (it makes dirty all lines) */
   3717 	for (i = 0; i < 2; i++) {
   3718 		tmoveto(term.c.x, term.c.y);  /* make use of the LIMIT in tmoveto */
   3719 		tcursor(CURSOR_SAVE);
   3720 		if (mincol < col && 0 < minrow) {
   3721 			tclearregion(mincol, 0, col - 1, minrow - 1);
   3722 		}
   3723 		if (0 < col && minrow < row) {
   3724 			tclearregion(0, minrow, col - 1, row - 1);
   3725 		}
   3726 		tswapscreen();
   3727 		tcursor(CURSOR_LOAD);
   3728 	}
   3729 
   3730 	#if SIXEL_PATCH
   3731 	/* expand images into new text cells */
   3732 	for (i = 0; i < 2; i++) {
   3733 		for (im = term.images; im; im = next) {
   3734 			next = im->next;
   3735 			#if SCROLLBACK_PATCH
   3736 			if (IS_SET(MODE_ALTSCREEN)) {
   3737 				if (im->y < 0 || im->y >= term.row) {
   3738 					delete_image(im);
   3739 					continue;
   3740 				}
   3741 				line = term.line[im->y];
   3742 			} else {
   3743 				if (im->y - term.scr < -HISTSIZE || im->y - term.scr >= term.row) {
   3744 					delete_image(im);
   3745 					continue;
   3746 				}
   3747 				line = TLINE(im->y);
   3748 			}
   3749 			#else
   3750 			if (im->y < 0 || im->y >= term.row) {
   3751 				delete_image(im);
   3752 				continue;
   3753 			}
   3754 			line = term.line[im->y];
   3755 			#endif // SCROLLBACK_PATCH
   3756 			x2 = MIN(im->x + im->cols, col) - 1;
   3757 			if (mincol < col && x2 >= mincol && im->x < col)
   3758 				tsetsixelattr(line, MAX(im->x, mincol), x2);
   3759 		}
   3760 		tswapscreen();
   3761 	}
   3762 	#endif // SIXEL_PATCH
   3763 }
   3764 #endif // REFLOW_PATCH
   3765 
   3766 void
   3767 resettitle(void)
   3768 {
   3769 	#if CSI_22_23_PATCH
   3770 	xsettitle(NULL, 0);
   3771 	#else
   3772 	xsettitle(NULL);
   3773 	#endif // CSI_22_23_PATCH
   3774 }
   3775 
   3776 void
   3777 drawregion(int x1, int y1, int x2, int y2)
   3778 {
   3779 	int y;
   3780 
   3781 	for (y = y1; y < y2; y++) {
   3782 		if (!term.dirty[y])
   3783 			continue;
   3784 
   3785 		term.dirty[y] = 0;
   3786 		#if SCROLLBACK_PATCH || REFLOW_PATCH
   3787 		xdrawline(TLINE(y), x1, y, x2);
   3788 		#else
   3789 		xdrawline(term.line[y], x1, y, x2);
   3790 		#endif // SCROLLBACK_PATCH
   3791 	}
   3792 }
   3793 
   3794 #include "patch/st_include.c"
   3795 
   3796 void
   3797 draw(void)
   3798 {
   3799 	int cx = term.c.x, ocx = term.ocx, ocy = term.ocy;
   3800 
   3801 	if (!xstartdraw())
   3802 		return;
   3803 
   3804 	/* adjust cursor position */
   3805 	LIMIT(term.ocx, 0, term.col-1);
   3806 	LIMIT(term.ocy, 0, term.row-1);
   3807 	if (term.line[term.ocy][term.ocx].mode & ATTR_WDUMMY)
   3808 		term.ocx--;
   3809 	if (term.line[term.c.y][cx].mode & ATTR_WDUMMY)
   3810 		cx--;
   3811 
   3812 	drawregion(0, 0, term.col, term.row);
   3813 
   3814 	#if KEYBOARDSELECT_PATCH && REFLOW_PATCH
   3815 	if (!kbds_drawcursor())
   3816 	#elif REFLOW_PATCH || SCROLLBACK_PATCH
   3817 	if (term.scr == 0)
   3818 	#endif // SCROLLBACK_PATCH | REFLOW_PATCH | KEYBOARDSELECT_PATCH
   3819 	#if LIGATURES_PATCH
   3820 	xdrawcursor(cx, term.c.y, term.line[term.c.y][cx],
   3821 			term.ocx, term.ocy, term.line[term.ocy][term.ocx],
   3822 			term.line[term.ocy], term.col);
   3823 	#else
   3824 	xdrawcursor(cx, term.c.y, term.line[term.c.y][cx],
   3825 			term.ocx, term.ocy, term.line[term.ocy][term.ocx]);
   3826 	#endif // LIGATURES_PATCH
   3827 	term.ocx = cx;
   3828 	term.ocy = term.c.y;
   3829 	xfinishdraw();
   3830 	if (ocx != term.ocx || ocy != term.ocy)
   3831 		xximspot(term.ocx, term.ocy);
   3832 }
   3833 
   3834 void
   3835 redraw(void)
   3836 {
   3837 	tfulldirt();
   3838 	draw();
   3839 }