*/ 1684 case '`': /* HPA */ 1685 DEFAULT(csiescseq.arg[0], 1); 1686 tmoveto(csiescseq.arg[0]-1, term.c.y); 1687 break; 1688 case 'H': /* CUP -- Move to */ 1689 case 'f': /* HVP */ 1690 DEFAULT(csiescseq.arg[0], 1); 1691 DEFAULT(csiescseq.arg[1], 1); 1692 tmoveato(csiescseq.arg[1]-1, csiescseq.arg[0]-1); 1693 break; 1694 case 'I': /* CHT -- Cursor Forward Tabulation tab stops */ 1695 DEFAULT(csiescseq.arg[0], 1); 1696 tputtab(csiescseq.arg[0]); 1697 break; 1698 case 'J': /* ED -- Clear screen */ 1699 switch (csiescseq.arg[0]) { 1700 case 0: /* below */ 1701 tclearregion(term.c.x, term.c.y, term.col-1, term.c.y); 1702 if (term.c.y < term.row-1) { 1703 tclearregion(0, term.c.y+1, term.col-1, 1704 term.row-1); 1705 } 1706 break; 1707 case 1: /* above */ 1708 if (term.c.y > 1) 1709 tclearregion(0, 0, term.col-1, term.c.y-1); 1710 tclearregion(0, term.c.y, term.c.x, term.c.y); 1711 break; 1712 case 2: /* all */ 1713 tclearregion(0, 0, term.col-1, term.row-1); 1714 break; 1715 default: 1716 goto unknown; 1717 } 1718 break; 1719 case 'K': /* EL -- Clear line */ 1720 switch (csiescseq.arg[0]) { 1721 case 0: /* right */ 1722 tclearregion(term.c.x, term.c.y, term.col-1, 1723 term.c.y); 1724 break; 1725 case 1: /* left */ 1726 tclearregion(0, term.c.y, term.c.x, term.c.y); 1727 break; 1728 case 2: /* all */ 1729 tclearregion(0, term.c.y, term.col-1, term.c.y); 1730 break; 1731 } 1732 break; 1733 case 'S': /* SU -- Scroll line up */ 1734 if (csiescseq.priv) break; 1735 DEFAULT(csiescseq.arg[0], 1); 1736 tscrollup(term.top, csiescseq.arg[0]); 1737 break; 1738 case 'T': /* SD -- Scroll line down */ 1739 DEFAULT(csiescseq.arg[0], 1); 1740 tscrolldown(term.top, csiescseq.arg[0]); 1741 break; 1742 case 'L': /* IL -- Insert blank lines */ 1743 DEFAULT(csiescseq.arg[0], 1); 1744 tinsertblankline(csiescseq.arg[0]); 1745 break; 1746 case 'l': /* RM -- Reset Mode */ 1747 tsetmode(csiescseq.priv, 0, csiescseq.arg, csiescseq.narg); 1748 break; 1749 case 'M': /* DL -- Delete lines */ 1750 DEFAULT(csiescseq.arg[0], 1); 1751 tdeleteline(csiescseq.arg[0]); 1752 break; 1753 case 'X': /* ECH -- Erase char */ 1754 DEFAULT(csiescseq.arg[0], 1); 1755 tclearregion(term.c.x, term.c.y, 1756 term.c.x + csiescseq.arg[0] - 1, term.c.y); 1757 break; 1758 case 'P': /* DCH -- Delete char */ 1759 DEFAULT(csiescseq.arg[0], 1); 1760 tdeletechar(csiescseq.arg[0]); 1761 break; 1762 case 'Z': /* CBT -- Cursor Backward Tabulation tab stops */ 1763 DEFAULT(csiescseq.arg[0], 1); 1764 tputtab(-csiescseq.arg[0]); 1765 break; 1766 case 'd': /* VPA -- Move to */ 1767 DEFAULT(csiescseq.arg[0], 1); 1768 tmoveato(term.c.x, csiescseq.arg[0]-1); 1769 break; 1770 case 'h': /* SM -- Set terminal mode */ 1771 tsetmode(csiescseq.priv, 1, csiescseq.arg, csiescseq.narg); 1772 break; 1773 case 'm': /* SGR -- Terminal attribute (color) */ 1774 tsetattr(csiescseq.arg, csiescseq.narg); 1775 break; 1776 case 'n': /* DSR -- Device Status Report */ 1777 switch (csiescseq.arg[0]) { 1778 case 5: /* Status Report "OK" `0n` */ 1779 ttywrite("\033[0n", sizeof("\033[0n") - 1, 0); 1780 break; 1781 case 6: /* Report Cursor Position (CPR) ";R" */ 1782 len = snprintf(buf, sizeof(buf), "\033[%i;%iR", 1783 term.c.y+1, term.c.x+1); 1784 ttywrite(buf, len, 0); 1785 break; 1786 default: 1787 goto unknown; 1788 } 1789 break; 1790 case 'r': /* DECSTBM -- Set Scrolling Region */ 1791 if (csiescseq.priv) { 1792 goto unknown; 1793 } else { 1794 DEFAULT(csiescseq.arg[0], 1); 1795 DEFAULT(csiescseq.arg[1], term.row); 1796 tsetscroll(csiescseq.arg[0]-1, csiescseq.arg[1]-1); 1797 tmoveato(0, 0); 1798 } 1799 break; 1800 case 's': /* DECSC -- Save cursor position (ANSI.SYS) */ 1801 tcursor(CURSOR_SAVE); 1802 break; 1803 case 'u': /* DECRC -- Restore cursor position (ANSI.SYS) */ 1804 tcursor(CURSOR_LOAD); 1805 break; 1806 case ' ': 1807 switch (csiescseq.mode[1]) { 1808 case 'q': /* DECSCUSR -- Set Cursor Style */ 1809 if (xsetcursor(csiescseq.arg[0])) 1810 goto unknown; 1811 break; 1812 default: 1813 goto unknown; 1814 } 1815 break; 1816 } 1817 } 1818 1819 void 1820 csidump(void) 1821 { 1822 size_t i; 1823 uint c; 1824 1825 fprintf(stderr, "ESC["); 1826 for (i = 0; i < csiescseq.len; i++) { 1827 c = csiescseq.buf[i] & 0xff; 1828 if (isprint(c)) { 1829 putc(c, stderr); 1830 } else if (c == '\n') { 1831 fprintf(stderr, "(\\n)"); 1832 } else if (c == '\r') { 1833 fprintf(stderr, "(\\r)"); 1834 } else if (c == 0x1b) { 1835 fprintf(stderr, "(\\e)"); 1836 } else { 1837 fprintf(stderr, "(%02x)", c); 1838 } 1839 } 1840 putc('\n', stderr); 1841 } 1842 1843 void 1844 csireset(void) 1845 { 1846 memset(&csiescseq, 0, sizeof(csiescseq)); 1847 } 1848 1849 void 1850 osc_color_response(int num, int index, int is_osc4) 1851 { 1852 int n; 1853 char buf[32]; 1854 unsigned char r, g, b; 1855 1856 if (xgetcolor(is_osc4 ? num : index, &r, &g, &b)) { 1857 fprintf(stderr, "erresc: failed to fetch %s color %d\n", 1858 is_osc4 ? "osc4" : "osc", 1859 is_osc4 ? num : index); 1860 return; 1861 } 1862 1863 n = snprintf(buf, sizeof buf, "\033]%s%d;rgb:%02x%02x/%02x%02x/%02x%02x\007", 1864 is_osc4 ? "4;" : "", num, r, r, g, g, b, b); 1865 if (n < 0 || n >= sizeof(buf)) { 1866 fprintf(stderr, "error: %s while printing %s response\n", 1867 n < 0 ? "snprintf failed" : "truncation occurred", 1868 is_osc4 ? "osc4" : "osc"); 1869 } else { 1870 ttywrite(buf, n, 1); 1871 } 1872 } 1873 1874 void 1875 strhandle(void) 1876 { 1877 char *p = NULL, *dec; 1878 int j, narg, par; 1879 const struct { int idx; char *str; } osc_table[] = { 1880 { defaultfg, "foreground" }, 1881 { defaultbg, "background" }, 1882 { defaultcs, "cursor" } 1883 }; 1884 1885 term.esc &= ~(ESC_STR_END|ESC_STR); 1886 strparse(); 1887 par = (narg = strescseq.narg) ? atoi(strescseq.args[0]) : 0; 1888 1889 switch (strescseq.type) { 1890 case ']': /* OSC -- Operating System Command */ 1891 switch (par) { 1892 case 0: 1893 if (narg > 1) { 1894 xsettitle(strescseq.args[1]); 1895 xseticontitle(strescseq.args[1]); 1896 } 1897 return; 1898 case 1: 1899 if (narg > 1) 1900 xseticontitle(strescseq.args[1]); 1901 return; 1902 case 2: 1903 if (narg > 1) 1904 xsettitle(strescseq.args[1]); 1905 return; 1906 case 52: 1907 if (narg > 2 && allowwindowops) { 1908 dec = base64dec(strescseq.args[2]); 1909 if (dec) { 1910 xsetsel(dec); 1911 xclipcopy(); 1912 } else { 1913 fprintf(stderr, "erresc: invalid base64\n"); 1914 } 1915 } 1916 return; 1917 case 10: 1918 case 11: 1919 case 12: 1920 if (narg < 2) 1921 break; 1922 p = strescseq.args[1]; 1923 if ((j = par - 10) < 0 || j >= LEN(osc_table)) 1924 break; /* shouldn't be possible */ 1925 1926 if (!strcmp(p, "?")) { 1927 osc_color_response(par, osc_table[j].idx, 0); 1928 } else if (xsetcolorname(osc_table[j].idx, p)) { 1929 fprintf(stderr, "erresc: invalid %s color: %s\n", 1930 osc_table[j].str, p); 1931 } else { 1932 tfulldirt(); 1933 } 1934 return; 1935 case 4: /* color set */ 1936 if (narg < 3) 1937 break; 1938 p = strescseq.args[2]; 1939 /* FALLTHROUGH */ 1940 case 104: /* color reset */ 1941 j = (narg > 1) ? atoi(strescseq.args[1]) : -1; 1942 1943 if (p && !strcmp(p, "?")) { 1944 osc_color_response(j, 0, 1); 1945 } else if (xsetcolorname(j, p)) { 1946 if (par == 104 && narg <= 1) { 1947 xloadcols(); 1948 return; /* color reset without parameter */ 1949 } 1950 fprintf(stderr, "erresc: invalid color j=%d, p=%s\n", 1951 j, p ? p : "(null)"); 1952 } else { 1953 /* 1954 * TODO if defaultbg color is changed, borders 1955 * are dirty 1956 */ 1957 tfulldirt(); 1958 } 1959 return; 1960 } 1961 break; 1962 case 'k': /* old title set compatibility */ 1963 xsettitle(strescseq.args[0]); 1964 return; 1965 case 'P': /* DCS -- Device Control String */ 1966 case '_': /* APC -- Application Program Command */ 1967 case '^': /* PM -- Privacy Message */ 1968 return; 1969 } 1970 1971 fprintf(stderr, "erresc: unknown str "); 1972 strdump(); 1973 } 1974 1975 void 1976 strparse(void) 1977 { 1978 int c; 1979 char *p = strescseq.buf; 1980 1981 strescseq.narg = 0; 1982 strescseq.buf[strescseq.len] = '\0'; 1983 1984 if (*p == '\0') 1985 return; 1986 1987 while (strescseq.narg < STR_ARG_SIZ) { 1988 strescseq.args[strescseq.narg++] = p; 1989 while ((c = *p) != ';' && c != '\0') 1990 ++p; 1991 if (c == '\0') 1992 return; 1993 *p++ = '\0'; 1994 } 1995 } 1996 1997 void 1998 strdump(void) 1999 { 2000 size_t i; 2001 uint c; 2002 2003 fprintf(stderr, "ESC%c", strescseq.type); 2004 for (i = 0; i < strescseq.len; i++) { 2005 c = strescseq.buf[i] & 0xff; 2006 if (c == '\0') { 2007 putc('\n', stderr); 2008 return; 2009 } else if (isprint(c)) { 2010 putc(c, stderr); 2011 } else if (c == '\n') { 2012 fprintf(stderr, "(\\n)"); 2013 } else if (c == '\r') { 2014 fprintf(stderr, "(\\r)"); 2015 } else if (c == 0x1b) { 2016 fprintf(stderr, "(\\e)"); 2017 } else { 2018 fprintf(stderr, "(%02x)", c); 2019 } 2020 } 2021 fprintf(stderr, "ESC\\\n"); 2022 } 2023 2024 void 2025 strreset(void) 2026 { 2027 strescseq = (STREscape){ 2028 .buf = xrealloc(strescseq.buf, STR_BUF_SIZ), 2029 .siz = STR_BUF_SIZ, 2030 }; 2031 } 2032 2033 void 2034 sendbreak(const Arg *arg) 2035 { 2036 if (tcsendbreak(cmdfd, 0)) 2037 perror("Error sending break"); 2038 } 2039 2040 void 2041 tprinter(char *s, size_t len) 2042 { 2043 if (iofd != -1 && xwrite(iofd, s, len) < 0) { 2044 perror("Error writing to output file"); 2045 close(iofd); 2046 iofd = -1; 2047 } 2048 } 2049 2050 void 2051 toggleprinter(const Arg *arg) 2052 { 2053 term.mode ^= MODE_PRINT; 2054 } 2055 2056 void 2057 printscreen(const Arg *arg) 2058 { 2059 tdump(); 2060 } 2061 2062 void 2063 printsel(const Arg *arg) 2064 { 2065 tdumpsel(); 2066 } 2067 2068 void 2069 tdumpsel(void) 2070 { 2071 char *ptr; 2072 2073 if ((ptr = getsel())) { 2074 tprinter(ptr, strlen(ptr)); 2075 free(ptr); 2076 } 2077 } 2078 2079 void 2080 tdumpline(int n) 2081 { 2082 char buf[UTF_SIZ]; 2083 const Glyph *bp, *end; 2084 2085 bp = &term.line[n][0]; 2086 end = &bp[MIN(tlinelen(n), term.col) - 1]; 2087 if (bp != end || bp->u != ' ') { 2088 for ( ; bp <= end; ++bp) 2089 tprinter(buf, utf8encode(bp->u, buf)); 2090 } 2091 tprinter("\n", 1); 2092 } 2093 2094 void 2095 tdump(void) 2096 { 2097 int i; 2098 2099 for (i = 0; i < term.row; ++i) 2100 tdumpline(i); 2101 } 2102 2103 void 2104 tputtab(int n) 2105 { 2106 uint x = term.c.x; 2107 2108 if (n > 0) { 2109 while (x < term.col && n--) 2110 for (++x; x < term.col && !term.tabs[x]; ++x) 2111 /* nothing */ ; 2112 } else if (n < 0) { 2113 while (x > 0 && n++) 2114 for (--x; x > 0 && !term.tabs[x]; --x) 2115 /* nothing */ ; 2116 } 2117 term.c.x = LIMIT(x, 0, term.col-1); 2118 } 2119 2120 void 2121 tdefutf8(char ascii) 2122 { 2123 if (ascii == 'G') 2124 term.mode |= MODE_UTF8; 2125 else if (ascii == '@') 2126 term.mode &= ~MODE_UTF8; 2127 } 2128 2129 void 2130 tdeftran(char ascii) 2131 { 2132 static char cs[] = "0B"; 2133 static int vcs[] = {CS_GRAPHIC0, CS_USA}; 2134 char *p; 2135 2136 if ((p = strchr(cs, ascii)) == NULL) { 2137 fprintf(stderr, "esc unhandled charset: ESC ( %c\n", ascii); 2138 } else { 2139 term.trantbl[term.icharset] = vcs[p - cs]; 2140 } 2141 } 2142 2143 void 2144 tdectest(char c) 2145 { 2146 int x, y; 2147 2148 if (c == '8') { /* DEC screen alignment test. */ 2149 for (x = 0; x < term.col; ++x) { 2150 for (y = 0; y < term.row; ++y) 2151 tsetchar('E', &term.c.attr, x, y); 2152 } 2153 } 2154 } 2155 2156 void 2157 tstrsequence(uchar c) 2158 { 2159 switch (c) { 2160 case 0x90: /* DCS -- Device Control String */ 2161 c = 'P'; 2162 break; 2163 case 0x9f: /* APC -- Application Program Command */ 2164 c = '_'; 2165 break; 2166 case 0x9e: /* PM -- Privacy Message */ 2167 c = '^'; 2168 break; 2169 case 0x9d: /* OSC -- Operating System Command */ 2170 c = ']'; 2171 break; 2172 } 2173 strreset(); 2174 strescseq.type = c; 2175 term.esc |= ESC_STR; 2176 } 2177 2178 void 2179 tcontrolcode(uchar ascii) 2180 { 2181 switch (ascii) { 2182 case '\t': /* HT */ 2183 tputtab(1); 2184 return; 2185 case '\b': /* BS */ 2186 tmoveto(term.c.x-1, term.c.y); 2187 return; 2188 case '\r': /* CR */ 2189 tmoveto(0, term.c.y); 2190 return; 2191 case '\f': /* LF */ 2192 case '\v': /* VT */ 2193 case '\n': /* LF */ 2194 /* go to first col if the mode is set */ 2195 tnewline(IS_SET(MODE_CRLF)); 2196 return; 2197 case '\a': /* BEL */ 2198 if (term.esc & ESC_STR_END) { 2199 /* backwards compatibility to xterm */ 2200 strhandle(); 2201 } else { 2202 xbell(); 2203 } 2204 break; 2205 case '\033': /* ESC */ 2206 csireset(); 2207 term.esc &= ~(ESC_CSI|ESC_ALTCHARSET|ESC_TEST); 2208 term.esc |= ESC_START; 2209 return; 2210 case '\016': /* SO (LS1 -- Locking shift 1) */ 2211 case '\017': /* SI (LS0 -- Locking shift 0) */ 2212 term.charset = 1 - (ascii - '\016'); 2213 return; 2214 case '\032': /* SUB */ 2215 tsetchar('?', &term.c.attr, term.c.x, term.c.y); 2216 /* FALLTHROUGH */ 2217 case '\030': /* CAN */ 2218 csireset(); 2219 break; 2220 case '\005': /* ENQ (IGNORED) */ 2221 case '\000': /* NUL (IGNORED) */ 2222 case '\021': /* XON (IGNORED) */ 2223 case '\023': /* XOFF (IGNORED) */ 2224 case 0177: /* DEL (IGNORED) */ 2225 return; 2226 case 0x80: /* TODO: PAD */ 2227 case 0x81: /* TODO: HOP */ 2228 case 0x82: /* TODO: BPH */ 2229 case 0x83: /* TODO: NBH */ 2230 case 0x84: /* TODO: IND */ 2231 break; 2232 case 0x85: /* NEL -- Next line */ 2233 tnewline(1); /* always go to first col */ 2234 break; 2235 case 0x86: /* TODO: SSA */ 2236 case 0x87: /* TODO: ESA */ 2237 break; 2238 case 0x88: /* HTS -- Horizontal tab stop */ 2239 term.tabs[term.c.x] = 1; 2240 break; 2241 case 0x89: /* TODO: HTJ */ 2242 case 0x8a: /* TODO: VTS */ 2243 case 0x8b: /* TODO: PLD */ 2244 case 0x8c: /* TODO: PLU */ 2245 case 0x8d: /* TODO: RI */ 2246 case 0x8e: /* TODO: SS2 */ 2247 case 0x8f: /* TODO: SS3 */ 2248 case 0x91: /* TODO: PU1 */ 2249 case 0x92: /* TODO: PU2 */ 2250 case 0x93: /* TODO: STS */ 2251 case 0x94: /* TODO: CCH */ 2252 case 0x95: /* TODO: MW */ 2253 case 0x96: /* TODO: SPA */ 2254 case 0x97: /* TODO: EPA */ 2255 case 0x98: /* TODO: SOS */ 2256 case 0x99: /* TODO: SGCI */ 2257 break; 2258 case 0x9a: /* DECID -- Identify Terminal */ 2259 ttywrite(vtiden, strlen(vtiden), 0); 2260 break; 2261 case 0x9b: /* TODO: CSI */ 2262 case 0x9c: /* TODO: ST */ 2263 break; 2264 case 0x90: /* DCS -- Device Control String */ 2265 case 0x9d: /* OSC -- Operating System Command */ 2266 case 0x9e: /* PM -- Privacy Message */ 2267 case 0x9f: /* APC -- Application Program Command */ 2268 tstrsequence(ascii); 2269 return; 2270 } 2271 /* only CAN, SUB, \a and C1 chars interrupt a sequence */ 2272 term.esc &= ~(ESC_STR_END|ESC_STR); 2273 } 2274 2275 /* 2276 * returns 1 when the sequence is finished and it hasn't to read 2277 * more characters for this sequence, otherwise 0 2278 */ 2279 int 2280 eschandle(uchar ascii) 2281 { 2282 switch (ascii) { 2283 case '[': 2284 term.esc |= ESC_CSI; 2285 return 0; 2286 case '#': 2287 term.esc |= ESC_TEST; 2288 return 0; 2289 case '%': 2290 term.esc |= ESC_UTF8; 2291 return 0; 2292 case 'P': /* DCS -- Device Control String */ 2293 case '_': /* APC -- Application Program Command */ 2294 case '^': /* PM -- Privacy Message */ 2295 case ']': /* OSC -- Operating System Command */ 2296 case 'k': /* old title set compatibility */ 2297 tstrsequence(ascii); 2298 return 0; 2299 case 'n': /* LS2 -- Locking shift 2 */ 2300 case 'o': /* LS3 -- Locking shift 3 */ 2301 term.charset = 2 + (ascii - 'n'); 2302 break; 2303 case '(': /* GZD4 -- set primary charset G0 */ 2304 case ')': /* G1D4 -- set secondary charset G1 */ 2305 case '*': /* G2D4 -- set tertiary charset G2 */ 2306 case '+': /* G3D4 -- set quaternary charset G3 */ 2307 term.icharset = ascii - '('; 2308 term.esc |= ESC_ALTCHARSET; 2309 return 0; 2310 case 'D': /* IND -- Linefeed */ 2311 if (term.c.y == term.bot) { 2312 tscrollup(term.top, 1); 2313 } else { 2314 tmoveto(term.c.x, term.c.y+1); 2315 } 2316 break; 2317 case 'E': /* NEL -- Next line */ 2318 tnewline(1); /* always go to first col */ 2319 break; 2320 case 'H': /* HTS -- Horizontal tab stop */ 2321 term.tabs[term.c.x] = 1; 2322 break; 2323 case 'M': /* RI -- Reverse index */ 2324 if (term.c.y == term.top) { 2325 tscrolldown(term.top, 1); 2326 } else { 2327 tmoveto(term.c.x, term.c.y-1); 2328 } 2329 break; 2330 case 'Z': /* DECID -- Identify Terminal */ 2331 ttywrite(vtiden, strlen(vtiden), 0); 2332 break; 2333 case 'c': /* RIS -- Reset to initial state */ 2334 treset(); 2335 resettitle(); 2336 xloadcols(); 2337 xsetmode(0, MODE_HIDE); 2338 break; 2339 case '=': /* DECPAM -- Application keypad */ 2340 xsetmode(1, MODE_APPKEYPAD); 2341 break; 2342 case '>': /* DECPNM -- Normal keypad */ 2343 xsetmode(0, MODE_APPKEYPAD); 2344 break; 2345 case '7': /* DECSC -- Save Cursor */ 2346 tcursor(CURSOR_SAVE); 2347 break; 2348 case '8': /* DECRC -- Restore Cursor */ 2349 tcursor(CURSOR_LOAD); 2350 break; 2351 case '\\': /* ST -- String Terminator */ 2352 if (term.esc & ESC_STR_END) 2353 strhandle(); 2354 break; 2355 default: 2356 fprintf(stderr, "erresc: unknown sequence ESC 0x%02X '%c'\n", 2357 (uchar) ascii, isprint(ascii)? ascii:'.'); 2358 break; 2359 } 2360 return 1; 2361 } 2362 2363 void 2364 tputc(Rune u) 2365 { 2366 char c[UTF_SIZ]; 2367 int control; 2368 int width, len; 2369 Glyph *gp; 2370 2371 control = ISCONTROL(u); 2372 if (u < 127 || !IS_SET(MODE_UTF8)) { 2373 c[0] = u; 2374 width = len = 1; 2375 } else { 2376 len = utf8encode(u, c); 2377 if (!control && (width = wcwidth(u)) == -1) 2378 width = 1; 2379 } 2380 2381 if (IS_SET(MODE_PRINT)) 2382 tprinter(c, len); 2383 2384 /* 2385 * STR sequence must be checked before anything else 2386 * because it uses all following characters until it 2387 * receives a ESC, a SUB, a ST or any other C1 control 2388 * character. 2389 */ 2390 if (term.esc & ESC_STR) { 2391 if (u == '\a' || u == 030 || u == 032 || u == 033 || 2392 ISCONTROLC1(u)) { 2393 term.esc &= ~(ESC_START|ESC_STR); 2394 term.esc |= ESC_STR_END; 2395 goto check_control_code; 2396 } 2397 2398 if (strescseq.len+len >= strescseq.siz) { 2399 /* 2400 * Here is a bug in terminals. If the user never sends 2401 * some code to stop the str or esc command, then st 2402 * will stop responding. But this is better than 2403 * silently failing with unknown characters. At least 2404 * then users will report back. 2405 * 2406 * In the case users ever get fixed, here is the code: 2407 */ 2408 /* 2409 * term.esc = 0; 2410 * strhandle(); 2411 */ 2412 if (strescseq.siz > (SIZE_MAX - UTF_SIZ) / 2) 2413 return; 2414 strescseq.siz *= 2; 2415 strescseq.buf = xrealloc(strescseq.buf, strescseq.siz); 2416 } 2417 2418 memmove(&strescseq.buf[strescseq.len], c, len); 2419 strescseq.len += len; 2420 return; 2421 } 2422 2423 check_control_code: 2424 /* 2425 * Actions of control codes must be performed as soon they arrive 2426 * because they can be embedded inside a control sequence, and 2427 * they must not cause conflicts with sequences. 2428 */ 2429 if (control) { 2430 /* in UTF-8 mode ignore handling C1 control characters */ 2431 if (IS_SET(MODE_UTF8) && ISCONTROLC1(u)) 2432 return; 2433 tcontrolcode(u); 2434 /* 2435 * control codes are not shown ever 2436 */ 2437 if (!term.esc) 2438 term.lastc = 0; 2439 return; 2440 } else if (term.esc & ESC_START) { 2441 if (term.esc & ESC_CSI) { 2442 csiescseq.buf[csiescseq.len++] = u; 2443 if (BETWEEN(u, 0x40, 0x7E) 2444 || csiescseq.len >= \ 2445 sizeof(csiescseq.buf)-1) { 2446 term.esc = 0; 2447 csiparse(); 2448 csihandle(); 2449 } 2450 return; 2451 } else if (term.esc & ESC_UTF8) { 2452 tdefutf8(u); 2453 } else if (term.esc & ESC_ALTCHARSET) { 2454 tdeftran(u); 2455 } else if (term.esc & ESC_TEST) { 2456 tdectest(u); 2457 } else { 2458 if (!eschandle(u)) 2459 return; 2460 /* sequence already finished */ 2461 } 2462 term.esc = 0; 2463 /* 2464 * All characters which form part of a sequence are not 2465 * printed 2466 */ 2467 return; 2468 } 2469 if (selected(term.c.x, term.c.y)) 2470 selclear(); 2471 2472 gp = &term.line[term.c.y][term.c.x]; 2473 if (IS_SET(MODE_WRAP) && (term.c.state & CURSOR_WRAPNEXT)) { 2474 gp->mode |= ATTR_WRAP; 2475 tnewline(1); 2476 gp = &term.line[term.c.y][term.c.x]; 2477 } 2478 2479 if (IS_SET(MODE_INSERT) && term.c.x+width < term.col) { 2480 memmove(gp+width, gp, (term.col - term.c.x - width) * sizeof(Glyph)); 2481 gp->mode &= ~ATTR_WIDE; 2482 } 2483 2484 if (term.c.x+width > term.col) { 2485 if (IS_SET(MODE_WRAP)) 2486 tnewline(1); 2487 else 2488 tmoveto(term.col - width, term.c.y); 2489 gp = &term.line[term.c.y][term.c.x]; 2490 } 2491 2492 tsetchar(u, &term.c.attr, term.c.x, term.c.y); 2493 term.lastc = u; 2494 2495 if (width == 2) { 2496 gp->mode |= ATTR_WIDE; 2497 if (term.c.x+1 < term.col) { 2498 if (gp[1].mode == ATTR_WIDE && term.c.x+2 < term.col) { 2499 gp[2].u = ' '; 2500 gp[2].mode &= ~ATTR_WDUMMY; 2501 } 2502 gp[1].u = '\0'; 2503 gp[1].mode = ATTR_WDUMMY; 2504 } 2505 } 2506 if (term.c.x+width < term.col) { 2507 tmoveto(term.c.x+width, term.c.y); 2508 } else { 2509 term.c.state |= CURSOR_WRAPNEXT; 2510 } 2511 } 2512 2513 int 2514 twrite(const char *buf, int buflen, int show_ctrl) 2515 { 2516 int charsize; 2517 Rune u; 2518 int n; 2519 2520 for (n = 0; n < buflen; n += charsize) { 2521 if (IS_SET(MODE_UTF8)) { 2522 /* process a complete utf8 char */ 2523 charsize = utf8decode(buf + n, &u, buflen - n); 2524 if (charsize == 0) 2525 break; 2526 } else { 2527 u = buf[n] & 0xFF; 2528 charsize = 1; 2529 } 2530 if (show_ctrl && ISCONTROL(u)) { 2531 if (u & 0x80) { 2532 u &= 0x7f; 2533 tputc('^'); 2534 tputc('['); 2535 } else if (u != '\n' && u != '\r' && u != '\t') { 2536 u ^= 0x40; 2537 tputc('^'); 2538 } 2539 } 2540 tputc(u); 2541 } 2542 return n; 2543 } 2544 2545 void 2546 tresize(int col, int row) 2547 { 2548 int i; 2549 int minrow = MIN(row, term.row); 2550 int mincol = MIN(col, term.col); 2551 int *bp; 2552 TCursor c; 2553 2554 if (col < 1 || row < 1) { 2555 fprintf(stderr, 2556 "tresize: error resizing to %dx%d\n", col, row); 2557 return; 2558 } 2559 2560 /* 2561 * slide screen to keep cursor where we expect it - 2562 * tscrollup would work here, but we can optimize to 2563 * memmove because we're freeing the earlier lines 2564 */ 2565 for (i = 0; i <= term.c.y - row; i++) { 2566 free(term.line[i]); 2567 free(term.alt[i]); 2568 } 2569 /* ensure that both src and dst are not NULL */ 2570 if (i > 0) { 2571 memmove(term.line, term.line + i, row * sizeof(Line)); 2572 memmove(term.alt, term.alt + i, row * sizeof(Line)); 2573 } 2574 for (i += row; i < term.row; i++) { 2575 free(term.line[i]); 2576 free(term.alt[i]); 2577 } 2578 2579 /* resize to new height */ 2580 term.line = xrealloc(term.line, row * sizeof(Line)); 2581 term.alt = xrealloc(term.alt, row * sizeof(Line)); 2582 term.dirty = xrealloc(term.dirty, row * sizeof(*term.dirty)); 2583 term.tabs = xrealloc(term.tabs, col * sizeof(*term.tabs)); 2584 2585 /* resize each row to new width, zero-pad if needed */ 2586 for (i = 0; i < minrow; i++) { 2587 term.line[i] = xrealloc(term.line[i], col * sizeof(Glyph)); 2588 term.alt[i] = xrealloc(term.alt[i], col * sizeof(Glyph)); 2589 } 2590 2591 /* allocate any new rows */ 2592 for (/* i = minrow */; i < row; i++) { 2593 term.line[i] = xmalloc(col * sizeof(Glyph)); 2594 term.alt[i] = xmalloc(col * sizeof(Glyph)); 2595 } 2596 if (col > term.col) { 2597 bp = term.tabs + term.col; 2598 2599 memset(bp, 0, sizeof(*term.tabs) * (col - term.col)); 2600 while (--bp > term.tabs && !*bp) 2601 /* nothing */ ; 2602 for (bp += tabspaces; bp < term.tabs + col; bp += tabspaces) 2603 *bp = 1; 2604 } 2605 /* update terminal size */ 2606 term.col = col; 2607 term.row = row; 2608 /* reset scrolling region */ 2609 tsetscroll(0, row-1); 2610 /* make use of the LIMIT in tmoveto */ 2611 tmoveto(term.c.x, term.c.y); 2612 /* Clearing both screens (it makes dirty all lines) */ 2613 c = term.c; 2614 for (i = 0; i < 2; i++) { 2615 if (mincol < col && 0 < minrow) { 2616 tclearregion(mincol, 0, col - 1, minrow - 1); 2617 } 2618 if (0 < col && minrow < row) { 2619 tclearregion(0, minrow, col - 1, row - 1); 2620 } 2621 tswapscreen(); 2622 tcursor(CURSOR_LOAD); 2623 } 2624 term.c = c; 2625 } 2626 2627 void 2628 resettitle(void) 2629 { 2630 xsettitle(NULL); 2631 } 2632 2633 void 2634 drawregion(int x1, int y1, int x2, int y2) 2635 { 2636 int y; 2637 2638 for (y = y1; y < y2; y++) { 2639 if (!term.dirty[y]) 2640 continue; 2641 2642 term.dirty[y] = 0; 2643 xdrawline(term.line[y], x1, y, x2); 2644 } 2645 } 2646 2647 void 2648 draw(void) 2649 { 2650 int cx = term.c.x, ocx = term.ocx, ocy = term.ocy; 2651 2652 if (!xstartdraw()) 2653 return; 2654 2655 /* adjust cursor position */ 2656 LIMIT(term.ocx, 0, term.col-1); 2657 LIMIT(term.ocy, 0, term.row-1); 2658 if (term.line[term.ocy][term.ocx].mode & ATTR_WDUMMY) 2659 term.ocx--; 2660 if (term.line[term.c.y][cx].mode & ATTR_WDUMMY) 2661 cx--; 2662 2663 drawregion(0, 0, term.col, term.row); 2664 xdrawcursor(cx, term.c.y, term.line[term.c.y][cx], 2665 term.ocx, term.ocy, term.line[term.ocy][term.ocx]); 2666 term.ocx = cx; 2667 term.ocy = term.c.y; 2668 xfinishdraw(); 2669 if (ocx != term.ocx || ocy != term.ocy) 2670 xximspot(term.ocx, term.ocy); 2671 } 2672 2673 void 2674 redraw(void) 2675 { 2676 tfulldirt(); 2677 draw(); 2678 }
st.c - st - simple terminal
git clone git://git.suckless.org/st
Log
Files
Refs
README
LICENSE
---
st.c (57763B)
---
     1 /* See LICENSE for license details. */
     2 #include 
     3 #include 
     4 #include 
     5 #include 
     6 #include 
     7 #include 
     8 #include 
     9 #include 
    10 #include 
    11 #include 
    12 #include 
    13 #include 
    14 #include 
    15 #include 
    16 #include 
    17 #include 
    18 #include 
    19 
    20 #include "st.h"
    21 #include "win.h"
    22 
    23 #if   defined(__linux)
    24  #include 
    25 #elif defined(__OpenBSD__) || defined(__NetBSD__) || defined(__APPLE__)
    26  #include 
    27 #elif defined(__FreeBSD__) || defined(__DragonFly__)
    28  #include 
    29 #endif
    30 
    31 /* Arbitrary sizes */
    32 #define UTF_INVALID   0xFFFD
    33 #define UTF_SIZ       4
    34 #define ESC_BUF_SIZ   (128*UTF_SIZ)
    35 #define ESC_ARG_SIZ   16
    36 #define STR_BUF_SIZ   ESC_BUF_SIZ
    37 #define STR_ARG_SIZ   ESC_ARG_SIZ
    38 
    39 /* macros */
    40 #define IS_SET(flag)                ((term.mode & (flag)) != 0)
    41 #define ISCONTROLC0(c)                (BETWEEN(c, 0, 0x1f) || (c) == 0x7f)
    42 #define ISCONTROLC1(c)                (BETWEEN(c, 0x80, 0x9f))
    43 #define ISCONTROL(c)                (ISCONTROLC0(c) || ISCONTROLC1(c))
    44 #define ISDELIM(u)                (u && wcschr(worddelimiters, u))
    45 
    46 enum term_mode {
    47         MODE_WRAP        = 1 << 0,
    48         MODE_INSERT      = 1 << 1,
    49         MODE_ALTSCREEN   = 1 << 2,
    50         MODE_CRLF        = 1 << 3,
    51         MODE_ECHO        = 1 << 4,
    52         MODE_PRINT       = 1 << 5,
    53         MODE_UTF8        = 1 << 6,
    54 };
    55 
    56 enum cursor_movement {
    57         CURSOR_SAVE,
    58         CURSOR_LOAD
    59 };
    60 
    61 enum cursor_state {
    62         CURSOR_DEFAULT  = 0,
    63         CURSOR_WRAPNEXT = 1,
    64         CURSOR_ORIGIN   = 2
    65 };
    66 
    67 enum charset {
    68         CS_GRAPHIC0,
    69         CS_GRAPHIC1,
    70         CS_UK,
    71         CS_USA,
    72         CS_MULTI,
    73         CS_GER,
    74         CS_FIN
    75 };
    76 
    77 enum escape_state {
    78         ESC_START      = 1,
    79         ESC_CSI        = 2,
    80         ESC_STR        = 4,  /* DCS, OSC, PM, APC */
    81         ESC_ALTCHARSET = 8,
    82         ESC_STR_END    = 16, /* a final string was encountered */
    83         ESC_TEST       = 32, /* Enter in test mode */
    84         ESC_UTF8       = 64,
    85 };
    86 
    87 typedef struct {
    88         Glyph attr; /* current char attributes */
    89         int x;
    90         int y;
    91         char state;
    92 } TCursor;
    93 
    94 typedef struct {
    95         int mode;
    96         int type;
    97         int snap;
    98         /*
    99          * Selection variables:
   100          * nb – normalized coordinates of the beginning of the selection
   101          * ne – normalized coordinates of the end of the selection
   102          * ob – original coordinates of the beginning of the selection
   103          * oe – original coordinates of the end of the selection
   104          */
   105         struct {
   106                 int x, y;
   107         } nb, ne, ob, oe;
   108 
   109         int alt;
   110 } Selection;
   111 
   112 /* Internal representation of the screen */
   113 typedef struct {
   114         int row;      /* nb row */
   115         int col;      /* nb col */
   116         Line *line;   /* screen */
   117         Line *alt;    /* alternate screen */
   118         int *dirty;   /* dirtyness of lines */
   119         TCursor c;    /* cursor */
   120         int ocx;      /* old cursor col */
   121         int ocy;      /* old cursor row */
   122         int top;      /* top    scroll limit */
   123         int bot;      /* bottom scroll limit */
   124         int mode;     /* terminal mode flags */
   125         int esc;      /* escape state flags */
   126         char trantbl[4]; /* charset table translation */
   127         int charset;  /* current charset */
   128         int icharset; /* selected charset for sequence */
   129         int *tabs;
   130         Rune lastc;   /* last printed char outside of sequence, 0 if control */
   131 } Term;
   132 
   133 /* CSI Escape sequence structs */
   134 /* ESC '[' [[ []  [;]]  []] */
   135 typedef struct {
   136         char buf[ESC_BUF_SIZ]; /* raw string */
   137         size_t len;            /* raw string length */
   138         char priv;
   139         int arg[ESC_ARG_SIZ];
   140         int narg;              /* nb of args */
   141         char mode[2];
   142 } CSIEscape;
   143 
   144 /* STR Escape sequence structs */
   145 /* ESC type [[ []  [;]] ] ESC '\' */
   146 typedef struct {
   147         char type;             /* ESC type ... */
   148         char *buf;             /* allocated raw string */
   149         size_t siz;            /* allocation size */
   150         size_t len;            /* raw string length */
   151         char *args[STR_ARG_SIZ];
   152         int narg;              /* nb of args */
   153 } STREscape;
   154 
   155 static void execsh(char *, char **);
   156 static void stty(char **);
   157 static void sigchld(int);
   158 static void ttywriteraw(const char *, size_t);
   159 
   160 static void csidump(void);
   161 static void csihandle(void);
   162 static void csiparse(void);
   163 static void csireset(void);
   164 static void osc_color_response(int, int, int);
   165 static int eschandle(uchar);
   166 static void strdump(void);
   167 static void strhandle(void);
   168 static void strparse(void);
   169 static void strreset(void);
   170 
   171 static void tprinter(char *, size_t);
   172 static void tdumpsel(void);
   173 static void tdumpline(int);
   174 static void tdump(void);
   175 static void tclearregion(int, int, int, int);
   176 static void tcursor(int);
   177 static void tdeletechar(int);
   178 static void tdeleteline(int);
   179 static void tinsertblank(int);
   180 static void tinsertblankline(int);
   181 static int tlinelen(int);
   182 static void tmoveto(int, int);
   183 static void tmoveato(int, int);
   184 static void tnewline(int);
   185 static void tputtab(int);
   186 static void tputc(Rune);
   187 static void treset(void);
   188 static void tscrollup(int, int);
   189 static void tscrolldown(int, int);
   190 static void tsetattr(const int *, int);
   191 static void tsetchar(Rune, const Glyph *, int, int);
   192 static void tsetdirt(int, int);
   193 static void tsetscroll(int, int);
   194 static void tswapscreen(void);
   195 static void tsetmode(int, int, const int *, int);
   196 static int twrite(const char *, int, int);
   197 static void tfulldirt(void);
   198 static void tcontrolcode(uchar );
   199 static void tdectest(char );
   200 static void tdefutf8(char);
   201 static int32_t tdefcolor(const int *, int *, int);
   202 static void tdeftran(char);
   203 static void tstrsequence(uchar);
   204 
   205 static void drawregion(int, int, int, int);
   206 
   207 static void selnormalize(void);
   208 static void selscroll(int, int);
   209 static void selsnap(int *, int *, int);
   210 
   211 static size_t utf8decode(const char *, Rune *, size_t);
   212 static Rune utf8decodebyte(char, size_t *);
   213 static char utf8encodebyte(Rune, size_t);
   214 static size_t utf8validate(Rune *, size_t);
   215 
   216 static char *base64dec(const char *);
   217 static char base64dec_getc(const char **);
   218 
   219 static ssize_t xwrite(int, const char *, size_t);
   220 
   221 /* Globals */
   222 static Term term;
   223 static Selection sel;
   224 static CSIEscape csiescseq;
   225 static STREscape strescseq;
   226 static int iofd = 1;
   227 static int cmdfd;
   228 static pid_t pid;
   229 
   230 static const uchar utfbyte[UTF_SIZ + 1] = {0x80,    0, 0xC0, 0xE0, 0xF0};
   231 static const uchar utfmask[UTF_SIZ + 1] = {0xC0, 0x80, 0xE0, 0xF0, 0xF8};
   232 static const Rune utfmin[UTF_SIZ + 1] = {       0,    0,  0x80,  0x800,  0x10000};
   233 static const Rune utfmax[UTF_SIZ + 1] = {0x10FFFF, 0x7F, 0x7FF, 0xFFFF, 0x10FFFF};
   234 
   235 ssize_t
   236 xwrite(int fd, const char *s, size_t len)
   237 {
   238         size_t aux = len;
   239         ssize_t r;
   240 
   241         while (len > 0) {
   242                 r = write(fd, s, len);
   243                 if (r < 0)
   244                         return r;
   245                 len -= r;
   246                 s += r;
   247         }
   248 
   249         return aux;
   250 }
   251 
   252 void *
   253 xmalloc(size_t len)
   254 {
   255         void *p;
   256 
   257         if (!(p = malloc(len)))
   258                 die("malloc: %s\n", strerror(errno));
   259 
   260         return p;
   261 }
   262 
   263 void *
   264 xrealloc(void *p, size_t len)
   265 {
   266         if ((p = realloc(p, len)) == NULL)
   267                 die("realloc: %s\n", strerror(errno));
   268 
   269         return p;
   270 }
   271 
   272 char *
   273 xstrdup(const char *s)
   274 {
   275         char *p;
   276 
   277         if ((p = strdup(s)) == NULL)
   278                 die("strdup: %s\n", strerror(errno));
   279 
   280         return p;
   281 }
   282 
   283 size_t
   284 utf8decode(const char *c, Rune *u, size_t clen)
   285 {
   286         size_t i, j, len, type;
   287         Rune udecoded;
   288 
   289         *u = UTF_INVALID;
   290         if (!clen)
   291                 return 0;
   292         udecoded = utf8decodebyte(c[0], &len);
   293         if (!BETWEEN(len, 1, UTF_SIZ))
   294                 return 1;
   295         for (i = 1, j = 1; i < clen && j < len; ++i, ++j) {
   296                 udecoded = (udecoded << 6) | utf8decodebyte(c[i], &type);
   297                 if (type != 0)
   298                         return j;
   299         }
   300         if (j < len)
   301                 return 0;
   302         *u = udecoded;
   303         utf8validate(u, len);
   304 
   305         return len;
   306 }
   307 
   308 Rune
   309 utf8decodebyte(char c, size_t *i)
   310 {
   311         for (*i = 0; *i < LEN(utfmask); ++(*i))
   312                 if (((uchar)c & utfmask[*i]) == utfbyte[*i])
   313                         return (uchar)c & ~utfmask[*i];
   314 
   315         return 0;
   316 }
   317 
   318 size_t
   319 utf8encode(Rune u, char *c)
   320 {
   321         size_t len, i;
   322 
   323         len = utf8validate(&u, 0);
   324         if (len > UTF_SIZ)
   325                 return 0;
   326 
   327         for (i = len - 1; i != 0; --i) {
   328                 c[i] = utf8encodebyte(u, 0);
   329                 u >>= 6;
   330         }
   331         c[0] = utf8encodebyte(u, len);
   332 
   333         return len;
   334 }
   335 
   336 char
   337 utf8encodebyte(Rune u, size_t i)
   338 {
   339         return utfbyte[i] | (u & ~utfmask[i]);
   340 }
   341 
   342 size_t
   343 utf8validate(Rune *u, size_t i)
   344 {
   345         if (!BETWEEN(*u, utfmin[i], utfmax[i]) || BETWEEN(*u, 0xD800, 0xDFFF))
   346                 *u = UTF_INVALID;
   347         for (i = 1; *u > utfmax[i]; ++i)
   348                 ;
   349 
   350         return i;
   351 }
   352 
   353 char
   354 base64dec_getc(const char **src)
   355 {
   356         while (**src && !isprint((unsigned char)**src))
   357                 (*src)++;
   358         return **src ? *((*src)++) : '=';  /* emulate padding if string ends */
   359 }
   360 
   361 char *
   362 base64dec(const char *src)
   363 {
   364         size_t in_len = strlen(src);
   365         char *result, *dst;
   366         static const char base64_digits[256] = {
   367                 [43] = 62, 0, 0, 0, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61,
   368                 0, 0, 0, -1, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12,
   369                 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 0, 0, 0, 0,
   370                 0, 0, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39,
   371                 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51
   372         };
   373 
   374         if (in_len % 4)
   375                 in_len += 4 - (in_len % 4);
   376         result = dst = xmalloc(in_len / 4 * 3 + 1);
   377         while (*src) {
   378                 int a = base64_digits[(unsigned char) base64dec_getc(&src)];
   379                 int b = base64_digits[(unsigned char) base64dec_getc(&src)];
   380                 int c = base64_digits[(unsigned char) base64dec_getc(&src)];
   381                 int d = base64_digits[(unsigned char) base64dec_getc(&src)];
   382 
   383                 /* invalid input. 'a' can be -1, e.g. if src is "\n" (c-str) */
   384                 if (a == -1 || b == -1)
   385                         break;
   386 
   387                 *dst++ = (a << 2) | ((b & 0x30) >> 4);
   388                 if (c == -1)
   389                         break;
   390                 *dst++ = ((b & 0x0f) << 4) | ((c & 0x3c) >> 2);
   391                 if (d == -1)
   392                         break;
   393                 *dst++ = ((c & 0x03) << 6) | d;
   394         }
   395         *dst = '\0';
   396         return result;
   397 }
   398 
   399 void
   400 selinit(void)
   401 {
   402         sel.mode = SEL_IDLE;
   403         sel.snap = 0;
   404         sel.ob.x = -1;
   405 }
   406 
   407 int
   408 tlinelen(int y)
   409 {
   410         int i = term.col;
   411 
   412         if (term.line[y][i - 1].mode & ATTR_WRAP)
   413                 return i;
   414 
   415         while (i > 0 && term.line[y][i - 1].u == ' ')
   416                 --i;
   417 
   418         return i;
   419 }
   420 
   421 void
   422 selstart(int col, int row, int snap)
   423 {
   424         selclear();
   425         sel.mode = SEL_EMPTY;
   426         sel.type = SEL_REGULAR;
   427         sel.alt = IS_SET(MODE_ALTSCREEN);
   428         sel.snap = snap;
   429         sel.oe.x = sel.ob.x = col;
   430         sel.oe.y = sel.ob.y = row;
   431         selnormalize();
   432 
   433         if (sel.snap != 0)
   434                 sel.mode = SEL_READY;
   435         tsetdirt(sel.nb.y, sel.ne.y);
   436 }
   437 
   438 void
   439 selextend(int col, int row, int type, int done)
   440 {
   441         int oldey, oldex, oldsby, oldsey, oldtype;
   442 
   443         if (sel.mode == SEL_IDLE)
   444                 return;
   445         if (done && sel.mode == SEL_EMPTY) {
   446                 selclear();
   447                 return;
   448         }
   449 
   450         oldey = sel.oe.y;
   451         oldex = sel.oe.x;
   452         oldsby = sel.nb.y;
   453         oldsey = sel.ne.y;
   454         oldtype = sel.type;
   455 
   456         sel.oe.x = col;
   457         sel.oe.y = row;
   458         selnormalize();
   459         sel.type = type;
   460 
   461         if (oldey != sel.oe.y || oldex != sel.oe.x || oldtype != sel.type || sel.mode == SEL_EMPTY)
   462                 tsetdirt(MIN(sel.nb.y, oldsby), MAX(sel.ne.y, oldsey));
   463 
   464         sel.mode = done ? SEL_IDLE : SEL_READY;
   465 }
   466 
   467 void
   468 selnormalize(void)
   469 {
   470         int i;
   471 
   472         if (sel.type == SEL_REGULAR && sel.ob.y != sel.oe.y) {
   473                 sel.nb.x = sel.ob.y < sel.oe.y ? sel.ob.x : sel.oe.x;
   474                 sel.ne.x = sel.ob.y < sel.oe.y ? sel.oe.x : sel.ob.x;
   475         } else {
   476                 sel.nb.x = MIN(sel.ob.x, sel.oe.x);
   477                 sel.ne.x = MAX(sel.ob.x, sel.oe.x);
   478         }
   479         sel.nb.y = MIN(sel.ob.y, sel.oe.y);
   480         sel.ne.y = MAX(sel.ob.y, sel.oe.y);
   481 
   482         selsnap(&sel.nb.x, &sel.nb.y, -1);
   483         selsnap(&sel.ne.x, &sel.ne.y, +1);
   484 
   485         /* expand selection over line breaks */
   486         if (sel.type == SEL_RECTANGULAR)
   487                 return;
   488         i = tlinelen(sel.nb.y);
   489         if (i < sel.nb.x)
   490                 sel.nb.x = i;
   491         if (tlinelen(sel.ne.y) <= sel.ne.x)
   492                 sel.ne.x = term.col - 1;
   493 }
   494 
   495 int
   496 selected(int x, int y)
   497 {
   498         if (sel.mode == SEL_EMPTY || sel.ob.x == -1 ||
   499                         sel.alt != IS_SET(MODE_ALTSCREEN))
   500                 return 0;
   501 
   502         if (sel.type == SEL_RECTANGULAR)
   503                 return BETWEEN(y, sel.nb.y, sel.ne.y)
   504                     && BETWEEN(x, sel.nb.x, sel.ne.x);
   505 
   506         return BETWEEN(y, sel.nb.y, sel.ne.y)
   507             && (y != sel.nb.y || x >= sel.nb.x)
   508             && (y != sel.ne.y || x <= sel.ne.x);
   509 }
   510 
   511 void
   512 selsnap(int *x, int *y, int direction)
   513 {
   514         int newx, newy, xt, yt;
   515         int delim, prevdelim;
   516         const Glyph *gp, *prevgp;
   517 
   518         switch (sel.snap) {
   519         case SNAP_WORD:
   520                 /*
   521                  * Snap around if the word wraps around at the end or
   522                  * beginning of a line.
   523                  */
   524                 prevgp = &term.line[*y][*x];
   525                 prevdelim = ISDELIM(prevgp->u);
   526                 for (;;) {
   527                         newx = *x + direction;
   528                         newy = *y;
   529                         if (!BETWEEN(newx, 0, term.col - 1)) {
   530                                 newy += direction;
   531                                 newx = (newx + term.col) % term.col;
   532                                 if (!BETWEEN(newy, 0, term.row - 1))
   533                                         break;
   534 
   535                                 if (direction > 0)
   536                                         yt = *y, xt = *x;
   537                                 else
   538                                         yt = newy, xt = newx;
   539                                 if (!(term.line[yt][xt].mode & ATTR_WRAP))
   540                                         break;
   541                         }
   542 
   543                         if (newx >= tlinelen(newy))
   544                                 break;
   545 
   546                         gp = &term.line[newy][newx];
   547                         delim = ISDELIM(gp->u);
   548                         if (!(gp->mode & ATTR_WDUMMY) && (delim != prevdelim
   549                                         || (delim && gp->u != prevgp->u)))
   550                                 break;
   551 
   552                         *x = newx;
   553                         *y = newy;
   554                         prevgp = gp;
   555                         prevdelim = delim;
   556                 }
   557                 break;
   558         case SNAP_LINE:
   559                 /*
   560                  * Snap around if the the previous line or the current one
   561                  * has set ATTR_WRAP at its end. Then the whole next or
   562                  * previous line will be selected.
   563                  */
   564                 *x = (direction < 0) ? 0 : term.col - 1;
   565                 if (direction < 0) {
   566                         for (; *y > 0; *y += direction) {
   567                                 if (!(term.line[*y-1][term.col-1].mode
   568                                                 & ATTR_WRAP)) {
   569                                         break;
   570                                 }
   571                         }
   572                 } else if (direction > 0) {
   573                         for (; *y < term.row-1; *y += direction) {
   574                                 if (!(term.line[*y][term.col-1].mode
   575                                                 & ATTR_WRAP)) {
   576                                         break;
   577                                 }
   578                         }
   579                 }
   580                 break;
   581         }
   582 }
   583 
   584 char *
   585 getsel(void)
   586 {
   587         char *str, *ptr;
   588         int y, bufsize, lastx, linelen;
   589         const Glyph *gp, *last;
   590 
   591         if (sel.ob.x == -1)
   592                 return NULL;
   593 
   594         bufsize = (term.col+1) * (sel.ne.y-sel.nb.y+1) * UTF_SIZ;
   595         ptr = str = xmalloc(bufsize);
   596 
   597         /* append every set & selected glyph to the selection */
   598         for (y = sel.nb.y; y <= sel.ne.y; y++) {
   599                 if ((linelen = tlinelen(y)) == 0) {
   600                         *ptr++ = '\n';
   601                         continue;
   602                 }
   603 
   604                 if (sel.type == SEL_RECTANGULAR) {
   605                         gp = &term.line[y][sel.nb.x];
   606                         lastx = sel.ne.x;
   607                 } else {
   608                         gp = &term.line[y][sel.nb.y == y ? sel.nb.x : 0];
   609                         lastx = (sel.ne.y == y) ? sel.ne.x : term.col-1;
   610                 }
   611                 last = &term.line[y][MIN(lastx, linelen-1)];
   612                 while (last >= gp && last->u == ' ')
   613                         --last;
   614 
   615                 for ( ; gp <= last; ++gp) {
   616                         if (gp->mode & ATTR_WDUMMY)
   617                                 continue;
   618 
   619                         ptr += utf8encode(gp->u, ptr);
   620                 }
   621 
   622                 /*
   623                  * Copy and pasting of line endings is inconsistent
   624                  * in the inconsistent terminal and GUI world.
   625                  * The best solution seems like to produce '\n' when
   626                  * something is copied from st and convert '\n' to
   627                  * '\r', when something to be pasted is received by
   628                  * st.
   629                  * FIXME: Fix the computer world.
   630                  */
   631                 if ((y < sel.ne.y || lastx >= linelen) &&
   632                     (!(last->mode & ATTR_WRAP) || sel.type == SEL_RECTANGULAR))
   633                         *ptr++ = '\n';
   634         }
   635         *ptr = 0;
   636         return str;
   637 }
   638 
   639 void
   640 selclear(void)
   641 {
   642         if (sel.ob.x == -1)
   643                 return;
   644         sel.mode = SEL_IDLE;
   645         sel.ob.x = -1;
   646         tsetdirt(sel.nb.y, sel.ne.y);
   647 }
   648 
   649 void
   650 die(const char *errstr, ...)
   651 {
   652         va_list ap;
   653 
   654         va_start(ap, errstr);
   655         vfprintf(stderr, errstr, ap);
   656         va_end(ap);
   657         exit(1);
   658 }
   659 
   660 void
   661 execsh(char *cmd, char **args)
   662 {
   663         char *sh, *prog, *arg;
   664         const struct passwd *pw;
   665 
   666         errno = 0;
   667         if ((pw = getpwuid(getuid())) == NULL) {
   668                 if (errno)
   669                         die("getpwuid: %s\n", strerror(errno));
   670                 else
   671                         die("who are you?\n");
   672         }
   673 
   674         if ((sh = getenv("SHELL")) == NULL)
   675                 sh = (pw->pw_shell[0]) ? pw->pw_shell : cmd;
   676 
   677         if (args) {
   678                 prog = args[0];
   679                 arg = NULL;
   680         } else if (scroll) {
   681                 prog = scroll;
   682                 arg = utmp ? utmp : sh;
   683         } else if (utmp) {
   684                 prog = utmp;
   685                 arg = NULL;
   686         } else {
   687                 prog = sh;
   688                 arg = NULL;
   689         }
   690         DEFAULT(args, ((char *[]) {prog, arg, NULL}));
   691 
   692         unsetenv("COLUMNS");
   693         unsetenv("LINES");
   694         unsetenv("TERMCAP");
   695         setenv("LOGNAME", pw->pw_name, 1);
   696         setenv("USER", pw->pw_name, 1);
   697         setenv("SHELL", sh, 1);
   698         setenv("HOME", pw->pw_dir, 1);
   699         setenv("TERM", termname, 1);
   700 
   701         signal(SIGCHLD, SIG_DFL);
   702         signal(SIGHUP, SIG_DFL);
   703         signal(SIGINT, SIG_DFL);
   704         signal(SIGQUIT, SIG_DFL);
   705         signal(SIGTERM, SIG_DFL);
   706         signal(SIGALRM, SIG_DFL);
   707 
   708         execvp(prog, args);
   709         _exit(1);
   710 }
   711 
   712 void
   713 sigchld(int a)
   714 {
   715         int stat;
   716         pid_t p;
   717 
   718         if ((p = waitpid(pid, &stat, WNOHANG)) < 0)
   719                 die("waiting for pid %hd failed: %s\n", pid, strerror(errno));
   720 
   721         if (pid != p)
   722                 return;
   723 
   724         if (WIFEXITED(stat) && WEXITSTATUS(stat))
   725                 die("child exited with status %d\n", WEXITSTATUS(stat));
   726         else if (WIFSIGNALED(stat))
   727                 die("child terminated due to signal %d\n", WTERMSIG(stat));
   728         _exit(0);
   729 }
   730 
   731 void
   732 stty(char **args)
   733 {
   734         char cmd[_POSIX_ARG_MAX], **p, *q, *s;
   735         size_t n, siz;
   736 
   737         if ((n = strlen(stty_args)) > sizeof(cmd)-1)
   738                 die("incorrect stty parameters\n");
   739         memcpy(cmd, stty_args, n);
   740         q = cmd + n;
   741         siz = sizeof(cmd) - n;
   742         for (p = args; p && (s = *p); ++p) {
   743                 if ((n = strlen(s)) > siz-1)
   744                         die("stty parameter length too long\n");
   745                 *q++ = ' ';
   746                 memcpy(q, s, n);
   747                 q += n;
   748                 siz -= n + 1;
   749         }
   750         *q = '\0';
   751         if (system(cmd) != 0)
   752                 perror("Couldn't call stty");
   753 }
   754 
   755 int
   756 ttynew(const char *line, char *cmd, const char *out, char **args)
   757 {
   758         int m, s;
   759 
   760         if (out) {
   761                 term.mode |= MODE_PRINT;
   762                 iofd = (!strcmp(out, "-")) ?
   763                           1 : open(out, O_WRONLY | O_CREAT, 0666);
   764                 if (iofd < 0) {
   765                         fprintf(stderr, "Error opening %s:%s\n",
   766                                 out, strerror(errno));
   767                 }
   768         }
   769 
   770         if (line) {
   771                 if ((cmdfd = open(line, O_RDWR)) < 0)
   772                         die("open line '%s' failed: %s\n",
   773                             line, strerror(errno));
   774                 dup2(cmdfd, 0);
   775                 stty(args);
   776                 return cmdfd;
   777         }
   778 
   779         /* seems to work fine on linux, openbsd and freebsd */
   780         if (openpty(&m, &s, NULL, NULL, NULL) < 0)
   781                 die("openpty failed: %s\n", strerror(errno));
   782 
   783         switch (pid = fork()) {
   784         case -1:
   785                 die("fork failed: %s\n", strerror(errno));
   786                 break;
   787         case 0:
   788                 close(iofd);
   789                 close(m);
   790                 setsid(); /* create a new process group */
   791                 dup2(s, 0);
   792                 dup2(s, 1);
   793                 dup2(s, 2);
   794                 if (ioctl(s, TIOCSCTTY, NULL) < 0)
   795                         die("ioctl TIOCSCTTY failed: %s\n", strerror(errno));
   796                 if (s > 2)
   797                         close(s);
   798 #ifdef __OpenBSD__
   799                 if (pledge("stdio getpw proc exec", NULL) == -1)
   800                         die("pledge\n");
   801 #endif
   802                 execsh(cmd, args);
   803                 break;
   804         default:
   805 #ifdef __OpenBSD__
   806                 if (pledge("stdio rpath tty proc", NULL) == -1)
   807                         die("pledge\n");
   808 #endif
   809                 close(s);
   810                 cmdfd = m;
   811                 signal(SIGCHLD, sigchld);
   812                 break;
   813         }
   814         return cmdfd;
   815 }
   816 
   817 size_t
   818 ttyread(void)
   819 {
   820         static char buf[BUFSIZ];
   821         static int buflen = 0;
   822         int ret, written;
   823 
   824         /* append read bytes to unprocessed bytes */
   825         ret = read(cmdfd, buf+buflen, LEN(buf)-buflen);
   826 
   827         switch (ret) {
   828         case 0:
   829                 exit(0);
   830         case -1:
   831                 die("couldn't read from shell: %s\n", strerror(errno));
   832         default:
   833                 buflen += ret;
   834                 written = twrite(buf, buflen, 0);
   835                 buflen -= written;
   836                 /* keep any incomplete UTF-8 byte sequence for the next call */
   837                 if (buflen > 0)
   838                         memmove(buf, buf + written, buflen);
   839                 return ret;
   840         }
   841 }
   842 
   843 void
   844 ttywrite(const char *s, size_t n, int may_echo)
   845 {
   846         const char *next;
   847 
   848         if (may_echo && IS_SET(MODE_ECHO))
   849                 twrite(s, n, 1);
   850 
   851         if (!IS_SET(MODE_CRLF)) {
   852                 ttywriteraw(s, n);
   853                 return;
   854         }
   855 
   856         /* This is similar to how the kernel handles ONLCR for ttys */
   857         while (n > 0) {
   858                 if (*s == '\r') {
   859                         next = s + 1;
   860                         ttywriteraw("\r\n", 2);
   861                 } else {
   862                         next = memchr(s, '\r', n);
   863                         DEFAULT(next, s + n);
   864                         ttywriteraw(s, next - s);
   865                 }
   866                 n -= next - s;
   867                 s = next;
   868         }
   869 }
   870 
   871 void
   872 ttywriteraw(const char *s, size_t n)
   873 {
   874         fd_set wfd, rfd;
   875         ssize_t r;
   876         size_t lim = 256;
   877 
   878         /*
   879          * Remember that we are using a pty, which might be a modem line.
   880          * Writing too much will clog the line. That's why we are doing this
   881          * dance.
   882          * FIXME: Migrate the world to Plan 9.
   883          */
   884         while (n > 0) {
   885                 FD_ZERO(&wfd);
   886                 FD_ZERO(&rfd);
   887                 FD_SET(cmdfd, &wfd);
   888                 FD_SET(cmdfd, &rfd);
   889 
   890                 /* Check if we can write. */
   891                 if (pselect(cmdfd+1, &rfd, &wfd, NULL, NULL, NULL) < 0) {
   892                         if (errno == EINTR)
   893                                 continue;
   894                         die("select failed: %s\n", strerror(errno));
   895                 }
   896                 if (FD_ISSET(cmdfd, &wfd)) {
   897                         /*
   898                          * Only write the bytes written by ttywrite() or the
   899                          * default of 256. This seems to be a reasonable value
   900                          * for a serial line. Bigger values might clog the I/O.
   901                          */
   902                         if ((r = write(cmdfd, s, (n < lim)? n : lim)) < 0)
   903                                 goto write_error;
   904                         if (r < n) {
   905                                 /*
   906                                  * We weren't able to write out everything.
   907                                  * This means the buffer is getting full
   908                                  * again. Empty it.
   909                                  */
   910                                 if (n < lim)
   911                                         lim = ttyread();
   912                                 n -= r;
   913                                 s += r;
   914                         } else {
   915                                 /* All bytes have been written. */
   916                                 break;
   917                         }
   918                 }
   919                 if (FD_ISSET(cmdfd, &rfd))
   920                         lim = ttyread();
   921         }
   922         return;
   923 
   924 write_error:
   925         die("write error on tty: %s\n", strerror(errno));
   926 }
   927 
   928 void
   929 ttyresize(int tw, int th)
   930 {
   931         struct winsize w;
   932 
   933         w.ws_row = term.row;
   934         w.ws_col = term.col;
   935         w.ws_xpixel = tw;
   936         w.ws_ypixel = th;
   937         if (ioctl(cmdfd, TIOCSWINSZ, &w) < 0)
   938                 fprintf(stderr, "Couldn't set window size: %s\n", strerror(errno));
   939 }
   940 
   941 void
   942 ttyhangup(void)
   943 {
   944         /* Send SIGHUP to shell */
   945         kill(pid, SIGHUP);
   946 }
   947 
   948 int
   949 tattrset(int attr)
   950 {
   951         int i, j;
   952 
   953         for (i = 0; i < term.row-1; i++) {
   954                 for (j = 0; j < term.col-1; j++) {
   955                         if (term.line[i][j].mode & attr)
   956                                 return 1;
   957                 }
   958         }
   959 
   960         return 0;
   961 }
   962 
   963 void
   964 tsetdirt(int top, int bot)
   965 {
   966         int i;
   967 
   968         LIMIT(top, 0, term.row-1);
   969         LIMIT(bot, 0, term.row-1);
   970 
   971         for (i = top; i <= bot; i++)
   972                 term.dirty[i] = 1;
   973 }
   974 
   975 void
   976 tsetdirtattr(int attr)
   977 {
   978         int i, j;
   979 
   980         for (i = 0; i < term.row-1; i++) {
   981                 for (j = 0; j < term.col-1; j++) {
   982                         if (term.line[i][j].mode & attr) {
   983                                 tsetdirt(i, i);
   984                                 break;
   985                         }
   986                 }
   987         }
   988 }
   989 
   990 void
   991 tfulldirt(void)
   992 {
   993         tsetdirt(0, term.row-1);
   994 }
   995 
   996 void
   997 tcursor(int mode)
   998 {
   999         static TCursor c[2];
  1000         int alt = IS_SET(MODE_ALTSCREEN);
  1001 
  1002         if (mode == CURSOR_SAVE) {
  1003                 c[alt] = term.c;
  1004         } else if (mode == CURSOR_LOAD) {
  1005                 term.c = c[alt];
  1006                 tmoveto(c[alt].x, c[alt].y);
  1007         }
  1008 }
  1009 
  1010 void
  1011 treset(void)
  1012 {
  1013         uint i;
  1014 
  1015         term.c = (TCursor){{
  1016                 .mode = ATTR_NULL,
  1017                 .fg = defaultfg,
  1018                 .bg = defaultbg
  1019         }, .x = 0, .y = 0, .state = CURSOR_DEFAULT};
  1020 
  1021         memset(term.tabs, 0, term.col * sizeof(*term.tabs));
  1022         for (i = tabspaces; i < term.col; i += tabspaces)
  1023                 term.tabs[i] = 1;
  1024         term.top = 0;
  1025         term.bot = term.row - 1;
  1026         term.mode = MODE_WRAP|MODE_UTF8;
  1027         memset(term.trantbl, CS_USA, sizeof(term.trantbl));
  1028         term.charset = 0;
  1029 
  1030         for (i = 0; i < 2; i++) {
  1031                 tmoveto(0, 0);
  1032                 tcursor(CURSOR_SAVE);
  1033                 tclearregion(0, 0, term.col-1, term.row-1);
  1034                 tswapscreen();
  1035         }
  1036 }
  1037 
  1038 void
  1039 tnew(int col, int row)
  1040 {
  1041         term = (Term){ .c = { .attr = { .fg = defaultfg, .bg = defaultbg } } };
  1042         tresize(col, row);
  1043         treset();
  1044 }
  1045 
  1046 void
  1047 tswapscreen(void)
  1048 {
  1049         Line *tmp = term.line;
  1050 
  1051         term.line = term.alt;
  1052         term.alt = tmp;
  1053         term.mode ^= MODE_ALTSCREEN;
  1054         tfulldirt();
  1055 }
  1056 
  1057 void
  1058 tscrolldown(int orig, int n)
  1059 {
  1060         int i;
  1061         Line temp;
  1062 
  1063         LIMIT(n, 0, term.bot-orig+1);
  1064 
  1065         tsetdirt(orig, term.bot-n);
  1066         tclearregion(0, term.bot-n+1, term.col-1, term.bot);
  1067 
  1068         for (i = term.bot; i >= orig+n; i--) {
  1069                 temp = term.line[i];
  1070                 term.line[i] = term.line[i-n];
  1071                 term.line[i-n] = temp;
  1072         }
  1073 
  1074         selscroll(orig, n);
  1075 }
  1076 
  1077 void
  1078 tscrollup(int orig, int n)
  1079 {
  1080         int i;
  1081         Line temp;
  1082 
  1083         LIMIT(n, 0, term.bot-orig+1);
  1084 
  1085         tclearregion(0, orig, term.col-1, orig+n-1);
  1086         tsetdirt(orig+n, term.bot);
  1087 
  1088         for (i = orig; i <= term.bot-n; i++) {
  1089                 temp = term.line[i];
  1090                 term.line[i] = term.line[i+n];
  1091                 term.line[i+n] = temp;
  1092         }
  1093 
  1094         selscroll(orig, -n);
  1095 }
  1096 
  1097 void
  1098 selscroll(int orig, int n)
  1099 {
  1100         if (sel.ob.x == -1 || sel.alt != IS_SET(MODE_ALTSCREEN))
  1101                 return;
  1102 
  1103         if (BETWEEN(sel.nb.y, orig, term.bot) != BETWEEN(sel.ne.y, orig, term.bot)) {
  1104                 selclear();
  1105         } else if (BETWEEN(sel.nb.y, orig, term.bot)) {
  1106                 sel.ob.y += n;
  1107                 sel.oe.y += n;
  1108                 if (sel.ob.y < term.top || sel.ob.y > term.bot ||
  1109                     sel.oe.y < term.top || sel.oe.y > term.bot) {
  1110                         selclear();
  1111                 } else {
  1112                         selnormalize();
  1113                 }
  1114         }
  1115 }
  1116 
  1117 void
  1118 tnewline(int first_col)
  1119 {
  1120         int y = term.c.y;
  1121 
  1122         if (y == term.bot) {
  1123                 tscrollup(term.top, 1);
  1124         } else {
  1125                 y++;
  1126         }
  1127         tmoveto(first_col ? 0 : term.c.x, y);
  1128 }
  1129 
  1130 void
  1131 csiparse(void)
  1132 {
  1133         char *p = csiescseq.buf, *np;
  1134         long int v;
  1135         int sep = ';'; /* colon or semi-colon, but not both */
  1136 
  1137         csiescseq.narg = 0;
  1138         if (*p == '?') {
  1139                 csiescseq.priv = 1;
  1140                 p++;
  1141         }
  1142 
  1143         csiescseq.buf[csiescseq.len] = '\0';
  1144         while (p < csiescseq.buf+csiescseq.len) {
  1145                 np = NULL;
  1146                 v = strtol(p, &np, 10);
  1147                 if (np == p)
  1148                         v = 0;
  1149                 if (v == LONG_MAX || v == LONG_MIN)
  1150                         v = -1;
  1151                 csiescseq.arg[csiescseq.narg++] = v;
  1152                 p = np;
  1153                 if (sep == ';' && *p == ':')
  1154                         sep = ':'; /* allow override to colon once */
  1155                 if (*p != sep || csiescseq.narg == ESC_ARG_SIZ)
  1156                         break;
  1157                 p++;
  1158         }
  1159         csiescseq.mode[0] = *p++;
  1160         csiescseq.mode[1] = (p < csiescseq.buf+csiescseq.len) ? *p : '\0';
  1161 }
  1162 
  1163 /* for absolute user moves, when decom is set */
  1164 void
  1165 tmoveato(int x, int y)
  1166 {
  1167         tmoveto(x, y + ((term.c.state & CURSOR_ORIGIN) ? term.top: 0));
  1168 }
  1169 
  1170 void
  1171 tmoveto(int x, int y)
  1172 {
  1173         int miny, maxy;
  1174 
  1175         if (term.c.state & CURSOR_ORIGIN) {
  1176                 miny = term.top;
  1177                 maxy = term.bot;
  1178         } else {
  1179                 miny = 0;
  1180                 maxy = term.row - 1;
  1181         }
  1182         term.c.state &= ~CURSOR_WRAPNEXT;
  1183         term.c.x = LIMIT(x, 0, term.col-1);
  1184         term.c.y = LIMIT(y, miny, maxy);
  1185 }
  1186 
  1187 void
  1188 tsetchar(Rune u, const Glyph *attr, int x, int y)
  1189 {
  1190         static const char *vt100_0[62] = { /* 0x41 - 0x7e */
  1191                 "↑", "↓", "→", "←", "█", "▚", "☃", /* A - G */
  1192                 0, 0, 0, 0, 0, 0, 0, 0, /* H - O */
  1193                 0, 0, 0, 0, 0, 0, 0, 0, /* P - W */
  1194                 0, 0, 0, 0, 0, 0, 0, " ", /* X - _ */
  1195                 "◆", "▒", "␉", "␌", "␍", "␊", "°", "±", /* ` - g */
  1196                 "␤", "␋", "┘", "┐", "┌", "└", "┼", "⎺", /* h - o */
  1197                 "⎻", "─", "⎼", "⎽", "├", "┤", "┴", "┬", /* p - w */
  1198                 "│", "≤", "≥", "π", "≠", "£", "·", /* x - ~ */
  1199         };
  1200 
  1201         /*
  1202          * The table is proudly stolen from rxvt.
  1203          */
  1204         if (term.trantbl[term.charset] == CS_GRAPHIC0 &&
  1205            BETWEEN(u, 0x41, 0x7e) && vt100_0[u - 0x41])
  1206                 utf8decode(vt100_0[u - 0x41], &u, UTF_SIZ);
  1207 
  1208         if (term.line[y][x].mode & ATTR_WIDE) {
  1209                 if (x+1 < term.col) {
  1210                         term.line[y][x+1].u = ' ';
  1211                         term.line[y][x+1].mode &= ~ATTR_WDUMMY;
  1212                 }
  1213         } else if (term.line[y][x].mode & ATTR_WDUMMY) {
  1214                 term.line[y][x-1].u = ' ';
  1215                 term.line[y][x-1].mode &= ~ATTR_WIDE;
  1216         }
  1217 
  1218         term.dirty[y] = 1;
  1219         term.line[y][x] = *attr;
  1220         term.line[y][x].u = u;
  1221 }
  1222 
  1223 void
  1224 tclearregion(int x1, int y1, int x2, int y2)
  1225 {
  1226         int x, y, temp;
  1227         Glyph *gp;
  1228 
  1229         if (x1 > x2)
  1230                 temp = x1, x1 = x2, x2 = temp;
  1231         if (y1 > y2)
  1232                 temp = y1, y1 = y2, y2 = temp;
  1233 
  1234         LIMIT(x1, 0, term.col-1);
  1235         LIMIT(x2, 0, term.col-1);
  1236         LIMIT(y1, 0, term.row-1);
  1237         LIMIT(y2, 0, term.row-1);
  1238 
  1239         for (y = y1; y <= y2; y++) {
  1240                 term.dirty[y] = 1;
  1241                 for (x = x1; x <= x2; x++) {
  1242                         gp = &term.line[y][x];
  1243                         if (selected(x, y))
  1244                                 selclear();
  1245                         gp->fg = term.c.attr.fg;
  1246                         gp->bg = term.c.attr.bg;
  1247                         gp->mode = 0;
  1248                         gp->u = ' ';
  1249                 }
  1250         }
  1251 }
  1252 
  1253 void
  1254 tdeletechar(int n)
  1255 {
  1256         int dst, src, size;
  1257         Glyph *line;
  1258 
  1259         LIMIT(n, 0, term.col - term.c.x);
  1260 
  1261         dst = term.c.x;
  1262         src = term.c.x + n;
  1263         size = term.col - src;
  1264         line = term.line[term.c.y];
  1265 
  1266         memmove(&line[dst], &line[src], size * sizeof(Glyph));
  1267         tclearregion(term.col-n, term.c.y, term.col-1, term.c.y);
  1268 }
  1269 
  1270 void
  1271 tinsertblank(int n)
  1272 {
  1273         int dst, src, size;
  1274         Glyph *line;
  1275 
  1276         LIMIT(n, 0, term.col - term.c.x);
  1277 
  1278         dst = term.c.x + n;
  1279         src = term.c.x;
  1280         size = term.col - dst;
  1281         line = term.line[term.c.y];
  1282 
  1283         memmove(&line[dst], &line[src], size * sizeof(Glyph));
  1284         tclearregion(src, term.c.y, dst - 1, term.c.y);
  1285 }
  1286 
  1287 void
  1288 tinsertblankline(int n)
  1289 {
  1290         if (BETWEEN(term.c.y, term.top, term.bot))
  1291                 tscrolldown(term.c.y, n);
  1292 }
  1293 
  1294 void
  1295 tdeleteline(int n)
  1296 {
  1297         if (BETWEEN(term.c.y, term.top, term.bot))
  1298                 tscrollup(term.c.y, n);
  1299 }
  1300 
  1301 int32_t
  1302 tdefcolor(const int *attr, int *npar, int l)
  1303 {
  1304         int32_t idx = -1;
  1305         uint r, g, b;
  1306 
  1307         switch (attr[*npar + 1]) {
  1308         case 2: /* direct color in RGB space */
  1309                 if (*npar + 4 >= l) {
  1310                         fprintf(stderr,
  1311                                 "erresc(38): Incorrect number of parameters (%d)\n",
  1312                                 *npar);
  1313                         break;
  1314                 }
  1315                 r = attr[*npar + 2];
  1316                 g = attr[*npar + 3];
  1317                 b = attr[*npar + 4];
  1318                 *npar += 4;
  1319                 if (!BETWEEN(r, 0, 255) || !BETWEEN(g, 0, 255) || !BETWEEN(b, 0, 255))
  1320                         fprintf(stderr, "erresc: bad rgb color (%u,%u,%u)\n",
  1321                                 r, g, b);
  1322                 else
  1323                         idx = TRUECOLOR(r, g, b);
  1324                 break;
  1325         case 5: /* indexed color */
  1326                 if (*npar + 2 >= l) {
  1327                         fprintf(stderr,
  1328                                 "erresc(38): Incorrect number of parameters (%d)\n",
  1329                                 *npar);
  1330                         break;
  1331                 }
  1332                 *npar += 2;
  1333                 if (!BETWEEN(attr[*npar], 0, 255))
  1334                         fprintf(stderr, "erresc: bad fgcolor %d\n", attr[*npar]);
  1335                 else
  1336                         idx = attr[*npar];
  1337                 break;
  1338         case 0: /* implemented defined (only foreground) */
  1339         case 1: /* transparent */
  1340         case 3: /* direct color in CMY space */
  1341         case 4: /* direct color in CMYK space */
  1342         default:
  1343                 fprintf(stderr,
  1344                         "erresc(38): gfx attr %d unknown\n", attr[*npar]);
  1345                 break;
  1346         }
  1347 
  1348         return idx;
  1349 }
  1350 
  1351 void
  1352 tsetattr(const int *attr, int l)
  1353 {
  1354         int i;
  1355         int32_t idx;
  1356 
  1357         for (i = 0; i < l; i++) {
  1358                 switch (attr[i]) {
  1359                 case 0:
  1360                         term.c.attr.mode &= ~(
  1361                                 ATTR_BOLD       |
  1362                                 ATTR_FAINT      |
  1363                                 ATTR_ITALIC     |
  1364                                 ATTR_UNDERLINE  |
  1365                                 ATTR_BLINK      |
  1366                                 ATTR_REVERSE    |
  1367                                 ATTR_INVISIBLE  |
  1368                                 ATTR_STRUCK     );
  1369                         term.c.attr.fg = defaultfg;
  1370                         term.c.attr.bg = defaultbg;
  1371                         break;
  1372                 case 1:
  1373                         term.c.attr.mode |= ATTR_BOLD;
  1374                         break;
  1375                 case 2:
  1376                         term.c.attr.mode |= ATTR_FAINT;
  1377                         break;
  1378                 case 3:
  1379                         term.c.attr.mode |= ATTR_ITALIC;
  1380                         break;
  1381                 case 4:
  1382                         term.c.attr.mode |= ATTR_UNDERLINE;
  1383                         break;
  1384                 case 5: /* slow blink */
  1385                         /* FALLTHROUGH */
  1386                 case 6: /* rapid blink */
  1387                         term.c.attr.mode |= ATTR_BLINK;
  1388                         break;
  1389                 case 7:
  1390                         term.c.attr.mode |= ATTR_REVERSE;
  1391                         break;
  1392                 case 8:
  1393                         term.c.attr.mode |= ATTR_INVISIBLE;
  1394                         break;
  1395                 case 9:
  1396                         term.c.attr.mode |= ATTR_STRUCK;
  1397                         break;
  1398                 case 22:
  1399                         term.c.attr.mode &= ~(ATTR_BOLD | ATTR_FAINT);
  1400                         break;
  1401                 case 23:
  1402                         term.c.attr.mode &= ~ATTR_ITALIC;
  1403                         break;
  1404                 case 24:
  1405                         term.c.attr.mode &= ~ATTR_UNDERLINE;
  1406                         break;
  1407                 case 25:
  1408                         term.c.attr.mode &= ~ATTR_BLINK;
  1409                         break;
  1410                 case 27:
  1411                         term.c.attr.mode &= ~ATTR_REVERSE;
  1412                         break;
  1413                 case 28:
  1414                         term.c.attr.mode &= ~ATTR_INVISIBLE;
  1415                         break;
  1416                 case 29:
  1417                         term.c.attr.mode &= ~ATTR_STRUCK;
  1418                         break;
  1419                 case 38:
  1420                         if ((idx = tdefcolor(attr, &i, l)) >= 0)
  1421                                 term.c.attr.fg = idx;
  1422                         break;
  1423                 case 39:
  1424                         term.c.attr.fg = defaultfg;
  1425                         break;
  1426                 case 48:
  1427                         if ((idx = tdefcolor(attr, &i, l)) >= 0)
  1428                                 term.c.attr.bg = idx;
  1429                         break;
  1430                 case 49:
  1431                         term.c.attr.bg = defaultbg;
  1432                         break;
  1433                 default:
  1434                         if (BETWEEN(attr[i], 30, 37)) {
  1435                                 term.c.attr.fg = attr[i] - 30;
  1436                         } else if (BETWEEN(attr[i], 40, 47)) {
  1437                                 term.c.attr.bg = attr[i] - 40;
  1438                         } else if (BETWEEN(attr[i], 90, 97)) {
  1439                                 term.c.attr.fg = attr[i] - 90 + 8;
  1440                         } else if (BETWEEN(attr[i], 100, 107)) {
  1441                                 term.c.attr.bg = attr[i] - 100 + 8;
  1442                         } else {
  1443                                 fprintf(stderr,
  1444                                         "erresc(default): gfx attr %d unknown\n",
  1445                                         attr[i]);
  1446                                 csidump();
  1447                         }
  1448                         break;
  1449                 }
  1450         }
  1451 }
  1452 
  1453 void
  1454 tsetscroll(int t, int b)
  1455 {
  1456         int temp;
  1457 
  1458         LIMIT(t, 0, term.row-1);
  1459         LIMIT(b, 0, term.row-1);
  1460         if (t > b) {
  1461                 temp = t;
  1462                 t = b;
  1463                 b = temp;
  1464         }
  1465         term.top = t;
  1466         term.bot = b;
  1467 }
  1468 
  1469 void
  1470 tsetmode(int priv, int set, const int *args, int narg)
  1471 {
  1472         int alt; const int *lim;
  1473 
  1474         for (lim = args + narg; args < lim; ++args) {
  1475                 if (priv) {
  1476                         switch (*args) {
  1477                         case 1: /* DECCKM -- Cursor key */
  1478                                 xsetmode(set, MODE_APPCURSOR);
  1479                                 break;
  1480                         case 5: /* DECSCNM -- Reverse video */
  1481                                 xsetmode(set, MODE_REVERSE);
  1482                                 break;
  1483                         case 6: /* DECOM -- Origin */
  1484                                 MODBIT(term.c.state, set, CURSOR_ORIGIN);
  1485                                 tmoveato(0, 0);
  1486                                 break;
  1487                         case 7: /* DECAWM -- Auto wrap */
  1488                                 MODBIT(term.mode, set, MODE_WRAP);
  1489                                 break;
  1490                         case 0:  /* Error (IGNORED) */
  1491                         case 2:  /* DECANM -- ANSI/VT52 (IGNORED) */
  1492                         case 3:  /* DECCOLM -- Column  (IGNORED) */
  1493                         case 4:  /* DECSCLM -- Scroll (IGNORED) */
  1494                         case 8:  /* DECARM -- Auto repeat (IGNORED) */
  1495                         case 18: /* DECPFF -- Printer feed (IGNORED) */
  1496                         case 19: /* DECPEX -- Printer extent (IGNORED) */
  1497                         case 42: /* DECNRCM -- National characters (IGNORED) */
  1498                         case 12: /* att610 -- Start blinking cursor (IGNORED) */
  1499                                 break;
  1500                         case 25: /* DECTCEM -- Text Cursor Enable Mode */
  1501                                 xsetmode(!set, MODE_HIDE);
  1502                                 break;
  1503                         case 9:    /* X10 mouse compatibility mode */
  1504                                 xsetpointermotion(0);
  1505                                 xsetmode(0, MODE_MOUSE);
  1506                                 xsetmode(set, MODE_MOUSEX10);
  1507                                 break;
  1508                         case 1000: /* 1000: report button press */
  1509                                 xsetpointermotion(0);
  1510                                 xsetmode(0, MODE_MOUSE);
  1511                                 xsetmode(set, MODE_MOUSEBTN);
  1512                                 break;
  1513                         case 1002: /* 1002: report motion on button press */
  1514                                 xsetpointermotion(0);
  1515                                 xsetmode(0, MODE_MOUSE);
  1516                                 xsetmode(set, MODE_MOUSEMOTION);
  1517                                 break;
  1518                         case 1003: /* 1003: enable all mouse motions */
  1519                                 xsetpointermotion(set);
  1520                                 xsetmode(0, MODE_MOUSE);
  1521                                 xsetmode(set, MODE_MOUSEMANY);
  1522                                 break;
  1523                         case 1004: /* 1004: send focus events to tty */
  1524                                 xsetmode(set, MODE_FOCUS);
  1525                                 break;
  1526                         case 1006: /* 1006: extended reporting mode */
  1527                                 xsetmode(set, MODE_MOUSESGR);
  1528                                 break;
  1529                         case 1034:
  1530                                 xsetmode(set, MODE_8BIT);
  1531                                 break;
  1532                         case 1049: /* swap screen & set/restore cursor as xterm */
  1533                                 if (!allowaltscreen)
  1534                                         break;
  1535                                 tcursor((set) ? CURSOR_SAVE : CURSOR_LOAD);
  1536                                 /* FALLTHROUGH */
  1537                         case 47: /* swap screen */
  1538                         case 1047:
  1539                                 if (!allowaltscreen)
  1540                                         break;
  1541                                 alt = IS_SET(MODE_ALTSCREEN);
  1542                                 if (alt) {
  1543                                         tclearregion(0, 0, term.col-1,
  1544                                                         term.row-1);
  1545                                 }
  1546                                 if (set ^ alt) /* set is always 1 or 0 */
  1547                                         tswapscreen();
  1548                                 if (*args != 1049)
  1549                                         break;
  1550                                 /* FALLTHROUGH */
  1551                         case 1048:
  1552                                 tcursor((set) ? CURSOR_SAVE : CURSOR_LOAD);
  1553                                 break;
  1554                         case 2004: /* 2004: bracketed paste mode */
  1555                                 xsetmode(set, MODE_BRCKTPASTE);
  1556                                 break;
  1557                         /* Not implemented mouse modes. See comments there. */
  1558                         case 1001: /* mouse highlight mode; can hang the
  1559                                       terminal by design when implemented. */
  1560                         case 1005: /* UTF-8 mouse mode; will confuse
  1561                                       applications not supporting UTF-8
  1562                                       and luit. */
  1563                         case 1015: /* urxvt mangled mouse mode; incompatible
  1564                                       and can be mistaken for other control
  1565                                       codes. */
  1566                                 break;
  1567                         default:
  1568                                 fprintf(stderr,
  1569                                         "erresc: unknown private set/reset mode %d\n",
  1570                                         *args);
  1571                                 break;
  1572                         }
  1573                 } else {
  1574                         switch (*args) {
  1575                         case 0:  /* Error (IGNORED) */
  1576                                 break;
  1577                         case 2:
  1578                                 xsetmode(set, MODE_KBDLOCK);
  1579                                 break;
  1580                         case 4:  /* IRM -- Insertion-replacement */
  1581                                 MODBIT(term.mode, set, MODE_INSERT);
  1582                                 break;
  1583                         case 12: /* SRM -- Send/Receive */
  1584                                 MODBIT(term.mode, !set, MODE_ECHO);
  1585                                 break;
  1586                         case 20: /* LNM -- Linefeed/new line */
  1587                                 MODBIT(term.mode, set, MODE_CRLF);
  1588                                 break;
  1589                         default:
  1590                                 fprintf(stderr,
  1591                                         "erresc: unknown set/reset mode %d\n",
  1592                                         *args);
  1593                                 break;
  1594                         }
  1595                 }
  1596         }
  1597 }
  1598 
  1599 void
  1600 csihandle(void)
  1601 {
  1602         char buf[40];
  1603         int len;
  1604 
  1605         switch (csiescseq.mode[0]) {
  1606         default:
  1607         unknown:
  1608                 fprintf(stderr, "erresc: unknown csi ");
  1609                 csidump();
  1610                 /* die(""); */
  1611                 break;
  1612         case '@': /* ICH -- Insert  blank char */
  1613                 DEFAULT(csiescseq.arg[0], 1);
  1614                 tinsertblank(csiescseq.arg[0]);
  1615                 break;
  1616         case 'A': /* CUU -- Cursor  Up */
  1617                 DEFAULT(csiescseq.arg[0], 1);
  1618                 tmoveto(term.c.x, term.c.y-csiescseq.arg[0]);
  1619                 break;
  1620         case 'B': /* CUD -- Cursor  Down */
  1621         case 'e': /* VPR --Cursor  Down */
  1622                 DEFAULT(csiescseq.arg[0], 1);
  1623                 tmoveto(term.c.x, term.c.y+csiescseq.arg[0]);
  1624                 break;
  1625         case 'i': /* MC -- Media Copy */
  1626                 switch (csiescseq.arg[0]) {
  1627                 case 0:
  1628                         tdump();
  1629                         break;
  1630                 case 1:
  1631                         tdumpline(term.c.y);
  1632                         break;
  1633                 case 2:
  1634                         tdumpsel();
  1635                         break;
  1636                 case 4:
  1637                         term.mode &= ~MODE_PRINT;
  1638                         break;
  1639                 case 5:
  1640                         term.mode |= MODE_PRINT;
  1641                         break;
  1642                 }
  1643                 break;
  1644         case 'c': /* DA -- Device Attributes */
  1645                 if (csiescseq.arg[0] == 0)
  1646                         ttywrite(vtiden, strlen(vtiden), 0);
  1647                 break;
  1648         case 'b': /* REP -- if last char is printable print it  more times */
  1649                 LIMIT(csiescseq.arg[0], 1, 65535);
  1650                 if (term.lastc)
  1651                         while (csiescseq.arg[0]-- > 0)
  1652                                 tputc(term.lastc);
  1653                 break;
  1654         case 'C': /* CUF -- Cursor  Forward */
  1655         case 'a': /* HPR -- Cursor  Forward */
  1656                 DEFAULT(csiescseq.arg[0], 1);
  1657                 tmoveto(term.c.x+csiescseq.arg[0], term.c.y);
  1658                 break;
  1659         case 'D': /* CUB -- Cursor  Backward */
  1660                 DEFAULT(csiescseq.arg[0], 1);
  1661                 tmoveto(term.c.x-csiescseq.arg[0], term.c.y);
  1662                 break;
  1663         case 'E': /* CNL -- Cursor  Down and first col */
  1664                 DEFAULT(csiescseq.arg[0], 1);
  1665                 tmoveto(0, term.c.y+csiescseq.arg[0]);
  1666                 break;
  1667         case 'F': /* CPL -- Cursor  Up and first col */
  1668                 DEFAULT(csiescseq.arg[0], 1);
  1669                 tmoveto(0, term.c.y-csiescseq.arg[0]);
  1670                 break;
  1671         case 'g': /* TBC -- Tabulation clear */
  1672                 switch (csiescseq.arg[0]) {
  1673                 case 0: /* clear current tab stop */
  1674                         term.tabs[term.c.x] = 0;
  1675                         break;
  1676                 case 3: /* clear all the tabs */
  1677                         memset(term.tabs, 0, term.col * sizeof(*term.tabs));
  1678                         break;
  1679                 default:
  1680                         goto unknown;
  1681                 }
  1682                 break;
  1683         case 'G': /* CHA -- Move to