st-flexipatch

My st-flexipatch configuration
git clone git://git.ethandl.dev/st-flexipatch
Log | Files | Refs | README | LICENSE

st.c (85205B)


      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:
   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:
   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 		#endif // UNDERCURL_PATCH
   1868 		default:
   1869 			if (BETWEEN(attr[i], 30, 37)) {
   1870 				#if MONOCHROME_PATCH
   1871 				term.c.attr.fg = defaultfg;
   1872 				#else
   1873 				term.c.attr.fg = attr[i] - 30;
   1874 				#endif // MONOCHROME_PATCH
   1875 			} else if (BETWEEN(attr[i], 40, 47)) {
   1876 				#if MONOCHROME_PATCH
   1877 				term.c.attr.bg = 0;
   1878 				#else
   1879 				term.c.attr.bg = attr[i] - 40;
   1880 				#endif // MONOCHROME_PATCH
   1881 			} else if (BETWEEN(attr[i], 90, 97)) {
   1882 				#if MONOCHROME_PATCH
   1883 				term.c.attr.fg = defaultfg;
   1884 				#else
   1885 				term.c.attr.fg = attr[i] - 90 + 8;
   1886 				#endif // MONOCHROME_PATCH
   1887 			} else if (BETWEEN(attr[i], 100, 107)) {
   1888 				#if MONOCHROME_PATCH
   1889 				term.c.attr.bg = 0;
   1890 				#else
   1891 				term.c.attr.bg = attr[i] - 100 + 8;
   1892 				#endif // MONOCHROME_PATCH
   1893 			} else {
   1894 				fprintf(stderr,
   1895 					"erresc(default): gfx attr %d unknown\n",
   1896 					attr[i]);
   1897 				csidump();
   1898 			}
   1899 			break;
   1900 		}
   1901 	}
   1902 }
   1903 
   1904 void
   1905 tsetscroll(int t, int b)
   1906 {
   1907 	int temp;
   1908 
   1909 	LIMIT(t, 0, term.row-1);
   1910 	LIMIT(b, 0, term.row-1);
   1911 	if (t > b) {
   1912 		temp = t;
   1913 		t = b;
   1914 		b = temp;
   1915 	}
   1916 	term.top = t;
   1917 	term.bot = b;
   1918 }
   1919 
   1920 void
   1921 tsetmode(int priv, int set, const int *args, int narg)
   1922 {
   1923 	int alt;
   1924 	const int *lim;
   1925 
   1926 	for (lim = args + narg; args < lim; ++args) {
   1927 		if (priv) {
   1928 			switch (*args) {
   1929 			case 1: /* DECCKM -- Cursor key */
   1930 				xsetmode(set, MODE_APPCURSOR);
   1931 				break;
   1932 			case 5: /* DECSCNM -- Reverse video */
   1933 				xsetmode(set, MODE_REVERSE);
   1934 				break;
   1935 			case 6: /* DECOM -- Origin */
   1936 				MODBIT(term.c.state, set, CURSOR_ORIGIN);
   1937 				tmoveato(0, 0);
   1938 				break;
   1939 			case 7: /* DECAWM -- Auto wrap */
   1940 				MODBIT(term.mode, set, MODE_WRAP);
   1941 				break;
   1942 			case 0:  /* Error (IGNORED) */
   1943 			case 2:  /* DECANM -- ANSI/VT52 (IGNORED) */
   1944 			case 3:  /* DECCOLM -- Column  (IGNORED) */
   1945 			case 4:  /* DECSCLM -- Scroll (IGNORED) */
   1946 			case 8:  /* DECARM -- Auto repeat (IGNORED) */
   1947 			case 18: /* DECPFF -- Printer feed (IGNORED) */
   1948 			case 19: /* DECPEX -- Printer extent (IGNORED) */
   1949 			case 42: /* DECNRCM -- National characters (IGNORED) */
   1950 			case 12: /* att610 -- Start blinking cursor (IGNORED) */
   1951 				break;
   1952 			case 25: /* DECTCEM -- Text Cursor Enable Mode */
   1953 				xsetmode(!set, MODE_HIDE);
   1954 				break;
   1955 			case 9:    /* X10 mouse compatibility mode */
   1956 				xsetpointermotion(0);
   1957 				xsetmode(0, MODE_MOUSE);
   1958 				xsetmode(set, MODE_MOUSEX10);
   1959 				break;
   1960 			case 1000: /* 1000: report button press */
   1961 				xsetpointermotion(0);
   1962 				xsetmode(0, MODE_MOUSE);
   1963 				xsetmode(set, MODE_MOUSEBTN);
   1964 				break;
   1965 			case 1002: /* 1002: report motion on button press */
   1966 				xsetpointermotion(0);
   1967 				xsetmode(0, MODE_MOUSE);
   1968 				xsetmode(set, MODE_MOUSEMOTION);
   1969 				break;
   1970 			case 1003: /* 1003: enable all mouse motions */
   1971 				xsetpointermotion(set);
   1972 				xsetmode(0, MODE_MOUSE);
   1973 				xsetmode(set, MODE_MOUSEMANY);
   1974 				break;
   1975 			case 1004: /* 1004: send focus events to tty */
   1976 				xsetmode(set, MODE_FOCUS);
   1977 				break;
   1978 			case 1006: /* 1006: extended reporting mode */
   1979 				xsetmode(set, MODE_MOUSESGR);
   1980 				break;
   1981 			case 1034:
   1982 				xsetmode(set, MODE_8BIT);
   1983 				break;
   1984 			case 1049: /* swap screen & set/restore cursor as xterm */
   1985 				if (!allowaltscreen)
   1986 					break;
   1987 				tcursor((set) ? CURSOR_SAVE : CURSOR_LOAD);
   1988 				/* FALLTHROUGH */
   1989 			case 47: /* swap screen */
   1990 			case 1047:
   1991 				if (!allowaltscreen)
   1992 					break;
   1993 				#if REFLOW_PATCH
   1994 				if (set)
   1995 					tloadaltscreen(*args != 47, *args == 1049);
   1996 				else
   1997 					tloaddefscreen(*args != 47, *args == 1049);
   1998 				break;
   1999 				#else
   2000 				alt = IS_SET(MODE_ALTSCREEN);
   2001 				if (alt) {
   2002 					#if COLUMNS_PATCH
   2003 					tclearregion(0, 0, term.maxcol-1, term.row-1);
   2004 					#else
   2005 					tclearregion(0, 0, term.col-1, term.row-1);
   2006 					#endif // COLUMNS_PATCH
   2007 				}
   2008 				if (set ^ alt) /* set is always 1 or 0 */
   2009 					tswapscreen();
   2010 				if (*args != 1049)
   2011 					break;
   2012 				/* FALLTHROUGH */
   2013 				#endif // REFLOW_PATCH
   2014 			case 1048:
   2015 				#if REFLOW_PATCH
   2016 				if (!allowaltscreen)
   2017 					break;
   2018 				#endif // REFLOW_PATCH
   2019 				tcursor((set) ? CURSOR_SAVE : CURSOR_LOAD);
   2020 				break;
   2021 			case 2004: /* 2004: bracketed paste mode */
   2022 				xsetmode(set, MODE_BRCKTPASTE);
   2023 				break;
   2024 			/* Not implemented mouse modes. See comments there. */
   2025 			case 1001: /* mouse highlight mode; can hang the
   2026 				      terminal by design when implemented. */
   2027 			case 1005: /* UTF-8 mouse mode; will confuse
   2028 				      applications not supporting UTF-8
   2029 				      and luit. */
   2030 			case 1015: /* urxvt mangled mouse mode; incompatible
   2031 				      and can be mistaken for other control
   2032 				      codes. */
   2033 				break;
   2034 			#if SIXEL_PATCH
   2035 			case 80: /* DECSDM -- Sixel Display Mode */
   2036 				MODBIT(term.mode, set, MODE_SIXEL_SDM);
   2037 				break;
   2038 			case 8452: /* sixel scrolling leaves cursor to right of graphic */
   2039 				MODBIT(term.mode, set, MODE_SIXEL_CUR_RT);
   2040 				break;
   2041 			#endif // SIXEL_PATCH
   2042 			default:
   2043 				fprintf(stderr,
   2044 					"erresc: unknown private set/reset mode %d\n",
   2045 					*args);
   2046 				break;
   2047 			}
   2048 		} else {
   2049 			switch (*args) {
   2050 			case 0:  /* Error (IGNORED) */
   2051 				break;
   2052 			case 2:
   2053 				xsetmode(set, MODE_KBDLOCK);
   2054 				break;
   2055 			case 4:  /* IRM -- Insertion-replacement */
   2056 				MODBIT(term.mode, set, MODE_INSERT);
   2057 				break;
   2058 			case 12: /* SRM -- Send/Receive */
   2059 				MODBIT(term.mode, !set, MODE_ECHO);
   2060 				break;
   2061 			case 20: /* LNM -- Linefeed/new line */
   2062 				MODBIT(term.mode, set, MODE_CRLF);
   2063 				break;
   2064 			default:
   2065 				fprintf(stderr,
   2066 					"erresc: unknown set/reset mode %d\n",
   2067 					*args);
   2068 				break;
   2069 			}
   2070 		}
   2071 	}
   2072 }
   2073 
   2074 void
   2075 csihandle(void)
   2076 {
   2077 	char buffer[40];
   2078 	int n = 0, len;
   2079 	#if SIXEL_PATCH
   2080 	ImageList *im, *next;
   2081 	int pi, pa;
   2082 	#endif // SIXEL_PATCH
   2083 	#if REFLOW_PATCH
   2084 	int x;
   2085 	#endif // REFLOW_PATCH
   2086 	#if COLUMNS_PATCH
   2087 	int maxcol = term.maxcol;
   2088 	#else
   2089 	int maxcol = term.col;
   2090 	#endif // COLUMNS_PATCH
   2091 
   2092 	switch (csiescseq.mode[0]) {
   2093 	default:
   2094 	unknown:
   2095 		fprintf(stderr, "erresc: unknown csi ");
   2096 		csidump();
   2097 		/* die(""); */
   2098 		break;
   2099 	case '@': /* ICH -- Insert <n> blank char */
   2100 		DEFAULT(csiescseq.arg[0], 1);
   2101 		tinsertblank(csiescseq.arg[0]);
   2102 		break;
   2103 	case 'A': /* CUU -- Cursor <n> Up */
   2104 		DEFAULT(csiescseq.arg[0], 1);
   2105 		tmoveto(term.c.x, term.c.y-csiescseq.arg[0]);
   2106 		break;
   2107 	case 'B': /* CUD -- Cursor <n> Down */
   2108 	case 'e': /* VPR --Cursor <n> Down */
   2109 		DEFAULT(csiescseq.arg[0], 1);
   2110 		tmoveto(term.c.x, term.c.y+csiescseq.arg[0]);
   2111 		break;
   2112 	case 'i': /* MC -- Media Copy */
   2113 		switch (csiescseq.arg[0]) {
   2114 		case 0:
   2115 			tdump();
   2116 			break;
   2117 		case 1:
   2118 			tdumpline(term.c.y);
   2119 			break;
   2120 		case 2:
   2121 			tdumpsel();
   2122 			break;
   2123 		case 4:
   2124 			term.mode &= ~MODE_PRINT;
   2125 			break;
   2126 		case 5:
   2127 			term.mode |= MODE_PRINT;
   2128 			break;
   2129 		}
   2130 		break;
   2131 	case 'c': /* DA -- Device Attributes */
   2132 		if (csiescseq.arg[0] == 0)
   2133 			ttywrite(vtiden, strlen(vtiden), 0);
   2134 		break;
   2135 	case 'b': /* REP -- if last char is printable print it <n> more times */
   2136 		LIMIT(csiescseq.arg[0], 1, 65535);
   2137 		if (term.lastc)
   2138 			while (csiescseq.arg[0]-- > 0)
   2139 				tputc(term.lastc);
   2140 		break;
   2141 	case 'C': /* CUF -- Cursor <n> Forward */
   2142 	case 'a': /* HPR -- Cursor <n> Forward */
   2143 		DEFAULT(csiescseq.arg[0], 1);
   2144 		tmoveto(term.c.x+csiescseq.arg[0], term.c.y);
   2145 		break;
   2146 	case 'D': /* CUB -- Cursor <n> Backward */
   2147 		DEFAULT(csiescseq.arg[0], 1);
   2148 		tmoveto(term.c.x-csiescseq.arg[0], term.c.y);
   2149 		break;
   2150 	case 'E': /* CNL -- Cursor <n> Down and first col */
   2151 		DEFAULT(csiescseq.arg[0], 1);
   2152 		tmoveto(0, term.c.y+csiescseq.arg[0]);
   2153 		break;
   2154 	case 'F': /* CPL -- Cursor <n> Up and first col */
   2155 		DEFAULT(csiescseq.arg[0], 1);
   2156 		tmoveto(0, term.c.y-csiescseq.arg[0]);
   2157 		break;
   2158 	case 'g': /* TBC -- Tabulation clear */
   2159 		switch (csiescseq.arg[0]) {
   2160 		case 0: /* clear current tab stop */
   2161 			term.tabs[term.c.x] = 0;
   2162 			break;
   2163 		case 3: /* clear all the tabs */
   2164 			memset(term.tabs, 0, term.col * sizeof(*term.tabs));
   2165 			break;
   2166 		default:
   2167 			goto unknown;
   2168 		}
   2169 		break;
   2170 	case 'G': /* CHA -- Move to <col> */
   2171 	case '`': /* HPA */
   2172 		DEFAULT(csiescseq.arg[0], 1);
   2173 		tmoveto(csiescseq.arg[0]-1, term.c.y);
   2174 		break;
   2175 	case 'H': /* CUP -- Move to <row> <col> */
   2176 	case 'f': /* HVP */
   2177 		DEFAULT(csiescseq.arg[0], 1);
   2178 		DEFAULT(csiescseq.arg[1], 1);
   2179 		tmoveato(csiescseq.arg[1]-1, csiescseq.arg[0]-1);
   2180 		break;
   2181 	case 'I': /* CHT -- Cursor Forward Tabulation <n> tab stops */
   2182 		DEFAULT(csiescseq.arg[0], 1);
   2183 		tputtab(csiescseq.arg[0]);
   2184 		break;
   2185 	case 'J': /* ED -- Clear screen */
   2186 		switch (csiescseq.arg[0]) {
   2187 		case 0: /* below */
   2188 			#if REFLOW_PATCH
   2189 			tclearregion(term.c.x, term.c.y, term.col-1, term.c.y, 1);
   2190 			if (term.c.y < term.row-1)
   2191 				tclearregion(0, term.c.y+1, term.col-1, term.row-1, 1);
   2192 			#else
   2193 			tclearregion(term.c.x, term.c.y, maxcol-1, term.c.y);
   2194 			if (term.c.y < term.row-1)
   2195 				tclearregion(0, term.c.y+1, maxcol-1, term.row-1);
   2196 			#endif // REFLOW_PATCH
   2197 			break;
   2198 		case 1: /* above */
   2199 			#if REFLOW_PATCH
   2200 			if (term.c.y > 0)
   2201 				tclearregion(0, 0, term.col-1, term.c.y-1, 1);
   2202 			tclearregion(0, term.c.y, term.c.x, term.c.y, 1);
   2203 			#else
   2204 			if (term.c.y > 0)
   2205 				tclearregion(0, 0, maxcol-1, term.c.y-1);
   2206 			tclearregion(0, term.c.y, term.c.x, term.c.y);
   2207 			#endif // REFLOW_PATCH
   2208 			break;
   2209 		case 2: /* screen */
   2210 			#if REFLOW_PATCH
   2211 			if (IS_SET(MODE_ALTSCREEN)) {
   2212 				tclearregion(0, 0, term.col-1, term.row-1, 1);
   2213 				#if SIXEL_PATCH
   2214 				tdeleteimages();
   2215 				#endif // SIXEL_PATCH
   2216 				break;
   2217 			}
   2218 			/* vte does this:
   2219 			tscrollup(0, term.row-1, term.row, SCROLL_SAVEHIST); */
   2220 			/* alacritty does this: */
   2221 			for (n = term.row-1; n >= 0 && tlinelen(term.line[n]) == 0; n--)
   2222 				;
   2223 			#if SIXEL_PATCH
   2224 			for (im = term.images; im; im = im->next)
   2225 				n = MAX(im->y - term.scr, n);
   2226 			#endif // SIXEL_PATCH
   2227 			if (n >= 0)
   2228 				tscrollup(0, term.row-1, n+1, SCROLL_SAVEHIST);
   2229 			tscrollup(0, term.row-1, term.row-n-1, SCROLL_NOSAVEHIST);
   2230 			break;
   2231 			#else // !REFLOW_PATCH
   2232 			#if SCROLLBACK_PATCH
   2233 			if (!IS_SET(MODE_ALTSCREEN)) {
   2234 				#if SCROLLBACK_PATCH
   2235 				kscrolldown(&((Arg){ .i = term.scr }));
   2236 				#endif
   2237 				int n, m, bot = term.bot;
   2238 				term.bot = term.row-1;
   2239 				for (n = term.row-1; n >= 0; n--) {
   2240 					for (m = 0; m < maxcol && term.line[n][m].u == ' ' && !term.line[n][m].mode; m++);
   2241 					if (m < maxcol) {
   2242 						#if SCROLLBACK_PATCH
   2243 						tscrollup(0, n+1, 1);
   2244 						#else
   2245 						tscrollup(0, n+1);
   2246 						#endif
   2247 						break;
   2248 					}
   2249 				}
   2250 				if (n < term.row-1)
   2251 					tclearregion(0, 0, maxcol-1, term.row-n-2);
   2252 				term.bot = bot;
   2253 				break;
   2254 			}
   2255 			#endif // SCROLLBACK_PATCH
   2256 
   2257 			tclearregion(0, 0, maxcol-1, term.row-1);
   2258 			#if SIXEL_PATCH
   2259 			tdeleteimages();
   2260 			#endif // SIXEL_PATCH
   2261 			#endif // REFLOW_PTCH
   2262 			break;
   2263 		case 3: /* scrollback */
   2264 			#if REFLOW_PATCH
   2265 			if (IS_SET(MODE_ALTSCREEN))
   2266 				break;
   2267 			kscrolldown(&((Arg){ .i = term.scr }));
   2268 			term.scr = 0;
   2269 			term.histi = 0;
   2270 			term.histf = 0;
   2271 			#if SIXEL_PATCH
   2272 			for (im = term.images; im; im = next) {
   2273 				next = im->next;
   2274 				if (im->y < 0)
   2275 					delete_image(im);
   2276 			}
   2277 			#endif // SIXEL_PATCH
   2278 			break;
   2279 			#else // !REFLOW_PATCH
   2280 			#if SCROLLBACK_PATCH
   2281 			if (!IS_SET(MODE_ALTSCREEN)) {
   2282 				term.scr = 0;
   2283 				term.histi = 0;
   2284 				term.histn = 0;
   2285 				Glyph g=(Glyph){.bg=term.c.attr.bg, .fg=term.c.attr.fg, .u=' ', .mode=0};
   2286 				for (int i = 0; i < HISTSIZE; i++) {
   2287 					for (int j = 0; j < maxcol; j++)
   2288 						term.hist[i][j] = g;
   2289 				}
   2290 			}
   2291 			#endif // SCROLLBACK_PATCH
   2292 			#if SIXEL_PATCH
   2293 			for (im = term.images; im; im = next) {
   2294 				next = im->next;
   2295 				if (im->y < 0)
   2296 					delete_image(im);
   2297 			}
   2298 			#endif // SIXEL_PATCH
   2299 			break;
   2300 			#endif // REFLOW_PATCH
   2301 		#if SIXEL_PATCH
   2302 		case 6: /* sixels */
   2303 			tdeleteimages();
   2304 			tfulldirt();
   2305 			break;
   2306 		#endif // SIXEL_PATCH
   2307 		default:
   2308 			goto unknown;
   2309 		}
   2310 		break;
   2311 	case 'K': /* EL -- Clear line */
   2312 		switch (csiescseq.arg[0]) {
   2313 		#if REFLOW_PATCH
   2314 		case 0: /* right */
   2315 			tclearregion(term.c.x, term.c.y, term.col-1, term.c.y, 1);
   2316 			break;
   2317 		case 1: /* left */
   2318 			tclearregion(0, term.c.y, term.c.x, term.c.y, 1);
   2319 			break;
   2320 		case 2: /* all */
   2321 			tclearregion(0, term.c.y, term.col-1, term.c.y, 1);
   2322 			break;
   2323 		}
   2324 		#else
   2325 		case 0: /* right */
   2326 			tclearregion(term.c.x, term.c.y, maxcol-1, term.c.y);
   2327 			break;
   2328 		case 1: /* left */
   2329 			tclearregion(0, term.c.y, term.c.x, term.c.y);
   2330 			break;
   2331 		case 2: /* all */
   2332 			tclearregion(0, term.c.y, maxcol-1, term.c.y);
   2333 			break;
   2334 		}
   2335 		#endif // REFLOW_PATCH
   2336 		break;
   2337 	case 'S': /* SU -- Scroll <n> line up ; XTSMGRAPHICS */
   2338 		if (csiescseq.priv) {
   2339 			#if SIXEL_PATCH
   2340 			if (csiescseq.narg > 1) {
   2341 				/* XTSMGRAPHICS */
   2342 				pi = csiescseq.arg[0];
   2343 				pa = csiescseq.arg[1];
   2344 				if (pi == 1 && (pa == 1 || pa == 2 || pa == 4)) {
   2345 					/* number of sixel color registers */
   2346 					/* (read, reset and read the maximum value give the same response) */
   2347 					n = snprintf(buffer, sizeof buffer, "\033[?1;0;%dS", DECSIXEL_PALETTE_MAX);
   2348 					ttywrite(buffer, n, 1);
   2349 					break;
   2350 				} else if (pi == 2 && (pa == 1 || pa == 2 || pa == 4)) {
   2351 					/* sixel graphics geometry (in pixels) */
   2352 					/* (read, reset and read the maximum value give the same response) */
   2353 					n = snprintf(buffer, sizeof buffer, "\033[?2;0;%d;%dS",
   2354 					             MIN(term.col * win.cw, DECSIXEL_WIDTH_MAX),
   2355 					             MIN(term.row * win.ch, DECSIXEL_HEIGHT_MAX));
   2356 					ttywrite(buffer, n, 1);
   2357 					break;
   2358 				}
   2359 				/* the number of color registers and sixel geometry can't be changed */
   2360 				n = snprintf(buffer, sizeof buffer, "\033[?%d;3;0S", pi); /* failure */
   2361 				ttywrite(buffer, n, 1);
   2362 			}
   2363 			#endif // SIXEL_PATCH
   2364 			goto unknown;
   2365 		}
   2366 		DEFAULT(csiescseq.arg[0], 1);
   2367 		#if REFLOW_PATCH
   2368 		/* xterm, urxvt, alacritty save this in history */
   2369 		tscrollup(term.top, term.bot, csiescseq.arg[0], SCROLL_SAVEHIST);
   2370 		#elif SIXEL_PATCH && SCROLLBACK_PATCH
   2371 		tscrollup(term.top, csiescseq.arg[0], 1);
   2372 		#elif SCROLLBACK_PATCH
   2373 		tscrollup(term.top, csiescseq.arg[0], 0);
   2374 		#else
   2375 		tscrollup(term.top, csiescseq.arg[0]);
   2376 		#endif // SCROLLBACK_PATCH
   2377 		break;
   2378 	case 'T': /* SD -- Scroll <n> line down */
   2379 		DEFAULT(csiescseq.arg[0], 1);
   2380 		tscrolldown(term.top, csiescseq.arg[0]);
   2381 		break;
   2382 	case 'L': /* IL -- Insert <n> blank lines */
   2383 		DEFAULT(csiescseq.arg[0], 1);
   2384 		tinsertblankline(csiescseq.arg[0]);
   2385 		break;
   2386 	case 'l': /* RM -- Reset Mode */
   2387 		tsetmode(csiescseq.priv, 0, csiescseq.arg, csiescseq.narg);
   2388 		break;
   2389 	case 'M': /* DL -- Delete <n> lines */
   2390 		DEFAULT(csiescseq.arg[0], 1);
   2391 		tdeleteline(csiescseq.arg[0]);
   2392 		break;
   2393 	case 'X': /* ECH -- Erase <n> char */
   2394 		#if REFLOW_PATCH
   2395 		if (csiescseq.arg[0] < 0)
   2396 			return;
   2397 		DEFAULT(csiescseq.arg[0], 1);
   2398 		x = MIN(term.c.x + csiescseq.arg[0], term.col) - 1;
   2399 		tclearregion(term.c.x, term.c.y, x, term.c.y, 1);
   2400 		#else
   2401 		DEFAULT(csiescseq.arg[0], 1);
   2402 		tclearregion(term.c.x, term.c.y,
   2403 				term.c.x + csiescseq.arg[0] - 1, term.c.y);
   2404 		#endif // REFLOW_PATCH
   2405 		break;
   2406 	case 'P': /* DCH -- Delete <n> char */
   2407 		DEFAULT(csiescseq.arg[0], 1);
   2408 		tdeletechar(csiescseq.arg[0]);
   2409 		break;
   2410 	case 'Z': /* CBT -- Cursor Backward Tabulation <n> tab stops */
   2411 		DEFAULT(csiescseq.arg[0], 1);
   2412 		tputtab(-csiescseq.arg[0]);
   2413 		break;
   2414 	case 'd': /* VPA -- Move to <row> */
   2415 		DEFAULT(csiescseq.arg[0], 1);
   2416 		tmoveato(term.c.x, csiescseq.arg[0]-1);
   2417 		break;
   2418 	case 'h': /* SM -- Set terminal mode */
   2419 		tsetmode(csiescseq.priv, 1, csiescseq.arg, csiescseq.narg);
   2420 		break;
   2421 	case 'm': /* SGR -- Terminal attribute (color) */
   2422 		tsetattr(csiescseq.arg, csiescseq.narg);
   2423 		break;
   2424 	case 'n': /* DSR -- Device Status Report */
   2425 		switch (csiescseq.arg[0]) {
   2426 		case 5: /* Status Report "OK" `0n` */
   2427 			ttywrite("\033[0n", sizeof("\033[0n") - 1, 0);
   2428 			break;
   2429 		case 6: /* Report Cursor Position (CPR) "<row>;<column>R" */
   2430 			len = snprintf(buffer, sizeof(buffer), "\033[%i;%iR",
   2431 			               term.c.y+1, term.c.x+1);
   2432 			ttywrite(buffer, len, 0);
   2433 			break;
   2434 		default:
   2435 			goto unknown;
   2436 		}
   2437 		break;
   2438 	case 'r': /* DECSTBM -- Set Scrolling Region */
   2439 		if (csiescseq.priv) {
   2440 			goto unknown;
   2441 		} else {
   2442 			DEFAULT(csiescseq.arg[0], 1);
   2443 			DEFAULT(csiescseq.arg[1], term.row);
   2444 			tsetscroll(csiescseq.arg[0]-1, csiescseq.arg[1]-1);
   2445 			tmoveato(0, 0);
   2446 		}
   2447 		break;
   2448 	case 's': /* DECSC -- Save cursor position (ANSI.SYS) */
   2449 		tcursor(CURSOR_SAVE);
   2450 		break;
   2451 	#if CSI_22_23_PATCH | SIXEL_PATCH
   2452 	case 't': /* title stack operations ; XTWINOPS */
   2453 		switch (csiescseq.arg[0]) {
   2454 		#if SIXEL_PATCH
   2455 		case 14: /* text area size in pixels */
   2456 			if (csiescseq.narg > 1)
   2457 				goto unknown;
   2458 			n = snprintf(buffer, sizeof buffer, "\033[4;%d;%dt",
   2459 			             term.row * win.ch, term.col * win.cw);
   2460 			ttywrite(buffer, n, 1);
   2461 			break;
   2462 		case 16: /* character cell size in pixels */
   2463 			n = snprintf(buffer, sizeof buffer, "\033[6;%d;%dt", win.ch, win.cw);
   2464 			ttywrite(buffer, n, 1);
   2465 			break;
   2466 		case 18: /* size of the text area in characters */
   2467 			n = snprintf(buffer, sizeof buffer, "\033[8;%d;%dt", term.row, term.col);
   2468 			ttywrite(buffer, n, 1);
   2469 			break;
   2470 		#endif // SIXEL_PATCH
   2471 		#if CSI_22_23_PATCH
   2472 		case 22: /* pust current title on stack */
   2473 			switch (csiescseq.arg[1]) {
   2474 			case 0:
   2475 			case 1:
   2476 			case 2:
   2477 				xpushtitle();
   2478 				break;
   2479 			default:
   2480 				goto unknown;
   2481 			}
   2482 			break;
   2483 		case 23: /* pop last title from stack */
   2484 			switch (csiescseq.arg[1]) {
   2485 			case 0:
   2486 			case 1:
   2487 			case 2:
   2488 				xsettitle(NULL, 1);
   2489 				break;
   2490 			default:
   2491 				goto unknown;
   2492 			}
   2493 			break;
   2494 		#endif // CSI_22_23_PATCH
   2495 		default:
   2496 			goto unknown;
   2497 		}
   2498 		break;
   2499 	#endif // CSI_22_23_PATCH | SIXEL_PATCH
   2500 	case 'u': /* DECRC -- Restore cursor position (ANSI.SYS) */
   2501 		tcursor(CURSOR_LOAD);
   2502 		break;
   2503 	case ' ':
   2504 		switch (csiescseq.mode[1]) {
   2505 		case 'q': /* DECSCUSR -- Set Cursor Style */
   2506 			if (xsetcursor(csiescseq.arg[0]))
   2507 				goto unknown;
   2508 			break;
   2509 		default:
   2510 			goto unknown;
   2511 		}
   2512 		break;
   2513 	}
   2514 }
   2515 
   2516 void
   2517 csidump(void)
   2518 {
   2519 	size_t i;
   2520 	uint c;
   2521 
   2522 	fprintf(stderr, "ESC[");
   2523 	for (i = 0; i < csiescseq.len; i++) {
   2524 		c = csiescseq.buf[i] & 0xff;
   2525 		if (isprint(c)) {
   2526 			putc(c, stderr);
   2527 		} else if (c == '\n') {
   2528 			fprintf(stderr, "(\\n)");
   2529 		} else if (c == '\r') {
   2530 			fprintf(stderr, "(\\r)");
   2531 		} else if (c == 0x1b) {
   2532 			fprintf(stderr, "(\\e)");
   2533 		} else {
   2534 			fprintf(stderr, "(%02x)", c);
   2535 		}
   2536 	}
   2537 	putc('\n', stderr);
   2538 }
   2539 
   2540 void
   2541 csireset(void)
   2542 {
   2543 	memset(&csiescseq, 0, sizeof(csiescseq));
   2544 }
   2545 
   2546 void
   2547 osc_color_response(int num, int index, int is_osc4)
   2548 {
   2549 	int n;
   2550 	char buf[32];
   2551 	unsigned char r, g, b;
   2552 
   2553 	if (xgetcolor(is_osc4 ? num : index, &r, &g, &b)) {
   2554 		fprintf(stderr, "erresc: failed to fetch %s color %d\n",
   2555 		        is_osc4 ? "osc4" : "osc",
   2556 		        is_osc4 ? num : index);
   2557 		return;
   2558 	}
   2559 
   2560 	n = snprintf(buf, sizeof buf, "\033]%s%d;rgb:%02x%02x/%02x%02x/%02x%02x%s",
   2561 	             is_osc4 ? "4;" : "", num, r, r, g, g, b, b, strescseq.term);
   2562 	if (n < 0 || n >= sizeof(buf)) {
   2563 		fprintf(stderr, "error: %s while printing %s response\n",
   2564 		        n < 0 ? "snprintf failed" : "truncation occurred",
   2565 		        is_osc4 ? "osc4" : "osc");
   2566 	} else {
   2567 		ttywrite(buf, n, 1);
   2568 	}
   2569 }
   2570 
   2571 void
   2572 strhandle(void)
   2573 {
   2574 	char *p = NULL, *dec;
   2575 	int j, narg, par;
   2576 	const struct { int idx; char *str; } osc_table[] = {
   2577 		{ defaultfg, "foreground" },
   2578 		{ defaultbg, "background" },
   2579 		{ defaultcs, "cursor" }
   2580 	};
   2581 	#if SIXEL_PATCH
   2582 	ImageList *im, *newimages, *next, *tail = NULL;
   2583 	int i, x1, y1, x2, y2, y, numimages;
   2584 	int cx, cy;
   2585 	Line line;
   2586 	#if SCROLLBACK_PATCH || REFLOW_PATCH
   2587 	int scr = IS_SET(MODE_ALTSCREEN) ? 0 : term.scr;
   2588 	#else
   2589 	int scr = 0;
   2590 	#endif // SCROLLBACK_PATCH
   2591 	#endif // SIXEL_PATCH
   2592 
   2593 	term.esc &= ~(ESC_STR_END|ESC_STR);
   2594 	strparse();
   2595 	par = (narg = strescseq.narg) ? atoi(strescseq.args[0]) : 0;
   2596 
   2597 	switch (strescseq.type) {
   2598 	case ']': /* OSC -- Operating System Command */
   2599 		switch (par) {
   2600 		case 0:
   2601 			if (narg > 1) {
   2602 				#if CSI_22_23_PATCH
   2603 				xsettitle(strescseq.args[1], 0);
   2604 				#else
   2605 				xsettitle(strescseq.args[1]);
   2606 				#endif // CSI_22_23_PATCH
   2607 				xseticontitle(strescseq.args[1]);
   2608 			}
   2609 			return;
   2610 		case 1:
   2611 			if (narg > 1)
   2612 				xseticontitle(strescseq.args[1]);
   2613 			return;
   2614 		case 2:
   2615 			if (narg > 1)
   2616 				#if CSI_22_23_PATCH
   2617 				xsettitle(strescseq.args[1], 0);
   2618 				#else
   2619 				xsettitle(strescseq.args[1]);
   2620 				#endif // CSI_22_23_PATCH
   2621 			return;
   2622 		case 52:
   2623 			if (narg > 2 && allowwindowops) {
   2624 				dec = base64dec(strescseq.args[2]);
   2625 				if (dec) {
   2626 					xsetsel(dec);
   2627 					xclipcopy();
   2628 				} else {
   2629 					fprintf(stderr, "erresc: invalid base64\n");
   2630 				}
   2631 			}
   2632 			return;
   2633 		#if OSC7_PATCH
   2634 		case 7:
   2635 			osc7parsecwd((const char *)strescseq.args[1]);
   2636 			return;
   2637 		#endif // OSC7_PATCH
   2638 		case 8: /* Clear Hyperlinks */
   2639 			return;
   2640 		case 10:
   2641 		case 11:
   2642 		case 12:
   2643 			if (narg < 2)
   2644 				break;
   2645 			p = strescseq.args[1];
   2646 			if ((j = par - 10) < 0 || j >= LEN(osc_table))
   2647 				break; /* shouldn't be possible */
   2648 
   2649 			if (!strcmp(p, "?")) {
   2650 				osc_color_response(par, osc_table[j].idx, 0);
   2651 			} else if (xsetcolorname(osc_table[j].idx, p)) {
   2652 				fprintf(stderr, "erresc: invalid %s color: %s\n",
   2653 				        osc_table[j].str, p);
   2654 			} else {
   2655 				tfulldirt();
   2656 			}
   2657 			return;
   2658 		case 4: /* color set */
   2659 			if (narg < 3)
   2660 				break;
   2661 			p = strescseq.args[2];
   2662 			/* FALLTHROUGH */
   2663 		case 104: /* color reset */
   2664 			j = (narg > 1) ? atoi(strescseq.args[1]) : -1;
   2665 
   2666 			if (p && !strcmp(p, "?")) {
   2667 				osc_color_response(j, 0, 1);
   2668 			} else if (xsetcolorname(j, p)) {
   2669 				if (par == 104 && narg <= 1) {
   2670 					xloadcols();
   2671 					return; /* color reset without parameter */
   2672 				}
   2673 				fprintf(stderr, "erresc: invalid color j=%d, p=%s\n",
   2674 				        j, p ? p : "(null)");
   2675 			} else {
   2676 				/*
   2677 				 * TODO if defaultbg color is changed, borders
   2678 				 * are dirty
   2679 				 */
   2680 				tfulldirt();
   2681 			}
   2682 			return;
   2683 		#if OSC133_PATCH
   2684 		case 133:
   2685 			if (narg < 2)
   2686 				break;
   2687 			switch (*strescseq.args[1]) {
   2688 			case 'A':
   2689 				term.c.attr.mode |= ATTR_FTCS_PROMPT;
   2690 				break;
   2691 			/* We don't handle these arguments yet */
   2692 			case 'B':
   2693 			case 'C':
   2694 			case 'D':
   2695 				break;
   2696 			default:
   2697 				fprintf(stderr, "erresc: unknown OSC 133 argument: %c\n", *strescseq.args[1]);
   2698 				break;
   2699 			}
   2700 			return;
   2701 		#endif // OSC133_PATCH
   2702 		}
   2703 		break;
   2704 	case 'k': /* old title set compatibility */
   2705 		#if CSI_22_23_PATCH
   2706 		xsettitle(strescseq.args[0], 0);
   2707 		#else
   2708 		xsettitle(strescseq.args[0]);
   2709 		#endif // CSI_22_23_PATCH
   2710 		return;
   2711 	case 'P': /* DCS -- Device Control String */
   2712 		#if SIXEL_PATCH
   2713 		if (IS_SET(MODE_SIXEL)) {
   2714 			term.mode &= ~MODE_SIXEL;
   2715 			if (!sixel_st.image.data) {
   2716 				sixel_parser_deinit(&sixel_st);
   2717 				return;
   2718 			}
   2719 			cx = IS_SET(MODE_SIXEL_SDM) ? 0 : term.c.x;
   2720 			cy = IS_SET(MODE_SIXEL_SDM) ? 0 : term.c.y;
   2721 			if ((numimages = sixel_parser_finalize(&sixel_st, &newimages,
   2722 					cx, cy + scr, win.cw, win.ch)) <= 0) {
   2723 				sixel_parser_deinit(&sixel_st);
   2724 				perror("sixel_parser_finalize() failed");
   2725 				return;
   2726 			}
   2727 			sixel_parser_deinit(&sixel_st);
   2728 			x1 = newimages->x;
   2729 			y1 = newimages->y;
   2730 			x2 = x1 + newimages->cols;
   2731 			y2 = y1 + numimages;
   2732 			/* Delete the old images that are covered by the new image(s). We also need
   2733 			 * to check if they have already been deleted before adding the new ones. */
   2734 			if (term.images) {
   2735 				char transparent[numimages];
   2736 				for (i = 0, im = newimages; im; im = im->next, i++) {
   2737 					transparent[i] = im->transparent;
   2738 				}
   2739 				for (im = term.images; im; im = next) {
   2740 					next = im->next;
   2741 					if (im->y >= y1 && im->y < y2) {
   2742 						y = im->y - scr;
   2743 						if (y >= 0 && y < term.row && term.dirty[y]) {
   2744 							line = term.line[y];
   2745 							j = MIN(im->x + im->cols, term.col);
   2746 							for (i = im->x; i < j; i++) {
   2747 								if (line[i].mode & ATTR_SIXEL)
   2748 									break;
   2749 							}
   2750 							if (i == j) {
   2751 								delete_image(im);
   2752 								continue;
   2753 							}
   2754 						}
   2755 						if (im->x >= x1 && im->x + im->cols <= x2 && !transparent[im->y - y1]) {
   2756 							delete_image(im);
   2757 							continue;
   2758 						}
   2759 					}
   2760 					tail = im;
   2761 				}
   2762 			}
   2763 			if (tail) {
   2764 				tail->next = newimages;
   2765 				newimages->prev = tail;
   2766 			} else {
   2767 				term.images = newimages;
   2768 			}
   2769 			#if COLUMNS_PATCH && !REFLOW_PATCH
   2770 			x2 = MIN(x2, term.maxcol) - 1;
   2771 			#else
   2772 			x2 = MIN(x2, term.col) - 1;
   2773 			#endif // COLUMNS_PATCH
   2774 			if (IS_SET(MODE_SIXEL_SDM)) {
   2775 				/* Sixel display mode: put the sixel in the upper left corner of
   2776 				 * the screen, disable scrolling (the sixel will be truncated if
   2777 				 * it is too long) and do not change the cursor position. */
   2778 				for (i = 0, im = newimages; im; im = next, i++) {
   2779 					next = im->next;
   2780 					if (i >= term.row) {
   2781 						delete_image(im);
   2782 						continue;
   2783 					}
   2784 					im->y = i + scr;
   2785 					tsetsixelattr(term.line[i], x1, x2);
   2786 					term.dirty[MIN(im->y, term.row-1)] = 1;
   2787 				}
   2788 			} else {
   2789 				for (i = 0, im = newimages; im; im = next, i++) {
   2790 					next = im->next;
   2791 					#if SCROLLBACK_PATCH || REFLOW_PATCH
   2792 					scr = IS_SET(MODE_ALTSCREEN) ? 0 : term.scr;
   2793 					#endif // SCROLLBACK_PATCH
   2794 					im->y = term.c.y + scr;
   2795 					tsetsixelattr(term.line[term.c.y], x1, x2);
   2796 					term.dirty[MIN(im->y, term.row-1)] = 1;
   2797 					if (i < numimages-1) {
   2798 						im->next = NULL;
   2799 						tnewline(0);
   2800 						im->next = next;
   2801 					}
   2802 				}
   2803 				/* if mode 8452 is set, sixel scrolling leaves cursor to right of graphic */
   2804 				if (IS_SET(MODE_SIXEL_CUR_RT))
   2805 					term.c.x = MIN(term.c.x + newimages->cols, term.col-1);
   2806 			}
   2807 		}
   2808 		#endif // SIXEL_PATCH
   2809 		#if SYNC_PATCH
   2810 		/* https://gitlab.com/gnachman/iterm2/-/wikis/synchronized-updates-spec */
   2811 		if (strstr(strescseq.buf, "=1s") == strescseq.buf)
   2812 			tsync_begin();  /* BSU */
   2813 		else if (strstr(strescseq.buf, "=2s") == strescseq.buf)
   2814 			tsync_end();  /* ESU */
   2815 		#endif // SYNC_PATCH
   2816 		#if SIXEL_PATCH || SYNC_PATCH
   2817 		return;
   2818 		#endif // SIXEL_PATCH | SYNC_PATCH
   2819 	case '_': /* APC -- Application Program Command */
   2820 	case '^': /* PM -- Privacy Message */
   2821 		return;
   2822 	}
   2823 
   2824 	fprintf(stderr, "erresc: unknown str ");
   2825 	strdump();
   2826 }
   2827 
   2828 void
   2829 strparse(void)
   2830 {
   2831 	int c;
   2832 	char *p = strescseq.buf;
   2833 
   2834 	strescseq.narg = 0;
   2835 	strescseq.buf[strescseq.len] = '\0';
   2836 
   2837 	if (*p == '\0')
   2838 		return;
   2839 
   2840 	/* preserve semicolons in window titles, icon names and OSC 7 sequences */
   2841 	if (strescseq.type == ']' && (
   2842 		p[0] <= '2'
   2843 	#if OSC7_PATCH
   2844 		|| p[0] == '7'
   2845 	#endif // OSC7_PATCH
   2846 	) && p[1] == ';') {
   2847 		strescseq.args[strescseq.narg++] = p;
   2848 		strescseq.args[strescseq.narg++] = p + 2;
   2849 		p[1] = '\0';
   2850 		return;
   2851 	}
   2852 
   2853 	while (strescseq.narg < STR_ARG_SIZ) {
   2854 		strescseq.args[strescseq.narg++] = p;
   2855 		while ((c = *p) != ';' && c != '\0')
   2856 			++p;
   2857 		if (c == '\0')
   2858 			return;
   2859 		*p++ = '\0';
   2860 	}
   2861 }
   2862 
   2863 void
   2864 strdump(void)
   2865 {
   2866 	size_t i;
   2867 	uint c;
   2868 
   2869 	fprintf(stderr, "ESC%c", strescseq.type);
   2870 	for (i = 0; i < strescseq.len; i++) {
   2871 		c = strescseq.buf[i] & 0xff;
   2872 		if (c == '\0') {
   2873 			putc('\n', stderr);
   2874 			return;
   2875 		} else if (isprint(c)) {
   2876 			putc(c, stderr);
   2877 		} else if (c == '\n') {
   2878 			fprintf(stderr, "(\\n)");
   2879 		} else if (c == '\r') {
   2880 			fprintf(stderr, "(\\r)");
   2881 		} else if (c == 0x1b) {
   2882 			fprintf(stderr, "(\\e)");
   2883 		} else {
   2884 			fprintf(stderr, "(%02x)", c);
   2885 		}
   2886 	}
   2887 	fprintf(stderr, (strescseq.term[0] == 0x1b) ? "ESC\\\n" : "BEL\n");
   2888 }
   2889 
   2890 void
   2891 strreset(void)
   2892 {
   2893 	strescseq = (STREscape){
   2894 		.buf = xrealloc(strescseq.buf, STR_BUF_SIZ),
   2895 		.siz = STR_BUF_SIZ,
   2896 	};
   2897 }
   2898 
   2899 void
   2900 sendbreak(const Arg *arg)
   2901 {
   2902 	if (tcsendbreak(cmdfd, 0))
   2903 		perror("Error sending break");
   2904 }
   2905 
   2906 void
   2907 tprinter(char *s, size_t len)
   2908 {
   2909 	if (iofd != -1 && xwrite(iofd, s, len) < 0) {
   2910 		perror("Error writing to output file");
   2911 		close(iofd);
   2912 		iofd = -1;
   2913 	}
   2914 }
   2915 
   2916 void
   2917 toggleprinter(const Arg *arg)
   2918 {
   2919 	term.mode ^= MODE_PRINT;
   2920 }
   2921 
   2922 void
   2923 printscreen(const Arg *arg)
   2924 {
   2925 	tdump();
   2926 }
   2927 
   2928 void
   2929 printsel(const Arg *arg)
   2930 {
   2931 	tdumpsel();
   2932 }
   2933 
   2934 void
   2935 tdumpsel(void)
   2936 {
   2937 	char *ptr;
   2938 
   2939 	if ((ptr = getsel())) {
   2940 		tprinter(ptr, strlen(ptr));
   2941 		free(ptr);
   2942 	}
   2943 }
   2944 
   2945 #if !REFLOW_PATCH
   2946 void
   2947 tdumpline(int n)
   2948 {
   2949 	char buf[UTF_SIZ];
   2950 	const Glyph *bp, *end;
   2951 
   2952 	bp = &term.line[n][0];
   2953 	end = &bp[MIN(tlinelen(n), term.col) - 1];
   2954 	if (bp != end || bp->u != ' ') {
   2955 		for ( ; bp <= end; ++bp)
   2956 			tprinter(buf, utf8encode(bp->u, buf));
   2957 	}
   2958 	tprinter("\n", 1);
   2959 }
   2960 #endif // REFLOW_PATCH
   2961 
   2962 void
   2963 tdump(void)
   2964 {
   2965 	int i;
   2966 
   2967 	for (i = 0; i < term.row; ++i)
   2968 		tdumpline(i);
   2969 }
   2970 
   2971 void
   2972 tputtab(int n)
   2973 {
   2974 	uint x = term.c.x;
   2975 
   2976 	if (n > 0) {
   2977 		while (x < term.col && n--)
   2978 			for (++x; x < term.col && !term.tabs[x]; ++x)
   2979 				/* nothing */ ;
   2980 	} else if (n < 0) {
   2981 		while (x > 0 && n++)
   2982 			for (--x; x > 0 && !term.tabs[x]; --x)
   2983 				/* nothing */ ;
   2984 	}
   2985 	term.c.x = LIMIT(x, 0, term.col-1);
   2986 }
   2987 
   2988 void
   2989 tdefutf8(char ascii)
   2990 {
   2991 	if (ascii == 'G')
   2992 		term.mode |= MODE_UTF8;
   2993 	else if (ascii == '@')
   2994 		term.mode &= ~MODE_UTF8;
   2995 }
   2996 
   2997 void
   2998 tdeftran(char ascii)
   2999 {
   3000 	static char cs[] = "0B";
   3001 	static int vcs[] = {CS_GRAPHIC0, CS_USA};
   3002 	char *p;
   3003 
   3004 	if ((p = strchr(cs, ascii)) == NULL) {
   3005 		fprintf(stderr, "esc unhandled charset: ESC ( %c\n", ascii);
   3006 	} else {
   3007 		term.trantbl[term.icharset] = vcs[p - cs];
   3008 	}
   3009 }
   3010 
   3011 void
   3012 tdectest(char c)
   3013 {
   3014 	int x, y;
   3015 
   3016 	if (c == '8') { /* DEC screen alignment test. */
   3017 		for (x = 0; x < term.col; ++x) {
   3018 			for (y = 0; y < term.row; ++y)
   3019 				tsetchar('E', &term.c.attr, x, y);
   3020 		}
   3021 	}
   3022 }
   3023 
   3024 void
   3025 tstrsequence(uchar c)
   3026 {
   3027 	#if SIXEL_PATCH
   3028 	strreset();
   3029 	#endif // SIXEL_PATCH
   3030 
   3031 	switch (c) {
   3032 	case 0x90:   /* DCS -- Device Control String */
   3033 		c = 'P';
   3034 		#if SIXEL_PATCH
   3035 		term.esc |= ESC_DCS;
   3036 		#endif // SIXEL_PATCH
   3037 		break;
   3038 	case 0x9f:   /* APC -- Application Program Command */
   3039 		c = '_';
   3040 		break;
   3041 	case 0x9e:   /* PM -- Privacy Message */
   3042 		c = '^';
   3043 		break;
   3044 	case 0x9d:   /* OSC -- Operating System Command */
   3045 		c = ']';
   3046 		break;
   3047 	}
   3048 	#if !SIXEL_PATCH
   3049 	strreset();
   3050 	#endif // SIXEL_PATCH
   3051 	strescseq.type = c;
   3052 	term.esc |= ESC_STR;
   3053 }
   3054 
   3055 void
   3056 tcontrolcode(uchar ascii)
   3057 {
   3058 	switch (ascii) {
   3059 	case '\t':   /* HT */
   3060 		tputtab(1);
   3061 		return;
   3062 	case '\b':   /* BS */
   3063 		tmoveto(term.c.x-1, term.c.y);
   3064 		return;
   3065 	case '\r':   /* CR */
   3066 		tmoveto(0, term.c.y);
   3067 		return;
   3068 	case '\f':   /* LF */
   3069 	case '\v':   /* VT */
   3070 	case '\n':   /* LF */
   3071 		/* go to first col if the mode is set */
   3072 		tnewline(IS_SET(MODE_CRLF));
   3073 		return;
   3074 	case '\a':   /* BEL */
   3075 		if (term.esc & ESC_STR_END) {
   3076 			/* backwards compatibility to xterm */
   3077 			strescseq.term = STR_TERM_BEL;
   3078 			strhandle();
   3079 		} else {
   3080 			xbell();
   3081 		}
   3082 		break;
   3083 	case '\033': /* ESC */
   3084 		csireset();
   3085 		term.esc &= ~(ESC_CSI|ESC_ALTCHARSET|ESC_TEST);
   3086 		term.esc |= ESC_START;
   3087 		return;
   3088 	case '\016': /* SO (LS1 -- Locking shift 1) */
   3089 	case '\017': /* SI (LS0 -- Locking shift 0) */
   3090 		term.charset = 1 - (ascii - '\016');
   3091 		return;
   3092 	case '\032': /* SUB */
   3093 		tsetchar('?', &term.c.attr, term.c.x, term.c.y);
   3094 		/* FALLTHROUGH */
   3095 	case '\030': /* CAN */
   3096 		csireset();
   3097 		break;
   3098 	case '\005': /* ENQ (IGNORED) */
   3099 	case '\000': /* NUL (IGNORED) */
   3100 	case '\021': /* XON (IGNORED) */
   3101 	case '\023': /* XOFF (IGNORED) */
   3102 	case 0177:   /* DEL (IGNORED) */
   3103 		return;
   3104 	case 0x80:   /* TODO: PAD */
   3105 	case 0x81:   /* TODO: HOP */
   3106 	case 0x82:   /* TODO: BPH */
   3107 	case 0x83:   /* TODO: NBH */
   3108 	case 0x84:   /* TODO: IND */
   3109 		break;
   3110 	case 0x85:   /* NEL -- Next line */
   3111 		tnewline(1); /* always go to first col */
   3112 		break;
   3113 	case 0x86:   /* TODO: SSA */
   3114 	case 0x87:   /* TODO: ESA */
   3115 		break;
   3116 	case 0x88:   /* HTS -- Horizontal tab stop */
   3117 		term.tabs[term.c.x] = 1;
   3118 		break;
   3119 	case 0x89:   /* TODO: HTJ */
   3120 	case 0x8a:   /* TODO: VTS */
   3121 	case 0x8b:   /* TODO: PLD */
   3122 	case 0x8c:   /* TODO: PLU */
   3123 	case 0x8d:   /* TODO: RI */
   3124 	case 0x8e:   /* TODO: SS2 */
   3125 	case 0x8f:   /* TODO: SS3 */
   3126 	case 0x91:   /* TODO: PU1 */
   3127 	case 0x92:   /* TODO: PU2 */
   3128 	case 0x93:   /* TODO: STS */
   3129 	case 0x94:   /* TODO: CCH */
   3130 	case 0x95:   /* TODO: MW */
   3131 	case 0x96:   /* TODO: SPA */
   3132 	case 0x97:   /* TODO: EPA */
   3133 	case 0x98:   /* TODO: SOS */
   3134 	case 0x99:   /* TODO: SGCI */
   3135 		break;
   3136 	case 0x9a:   /* DECID -- Identify Terminal */
   3137 		ttywrite(vtiden, strlen(vtiden), 0);
   3138 		break;
   3139 	case 0x9b:   /* TODO: CSI */
   3140 	case 0x9c:   /* TODO: ST */
   3141 		break;
   3142 	case 0x90:   /* DCS -- Device Control String */
   3143 	case 0x9d:   /* OSC -- Operating System Command */
   3144 	case 0x9e:   /* PM -- Privacy Message */
   3145 	case 0x9f:   /* APC -- Application Program Command */
   3146 		tstrsequence(ascii);
   3147 		return;
   3148 	}
   3149 	/* only CAN, SUB, \a and C1 chars interrupt a sequence */
   3150 	term.esc &= ~(ESC_STR_END|ESC_STR);
   3151 }
   3152 
   3153 #if SIXEL_PATCH
   3154 void
   3155 dcshandle(void)
   3156 {
   3157 	int bgcolor, transparent;
   3158 	unsigned char r, g, b, a = 255;
   3159 
   3160 	switch (csiescseq.mode[0]) {
   3161 	default:
   3162 	unknown:
   3163 		fprintf(stderr, "erresc: unknown csi ");
   3164 		csidump();
   3165 		/* die(""); */
   3166 		break;
   3167 	#if SYNC_PATCH
   3168 	case '=':
   3169 		/* https://gitlab.com/gnachman/iterm2/-/wikis/synchronized-updates-spec */
   3170 		if (csiescseq.buf[2] == 's' && csiescseq.buf[1] == '1')
   3171 			tsync_begin();  /* BSU */
   3172 		else if (csiescseq.buf[2] == 's' && csiescseq.buf[1] == '2')
   3173 			tsync_end();  /* ESU */
   3174 		else
   3175 			goto unknown;
   3176 		break;
   3177 	#endif // SYNC_PATCH
   3178 	case 'q': /* DECSIXEL */
   3179 		transparent = (csiescseq.narg >= 2 && csiescseq.arg[1] == 1);
   3180 		if (IS_TRUECOL(term.c.attr.bg)) {
   3181 			r = term.c.attr.bg >> 16 & 255;
   3182 			g = term.c.attr.bg >> 8 & 255;
   3183 			b = term.c.attr.bg >> 0 & 255;
   3184 		} else {
   3185 			xgetcolor(term.c.attr.bg, &r, &g, &b);
   3186 			if (term.c.attr.bg == defaultbg)
   3187 				a = dc.col[defaultbg].pixel >> 24 & 255;
   3188 		}
   3189 		bgcolor = a << 24 | r << 16 | g << 8 | b;
   3190 		if (sixel_parser_init(&sixel_st, transparent, (255 << 24), bgcolor, 1, win.cw, win.ch) != 0)
   3191 			perror("sixel_parser_init() failed");
   3192 		term.mode |= MODE_SIXEL;
   3193 		break;
   3194 	}
   3195 }
   3196 #endif // SIXEL_PATCH
   3197 
   3198 /*
   3199  * returns 1 when the sequence is finished and it hasn't to read
   3200  * more characters for this sequence, otherwise 0
   3201  */
   3202 int
   3203 eschandle(uchar ascii)
   3204 {
   3205 	switch (ascii) {
   3206 	case '[':
   3207 		term.esc |= ESC_CSI;
   3208 		return 0;
   3209 	case '#':
   3210 		term.esc |= ESC_TEST;
   3211 		return 0;
   3212 	case '%':
   3213 		term.esc |= ESC_UTF8;
   3214 		return 0;
   3215 	case 'P': /* DCS -- Device Control String */
   3216 		#if SIXEL_PATCH
   3217 		term.esc |= ESC_DCS;
   3218 		#endif // SIXEL_PATCH
   3219 	case '_': /* APC -- Application Program Command */
   3220 	case '^': /* PM -- Privacy Message */
   3221 	case ']': /* OSC -- Operating System Command */
   3222 	case 'k': /* old title set compatibility */
   3223 		tstrsequence(ascii);
   3224 		return 0;
   3225 	case 'n': /* LS2 -- Locking shift 2 */
   3226 	case 'o': /* LS3 -- Locking shift 3 */
   3227 		term.charset = 2 + (ascii - 'n');
   3228 		break;
   3229 	case '(': /* GZD4 -- set primary charset G0 */
   3230 	case ')': /* G1D4 -- set secondary charset G1 */
   3231 	case '*': /* G2D4 -- set tertiary charset G2 */
   3232 	case '+': /* G3D4 -- set quaternary charset G3 */
   3233 		term.icharset = ascii - '(';
   3234 		term.esc |= ESC_ALTCHARSET;
   3235 		return 0;
   3236 	case 'D': /* IND -- Linefeed */
   3237 		if (term.c.y == term.bot) {
   3238 			#if REFLOW_PATCH
   3239 			tscrollup(term.top, term.bot, 1, SCROLL_SAVEHIST);
   3240 			#elif SCROLLBACK_PATCH
   3241 			tscrollup(term.top, 1, 1);
   3242 			#else
   3243 			tscrollup(term.top, 1);
   3244 			#endif // SCROLLBACK_PATCH
   3245 		} else {
   3246 			tmoveto(term.c.x, term.c.y+1);
   3247 		}
   3248 		break;
   3249 	case 'E': /* NEL -- Next line */
   3250 		tnewline(1); /* always go to first col */
   3251 		break;
   3252 	case 'H': /* HTS -- Horizontal tab stop */
   3253 		term.tabs[term.c.x] = 1;
   3254 		break;
   3255 	case 'M': /* RI -- Reverse index */
   3256 		if (term.c.y == term.top) {
   3257 			tscrolldown(term.top, 1);
   3258 		} else {
   3259 			tmoveto(term.c.x, term.c.y-1);
   3260 		}
   3261 		break;
   3262 	case 'Z': /* DECID -- Identify Terminal */
   3263 		ttywrite(vtiden, strlen(vtiden), 0);
   3264 		break;
   3265 	case 'c': /* RIS -- Reset to initial state */
   3266 		treset();
   3267 		#if CSI_22_23_PATCH
   3268 		xfreetitlestack();
   3269 		#endif // CSI_22_23_PATCH
   3270 		resettitle();
   3271 		xloadcols();
   3272 		xsetmode(0, MODE_HIDE);
   3273 		#if SCROLLBACK_PATCH && !REFLOW_PATCH
   3274 		if (!IS_SET(MODE_ALTSCREEN)) {
   3275 			term.scr = 0;
   3276 			term.histi = 0;
   3277 			term.histn = 0;
   3278 		}
   3279 		#endif // SCROLLBACK_PATCH
   3280 		break;
   3281 	case '=': /* DECPAM -- Application keypad */
   3282 		xsetmode(1, MODE_APPKEYPAD);
   3283 		break;
   3284 	case '>': /* DECPNM -- Normal keypad */
   3285 		xsetmode(0, MODE_APPKEYPAD);
   3286 		break;
   3287 	case '7': /* DECSC -- Save Cursor */
   3288 		tcursor(CURSOR_SAVE);
   3289 		break;
   3290 	case '8': /* DECRC -- Restore Cursor */
   3291 		tcursor(CURSOR_LOAD);
   3292 		break;
   3293 	case '\\': /* ST -- String Terminator */
   3294 		if (term.esc & ESC_STR_END) {
   3295 			strescseq.term = STR_TERM_ST;
   3296 			strhandle();
   3297 		}
   3298 		break;
   3299 	default:
   3300 		fprintf(stderr, "erresc: unknown sequence ESC 0x%02X '%c'\n",
   3301 			(uchar) ascii, isprint(ascii)? ascii:'.');
   3302 		break;
   3303 	}
   3304 	return 1;
   3305 }
   3306 
   3307 void
   3308 tputc(Rune u)
   3309 {
   3310 	char c[UTF_SIZ];
   3311 	int control;
   3312 	int width, len;
   3313 	Glyph *gp;
   3314 
   3315 	control = ISCONTROL(u);
   3316 	if (u < 127 || !IS_SET(MODE_UTF8))
   3317 	{
   3318 		c[0] = u;
   3319 		width = len = 1;
   3320 	} else {
   3321 		len = utf8encode(u, c);
   3322 		if (!control && (width = wcwidth(u)) == -1)
   3323 			width = 1;
   3324 	}
   3325 
   3326 	if (IS_SET(MODE_PRINT))
   3327 		tprinter(c, len);
   3328 
   3329 	/*
   3330 	 * STR sequence must be checked before anything else
   3331 	 * because it uses all following characters until it
   3332 	 * receives a ESC, a SUB, a ST or any other C1 control
   3333 	 * character.
   3334 	 */
   3335 	if (term.esc & ESC_STR) {
   3336 		if (u == '\a' || u == 030 || u == 032 || u == 033 ||
   3337 		   ISCONTROLC1(u)) {
   3338 			#if SIXEL_PATCH
   3339 			term.esc &= ~(ESC_START|ESC_STR|ESC_DCS);
   3340 			#else
   3341 			term.esc &= ~(ESC_START|ESC_STR);
   3342 			#endif // SIXEL_PATCH
   3343 			term.esc |= ESC_STR_END;
   3344 			goto check_control_code;
   3345 		}
   3346 
   3347 		#if SIXEL_PATCH
   3348 		if (term.esc & ESC_DCS)
   3349 			goto check_control_code;
   3350 		#endif // SIXEL_PATCH
   3351 
   3352 		if (strescseq.len+len >= strescseq.siz) {
   3353 			/*
   3354 			 * Here is a bug in terminals. If the user never sends
   3355 			 * some code to stop the str or esc command, then st
   3356 			 * will stop responding. But this is better than
   3357 			 * silently failing with unknown characters. At least
   3358 			 * then users will report back.
   3359 			 *
   3360 			 * In the case users ever get fixed, here is the code:
   3361 			 */
   3362 			/*
   3363 			 * term.esc = 0;
   3364 			 * strhandle();
   3365 			 */
   3366 			if (strescseq.siz > (SIZE_MAX - UTF_SIZ) / 2)
   3367 				return;
   3368 			strescseq.siz *= 2;
   3369 			strescseq.buf = xrealloc(strescseq.buf, strescseq.siz);
   3370 		}
   3371 
   3372 		memmove(&strescseq.buf[strescseq.len], c, len);
   3373 		strescseq.len += len;
   3374 		return;
   3375 	}
   3376 
   3377 check_control_code:
   3378 	/*
   3379 	 * Actions of control codes must be performed as soon they arrive
   3380 	 * because they can be embedded inside a control sequence, and
   3381 	 * they must not cause conflicts with sequences.
   3382 	 */
   3383 	if (control) {
   3384 		/* in UTF-8 mode ignore handling C1 control characters */
   3385 		if (IS_SET(MODE_UTF8) && ISCONTROLC1(u))
   3386 			return;
   3387 		tcontrolcode(u);
   3388 		/*
   3389 		 * control codes are not shown ever
   3390 		 */
   3391 		if (!term.esc)
   3392 			term.lastc = 0;
   3393 		return;
   3394 	} else if (term.esc & ESC_START) {
   3395 		if (term.esc & ESC_CSI) {
   3396 			csiescseq.buf[csiescseq.len++] = u;
   3397 			if (BETWEEN(u, 0x40, 0x7E)
   3398 					|| csiescseq.len >= \
   3399 					sizeof(csiescseq.buf)-1) {
   3400 				term.esc = 0;
   3401 				csiparse();
   3402 				csihandle();
   3403 			}
   3404 			return;
   3405 		#if SIXEL_PATCH
   3406 		} else if (term.esc & ESC_DCS) {
   3407 			csiescseq.buf[csiescseq.len++] = u;
   3408 			if (BETWEEN(u, 0x40, 0x7E)
   3409 					|| csiescseq.len >= \
   3410 					sizeof(csiescseq.buf)-1) {
   3411 				csiparse();
   3412 				dcshandle();
   3413 			}
   3414 			return;
   3415 		#endif // SIXEL_PATCH
   3416 		} else if (term.esc & ESC_UTF8) {
   3417 			tdefutf8(u);
   3418 		} else if (term.esc & ESC_ALTCHARSET) {
   3419 			tdeftran(u);
   3420 		} else if (term.esc & ESC_TEST) {
   3421 			tdectest(u);
   3422 		} else {
   3423 			if (!eschandle(u))
   3424 				return;
   3425 			/* sequence already finished */
   3426 		}
   3427 		term.esc = 0;
   3428 		/*
   3429 		 * All characters which form part of a sequence are not
   3430 		 * printed
   3431 		 */
   3432 		return;
   3433 	}
   3434 
   3435 	#if REFLOW_PATCH
   3436 	/* selected() takes relative coordinates */
   3437 	if (selected(term.c.x + term.scr, term.c.y + term.scr))
   3438 		selclear();
   3439 	#else
   3440 	if (selected(term.c.x, term.c.y))
   3441 		selclear();
   3442 	#endif // REFLOW_PATCH
   3443 
   3444 	gp = &term.line[term.c.y][term.c.x];
   3445 	if (IS_SET(MODE_WRAP) && (term.c.state & CURSOR_WRAPNEXT)) {
   3446 		gp->mode |= ATTR_WRAP;
   3447 		tnewline(1);
   3448 		gp = &term.line[term.c.y][term.c.x];
   3449 	}
   3450 
   3451 	if (IS_SET(MODE_INSERT) && term.c.x+width < term.col) {
   3452 		memmove(gp+width, gp, (term.col - term.c.x - width) * sizeof(Glyph));
   3453 		gp->mode &= ~ATTR_WIDE;
   3454 	}
   3455 
   3456 	if (term.c.x+width > term.col) {
   3457 		if (IS_SET(MODE_WRAP))
   3458 			tnewline(1);
   3459 		else
   3460 			tmoveto(term.col - width, term.c.y);
   3461 		gp = &term.line[term.c.y][term.c.x];
   3462 	}
   3463 
   3464 	tsetchar(u, &term.c.attr, term.c.x, term.c.y);
   3465 	#if OSC133_PATCH
   3466 	term.c.attr.mode &= ~ATTR_FTCS_PROMPT;
   3467 	#endif // OSC133_PATCH
   3468 	term.lastc = u;
   3469 
   3470 	if (width == 2) {
   3471 		gp->mode |= ATTR_WIDE;
   3472 		if (term.c.x+1 < term.col) {
   3473 			if (gp[1].mode == ATTR_WIDE && term.c.x+2 < term.col) {
   3474 				gp[2].u = ' ';
   3475 				gp[2].mode &= ~ATTR_WDUMMY;
   3476 			}
   3477 			gp[1].u = '\0';
   3478 			gp[1].mode = ATTR_WDUMMY;
   3479 		}
   3480 	}
   3481 	if (term.c.x+width < term.col) {
   3482 		tmoveto(term.c.x+width, term.c.y);
   3483 	} else {
   3484 		#if REFLOW_PATCH
   3485 		term.wrapcwidth[IS_SET(MODE_ALTSCREEN)] = width;
   3486 		#endif // REFLOW_PATCH
   3487 		term.c.state |= CURSOR_WRAPNEXT;
   3488 	}
   3489 }
   3490 
   3491 int
   3492 twrite(const char *buf, int buflen, int show_ctrl)
   3493 {
   3494 	int charsize;
   3495 	Rune u;
   3496 	int n;
   3497 
   3498 	#if SYNC_PATCH
   3499 	int su0 = su;
   3500 	twrite_aborted = 0;
   3501 	#endif // SYNC_PATCH
   3502 
   3503 	for (n = 0; n < buflen; n += charsize) {
   3504 		#if SIXEL_PATCH
   3505 		if (IS_SET(MODE_SIXEL) && sixel_st.state != PS_ESC) {
   3506 			charsize = sixel_parser_parse(&sixel_st, (const unsigned char*)buf + n, buflen - n);
   3507 			continue;
   3508 		} else if (IS_SET(MODE_UTF8))
   3509 		#else
   3510 		if (IS_SET(MODE_UTF8))
   3511 		#endif // SIXEL_PATCH
   3512 		{
   3513 			/* process a complete utf8 char */
   3514 			charsize = utf8decode(buf + n, &u, buflen - n);
   3515 			if (charsize == 0)
   3516 				break;
   3517 		} else {
   3518 			u = buf[n] & 0xFF;
   3519 			charsize = 1;
   3520 		}
   3521 		#if SYNC_PATCH
   3522 		if (su0 && !su) {
   3523 			twrite_aborted = 1;
   3524 			break;  // ESU - allow rendering before a new BSU
   3525 		}
   3526 		#endif // SYNC_PATCH
   3527 		if (show_ctrl && ISCONTROL(u)) {
   3528 			if (u & 0x80) {
   3529 				u &= 0x7f;
   3530 				tputc('^');
   3531 				tputc('[');
   3532 			} else if (u != '\n' && u != '\r' && u != '\t') {
   3533 				u ^= 0x40;
   3534 				tputc('^');
   3535 			}
   3536 		}
   3537 		tputc(u);
   3538 	}
   3539 	return n;
   3540 }
   3541 
   3542 #if !REFLOW_PATCH
   3543 void
   3544 tresize(int col, int row)
   3545 {
   3546 	int i, j;
   3547 	#if COLUMNS_PATCH
   3548 	int tmp = col;
   3549 	int minrow, mincol;
   3550 
   3551 	if (!term.maxcol)
   3552 		term.maxcol = term.col;
   3553 	col = MAX(col, term.maxcol);
   3554 	minrow = MIN(row, term.row);
   3555 	mincol = MIN(col, term.maxcol);
   3556 	#else
   3557 	int minrow = MIN(row, term.row);
   3558 	int mincol = MIN(col, term.col);
   3559 	#endif // COLUMNS_PATCH
   3560 	int *bp;
   3561 	#if SIXEL_PATCH
   3562 	int x2;
   3563 	Line line;
   3564 	ImageList *im, *next;
   3565 	#endif // SIXEL_PATCH
   3566 
   3567 	#if KEYBOARDSELECT_PATCH
   3568 	if ( row < term.row  || col < term.col )
   3569 		toggle_winmode(trt_kbdselect(XK_Escape, NULL, 0));
   3570 	#endif // KEYBOARDSELECT_PATCH
   3571 
   3572 	if (col < 1 || row < 1) {
   3573 		fprintf(stderr,
   3574 		        "tresize: error resizing to %dx%d\n", col, row);
   3575 		return;
   3576 	}
   3577 
   3578 	/* scroll both screens independently */
   3579 	if (row < term.row) {
   3580 		tcursor(CURSOR_SAVE);
   3581 		tsetscroll(0, term.row - 1);
   3582 		for (i = 0; i < 2; i++) {
   3583 			if (term.c.y >= row) {
   3584 				#if SCROLLBACK_PATCH
   3585 				tscrollup(0, term.c.y - row + 1, !IS_SET(MODE_ALTSCREEN));
   3586 				#else
   3587 				tscrollup(0, term.c.y - row + 1);
   3588 				#endif // SCROLLBACK_PATCH
   3589 			}
   3590 			for (j = row; j < term.row; j++)
   3591 				free(term.line[j]);
   3592 			tswapscreen();
   3593 			tcursor(CURSOR_LOAD);
   3594 		}
   3595 	}
   3596 
   3597 	/* resize to new height */
   3598 	term.line = xrealloc(term.line, row * sizeof(Line));
   3599 	term.alt  = xrealloc(term.alt,  row * sizeof(Line));
   3600 	term.dirty = xrealloc(term.dirty, row * sizeof(*term.dirty));
   3601 	term.tabs = xrealloc(term.tabs, col * sizeof(*term.tabs));
   3602 
   3603 	#if SCROLLBACK_PATCH
   3604 	Glyph gc=(Glyph){.bg=term.c.attr.bg, .fg=term.c.attr.fg, .u=' ', .mode=0};
   3605 	for (i = 0; i < HISTSIZE; i++) {
   3606 		term.hist[i] = xrealloc(term.hist[i], col * sizeof(Glyph));
   3607 		for (j = mincol; j < col; j++)
   3608 			term.hist[i][j] = gc;
   3609 	}
   3610 	#endif // SCROLLBACK_PATCH
   3611 
   3612 	/* resize each row to new width, zero-pad if needed */
   3613 	for (i = 0; i < minrow; i++) {
   3614 		term.line[i] = xrealloc(term.line[i], col * sizeof(Glyph));
   3615 		term.alt[i]  = xrealloc(term.alt[i],  col * sizeof(Glyph));
   3616 	}
   3617 
   3618 	/* allocate any new rows */
   3619 	for (/* i = minrow */; i < row; i++) {
   3620 		term.line[i] = xmalloc(col * sizeof(Glyph));
   3621 		term.alt[i] = xmalloc(col * sizeof(Glyph));
   3622 	}
   3623 	#if COLUMNS_PATCH
   3624 	if (col > term.maxcol)
   3625 	#else
   3626 	if (col > term.col)
   3627 	#endif // COLUMNS_PATCH
   3628 	{
   3629 		#if COLUMNS_PATCH
   3630 		bp = term.tabs + term.maxcol;
   3631 		memset(bp, 0, sizeof(*term.tabs) * (col - term.maxcol));
   3632 		#else
   3633 		bp = term.tabs + term.col;
   3634 		memset(bp, 0, sizeof(*term.tabs) * (col - term.col));
   3635 		#endif // COLUMNS_PATCH
   3636 
   3637 		while (--bp > term.tabs && !*bp)
   3638 			/* nothing */ ;
   3639 		for (bp += tabspaces; bp < term.tabs + col; bp += tabspaces)
   3640 			*bp = 1;
   3641 	}
   3642 
   3643 	/* update terminal size */
   3644 	#if COLUMNS_PATCH
   3645 	term.col = tmp;
   3646 	term.maxcol = col;
   3647 	#else
   3648 	term.col = col;
   3649 	#endif // COLUMNS_PATCH
   3650 	term.row = row;
   3651 
   3652 	/* reset scrolling region */
   3653 	tsetscroll(0, row-1);
   3654 	/* Clearing both screens (it makes dirty all lines) */
   3655 	for (i = 0; i < 2; i++) {
   3656 		tmoveto(term.c.x, term.c.y);  /* make use of the LIMIT in tmoveto */
   3657 		tcursor(CURSOR_SAVE);
   3658 		if (mincol < col && 0 < minrow) {
   3659 			tclearregion(mincol, 0, col - 1, minrow - 1);
   3660 		}
   3661 		if (0 < col && minrow < row) {
   3662 			tclearregion(0, minrow, col - 1, row - 1);
   3663 		}
   3664 		tswapscreen();
   3665 		tcursor(CURSOR_LOAD);
   3666 	}
   3667 
   3668 	#if SIXEL_PATCH
   3669 	/* expand images into new text cells */
   3670 	for (i = 0; i < 2; i++) {
   3671 		for (im = term.images; im; im = next) {
   3672 			next = im->next;
   3673 			#if SCROLLBACK_PATCH
   3674 			if (IS_SET(MODE_ALTSCREEN)) {
   3675 				if (im->y < 0 || im->y >= term.row) {
   3676 					delete_image(im);
   3677 					continue;
   3678 				}
   3679 				line = term.line[im->y];
   3680 			} else {
   3681 				if (im->y - term.scr < -HISTSIZE || im->y - term.scr >= term.row) {
   3682 					delete_image(im);
   3683 					continue;
   3684 				}
   3685 				line = TLINE(im->y);
   3686 			}
   3687 			#else
   3688 			if (im->y < 0 || im->y >= term.row) {
   3689 				delete_image(im);
   3690 				continue;
   3691 			}
   3692 			line = term.line[im->y];
   3693 			#endif // SCROLLBACK_PATCH
   3694 			x2 = MIN(im->x + im->cols, col) - 1;
   3695 			if (mincol < col && x2 >= mincol && im->x < col)
   3696 				tsetsixelattr(line, MAX(im->x, mincol), x2);
   3697 		}
   3698 		tswapscreen();
   3699 	}
   3700 	#endif // SIXEL_PATCH
   3701 }
   3702 #endif // REFLOW_PATCH
   3703 
   3704 void
   3705 resettitle(void)
   3706 {
   3707 	#if CSI_22_23_PATCH
   3708 	xsettitle(NULL, 0);
   3709 	#else
   3710 	xsettitle(NULL);
   3711 	#endif // CSI_22_23_PATCH
   3712 }
   3713 
   3714 void
   3715 drawregion(int x1, int y1, int x2, int y2)
   3716 {
   3717 	int y;
   3718 
   3719 	for (y = y1; y < y2; y++) {
   3720 		if (!term.dirty[y])
   3721 			continue;
   3722 
   3723 		term.dirty[y] = 0;
   3724 		#if SCROLLBACK_PATCH || REFLOW_PATCH
   3725 		xdrawline(TLINE(y), x1, y, x2);
   3726 		#else
   3727 		xdrawline(term.line[y], x1, y, x2);
   3728 		#endif // SCROLLBACK_PATCH
   3729 	}
   3730 }
   3731 
   3732 #include "patch/st_include.c"
   3733 
   3734 void
   3735 draw(void)
   3736 {
   3737 	int cx = term.c.x, ocx = term.ocx, ocy = term.ocy;
   3738 
   3739 	if (!xstartdraw())
   3740 		return;
   3741 
   3742 	/* adjust cursor position */
   3743 	LIMIT(term.ocx, 0, term.col-1);
   3744 	LIMIT(term.ocy, 0, term.row-1);
   3745 	if (term.line[term.ocy][term.ocx].mode & ATTR_WDUMMY)
   3746 		term.ocx--;
   3747 	if (term.line[term.c.y][cx].mode & ATTR_WDUMMY)
   3748 		cx--;
   3749 
   3750 	drawregion(0, 0, term.col, term.row);
   3751 
   3752 	#if KEYBOARDSELECT_PATCH && REFLOW_PATCH
   3753 	if (!kbds_drawcursor())
   3754 	#elif REFLOW_PATCH || SCROLLBACK_PATCH
   3755 	if (term.scr == 0)
   3756 	#endif // SCROLLBACK_PATCH | REFLOW_PATCH | KEYBOARDSELECT_PATCH
   3757 	#if LIGATURES_PATCH
   3758 	xdrawcursor(cx, term.c.y, term.line[term.c.y][cx],
   3759 			term.ocx, term.ocy, term.line[term.ocy][term.ocx],
   3760 			term.line[term.ocy], term.col);
   3761 	#else
   3762 	xdrawcursor(cx, term.c.y, term.line[term.c.y][cx],
   3763 			term.ocx, term.ocy, term.line[term.ocy][term.ocx]);
   3764 	#endif // LIGATURES_PATCH
   3765 	term.ocx = cx;
   3766 	term.ocy = term.c.y;
   3767 	xfinishdraw();
   3768 	if (ocx != term.ocx || ocy != term.ocy)
   3769 		xximspot(term.ocx, term.ocy);
   3770 }
   3771 
   3772 void
   3773 redraw(void)
   3774 {
   3775 	tfulldirt();
   3776 	draw();
   3777 }