From 956863631ac3be9cac9fb025f0f41819f7e1d536 Mon Sep 17 00:00:00 2001 From: vx-clutch Date: Mon, 9 Feb 2026 19:43:49 -0500 Subject: [PATCH] boxdraw --- Makefile | 3 +- Makefile.orig | 51 + boxdraw.c | 194 +++ boxdraw.o | Bin 0 -> 7488 bytes boxdraw_data.h | 214 +++ config.def.h | 12 + config.def.h.orig | 480 ++++++ config.h | 12 + patches/st-boxdraw_v2-0.8.5.diff | 583 +++++++ st | Bin 94384 -> 103000 bytes st.c | 3 + st.c.orig | 2705 ++++++++++++++++++++++++++++++ st.h | 10 + st.h.orig | 126 ++ st.o | Bin 78768 -> 78856 bytes x.c | 21 +- x.c.orig | 101 ++ x.o | Bin 73000 -> 73560 bytes 18 files changed, 4509 insertions(+), 6 deletions(-) create mode 100644 Makefile.orig create mode 100644 boxdraw.c create mode 100644 boxdraw.o create mode 100644 boxdraw_data.h create mode 100644 config.def.h.orig create mode 100644 patches/st-boxdraw_v2-0.8.5.diff create mode 100644 st.c.orig create mode 100644 st.h.orig diff --git a/Makefile b/Makefile index 15db421..a64b4c2 100644 --- a/Makefile +++ b/Makefile @@ -4,7 +4,7 @@ include config.mk -SRC = st.c x.c +SRC = st.c x.c boxdraw.c OBJ = $(SRC:.c=.o) all: st @@ -17,6 +17,7 @@ config.h: st.o: config.h st.h win.h x.o: arg.h config.h st.h win.h +boxdraw.o: config.h st.h boxdraw_data.h $(OBJ): config.h config.mk diff --git a/Makefile.orig b/Makefile.orig new file mode 100644 index 0000000..15db421 --- /dev/null +++ b/Makefile.orig @@ -0,0 +1,51 @@ +# st - simple terminal +# See LICENSE file for copyright and license details. +.POSIX: + +include config.mk + +SRC = st.c x.c +OBJ = $(SRC:.c=.o) + +all: st + +config.h: + cp config.def.h config.h + +.c.o: + $(CC) $(STCFLAGS) -c $< + +st.o: config.h st.h win.h +x.o: arg.h config.h st.h win.h + +$(OBJ): config.h config.mk + +st: $(OBJ) + $(CC) -o $@ $(OBJ) $(STLDFLAGS) + +clean: + rm -f st $(OBJ) st-$(VERSION).tar.gz + +dist: clean + mkdir -p st-$(VERSION) + cp -R FAQ LEGACY TODO LICENSE Makefile README config.mk\ + config.def.h st.info st.1 arg.h st.h win.h $(SRC)\ + st-$(VERSION) + tar -cf - st-$(VERSION) | gzip > st-$(VERSION).tar.gz + rm -rf st-$(VERSION) + +install: st + mkdir -p $(DESTDIR)$(PREFIX)/bin + cp -f st $(DESTDIR)$(PREFIX)/bin + chmod 755 $(DESTDIR)$(PREFIX)/bin/st + mkdir -p $(DESTDIR)$(MANPREFIX)/man1 + sed "s/VERSION/$(VERSION)/g" < st.1 > $(DESTDIR)$(MANPREFIX)/man1/st.1 + chmod 644 $(DESTDIR)$(MANPREFIX)/man1/st.1 + tic -sx st.info + @echo Please see the README file regarding the terminfo entry of st. + +uninstall: + rm -f $(DESTDIR)$(PREFIX)/bin/st + rm -f $(DESTDIR)$(MANPREFIX)/man1/st.1 + +.PHONY: all clean dist install uninstall diff --git a/boxdraw.c b/boxdraw.c new file mode 100644 index 0000000..28a92d0 --- /dev/null +++ b/boxdraw.c @@ -0,0 +1,194 @@ +/* + * Copyright 2018 Avi Halachmi (:avih) avihpit@yahoo.com https://github.com/avih + * MIT/X Consortium License + */ + +#include +#include "st.h" +#include "boxdraw_data.h" + +/* Rounded non-negative integers division of n / d */ +#define DIV(n, d) (((n) + (d) / 2) / (d)) + +static Display *xdpy; +static Colormap xcmap; +static XftDraw *xd; +static Visual *xvis; + +static void drawbox(int, int, int, int, XftColor *, XftColor *, ushort); +static void drawboxlines(int, int, int, int, XftColor *, ushort); + +/* public API */ + +void +boxdraw_xinit(Display *dpy, Colormap cmap, XftDraw *draw, Visual *vis) +{ + xdpy = dpy; xcmap = cmap; xd = draw, xvis = vis; +} + +int +isboxdraw(Rune u) +{ + Rune block = u & ~0xff; + return (boxdraw && block == 0x2500 && boxdata[(uint8_t)u]) || + (boxdraw_braille && block == 0x2800); +} + +/* the "index" is actually the entire shape data encoded as ushort */ +ushort +boxdrawindex(const Glyph *g) +{ + if (boxdraw_braille && (g->u & ~0xff) == 0x2800) + return BRL | (uint8_t)g->u; + if (boxdraw_bold && (g->mode & ATTR_BOLD)) + return BDB | boxdata[(uint8_t)g->u]; + return boxdata[(uint8_t)g->u]; +} + +void +drawboxes(int x, int y, int cw, int ch, XftColor *fg, XftColor *bg, + const XftGlyphFontSpec *specs, int len) +{ + for ( ; len-- > 0; x += cw, specs++) + drawbox(x, y, cw, ch, fg, bg, (ushort)specs->glyph); +} + +/* implementation */ + +void +drawbox(int x, int y, int w, int h, XftColor *fg, XftColor *bg, ushort bd) +{ + ushort cat = bd & ~(BDB | 0xff); /* mask out bold and data */ + if (bd & (BDL | BDA)) { + /* lines (light/double/heavy/arcs) */ + drawboxlines(x, y, w, h, fg, bd); + + } else if (cat == BBD) { + /* lower (8-X)/8 block */ + int d = DIV((uint8_t)bd * h, 8); + XftDrawRect(xd, fg, x, y + d, w, h - d); + + } else if (cat == BBU) { + /* upper X/8 block */ + XftDrawRect(xd, fg, x, y, w, DIV((uint8_t)bd * h, 8)); + + } else if (cat == BBL) { + /* left X/8 block */ + XftDrawRect(xd, fg, x, y, DIV((uint8_t)bd * w, 8), h); + + } else if (cat == BBR) { + /* right (8-X)/8 block */ + int d = DIV((uint8_t)bd * w, 8); + XftDrawRect(xd, fg, x + d, y, w - d, h); + + } else if (cat == BBQ) { + /* Quadrants */ + int w2 = DIV(w, 2), h2 = DIV(h, 2); + if (bd & TL) + XftDrawRect(xd, fg, x, y, w2, h2); + if (bd & TR) + XftDrawRect(xd, fg, x + w2, y, w - w2, h2); + if (bd & BL) + XftDrawRect(xd, fg, x, y + h2, w2, h - h2); + if (bd & BR) + XftDrawRect(xd, fg, x + w2, y + h2, w - w2, h - h2); + + } else if (bd & BBS) { + /* Shades - data is 1/2/3 for 25%/50%/75% alpha, respectively */ + int d = (uint8_t)bd; + XftColor xfc; + XRenderColor xrc = { .alpha = 0xffff }; + + xrc.red = DIV(fg->color.red * d + bg->color.red * (4 - d), 4); + xrc.green = DIV(fg->color.green * d + bg->color.green * (4 - d), 4); + xrc.blue = DIV(fg->color.blue * d + bg->color.blue * (4 - d), 4); + + XftColorAllocValue(xdpy, xvis, xcmap, &xrc, &xfc); + XftDrawRect(xd, &xfc, x, y, w, h); + XftColorFree(xdpy, xvis, xcmap, &xfc); + + } else if (cat == BRL) { + /* braille, each data bit corresponds to one dot at 2x4 grid */ + int w1 = DIV(w, 2); + int h1 = DIV(h, 4), h2 = DIV(h, 2), h3 = DIV(3 * h, 4); + + if (bd & 1) XftDrawRect(xd, fg, x, y, w1, h1); + if (bd & 2) XftDrawRect(xd, fg, x, y + h1, w1, h2 - h1); + if (bd & 4) XftDrawRect(xd, fg, x, y + h2, w1, h3 - h2); + if (bd & 8) XftDrawRect(xd, fg, x + w1, y, w - w1, h1); + if (bd & 16) XftDrawRect(xd, fg, x + w1, y + h1, w - w1, h2 - h1); + if (bd & 32) XftDrawRect(xd, fg, x + w1, y + h2, w - w1, h3 - h2); + if (bd & 64) XftDrawRect(xd, fg, x, y + h3, w1, h - h3); + if (bd & 128) XftDrawRect(xd, fg, x + w1, y + h3, w - w1, h - h3); + + } +} + +void +drawboxlines(int x, int y, int w, int h, XftColor *fg, ushort bd) +{ + /* s: stem thickness. width/8 roughly matches underscore thickness. */ + /* We draw bold as 1.5 * normal-stem and at least 1px thicker. */ + /* doubles draw at least 3px, even when w or h < 3. bold needs 6px. */ + int mwh = MIN(w, h); + int base_s = MAX(1, DIV(mwh, 8)); + int bold = (bd & BDB) && mwh >= 6; /* possibly ignore boldness */ + int s = bold ? MAX(base_s + 1, DIV(3 * base_s, 2)) : base_s; + int w2 = DIV(w - s, 2), h2 = DIV(h - s, 2); + /* the s-by-s square (x + w2, y + h2, s, s) is the center texel. */ + /* The base length (per direction till edge) includes this square. */ + + int light = bd & (LL | LU | LR | LD); + int double_ = bd & (DL | DU | DR | DD); + + if (light) { + /* d: additional (negative) length to not-draw the center */ + /* texel - at arcs and avoid drawing inside (some) doubles */ + int arc = bd & BDA; + int multi_light = light & (light - 1); + int multi_double = double_ & (double_ - 1); + /* light crosses double only at DH+LV, DV+LH (ref. shapes) */ + int d = arc || (multi_double && !multi_light) ? -s : 0; + + if (bd & LL) + XftDrawRect(xd, fg, x, y + h2, w2 + s + d, s); + if (bd & LU) + XftDrawRect(xd, fg, x + w2, y, s, h2 + s + d); + if (bd & LR) + XftDrawRect(xd, fg, x + w2 - d, y + h2, w - w2 + d, s); + if (bd & LD) + XftDrawRect(xd, fg, x + w2, y + h2 - d, s, h - h2 + d); + } + + /* double lines - also align with light to form heavy when combined */ + if (double_) { + /* + * going clockwise, for each double-ray: p is additional length + * to the single-ray nearer to the previous direction, and n to + * the next. p and n adjust from the base length to lengths + * which consider other doubles - shorter to avoid intersections + * (p, n), or longer to draw the far-corner texel (n). + */ + int dl = bd & DL, du = bd & DU, dr = bd & DR, dd = bd & DD; + if (dl) { + int p = dd ? -s : 0, n = du ? -s : dd ? s : 0; + XftDrawRect(xd, fg, x, y + h2 + s, w2 + s + p, s); + XftDrawRect(xd, fg, x, y + h2 - s, w2 + s + n, s); + } + if (du) { + int p = dl ? -s : 0, n = dr ? -s : dl ? s : 0; + XftDrawRect(xd, fg, x + w2 - s, y, s, h2 + s + p); + XftDrawRect(xd, fg, x + w2 + s, y, s, h2 + s + n); + } + if (dr) { + int p = du ? -s : 0, n = dd ? -s : du ? s : 0; + XftDrawRect(xd, fg, x + w2 - p, y + h2 - s, w - w2 + p, s); + XftDrawRect(xd, fg, x + w2 - n, y + h2 + s, w - w2 + n, s); + } + if (dd) { + int p = dr ? -s : 0, n = dl ? -s : dr ? s : 0; + XftDrawRect(xd, fg, x + w2 + s, y + h2 - p, s, h - h2 + p); + XftDrawRect(xd, fg, x + w2 - s, y + h2 - n, s, h - h2 + n); + } + } +} diff --git a/boxdraw.o b/boxdraw.o new file mode 100644 index 0000000000000000000000000000000000000000..6375decdd6c77ab4ba5b067b9f2f7e6d542a307c GIT binary patch literal 7488 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 XqoBC+M{jB|eU$%yXQDt$rd$7CPPan* literal 0 HcmV?d00001 diff --git a/boxdraw_data.h b/boxdraw_data.h new file mode 100644 index 0000000..7890500 --- /dev/null +++ b/boxdraw_data.h @@ -0,0 +1,214 @@ +/* + * Copyright 2018 Avi Halachmi (:avih) avihpit@yahoo.com https://github.com/avih + * MIT/X Consortium License + */ + +/* + * U+25XX codepoints data + * + * References: + * http://www.unicode.org/charts/PDF/U2500.pdf + * http://www.unicode.org/charts/PDF/U2580.pdf + * + * Test page: + * https://github.com/GNOME/vte/blob/master/doc/boxes.txt + */ + +/* Each shape is encoded as 16-bits. Higher bits are category, lower are data */ +/* Categories (mutually exclusive except BDB): */ +/* For convenience, BDL/BDA/BBS/BDB are 1 bit each, the rest are enums */ +#define BDL (1<<8) /* Box Draw Lines (light/double/heavy) */ +#define BDA (1<<9) /* Box Draw Arc (light) */ + +#define BBD (1<<10) /* Box Block Down (lower) X/8 */ +#define BBL (2<<10) /* Box Block Left X/8 */ +#define BBU (3<<10) /* Box Block Upper X/8 */ +#define BBR (4<<10) /* Box Block Right X/8 */ +#define BBQ (5<<10) /* Box Block Quadrants */ +#define BRL (6<<10) /* Box Braille (data is lower byte of U28XX) */ + +#define BBS (1<<14) /* Box Block Shades */ +#define BDB (1<<15) /* Box Draw is Bold */ + +/* (BDL/BDA) Light/Double/Heavy x Left/Up/Right/Down/Horizontal/Vertical */ +/* Heavy is light+double (literally drawing light+double align to form heavy) */ +#define LL (1<<0) +#define LU (1<<1) +#define LR (1<<2) +#define LD (1<<3) +#define LH (LL+LR) +#define LV (LU+LD) + +#define DL (1<<4) +#define DU (1<<5) +#define DR (1<<6) +#define DD (1<<7) +#define DH (DL+DR) +#define DV (DU+DD) + +#define HL (LL+DL) +#define HU (LU+DU) +#define HR (LR+DR) +#define HD (LD+DD) +#define HH (HL+HR) +#define HV (HU+HD) + +/* (BBQ) Quadrants Top/Bottom x Left/Right */ +#define TL (1<<0) +#define TR (1<<1) +#define BL (1<<2) +#define BR (1<<3) + +/* Data for U+2500 - U+259F except dashes/diagonals */ +static const unsigned short boxdata[256] = { + /* light lines */ + [0x00] = BDL + LH, /* light horizontal */ + [0x02] = BDL + LV, /* light vertical */ + [0x0c] = BDL + LD + LR, /* light down and right */ + [0x10] = BDL + LD + LL, /* light down and left */ + [0x14] = BDL + LU + LR, /* light up and right */ + [0x18] = BDL + LU + LL, /* light up and left */ + [0x1c] = BDL + LV + LR, /* light vertical and right */ + [0x24] = BDL + LV + LL, /* light vertical and left */ + [0x2c] = BDL + LH + LD, /* light horizontal and down */ + [0x34] = BDL + LH + LU, /* light horizontal and up */ + [0x3c] = BDL + LV + LH, /* light vertical and horizontal */ + [0x74] = BDL + LL, /* light left */ + [0x75] = BDL + LU, /* light up */ + [0x76] = BDL + LR, /* light right */ + [0x77] = BDL + LD, /* light down */ + + /* heavy [+light] lines */ + [0x01] = BDL + HH, + [0x03] = BDL + HV, + [0x0d] = BDL + HR + LD, + [0x0e] = BDL + HD + LR, + [0x0f] = BDL + HD + HR, + [0x11] = BDL + HL + LD, + [0x12] = BDL + HD + LL, + [0x13] = BDL + HD + HL, + [0x15] = BDL + HR + LU, + [0x16] = BDL + HU + LR, + [0x17] = BDL + HU + HR, + [0x19] = BDL + HL + LU, + [0x1a] = BDL + HU + LL, + [0x1b] = BDL + HU + HL, + [0x1d] = BDL + HR + LV, + [0x1e] = BDL + HU + LD + LR, + [0x1f] = BDL + HD + LR + LU, + [0x20] = BDL + HV + LR, + [0x21] = BDL + HU + HR + LD, + [0x22] = BDL + HD + HR + LU, + [0x23] = BDL + HV + HR, + [0x25] = BDL + HL + LV, + [0x26] = BDL + HU + LD + LL, + [0x27] = BDL + HD + LU + LL, + [0x28] = BDL + HV + LL, + [0x29] = BDL + HU + HL + LD, + [0x2a] = BDL + HD + HL + LU, + [0x2b] = BDL + HV + HL, + [0x2d] = BDL + HL + LD + LR, + [0x2e] = BDL + HR + LL + LD, + [0x2f] = BDL + HH + LD, + [0x30] = BDL + HD + LH, + [0x31] = BDL + HD + HL + LR, + [0x32] = BDL + HR + HD + LL, + [0x33] = BDL + HH + HD, + [0x35] = BDL + HL + LU + LR, + [0x36] = BDL + HR + LU + LL, + [0x37] = BDL + HH + LU, + [0x38] = BDL + HU + LH, + [0x39] = BDL + HU + HL + LR, + [0x3a] = BDL + HU + HR + LL, + [0x3b] = BDL + HH + HU, + [0x3d] = BDL + HL + LV + LR, + [0x3e] = BDL + HR + LV + LL, + [0x3f] = BDL + HH + LV, + [0x40] = BDL + HU + LH + LD, + [0x41] = BDL + HD + LH + LU, + [0x42] = BDL + HV + LH, + [0x43] = BDL + HU + HL + LD + LR, + [0x44] = BDL + HU + HR + LD + LL, + [0x45] = BDL + HD + HL + LU + LR, + [0x46] = BDL + HD + HR + LU + LL, + [0x47] = BDL + HH + HU + LD, + [0x48] = BDL + HH + HD + LU, + [0x49] = BDL + HV + HL + LR, + [0x4a] = BDL + HV + HR + LL, + [0x4b] = BDL + HV + HH, + [0x78] = BDL + HL, + [0x79] = BDL + HU, + [0x7a] = BDL + HR, + [0x7b] = BDL + HD, + [0x7c] = BDL + HR + LL, + [0x7d] = BDL + HD + LU, + [0x7e] = BDL + HL + LR, + [0x7f] = BDL + HU + LD, + + /* double [+light] lines */ + [0x50] = BDL + DH, + [0x51] = BDL + DV, + [0x52] = BDL + DR + LD, + [0x53] = BDL + DD + LR, + [0x54] = BDL + DR + DD, + [0x55] = BDL + DL + LD, + [0x56] = BDL + DD + LL, + [0x57] = BDL + DL + DD, + [0x58] = BDL + DR + LU, + [0x59] = BDL + DU + LR, + [0x5a] = BDL + DU + DR, + [0x5b] = BDL + DL + LU, + [0x5c] = BDL + DU + LL, + [0x5d] = BDL + DL + DU, + [0x5e] = BDL + DR + LV, + [0x5f] = BDL + DV + LR, + [0x60] = BDL + DV + DR, + [0x61] = BDL + DL + LV, + [0x62] = BDL + DV + LL, + [0x63] = BDL + DV + DL, + [0x64] = BDL + DH + LD, + [0x65] = BDL + DD + LH, + [0x66] = BDL + DD + DH, + [0x67] = BDL + DH + LU, + [0x68] = BDL + DU + LH, + [0x69] = BDL + DH + DU, + [0x6a] = BDL + DH + LV, + [0x6b] = BDL + DV + LH, + [0x6c] = BDL + DH + DV, + + /* (light) arcs */ + [0x6d] = BDA + LD + LR, + [0x6e] = BDA + LD + LL, + [0x6f] = BDA + LU + LL, + [0x70] = BDA + LU + LR, + + /* Lower (Down) X/8 block (data is 8 - X) */ + [0x81] = BBD + 7, [0x82] = BBD + 6, [0x83] = BBD + 5, [0x84] = BBD + 4, + [0x85] = BBD + 3, [0x86] = BBD + 2, [0x87] = BBD + 1, [0x88] = BBD + 0, + + /* Left X/8 block (data is X) */ + [0x89] = BBL + 7, [0x8a] = BBL + 6, [0x8b] = BBL + 5, [0x8c] = BBL + 4, + [0x8d] = BBL + 3, [0x8e] = BBL + 2, [0x8f] = BBL + 1, + + /* upper 1/2 (4/8), 1/8 block (X), right 1/2, 1/8 block (8-X) */ + [0x80] = BBU + 4, [0x94] = BBU + 1, + [0x90] = BBR + 4, [0x95] = BBR + 7, + + /* Quadrants */ + [0x96] = BBQ + BL, + [0x97] = BBQ + BR, + [0x98] = BBQ + TL, + [0x99] = BBQ + TL + BL + BR, + [0x9a] = BBQ + TL + BR, + [0x9b] = BBQ + TL + TR + BL, + [0x9c] = BBQ + TL + TR + BR, + [0x9d] = BBQ + TR, + [0x9e] = BBQ + BL + TR, + [0x9f] = BBQ + BL + TR + BR, + + /* Shades, data is an alpha value in 25% units (1/4, 1/2, 3/4) */ + [0x91] = BBS + 1, [0x92] = BBS + 2, [0x93] = BBS + 3, + + /* U+2504 - U+250B, U+254C - U+254F: unsupported (dashes) */ + /* U+2571 - U+2573: unsupported (diagonals) */ +}; diff --git a/config.def.h b/config.def.h index 2bb40e4..60acef8 100644 --- a/config.def.h +++ b/config.def.h @@ -73,6 +73,18 @@ static unsigned int blinktimeout = 800; */ static unsigned int cursorthickness = 2; +/* + * 1: render most of the lines/blocks characters without using the font for + * perfect alignment between cells (U2500 - U259F except dashes/diagonals). + * Bold affects lines thickness if boxdraw_bold is not 0. Italic is ignored. + * 0: disable (render all U25XX glyphs normally from the font). + */ +const int boxdraw = 0; +const int boxdraw_bold = 0; + +/* braille (U28XX): 1: render as adjacent "pixels", 0: use font */ +const int boxdraw_braille = 0; + /* * bell volume. It must be a value between -100 and 100. Use 0 for disabling * it diff --git a/config.def.h.orig b/config.def.h.orig new file mode 100644 index 0000000..2bb40e4 --- /dev/null +++ b/config.def.h.orig @@ -0,0 +1,480 @@ +/* See LICENSE file for copyright and license details. */ + +/* + * appearance + * + * font: see http://freedesktop.org/software/fontconfig/fontconfig-user.html + */ +static char *font = "Liberation Mono:pixelsize=12:antialias=true:autohint=true"; +/* Spare fonts */ +static char *font2[] = { +/* "Inconsolata for Powerline:pixelsize=12:antialias=true:autohint=true", */ +/* "Hack Nerd Font Mono:pixelsize=11:antialias=true:autohint=true", */ +}; + +static int borderpx = 2; + +/* + * What program is execed by st depends of these precedence rules: + * 1: program passed with -e + * 2: scroll and/or utmp + * 3: SHELL environment variable + * 4: value of shell in /etc/passwd + * 5: value of shell in config.h + */ +static char *shell = "/bin/sh"; +char *utmp = NULL; +/* scroll program: to enable use a string like "scroll" */ +char *scroll = NULL; +char *stty_args = "stty raw pass8 nl -echo -iexten -cstopb 38400"; + +/* identification sequence returned in DA and DECID */ +char *vtiden = "\033[?6c"; + +/* Kerning / character bounding-box multipliers */ +static float cwscale = 1.0; +static float chscale = 1.0; + +/* + * word delimiter string + * + * More advanced example: L" `'\"()[]{}" + */ +wchar_t *worddelimiters = L" "; + +/* selection timeouts (in milliseconds) */ +static unsigned int doubleclicktimeout = 300; +static unsigned int tripleclicktimeout = 600; + +/* alt screens */ +int allowaltscreen = 1; + +/* allow certain non-interactive (insecure) window operations such as: + setting the clipboard text */ +int allowwindowops = 0; + +/* + * draw latency range in ms - from new content/keypress/etc until drawing. + * within this range, st draws when content stops arriving (idle). mostly it's + * near minlatency, but it waits longer for slow updates to avoid partial draw. + * low minlatency will tear/flicker more, as it can "detect" idle too early. + */ +static double minlatency = 2; +static double maxlatency = 33; + +/* + * blinking timeout (set to 0 to disable blinking) for the terminal blinking + * attribute. + */ +static unsigned int blinktimeout = 800; + +/* + * thickness of underline and bar cursors + */ +static unsigned int cursorthickness = 2; + +/* + * bell volume. It must be a value between -100 and 100. Use 0 for disabling + * it + */ +static int bellvolume = 0; + +/* default TERM value */ +char *termname = "st-256color"; + +/* + * spaces per tab + * + * When you are changing this value, don't forget to adapt the »it« value in + * the st.info and appropriately install the st.info in the environment where + * you use this st version. + * + * it#$tabspaces, + * + * Secondly make sure your kernel is not expanding tabs. When running `stty + * -a` »tab0« should appear. You can tell the terminal to not expand tabs by + * running following command: + * + * stty tabs + */ +unsigned int tabspaces = 8; + +/* Terminal colors (16 first used in escape sequence) */ +static const char *colorname[] = { + /* 8 normal colors */ + "#2e3436", + "#a40000", + "#4e9a06", + "#c4a000", + "#3465a4", + "#75507b", + "#34a0a4", + "#babdb9", + + /* 8 bright colors */ + "#555753", + "#ef2929", + "#8ae234", + "#fce94f", + "#729fcf", + "#ad7fa8", + "#72d9cf", + "#eeeeec", + + [255] = 0, + + /* more colors can be added after 255 to use with DefaultXX */ + "#cccccc", + "#555555", + "#e4e4ef", /* default foreground colour */ + "#181818", /* default background colour */ +}; + + +/* + * Default colors (colorname index) + * foreground, background, cursor, reverse cursor + */ +unsigned int defaultfg = 258; +unsigned int defaultbg = 259; +unsigned int defaultcs = 256; +static unsigned int defaultrcs = 257; + +/* + * Default shape of cursor + * 2: Block ("█") + * 4: Underline ("_") + * 6: Bar ("|") + * 7: Snowman ("☃") + */ +static unsigned int cursorshape = 2; + +/* + * Default columns and rows numbers + */ + +static unsigned int cols = 80; +static unsigned int rows = 24; + +/* + * Default colour and shape of the mouse cursor + */ +static unsigned int mouseshape = XC_xterm; +static unsigned int mousefg = 7; +static unsigned int mousebg = 0; + +/* + * Color used to display font attributes when fontconfig selected a font which + * doesn't match the ones requested. + */ +static unsigned int defaultattr = 11; + +/* + * Force mouse select/shortcuts while mask is active (when MODE_MOUSE is set). + * Note that if you want to use ShiftMask with selmasks, set this to an other + * modifier, set to 0 to not use it. + */ +static uint forcemousemod = ShiftMask; + +/* + * Internal mouse shortcuts. + * Beware that overloading Button1 will disable the selection. + */ +static MouseShortcut mshortcuts[] = { + /* mask button function argument release */ + { XK_ANY_MOD, Button2, selpaste, {.i = 0}, 1 }, + { ShiftMask, Button4, ttysend, {.s = "\033[5;2~"} }, + { XK_ANY_MOD, Button4, ttysend, {.s = "\031"} }, + { ShiftMask, Button5, ttysend, {.s = "\033[6;2~"} }, + { XK_ANY_MOD, Button5, ttysend, {.s = "\005"} }, +}; + +/* Internal keyboard shortcuts. */ +#define MODKEY Mod1Mask +#define TERMMOD (ControlMask|ShiftMask) + +static Shortcut shortcuts[] = { + /* mask keysym function argument */ + { XK_ANY_MOD, XK_Break, sendbreak, {.i = 0} }, + { ControlMask, XK_Print, toggleprinter, {.i = 0} }, + { ShiftMask, XK_Print, printscreen, {.i = 0} }, + { XK_ANY_MOD, XK_Print, printsel, {.i = 0} }, + { TERMMOD, XK_Prior, zoom, {.f = +1} }, + { TERMMOD, XK_Next, zoom, {.f = -1} }, + { TERMMOD, XK_Home, zoomreset, {.f = 0} }, + { TERMMOD, XK_C, clipcopy, {.i = 0} }, + { TERMMOD, XK_V, clippaste, {.i = 0} }, + { TERMMOD, XK_Y, selpaste, {.i = 0} }, + { ShiftMask, XK_Insert, selpaste, {.i = 0} }, + { TERMMOD, XK_Num_Lock, numlock, {.i = 0} }, +}; + +/* + * Special keys (change & recompile st.info accordingly) + * + * Mask value: + * * Use XK_ANY_MOD to match the key no matter modifiers state + * * Use XK_NO_MOD to match the key alone (no modifiers) + * appkey value: + * * 0: no value + * * > 0: keypad application mode enabled + * * = 2: term.numlock = 1 + * * < 0: keypad application mode disabled + * appcursor value: + * * 0: no value + * * > 0: cursor application mode enabled + * * < 0: cursor application mode disabled + * + * Be careful with the order of the definitions because st searches in + * this table sequentially, so any XK_ANY_MOD must be in the last + * position for a key. + */ + +/* + * If you want keys other than the X11 function keys (0xFD00 - 0xFFFF) + * to be mapped below, add them to this array. + */ +static KeySym mappedkeys[] = { -1 }; + +/* + * State bits to ignore when matching key or button events. By default, + * numlock (Mod2Mask) and keyboard layout (XK_SWITCH_MOD) are ignored. + */ +static uint ignoremod = Mod2Mask|XK_SWITCH_MOD; + +/* + * This is the huge key array which defines all compatibility to the Linux + * world. Please decide about changes wisely. + */ +static Key key[] = { + /* keysym mask string appkey appcursor */ + { XK_KP_Home, ShiftMask, "\033[2J", 0, -1}, + { XK_KP_Home, ShiftMask, "\033[1;2H", 0, +1}, + { XK_KP_Home, XK_ANY_MOD, "\033[H", 0, -1}, + { XK_KP_Home, XK_ANY_MOD, "\033[1~", 0, +1}, + { XK_KP_Up, XK_ANY_MOD, "\033Ox", +1, 0}, + { XK_KP_Up, XK_ANY_MOD, "\033[A", 0, -1}, + { XK_KP_Up, XK_ANY_MOD, "\033OA", 0, +1}, + { XK_KP_Down, XK_ANY_MOD, "\033Or", +1, 0}, + { XK_KP_Down, XK_ANY_MOD, "\033[B", 0, -1}, + { XK_KP_Down, XK_ANY_MOD, "\033OB", 0, +1}, + { XK_KP_Left, XK_ANY_MOD, "\033Ot", +1, 0}, + { XK_KP_Left, XK_ANY_MOD, "\033[D", 0, -1}, + { XK_KP_Left, XK_ANY_MOD, "\033OD", 0, +1}, + { XK_KP_Right, XK_ANY_MOD, "\033Ov", +1, 0}, + { XK_KP_Right, XK_ANY_MOD, "\033[C", 0, -1}, + { XK_KP_Right, XK_ANY_MOD, "\033OC", 0, +1}, + { XK_KP_Prior, ShiftMask, "\033[5;2~", 0, 0}, + { XK_KP_Prior, XK_ANY_MOD, "\033[5~", 0, 0}, + { XK_KP_Begin, XK_ANY_MOD, "\033[E", 0, 0}, + { XK_KP_End, ControlMask, "\033[J", -1, 0}, + { XK_KP_End, ControlMask, "\033[1;5F", +1, 0}, + { XK_KP_End, ShiftMask, "\033[K", -1, 0}, + { XK_KP_End, ShiftMask, "\033[1;2F", +1, 0}, + { XK_KP_End, XK_ANY_MOD, "\033[4~", 0, 0}, + { XK_KP_Next, ShiftMask, "\033[6;2~", 0, 0}, + { XK_KP_Next, XK_ANY_MOD, "\033[6~", 0, 0}, + { XK_KP_Insert, ShiftMask, "\033[2;2~", +1, 0}, + { XK_KP_Insert, ShiftMask, "\033[4l", -1, 0}, + { XK_KP_Insert, ControlMask, "\033[L", -1, 0}, + { XK_KP_Insert, ControlMask, "\033[2;5~", +1, 0}, + { XK_KP_Insert, XK_ANY_MOD, "\033[4h", -1, 0}, + { XK_KP_Insert, XK_ANY_MOD, "\033[2~", +1, 0}, + { XK_KP_Delete, ControlMask, "\033[M", -1, 0}, + { XK_KP_Delete, ControlMask, "\033[3;5~", +1, 0}, + { XK_KP_Delete, ShiftMask, "\033[2K", -1, 0}, + { XK_KP_Delete, ShiftMask, "\033[3;2~", +1, 0}, + { XK_KP_Delete, XK_ANY_MOD, "\033[P", -1, 0}, + { XK_KP_Delete, XK_ANY_MOD, "\033[3~", +1, 0}, + { XK_KP_Multiply, XK_ANY_MOD, "\033Oj", +2, 0}, + { XK_KP_Add, XK_ANY_MOD, "\033Ok", +2, 0}, + { XK_KP_Enter, XK_ANY_MOD, "\033OM", +2, 0}, + { XK_KP_Enter, XK_ANY_MOD, "\r", -1, 0}, + { XK_KP_Subtract, XK_ANY_MOD, "\033Om", +2, 0}, + { XK_KP_Decimal, XK_ANY_MOD, "\033On", +2, 0}, + { XK_KP_Divide, XK_ANY_MOD, "\033Oo", +2, 0}, + { XK_KP_0, XK_ANY_MOD, "\033Op", +2, 0}, + { XK_KP_1, XK_ANY_MOD, "\033Oq", +2, 0}, + { XK_KP_2, XK_ANY_MOD, "\033Or", +2, 0}, + { XK_KP_3, XK_ANY_MOD, "\033Os", +2, 0}, + { XK_KP_4, XK_ANY_MOD, "\033Ot", +2, 0}, + { XK_KP_5, XK_ANY_MOD, "\033Ou", +2, 0}, + { XK_KP_6, XK_ANY_MOD, "\033Ov", +2, 0}, + { XK_KP_7, XK_ANY_MOD, "\033Ow", +2, 0}, + { XK_KP_8, XK_ANY_MOD, "\033Ox", +2, 0}, + { XK_KP_9, XK_ANY_MOD, "\033Oy", +2, 0}, + { XK_Up, ShiftMask, "\033[1;2A", 0, 0}, + { XK_Up, Mod1Mask, "\033[1;3A", 0, 0}, + { XK_Up, ShiftMask|Mod1Mask,"\033[1;4A", 0, 0}, + { XK_Up, ControlMask, "\033[1;5A", 0, 0}, + { XK_Up, ShiftMask|ControlMask,"\033[1;6A", 0, 0}, + { XK_Up, ControlMask|Mod1Mask,"\033[1;7A", 0, 0}, + { XK_Up,ShiftMask|ControlMask|Mod1Mask,"\033[1;8A", 0, 0}, + { XK_Up, XK_ANY_MOD, "\033[A", 0, -1}, + { XK_Up, XK_ANY_MOD, "\033OA", 0, +1}, + { XK_Down, ShiftMask, "\033[1;2B", 0, 0}, + { XK_Down, Mod1Mask, "\033[1;3B", 0, 0}, + { XK_Down, ShiftMask|Mod1Mask,"\033[1;4B", 0, 0}, + { XK_Down, ControlMask, "\033[1;5B", 0, 0}, + { XK_Down, ShiftMask|ControlMask,"\033[1;6B", 0, 0}, + { XK_Down, ControlMask|Mod1Mask,"\033[1;7B", 0, 0}, + { XK_Down,ShiftMask|ControlMask|Mod1Mask,"\033[1;8B",0, 0}, + { XK_Down, XK_ANY_MOD, "\033[B", 0, -1}, + { XK_Down, XK_ANY_MOD, "\033OB", 0, +1}, + { XK_Left, ShiftMask, "\033[1;2D", 0, 0}, + { XK_Left, Mod1Mask, "\033[1;3D", 0, 0}, + { XK_Left, ShiftMask|Mod1Mask,"\033[1;4D", 0, 0}, + { XK_Left, ControlMask, "\033[1;5D", 0, 0}, + { XK_Left, ShiftMask|ControlMask,"\033[1;6D", 0, 0}, + { XK_Left, ControlMask|Mod1Mask,"\033[1;7D", 0, 0}, + { XK_Left,ShiftMask|ControlMask|Mod1Mask,"\033[1;8D",0, 0}, + { XK_Left, XK_ANY_MOD, "\033[D", 0, -1}, + { XK_Left, XK_ANY_MOD, "\033OD", 0, +1}, + { XK_Right, ShiftMask, "\033[1;2C", 0, 0}, + { XK_Right, Mod1Mask, "\033[1;3C", 0, 0}, + { XK_Right, ShiftMask|Mod1Mask,"\033[1;4C", 0, 0}, + { XK_Right, ControlMask, "\033[1;5C", 0, 0}, + { XK_Right, ShiftMask|ControlMask,"\033[1;6C", 0, 0}, + { XK_Right, ControlMask|Mod1Mask,"\033[1;7C", 0, 0}, + { XK_Right,ShiftMask|ControlMask|Mod1Mask,"\033[1;8C",0, 0}, + { XK_Right, XK_ANY_MOD, "\033[C", 0, -1}, + { XK_Right, XK_ANY_MOD, "\033OC", 0, +1}, + { XK_ISO_Left_Tab, ShiftMask, "\033[Z", 0, 0}, + { XK_Return, Mod1Mask, "\033\r", 0, 0}, + { XK_Return, XK_ANY_MOD, "\r", 0, 0}, + { XK_Insert, ShiftMask, "\033[4l", -1, 0}, + { XK_Insert, ShiftMask, "\033[2;2~", +1, 0}, + { XK_Insert, ControlMask, "\033[L", -1, 0}, + { XK_Insert, ControlMask, "\033[2;5~", +1, 0}, + { XK_Insert, XK_ANY_MOD, "\033[4h", -1, 0}, + { XK_Insert, XK_ANY_MOD, "\033[2~", +1, 0}, + { XK_Delete, ControlMask, "\033[M", -1, 0}, + { XK_Delete, ControlMask, "\033[3;5~", +1, 0}, + { XK_Delete, ShiftMask, "\033[2K", -1, 0}, + { XK_Delete, ShiftMask, "\033[3;2~", +1, 0}, + { XK_Delete, XK_ANY_MOD, "\033[P", -1, 0}, + { XK_Delete, XK_ANY_MOD, "\033[3~", +1, 0}, + { XK_BackSpace, XK_NO_MOD, "\177", 0, 0}, + { XK_BackSpace, Mod1Mask, "\033\177", 0, 0}, + { XK_Home, ShiftMask, "\033[2J", 0, -1}, + { XK_Home, ShiftMask, "\033[1;2H", 0, +1}, + { XK_Home, XK_ANY_MOD, "\033[H", 0, -1}, + { XK_Home, XK_ANY_MOD, "\033[1~", 0, +1}, + { XK_End, ControlMask, "\033[J", -1, 0}, + { XK_End, ControlMask, "\033[1;5F", +1, 0}, + { XK_End, ShiftMask, "\033[K", -1, 0}, + { XK_End, ShiftMask, "\033[1;2F", +1, 0}, + { XK_End, XK_ANY_MOD, "\033[4~", 0, 0}, + { XK_Prior, ControlMask, "\033[5;5~", 0, 0}, + { XK_Prior, ShiftMask, "\033[5;2~", 0, 0}, + { XK_Prior, XK_ANY_MOD, "\033[5~", 0, 0}, + { XK_Next, ControlMask, "\033[6;5~", 0, 0}, + { XK_Next, ShiftMask, "\033[6;2~", 0, 0}, + { XK_Next, XK_ANY_MOD, "\033[6~", 0, 0}, + { XK_F1, XK_NO_MOD, "\033OP" , 0, 0}, + { XK_F1, /* F13 */ ShiftMask, "\033[1;2P", 0, 0}, + { XK_F1, /* F25 */ ControlMask, "\033[1;5P", 0, 0}, + { XK_F1, /* F37 */ Mod4Mask, "\033[1;6P", 0, 0}, + { XK_F1, /* F49 */ Mod1Mask, "\033[1;3P", 0, 0}, + { XK_F1, /* F61 */ Mod3Mask, "\033[1;4P", 0, 0}, + { XK_F2, XK_NO_MOD, "\033OQ" , 0, 0}, + { XK_F2, /* F14 */ ShiftMask, "\033[1;2Q", 0, 0}, + { XK_F2, /* F26 */ ControlMask, "\033[1;5Q", 0, 0}, + { XK_F2, /* F38 */ Mod4Mask, "\033[1;6Q", 0, 0}, + { XK_F2, /* F50 */ Mod1Mask, "\033[1;3Q", 0, 0}, + { XK_F2, /* F62 */ Mod3Mask, "\033[1;4Q", 0, 0}, + { XK_F3, XK_NO_MOD, "\033OR" , 0, 0}, + { XK_F3, /* F15 */ ShiftMask, "\033[1;2R", 0, 0}, + { XK_F3, /* F27 */ ControlMask, "\033[1;5R", 0, 0}, + { XK_F3, /* F39 */ Mod4Mask, "\033[1;6R", 0, 0}, + { XK_F3, /* F51 */ Mod1Mask, "\033[1;3R", 0, 0}, + { XK_F3, /* F63 */ Mod3Mask, "\033[1;4R", 0, 0}, + { XK_F4, XK_NO_MOD, "\033OS" , 0, 0}, + { XK_F4, /* F16 */ ShiftMask, "\033[1;2S", 0, 0}, + { XK_F4, /* F28 */ ControlMask, "\033[1;5S", 0, 0}, + { XK_F4, /* F40 */ Mod4Mask, "\033[1;6S", 0, 0}, + { XK_F4, /* F52 */ Mod1Mask, "\033[1;3S", 0, 0}, + { XK_F5, XK_NO_MOD, "\033[15~", 0, 0}, + { XK_F5, /* F17 */ ShiftMask, "\033[15;2~", 0, 0}, + { XK_F5, /* F29 */ ControlMask, "\033[15;5~", 0, 0}, + { XK_F5, /* F41 */ Mod4Mask, "\033[15;6~", 0, 0}, + { XK_F5, /* F53 */ Mod1Mask, "\033[15;3~", 0, 0}, + { XK_F6, XK_NO_MOD, "\033[17~", 0, 0}, + { XK_F6, /* F18 */ ShiftMask, "\033[17;2~", 0, 0}, + { XK_F6, /* F30 */ ControlMask, "\033[17;5~", 0, 0}, + { XK_F6, /* F42 */ Mod4Mask, "\033[17;6~", 0, 0}, + { XK_F6, /* F54 */ Mod1Mask, "\033[17;3~", 0, 0}, + { XK_F7, XK_NO_MOD, "\033[18~", 0, 0}, + { XK_F7, /* F19 */ ShiftMask, "\033[18;2~", 0, 0}, + { XK_F7, /* F31 */ ControlMask, "\033[18;5~", 0, 0}, + { XK_F7, /* F43 */ Mod4Mask, "\033[18;6~", 0, 0}, + { XK_F7, /* F55 */ Mod1Mask, "\033[18;3~", 0, 0}, + { XK_F8, XK_NO_MOD, "\033[19~", 0, 0}, + { XK_F8, /* F20 */ ShiftMask, "\033[19;2~", 0, 0}, + { XK_F8, /* F32 */ ControlMask, "\033[19;5~", 0, 0}, + { XK_F8, /* F44 */ Mod4Mask, "\033[19;6~", 0, 0}, + { XK_F8, /* F56 */ Mod1Mask, "\033[19;3~", 0, 0}, + { XK_F9, XK_NO_MOD, "\033[20~", 0, 0}, + { XK_F9, /* F21 */ ShiftMask, "\033[20;2~", 0, 0}, + { XK_F9, /* F33 */ ControlMask, "\033[20;5~", 0, 0}, + { XK_F9, /* F45 */ Mod4Mask, "\033[20;6~", 0, 0}, + { XK_F9, /* F57 */ Mod1Mask, "\033[20;3~", 0, 0}, + { XK_F10, XK_NO_MOD, "\033[21~", 0, 0}, + { XK_F10, /* F22 */ ShiftMask, "\033[21;2~", 0, 0}, + { XK_F10, /* F34 */ ControlMask, "\033[21;5~", 0, 0}, + { XK_F10, /* F46 */ Mod4Mask, "\033[21;6~", 0, 0}, + { XK_F10, /* F58 */ Mod1Mask, "\033[21;3~", 0, 0}, + { XK_F11, XK_NO_MOD, "\033[23~", 0, 0}, + { XK_F11, /* F23 */ ShiftMask, "\033[23;2~", 0, 0}, + { XK_F11, /* F35 */ ControlMask, "\033[23;5~", 0, 0}, + { XK_F11, /* F47 */ Mod4Mask, "\033[23;6~", 0, 0}, + { XK_F11, /* F59 */ Mod1Mask, "\033[23;3~", 0, 0}, + { XK_F12, XK_NO_MOD, "\033[24~", 0, 0}, + { XK_F12, /* F24 */ ShiftMask, "\033[24;2~", 0, 0}, + { XK_F12, /* F36 */ ControlMask, "\033[24;5~", 0, 0}, + { XK_F12, /* F48 */ Mod4Mask, "\033[24;6~", 0, 0}, + { XK_F12, /* F60 */ Mod1Mask, "\033[24;3~", 0, 0}, + { XK_F13, XK_NO_MOD, "\033[1;2P", 0, 0}, + { XK_F14, XK_NO_MOD, "\033[1;2Q", 0, 0}, + { XK_F15, XK_NO_MOD, "\033[1;2R", 0, 0}, + { XK_F16, XK_NO_MOD, "\033[1;2S", 0, 0}, + { XK_F17, XK_NO_MOD, "\033[15;2~", 0, 0}, + { XK_F18, XK_NO_MOD, "\033[17;2~", 0, 0}, + { XK_F19, XK_NO_MOD, "\033[18;2~", 0, 0}, + { XK_F20, XK_NO_MOD, "\033[19;2~", 0, 0}, + { XK_F21, XK_NO_MOD, "\033[20;2~", 0, 0}, + { XK_F22, XK_NO_MOD, "\033[21;2~", 0, 0}, + { XK_F23, XK_NO_MOD, "\033[23;2~", 0, 0}, + { XK_F24, XK_NO_MOD, "\033[24;2~", 0, 0}, + { XK_F25, XK_NO_MOD, "\033[1;5P", 0, 0}, + { XK_F26, XK_NO_MOD, "\033[1;5Q", 0, 0}, + { XK_F27, XK_NO_MOD, "\033[1;5R", 0, 0}, + { XK_F28, XK_NO_MOD, "\033[1;5S", 0, 0}, + { XK_F29, XK_NO_MOD, "\033[15;5~", 0, 0}, + { XK_F30, XK_NO_MOD, "\033[17;5~", 0, 0}, + { XK_F31, XK_NO_MOD, "\033[18;5~", 0, 0}, + { XK_F32, XK_NO_MOD, "\033[19;5~", 0, 0}, + { XK_F33, XK_NO_MOD, "\033[20;5~", 0, 0}, + { XK_F34, XK_NO_MOD, "\033[21;5~", 0, 0}, + { XK_F35, XK_NO_MOD, "\033[23;5~", 0, 0}, +}; + +/* + * Selection types' masks. + * Use the same masks as usual. + * Button1Mask is always unset, to make masks match between ButtonPress. + * ButtonRelease and MotionNotify. + * If no match is found, regular selection is used. + */ +static uint selmasks[] = { + [SEL_RECTANGULAR] = Mod1Mask, +}; + +/* + * Printable characters in ASCII, used to estimate the advance width + * of single wide characters. + */ +static char ascii_printable[] = + " !\"#$%&'()*+,-./0123456789:;<=>?" + "@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_" + "`abcdefghijklmnopqrstuvwxyz{|}~"; diff --git a/config.h b/config.h index 2bb40e4..3a5d94d 100644 --- a/config.h +++ b/config.h @@ -73,6 +73,18 @@ static unsigned int blinktimeout = 800; */ static unsigned int cursorthickness = 2; +/* + * 1: render most of the lines/blocks characters without using the font for + * perfect alignment between cells (U2500 - U259F except dashes/diagonals). + * Bold affects lines thickness if boxdraw_bold is not 0. Italic is ignored. + * 0: disable (render all U25XX glyphs normally from the font). + */ +const int boxdraw = 1; +const int boxdraw_bold = 0; + +/* braille (U28XX): 1: render as adjacent "pixels", 0: use font */ +const int boxdraw_braille = 0; + /* * bell volume. It must be a value between -100 and 100. Use 0 for disabling * it diff --git a/patches/st-boxdraw_v2-0.8.5.diff b/patches/st-boxdraw_v2-0.8.5.diff new file mode 100644 index 0000000..1ba0e46 --- /dev/null +++ b/patches/st-boxdraw_v2-0.8.5.diff @@ -0,0 +1,583 @@ +From 46a1124957b8de5e7f827656b64bfc3baeaa097f Mon Sep 17 00:00:00 2001 +From: wael <40663@protonmail.com> +Date: Mon, 11 Apr 2022 17:04:30 +0300 +Subject: [PATCH] [st][patch][boxdraw] update to 0.8.5 + +--- + Makefile | 3 +- + boxdraw.c | 194 ++++++++++++++++++++++++++++++++++++++++++++ + boxdraw_data.h | 214 +++++++++++++++++++++++++++++++++++++++++++++++++ + config.def.h | 12 +++ + st.c | 3 + + st.h | 10 +++ + x.c | 21 +++-- + 7 files changed, 451 insertions(+), 6 deletions(-) + create mode 100644 boxdraw.c + create mode 100644 boxdraw_data.h + +diff --git a/Makefile b/Makefile +index 470ac86..6dfa212 100644 +--- a/Makefile ++++ b/Makefile +@@ -4,7 +4,7 @@ + + include config.mk + +-SRC = st.c x.c ++SRC = st.c x.c boxdraw.c + OBJ = $(SRC:.c=.o) + + all: options st +@@ -23,6 +23,7 @@ config.h: + + st.o: config.h st.h win.h + x.o: arg.h config.h st.h win.h ++boxdraw.o: config.h st.h boxdraw_data.h + + $(OBJ): config.h config.mk + +diff --git a/boxdraw.c b/boxdraw.c +new file mode 100644 +index 0000000..28a92d0 +--- /dev/null ++++ b/boxdraw.c +@@ -0,0 +1,194 @@ ++/* ++ * Copyright 2018 Avi Halachmi (:avih) avihpit@yahoo.com https://github.com/avih ++ * MIT/X Consortium License ++ */ ++ ++#include ++#include "st.h" ++#include "boxdraw_data.h" ++ ++/* Rounded non-negative integers division of n / d */ ++#define DIV(n, d) (((n) + (d) / 2) / (d)) ++ ++static Display *xdpy; ++static Colormap xcmap; ++static XftDraw *xd; ++static Visual *xvis; ++ ++static void drawbox(int, int, int, int, XftColor *, XftColor *, ushort); ++static void drawboxlines(int, int, int, int, XftColor *, ushort); ++ ++/* public API */ ++ ++void ++boxdraw_xinit(Display *dpy, Colormap cmap, XftDraw *draw, Visual *vis) ++{ ++ xdpy = dpy; xcmap = cmap; xd = draw, xvis = vis; ++} ++ ++int ++isboxdraw(Rune u) ++{ ++ Rune block = u & ~0xff; ++ return (boxdraw && block == 0x2500 && boxdata[(uint8_t)u]) || ++ (boxdraw_braille && block == 0x2800); ++} ++ ++/* the "index" is actually the entire shape data encoded as ushort */ ++ushort ++boxdrawindex(const Glyph *g) ++{ ++ if (boxdraw_braille && (g->u & ~0xff) == 0x2800) ++ return BRL | (uint8_t)g->u; ++ if (boxdraw_bold && (g->mode & ATTR_BOLD)) ++ return BDB | boxdata[(uint8_t)g->u]; ++ return boxdata[(uint8_t)g->u]; ++} ++ ++void ++drawboxes(int x, int y, int cw, int ch, XftColor *fg, XftColor *bg, ++ const XftGlyphFontSpec *specs, int len) ++{ ++ for ( ; len-- > 0; x += cw, specs++) ++ drawbox(x, y, cw, ch, fg, bg, (ushort)specs->glyph); ++} ++ ++/* implementation */ ++ ++void ++drawbox(int x, int y, int w, int h, XftColor *fg, XftColor *bg, ushort bd) ++{ ++ ushort cat = bd & ~(BDB | 0xff); /* mask out bold and data */ ++ if (bd & (BDL | BDA)) { ++ /* lines (light/double/heavy/arcs) */ ++ drawboxlines(x, y, w, h, fg, bd); ++ ++ } else if (cat == BBD) { ++ /* lower (8-X)/8 block */ ++ int d = DIV((uint8_t)bd * h, 8); ++ XftDrawRect(xd, fg, x, y + d, w, h - d); ++ ++ } else if (cat == BBU) { ++ /* upper X/8 block */ ++ XftDrawRect(xd, fg, x, y, w, DIV((uint8_t)bd * h, 8)); ++ ++ } else if (cat == BBL) { ++ /* left X/8 block */ ++ XftDrawRect(xd, fg, x, y, DIV((uint8_t)bd * w, 8), h); ++ ++ } else if (cat == BBR) { ++ /* right (8-X)/8 block */ ++ int d = DIV((uint8_t)bd * w, 8); ++ XftDrawRect(xd, fg, x + d, y, w - d, h); ++ ++ } else if (cat == BBQ) { ++ /* Quadrants */ ++ int w2 = DIV(w, 2), h2 = DIV(h, 2); ++ if (bd & TL) ++ XftDrawRect(xd, fg, x, y, w2, h2); ++ if (bd & TR) ++ XftDrawRect(xd, fg, x + w2, y, w - w2, h2); ++ if (bd & BL) ++ XftDrawRect(xd, fg, x, y + h2, w2, h - h2); ++ if (bd & BR) ++ XftDrawRect(xd, fg, x + w2, y + h2, w - w2, h - h2); ++ ++ } else if (bd & BBS) { ++ /* Shades - data is 1/2/3 for 25%/50%/75% alpha, respectively */ ++ int d = (uint8_t)bd; ++ XftColor xfc; ++ XRenderColor xrc = { .alpha = 0xffff }; ++ ++ xrc.red = DIV(fg->color.red * d + bg->color.red * (4 - d), 4); ++ xrc.green = DIV(fg->color.green * d + bg->color.green * (4 - d), 4); ++ xrc.blue = DIV(fg->color.blue * d + bg->color.blue * (4 - d), 4); ++ ++ XftColorAllocValue(xdpy, xvis, xcmap, &xrc, &xfc); ++ XftDrawRect(xd, &xfc, x, y, w, h); ++ XftColorFree(xdpy, xvis, xcmap, &xfc); ++ ++ } else if (cat == BRL) { ++ /* braille, each data bit corresponds to one dot at 2x4 grid */ ++ int w1 = DIV(w, 2); ++ int h1 = DIV(h, 4), h2 = DIV(h, 2), h3 = DIV(3 * h, 4); ++ ++ if (bd & 1) XftDrawRect(xd, fg, x, y, w1, h1); ++ if (bd & 2) XftDrawRect(xd, fg, x, y + h1, w1, h2 - h1); ++ if (bd & 4) XftDrawRect(xd, fg, x, y + h2, w1, h3 - h2); ++ if (bd & 8) XftDrawRect(xd, fg, x + w1, y, w - w1, h1); ++ if (bd & 16) XftDrawRect(xd, fg, x + w1, y + h1, w - w1, h2 - h1); ++ if (bd & 32) XftDrawRect(xd, fg, x + w1, y + h2, w - w1, h3 - h2); ++ if (bd & 64) XftDrawRect(xd, fg, x, y + h3, w1, h - h3); ++ if (bd & 128) XftDrawRect(xd, fg, x + w1, y + h3, w - w1, h - h3); ++ ++ } ++} ++ ++void ++drawboxlines(int x, int y, int w, int h, XftColor *fg, ushort bd) ++{ ++ /* s: stem thickness. width/8 roughly matches underscore thickness. */ ++ /* We draw bold as 1.5 * normal-stem and at least 1px thicker. */ ++ /* doubles draw at least 3px, even when w or h < 3. bold needs 6px. */ ++ int mwh = MIN(w, h); ++ int base_s = MAX(1, DIV(mwh, 8)); ++ int bold = (bd & BDB) && mwh >= 6; /* possibly ignore boldness */ ++ int s = bold ? MAX(base_s + 1, DIV(3 * base_s, 2)) : base_s; ++ int w2 = DIV(w - s, 2), h2 = DIV(h - s, 2); ++ /* the s-by-s square (x + w2, y + h2, s, s) is the center texel. */ ++ /* The base length (per direction till edge) includes this square. */ ++ ++ int light = bd & (LL | LU | LR | LD); ++ int double_ = bd & (DL | DU | DR | DD); ++ ++ if (light) { ++ /* d: additional (negative) length to not-draw the center */ ++ /* texel - at arcs and avoid drawing inside (some) doubles */ ++ int arc = bd & BDA; ++ int multi_light = light & (light - 1); ++ int multi_double = double_ & (double_ - 1); ++ /* light crosses double only at DH+LV, DV+LH (ref. shapes) */ ++ int d = arc || (multi_double && !multi_light) ? -s : 0; ++ ++ if (bd & LL) ++ XftDrawRect(xd, fg, x, y + h2, w2 + s + d, s); ++ if (bd & LU) ++ XftDrawRect(xd, fg, x + w2, y, s, h2 + s + d); ++ if (bd & LR) ++ XftDrawRect(xd, fg, x + w2 - d, y + h2, w - w2 + d, s); ++ if (bd & LD) ++ XftDrawRect(xd, fg, x + w2, y + h2 - d, s, h - h2 + d); ++ } ++ ++ /* double lines - also align with light to form heavy when combined */ ++ if (double_) { ++ /* ++ * going clockwise, for each double-ray: p is additional length ++ * to the single-ray nearer to the previous direction, and n to ++ * the next. p and n adjust from the base length to lengths ++ * which consider other doubles - shorter to avoid intersections ++ * (p, n), or longer to draw the far-corner texel (n). ++ */ ++ int dl = bd & DL, du = bd & DU, dr = bd & DR, dd = bd & DD; ++ if (dl) { ++ int p = dd ? -s : 0, n = du ? -s : dd ? s : 0; ++ XftDrawRect(xd, fg, x, y + h2 + s, w2 + s + p, s); ++ XftDrawRect(xd, fg, x, y + h2 - s, w2 + s + n, s); ++ } ++ if (du) { ++ int p = dl ? -s : 0, n = dr ? -s : dl ? s : 0; ++ XftDrawRect(xd, fg, x + w2 - s, y, s, h2 + s + p); ++ XftDrawRect(xd, fg, x + w2 + s, y, s, h2 + s + n); ++ } ++ if (dr) { ++ int p = du ? -s : 0, n = dd ? -s : du ? s : 0; ++ XftDrawRect(xd, fg, x + w2 - p, y + h2 - s, w - w2 + p, s); ++ XftDrawRect(xd, fg, x + w2 - n, y + h2 + s, w - w2 + n, s); ++ } ++ if (dd) { ++ int p = dr ? -s : 0, n = dl ? -s : dr ? s : 0; ++ XftDrawRect(xd, fg, x + w2 + s, y + h2 - p, s, h - h2 + p); ++ XftDrawRect(xd, fg, x + w2 - s, y + h2 - n, s, h - h2 + n); ++ } ++ } ++} +diff --git a/boxdraw_data.h b/boxdraw_data.h +new file mode 100644 +index 0000000..7890500 +--- /dev/null ++++ b/boxdraw_data.h +@@ -0,0 +1,214 @@ ++/* ++ * Copyright 2018 Avi Halachmi (:avih) avihpit@yahoo.com https://github.com/avih ++ * MIT/X Consortium License ++ */ ++ ++/* ++ * U+25XX codepoints data ++ * ++ * References: ++ * http://www.unicode.org/charts/PDF/U2500.pdf ++ * http://www.unicode.org/charts/PDF/U2580.pdf ++ * ++ * Test page: ++ * https://github.com/GNOME/vte/blob/master/doc/boxes.txt ++ */ ++ ++/* Each shape is encoded as 16-bits. Higher bits are category, lower are data */ ++/* Categories (mutually exclusive except BDB): */ ++/* For convenience, BDL/BDA/BBS/BDB are 1 bit each, the rest are enums */ ++#define BDL (1<<8) /* Box Draw Lines (light/double/heavy) */ ++#define BDA (1<<9) /* Box Draw Arc (light) */ ++ ++#define BBD (1<<10) /* Box Block Down (lower) X/8 */ ++#define BBL (2<<10) /* Box Block Left X/8 */ ++#define BBU (3<<10) /* Box Block Upper X/8 */ ++#define BBR (4<<10) /* Box Block Right X/8 */ ++#define BBQ (5<<10) /* Box Block Quadrants */ ++#define BRL (6<<10) /* Box Braille (data is lower byte of U28XX) */ ++ ++#define BBS (1<<14) /* Box Block Shades */ ++#define BDB (1<<15) /* Box Draw is Bold */ ++ ++/* (BDL/BDA) Light/Double/Heavy x Left/Up/Right/Down/Horizontal/Vertical */ ++/* Heavy is light+double (literally drawing light+double align to form heavy) */ ++#define LL (1<<0) ++#define LU (1<<1) ++#define LR (1<<2) ++#define LD (1<<3) ++#define LH (LL+LR) ++#define LV (LU+LD) ++ ++#define DL (1<<4) ++#define DU (1<<5) ++#define DR (1<<6) ++#define DD (1<<7) ++#define DH (DL+DR) ++#define DV (DU+DD) ++ ++#define HL (LL+DL) ++#define HU (LU+DU) ++#define HR (LR+DR) ++#define HD (LD+DD) ++#define HH (HL+HR) ++#define HV (HU+HD) ++ ++/* (BBQ) Quadrants Top/Bottom x Left/Right */ ++#define TL (1<<0) ++#define TR (1<<1) ++#define BL (1<<2) ++#define BR (1<<3) ++ ++/* Data for U+2500 - U+259F except dashes/diagonals */ ++static const unsigned short boxdata[256] = { ++ /* light lines */ ++ [0x00] = BDL + LH, /* light horizontal */ ++ [0x02] = BDL + LV, /* light vertical */ ++ [0x0c] = BDL + LD + LR, /* light down and right */ ++ [0x10] = BDL + LD + LL, /* light down and left */ ++ [0x14] = BDL + LU + LR, /* light up and right */ ++ [0x18] = BDL + LU + LL, /* light up and left */ ++ [0x1c] = BDL + LV + LR, /* light vertical and right */ ++ [0x24] = BDL + LV + LL, /* light vertical and left */ ++ [0x2c] = BDL + LH + LD, /* light horizontal and down */ ++ [0x34] = BDL + LH + LU, /* light horizontal and up */ ++ [0x3c] = BDL + LV + LH, /* light vertical and horizontal */ ++ [0x74] = BDL + LL, /* light left */ ++ [0x75] = BDL + LU, /* light up */ ++ [0x76] = BDL + LR, /* light right */ ++ [0x77] = BDL + LD, /* light down */ ++ ++ /* heavy [+light] lines */ ++ [0x01] = BDL + HH, ++ [0x03] = BDL + HV, ++ [0x0d] = BDL + HR + LD, ++ [0x0e] = BDL + HD + LR, ++ [0x0f] = BDL + HD + HR, ++ [0x11] = BDL + HL + LD, ++ [0x12] = BDL + HD + LL, ++ [0x13] = BDL + HD + HL, ++ [0x15] = BDL + HR + LU, ++ [0x16] = BDL + HU + LR, ++ [0x17] = BDL + HU + HR, ++ [0x19] = BDL + HL + LU, ++ [0x1a] = BDL + HU + LL, ++ [0x1b] = BDL + HU + HL, ++ [0x1d] = BDL + HR + LV, ++ [0x1e] = BDL + HU + LD + LR, ++ [0x1f] = BDL + HD + LR + LU, ++ [0x20] = BDL + HV + LR, ++ [0x21] = BDL + HU + HR + LD, ++ [0x22] = BDL + HD + HR + LU, ++ [0x23] = BDL + HV + HR, ++ [0x25] = BDL + HL + LV, ++ [0x26] = BDL + HU + LD + LL, ++ [0x27] = BDL + HD + LU + LL, ++ [0x28] = BDL + HV + LL, ++ [0x29] = BDL + HU + HL + LD, ++ [0x2a] = BDL + HD + HL + LU, ++ [0x2b] = BDL + HV + HL, ++ [0x2d] = BDL + HL + LD + LR, ++ [0x2e] = BDL + HR + LL + LD, ++ [0x2f] = BDL + HH + LD, ++ [0x30] = BDL + HD + LH, ++ [0x31] = BDL + HD + HL + LR, ++ [0x32] = BDL + HR + HD + LL, ++ [0x33] = BDL + HH + HD, ++ [0x35] = BDL + HL + LU + LR, ++ [0x36] = BDL + HR + LU + LL, ++ [0x37] = BDL + HH + LU, ++ [0x38] = BDL + HU + LH, ++ [0x39] = BDL + HU + HL + LR, ++ [0x3a] = BDL + HU + HR + LL, ++ [0x3b] = BDL + HH + HU, ++ [0x3d] = BDL + HL + LV + LR, ++ [0x3e] = BDL + HR + LV + LL, ++ [0x3f] = BDL + HH + LV, ++ [0x40] = BDL + HU + LH + LD, ++ [0x41] = BDL + HD + LH + LU, ++ [0x42] = BDL + HV + LH, ++ [0x43] = BDL + HU + HL + LD + LR, ++ [0x44] = BDL + HU + HR + LD + LL, ++ [0x45] = BDL + HD + HL + LU + LR, ++ [0x46] = BDL + HD + HR + LU + LL, ++ [0x47] = BDL + HH + HU + LD, ++ [0x48] = BDL + HH + HD + LU, ++ [0x49] = BDL + HV + HL + LR, ++ [0x4a] = BDL + HV + HR + LL, ++ [0x4b] = BDL + HV + HH, ++ [0x78] = BDL + HL, ++ [0x79] = BDL + HU, ++ [0x7a] = BDL + HR, ++ [0x7b] = BDL + HD, ++ [0x7c] = BDL + HR + LL, ++ [0x7d] = BDL + HD + LU, ++ [0x7e] = BDL + HL + LR, ++ [0x7f] = BDL + HU + LD, ++ ++ /* double [+light] lines */ ++ [0x50] = BDL + DH, ++ [0x51] = BDL + DV, ++ [0x52] = BDL + DR + LD, ++ [0x53] = BDL + DD + LR, ++ [0x54] = BDL + DR + DD, ++ [0x55] = BDL + DL + LD, ++ [0x56] = BDL + DD + LL, ++ [0x57] = BDL + DL + DD, ++ [0x58] = BDL + DR + LU, ++ [0x59] = BDL + DU + LR, ++ [0x5a] = BDL + DU + DR, ++ [0x5b] = BDL + DL + LU, ++ [0x5c] = BDL + DU + LL, ++ [0x5d] = BDL + DL + DU, ++ [0x5e] = BDL + DR + LV, ++ [0x5f] = BDL + DV + LR, ++ [0x60] = BDL + DV + DR, ++ [0x61] = BDL + DL + LV, ++ [0x62] = BDL + DV + LL, ++ [0x63] = BDL + DV + DL, ++ [0x64] = BDL + DH + LD, ++ [0x65] = BDL + DD + LH, ++ [0x66] = BDL + DD + DH, ++ [0x67] = BDL + DH + LU, ++ [0x68] = BDL + DU + LH, ++ [0x69] = BDL + DH + DU, ++ [0x6a] = BDL + DH + LV, ++ [0x6b] = BDL + DV + LH, ++ [0x6c] = BDL + DH + DV, ++ ++ /* (light) arcs */ ++ [0x6d] = BDA + LD + LR, ++ [0x6e] = BDA + LD + LL, ++ [0x6f] = BDA + LU + LL, ++ [0x70] = BDA + LU + LR, ++ ++ /* Lower (Down) X/8 block (data is 8 - X) */ ++ [0x81] = BBD + 7, [0x82] = BBD + 6, [0x83] = BBD + 5, [0x84] = BBD + 4, ++ [0x85] = BBD + 3, [0x86] = BBD + 2, [0x87] = BBD + 1, [0x88] = BBD + 0, ++ ++ /* Left X/8 block (data is X) */ ++ [0x89] = BBL + 7, [0x8a] = BBL + 6, [0x8b] = BBL + 5, [0x8c] = BBL + 4, ++ [0x8d] = BBL + 3, [0x8e] = BBL + 2, [0x8f] = BBL + 1, ++ ++ /* upper 1/2 (4/8), 1/8 block (X), right 1/2, 1/8 block (8-X) */ ++ [0x80] = BBU + 4, [0x94] = BBU + 1, ++ [0x90] = BBR + 4, [0x95] = BBR + 7, ++ ++ /* Quadrants */ ++ [0x96] = BBQ + BL, ++ [0x97] = BBQ + BR, ++ [0x98] = BBQ + TL, ++ [0x99] = BBQ + TL + BL + BR, ++ [0x9a] = BBQ + TL + BR, ++ [0x9b] = BBQ + TL + TR + BL, ++ [0x9c] = BBQ + TL + TR + BR, ++ [0x9d] = BBQ + TR, ++ [0x9e] = BBQ + BL + TR, ++ [0x9f] = BBQ + BL + TR + BR, ++ ++ /* Shades, data is an alpha value in 25% units (1/4, 1/2, 3/4) */ ++ [0x91] = BBS + 1, [0x92] = BBS + 2, [0x93] = BBS + 3, ++ ++ /* U+2504 - U+250B, U+254C - U+254F: unsupported (dashes) */ ++ /* U+2571 - U+2573: unsupported (diagonals) */ ++}; +diff --git a/config.def.h b/config.def.h +index 91ab8ca..7bb3ff7 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -67,6 +67,18 @@ static unsigned int blinktimeout = 800; + */ + static unsigned int cursorthickness = 2; + ++/* ++ * 1: render most of the lines/blocks characters without using the font for ++ * perfect alignment between cells (U2500 - U259F except dashes/diagonals). ++ * Bold affects lines thickness if boxdraw_bold is not 0. Italic is ignored. ++ * 0: disable (render all U25XX glyphs normally from the font). ++ */ ++const int boxdraw = 0; ++const int boxdraw_bold = 0; ++ ++/* braille (U28XX): 1: render as adjacent "pixels", 0: use font */ ++const int boxdraw_braille = 0; ++ + /* + * bell volume. It must be a value between -100 and 100. Use 0 for disabling + * it +diff --git a/st.c b/st.c +index f43cfd3..baa2bed 100644 +--- a/st.c ++++ b/st.c +@@ -1214,6 +1214,9 @@ tsetchar(Rune u, const Glyph *attr, int x, int y) + term.dirty[y] = 1; + term.line[y][x] = *attr; + term.line[y][x].u = u; ++ ++ if (isboxdraw(u)) ++ term.line[y][x].mode |= ATTR_BOXDRAW; + } + + void +diff --git a/st.h b/st.h +index 519b9bd..07a7c66 100644 +--- a/st.h ++++ b/st.h +@@ -33,6 +33,7 @@ enum glyph_attribute { + ATTR_WRAP = 1 << 8, + ATTR_WIDE = 1 << 9, + ATTR_WDUMMY = 1 << 10, ++ ATTR_BOXDRAW = 1 << 11, + ATTR_BOLD_FAINT = ATTR_BOLD | ATTR_FAINT, + }; + +@@ -113,6 +114,14 @@ char *xstrdup(const char *); + + int xgetcolor(int x, unsigned char *r, unsigned char *g, unsigned char *b); + ++int isboxdraw(Rune); ++ushort boxdrawindex(const Glyph *); ++#ifdef XFT_VERSION ++/* only exposed to x.c, otherwise we'll need Xft.h for the types */ ++void boxdraw_xinit(Display *, Colormap, XftDraw *, Visual *); ++void drawboxes(int, int, int, int, XftColor *, XftColor *, const XftGlyphFontSpec *, int); ++#endif ++ + /* config.h globals */ + extern char *utmp; + extern char *scroll; +@@ -126,3 +135,4 @@ extern unsigned int tabspaces; + extern unsigned int defaultfg; + extern unsigned int defaultbg; + extern unsigned int defaultcs; ++extern const int boxdraw, boxdraw_bold, boxdraw_braille; +diff --git a/x.c b/x.c +index 2a3bd38..bf6bbf9 100644 +--- a/x.c ++++ b/x.c +@@ -1237,6 +1237,8 @@ xinit(int cols, int rows) + xsel.xtarget = XInternAtom(xw.dpy, "UTF8_STRING", 0); + if (xsel.xtarget == None) + xsel.xtarget = XA_STRING; ++ ++ boxdraw_xinit(xw.dpy, xw.cmap, xw.draw, xw.vis); + } + + int +@@ -1283,8 +1285,13 @@ xmakeglyphfontspecs(XftGlyphFontSpec *specs, const Glyph *glyphs, int len, int x + yp = winy + font->ascent; + } + +- /* Lookup character index with default font. */ +- glyphidx = XftCharIndex(xw.dpy, font->match, rune); ++ if (mode & ATTR_BOXDRAW) { ++ /* minor shoehorning: boxdraw uses only this ushort */ ++ glyphidx = boxdrawindex(&glyphs[i]); ++ } else { ++ /* Lookup character index with default font. */ ++ glyphidx = XftCharIndex(xw.dpy, font->match, rune); ++ } + if (glyphidx) { + specs[numspecs].font = font->match; + specs[numspecs].glyph = glyphidx; +@@ -1488,8 +1495,12 @@ xdrawglyphfontspecs(const XftGlyphFontSpec *specs, Glyph base, int len, int x, i + r.width = width; + XftDrawSetClipRectangles(xw.draw, winx, winy, &r, 1); + +- /* Render the glyphs. */ +- XftDrawGlyphFontSpec(xw.draw, fg, specs, len); ++ if (base.mode & ATTR_BOXDRAW) { ++ drawboxes(winx, winy, width / len, win.ch, fg, bg, specs, len); ++ } else { ++ /* Render the glyphs. */ ++ XftDrawGlyphFontSpec(xw.draw, fg, specs, len); ++ } + + /* Render underline and strikethrough. */ + if (base.mode & ATTR_UNDERLINE) { +@@ -1532,7 +1543,7 @@ xdrawcursor(int cx, int cy, Glyph g, int ox, int oy, Glyph og) + /* + * Select the right color for the right mode. + */ +- g.mode &= ATTR_BOLD|ATTR_ITALIC|ATTR_UNDERLINE|ATTR_STRUCK|ATTR_WIDE; ++ g.mode &= ATTR_BOLD|ATTR_ITALIC|ATTR_UNDERLINE|ATTR_STRUCK|ATTR_WIDE|ATTR_BOXDRAW; + + if (IS_SET(MODE_REVERSE)) { + g.mode |= ATTR_REVERSE; +-- +2.35.1 + diff --git a/st b/st index dccf9ccdb3aada807ab4e01f6043b54e9a34a1fd..b6b886373032c38178fc3059f91825f19a18ec9a 100755 GIT binary patch literal 103000 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 DY>K-Y literal 94384 zcmbrn349bq`aV9BOhbSS9ge|>M1l@V5F{$m3@|za6X{^U$f2wPqR}jPBFq3Pki<@e zwjIFxSPyh})zx*uW4Y9vL*t*W<N@mMk~c8kZ7YUyoh2Pzx> z$LKs!U(DSWhe-Uz+yoO^;^8u#Wf>p%Tjmi?%G0~JW=6VpvKaY4?EPu=|Jt}9q-AosA zQ={}THy@zvP!2P3JjbGWMMC2v5vSjAjsXWBJ{Nbga+++rMbDTgFE7`)}*Q`CWF1sT$Hp9p<6D(PsFFt#6a{qQa_TS}xru3G>v&KHr@3?$vi=zIPpy*Pt zm(`+_l>6f%^@_SKp{iBPNX9dWiu8RJPr0`TKH_PN4>d&a*Y_!6W*e#(_z7Z46)G2a zCNaebtq}MDVpdCNnZWlFvrB}Q3VbIqt146|@L1yZ#4`jQMaOHwI7#51#GQ#P0(T{ri5t%$pl?TFHczNQ;5gzg#PtHV zTmsycxLn|4#NCKj2z-#3O%qxs@LpmE@lt_z5ceQ16nHc7MZ_}%-awp8JXPRI;){uM z1ztl3#N`6dB)*(@g}@IGJBgPGd@pfd;-v!LNqhxyp}=E_uOyx!@F?Pb#8U+x zPMk)ZEASBF{=^=EuO+^UI9=fW!~=*^1-^`UAaRnwJ&CU-wg}vnco1>p8PWd4>BJ2J z#}QvcTrY4-PvC2b%LP70d>!!$fe#W7CSE4+Ug8Ymr2_9DzMi;H;LXH05YG^J192wt zRDmmrUBtNpuO%Kr>=F1=;-SRp0)I$6j5t-`cZuD^Ndmu4d?T?%;OB{NB5ph_+Mn1% z+#v8%#2j=(^#VUZJe;^(;F-iDh*t>w0I`pFnZWlFXAv(I_)g-Ri3G^4=2th&J}nF@hD=Cz}FIUb_=Bo+@CmyI91@wh({AA3EY!-46#MvuEe(zH@1lO zC(b2q5IBzbHsX4LTP_A3OI$ASG2(H=D+E4BJf3)&zFMllK4-=xdN{xzLVG^@TbIg5vL3MA@L;QRDs_mo=lu1@ax1=h%Ev?Pkc9V z<0;Yp#8ZhI1b&M69^!g|pCG=MxLn|w#CgOk1b%?{KH_Bp-%ETy@lt{BB+e%;6nHH0 zpNVG(Jc^hTP-v>a!-=O8=L$T8_yJ;%z}FH#NSrQkf8vLTQw6?^nATG$N#LHuj}Th~ z?n?Y9abvS+f8rU$4FbmzKSo?Ha7!}qOyY8Zj}gxzULo*7Vn6XRf%g&zh?feygLpP^ zp}?Do=Mc{jcmwfV;;8~x5 z*dp-r#0A8SCq?@c7ZNuJ{1kB!alOD#5I;p+F7QlZm3W1~4-gj0uLwFh;s!VLi`M|N8oFTb>eh^`x8G)oGS2T#6jXDV7UKJ zf3%^lIh3t!nxbfX74?_K-0@l0lwY%6sjYodhq>e!+RbYDJ^yOCH;ZhskiP0XT)Mi@ z>$NCuKK+g4J@YSgj~;BbSOVQ9DB6DG2{dYrG!!A$$yNU&KhlSvIiG(UpWGXxxZZxK?cb>f zJ(igcm+jvcdJ+d(wplFhNPCNAPFijIv#8Uxe5{T`>55tyK05_2g5+1q<(1`%S{`4e zU!=Aa%=u|G1hZ7>(xN+Hkmf}c-SHHCdiNG3uTIf6Diy!lnta!uum+qZoK`^ z+xk(TXiN=sP}GA-s$mTr1*aiM57PTfB6DvG=0;r2@|c#ArnxuL-zbkc<~lboTglt1 z1Z^*|`qR!S!JKQYO2wWyRy_ph0tU6VI0^#q<9R;6?_$8zCFE8wUZ`|-3H>qscCw66nds)+M@bVoJ3x>QA? zJ?9qLKZFNmxIwo+1fMVHjkr#E0afy_39?y;Hn*S1Ea&72W4dQ1UEFMS~4G zcrB7B@wN?$=5r?N>9sy57b8$&|8?0=;uG?i4N6{VuV_~ONg_v$@# z>3cH@jUIdgVcB_Sl!^oKO5O$(C|-<|Z5p|aSILv!L{f!pMf+qtPZ)3O+~i@=2ZEW; zy4S1qP^_nP>ET#5^J+p}5hbd>mhETJdr+XWShKWd4&_R$<|O`yN#w%(Q!dQE>jn9L z41TTomuxQhF7y|KjMG!zzYTS98WR3XRLyEY-SHuwu6k{gJf=>w2La@qN=W`8t2n*eg66y9g1-k+~b-RXV_^-M|S9d&# zXKNNy07Mp(87Dm`DClpc@Wd!?g6EGyY(?MrqYzaH3vHsBV3$YloRe3P9rRdpf{8S@ zEV^wWMh*W!wONvjUq`@jz5Bw_%xX@0Zs!22*^`W(O_9iIwvE^r3VLm>`uW!l*T>7Y z;d+joaI@}{6Sa0)eTO<o4&)#6ppL;{^yTyr3;Qna(#+6e_iGJURu$Mky5!K~wPiO$BBSbe4t4vnEom$|6p7SIKf;ULBH{PMNUjf( zS5CGR|K#^(YiEoTMk!mNq-hk%!^jsA`Tm8G4>R&|M27sRKg2J*Fn%WE z;~76*#Ao~&@ns!Zq;FXy(U4WO7*H@hYV}IV=76Ux5WlNx+)~p&{wb?!AQmtIkhgDk+pjToNmX)C2X%j7NaANdf&GIT{?0?>ZSxz5Z zspS2jXq!0e%d6&fw&kC!wZ!?SL(hi}ECI>x2G-Pf^uW=Av7!k*!E`!q|!V0qPR9s>IXvMX2=H)rT`<#jT&>ZboV+52)(Q0xq zTy?{AfS#NLl39Drq2ShU&~;8@ErzU<#Gs+%9aPjqsEz%$@JH&<)WB!q?^XBj*{I_M zJ;w=s*LE9sgDb`aSIWrXLlX*4-z`>I_gia za%EhB@Pd=3JMW0g-Ie%&>B?`?imh_F&ngJq9Ikb_Ps0}tR^2B6BL2R$(zo!K^6zF4 z{`8C=;M3i;cp9G`MKRbY+F9*5jBld?6t&VTh-121>A9#+UMVqua}}jY?;+Ty^?eeK zHc%5!ozIja@l^3N{)stOb>9K6#ow`38UYVS13hB`e7buiX|6^7qJ=J44kT+iXlcm- z3YxIbBb8VC0ZW2FLdkl$YL_C~r)P8{x0F*O@f>&>PoM5?fTOuv_{?a=)93?H zeA>V|M!Wj%hZE@Rb7gF!N6K;?@xwYV18`N#qpP(VUHXuqN*+^%0ckOYEOnkU(IUUd zO1GA4(vygWawVHnsaTU33o#Ic5324dh_ePZhCf!_kHQ;~i>n|n>TffUS${4mAAY&y zLqrHGMj8U_YMwV=w|@X1)-%K{^A@=3*C!=Wb>-k0;Fr`HgbdDfx|x4t1_`a1!bQHrKKF?TyW_N5g## zE9U%gPu=kalDM`OB&~s9mhdyKx`Nk@3Bt5Q{JnaS%VZG0-I zXsuva@wyJrn+UC01Bx7N->CRrnLh0>mU9NiBdm+phCjP-59dG6RR1~Di%b;N=CnnN zRWK4c-Hn_SJ<(ZfOJLX3A95!8Tqg>)Yh`N%sdZ=WqaG_s? z$$Wv%njK#Ze`ty%J>@Fok#?G6;BooZUB006DsM2yi5l7H6HTWb7F!X9bjKX#yTi3%{`!+YX}iz@#c0DerlNLMPq_&+u9VSu`g@~v zC5J>YWS{#o1ZlgBi70`XrzTg4DMWXtAj0^WU3zoWPB`19p5hUEzMk}q<0F8X_r1TN zD(t@iVr_^g)Lq+{wo9$CniaQ(`sG;1n8d5lEuUS0*2>AN92K-xv*EI_qD$M5omQV+ z(I6`D4at%Y6urw6~XGm{`*b#y;zz>UGdUiOLxOH)hBo!Xf zSp;SjNL6Y{HA7BN5DZid*}{+tgs5(N z3UZN)KL==24aP4AIXySxpdXR1AX1moy{t={R#6d(gE?GM9_XFrIuURe4ZRp_1A(^5 z|BmG0JY8Dhfi5fP{~ytl8)?%_-(bHYTxx6%!TGXuYBh{!-MtJM3ggFk`B#MZ(YvWF z-Q|UELI9ybo*z7xQV$SIVMi%o+}Uzm`(#zcpo2EvsCO%Bp*-@E=MhrWascZJdGRCg z2cJqt|5BT;ne&`h4sA5Ri)zt9wU38%apBL@zPG@0HvA!i0|<5lC57Km`woWZNce>) z^%Xz~;S#lP3Opx6k7HmhU24kTfRk}k{vY>43#TAzT)upyTuzg!Z^9JaMcM!dd;Or( zq_x67SNK1LU&(W*bSn0md)26r?|_YiJ2`|-1l>^SUIfea}Q<6)8Q(?VrK(gFi%o~K4+@f{8fVEu`w(*Ni$#!80EWA7?#t>M;+7JGQOe*bF?JP z8sq9cC}gJ59;;NWDasaX@I)JU5a-hiwy9P38*X9JeGQJf`&odi+zar;=C>~WgPwA$ zyu7*MKpf6_jKyFeg!1VFCnAz2QFqv<lskOa!JP;Yi3FtRl^ENF5$tEqK2`s3eP$W-US`GL+tW9 z6X#vjG(xia+r!F8iVJk~*4UgKM`LkjrQMRP)+C6sEkSzrVF}#?#YuoAU(%a3+Dab& zPRScM3^JG--qOQDoB`>&G$>kxP$p|IPr67DHP}Ll#1N=w=mGd*=a1MzqL}hi6A;La z@W3JLnRB7o6YGm?>$>nxT1d&v8nuZd0<`=E2MSQCWXNj??-^%wprHiyz_u7R$!d+x zR2OtPtPtPB#6$P%pJ8HD31ylA?P;S-d!TMmrnP38vS(VAX)i{mah}4+TmA>N({ug= z-%|fHJl|YB8Trb-ZKYftVLWJ`39WsH<qr!vuvoJpCu1>4u|If?OUeWcOhc_JdFAhAe{GfFV}zI!boLHPvo5*qBBbcI zgd~_u?CS%)Jy0powtId8ojW39!}dP3U$(ZNdl=eb1$OEL^zdx!&e7QN%>RmaLY1_g zqxCCBm;BtOLda+gWFUu1e85${e$yaF$i}6Brl|?8aQUmxGP4HZ#TM z?8$6wNtJB&_k|Uv+Mjn>Evco4L9J+84VP$y!0oltxMtc|$Oa3i;!oizg;KCC%!)4r zXRTI~mS%7+Q0&+A@_yUT`SW z>AQix=ja>A{-<9j`gGZA4Ebq$n~--HvK}FVfUPC6TP0{_rJBj!BhlYliS&$RUD206 zV(4Oon%0D9hwr|RL5PO=xD~RNj89r2cZZF4TOoR|#dx6=b|x$a%+Z?I5jE7TIjs+3 zldyk59Y<_qMb(sLK@PK~AEbfuGt!0Lp{mcv5xM^&LO<*OYdr8oAe96YFDL zQ8IK7S~AI1#(gLjYQZhkrLuGdih~|`b(a{m6RIBmp0j|du(RERs=O+x zq=hFt<(QW10*%>NsE>Bl2fh|rlcTh{`KZXf_N}+ zm<;+E-A#t}7`6c;F&N^8!+~qDle{TAZs6A%XNAPFo9ld zWmo)S%gHmcEB42W>fzN4^sb#s{J@c*R-)$J6M{h&gknrao0zGw7_}-P+qE-*`%2*8 zgZY{;e*zCogw2_U%LKi|#!HrV!khL(wzZmBW@|s^#P{8W&Owu+4u^0&f;2upmG9k`586P27cfW(Du^l71 z?tU6xc~yqWaC;G-jsiZV`FNs3K0!xbIdC>z7sdsT{6;lwaGS*|zg_O@Q!UnUKJ7l| zfY_*!gf$m$w50mrab{^}yxLYz@gccn3MFJ^$j{D%*SH!In8WF}@NqG>jq{zn%I{3V z0wNjBlj?)VskwKf|K#Hi6dc?wz!N3iN+A6pfPFKaMFYQsgYH6;B6e@brycd-05ugh z5>()2ycJ^(($D^guD4}$-mhqOD6IQFCKdN!;-OfBaQIaSm@d_K zVlFmSVbK-q2E|n`tK=U&i>ws}m+tt$q+6W~UfA{^`o@MO(Yi#HiJlcm6DqqUTA17Hp3JyA)p{dMU&WT;8^wyWi_;re+4{kwxhz+h1QF^YSt#)7#u=U-F+1tqZ(^c45qw?AVkH2 zQi*pb0m3dYlIm&WWOM-xuQa=4DyH?&1{4-&B?B=V&&BBRFg(UP=r^HH7?Cj<*lcok zrqCM*tT+;{9u|%U7yQDM#>I`*G|bYrX_AbWfNz21Ho(Z%G2W9F8ng)PozYn@3DHdZ{*@Q+}!yfE^xyKP}+&OPQe%)Q*D$> z@_{~py8aceSKAdFx3jP%M_%|6PK$8r7IFSC_h*hR0Yyn6iM7W!GWV1hXmfz zp)94RMp_Fkuzo?B@b{v1!qenZD^AMfl9xfEo=cL8gYYQ9Kj8wOR7{nM@tWmVYO&qH zSnb!fD3hqJ+gM$CqEm5g_kXOcHPse+YsEo-%Q>aCk|Bybq7rwM!7Vt`s%1P*wt1U1 z+e_cv=fy>$b3^5l0LThmLsky~U$2~1gW(}`50h%!<(1Y&QZ~~BAA?km!)fpqRhmWo zQC9OXrpYUBvo3P<1(9l`4x1n^EJ$D=H*Fhsc_8Wb@M>KuM@p`Fu0orM>4vs>JOx^bwVs?wyi&Xos@TZ1; z$DpjbThP={^aB7;+vg-y2Gn%T2G-SiwS6AD0wG>)!(D46K*LyYv@73QSyC<*T=E#) zIb4Al)y<+~%l>6@=l#Fe5vseFQm`H^WS}7c8-JRk5T26qg1w;!1mnj9H0$hs_~WI^ zkw~k>ohT+A$NAJFC`Y!T916egK80r*C{MsO(n#4uP^Pq@>;iwhv;|Kr!KiqKg@lKM zP`_VESjdG~g3#X|ca7u{zB7q2;8u{HpT%|eUx1C3HAu4* zX+Ur%33N$?gF%9Mhh__I^V?+m82n=JOlR`@nB{FGa!e%AX2|xv=E#8`>%2C}Ze+5V z5u`L;V44)BaXU#7L7Fp~I~jiC36|9=GED&H1?IcO7Vhhi2HIQlhQxvTbHwS=0zBD7 zM|JnxO#BYUOI>=EarP;Asv+FimV=+3LU6`yB)&a@tJ`L+`av9eg;7#IqR_%Add4L9 zz|N5i*CZQFvKb%~$1{m!a>3sHK*Wb}*WvILh~WuNh@J~bI+Ddek<(yAvP@U=dgaY5 zk3$Ie3&M5jUuII4=R0`K9`k!MrLO{}V5{Jof_={JvsFaChd^DLfv2bqGyX-!uR?sG zdre=_c|H`xp8%9Jp~`>PGX4&~?w*5$#`V~N4J{HBOWIJVf?@(GhU^7JzMy!x4aGfz z;&M>9+yn8HmkbBZPvj`-SYtccie}&V27Wzbu}EKxXK;Kb)Wj&HVe(f& z{h^@70DvKrqvCS7Ap%`m(uR==|H*CGJ=*gfl1>vzA8iw#2R~jx(A}3J7h?^~;t)4M zP|z&I=ys+z3yLPN82eevmyt`#bi_obmXeE8v?5Uqbjj_{PA8V?6kL@TG>Ihi?dcp75glBKsit3N4{0A`nyUdl}Ff zaaRJs0dQKpa}AIHa56N`Bs+aM4g^Cto8U0O?vTp_djQHp0|<)xZUHEWgf0cbG^_0> zV_A%QFsZO<+9Oz#{tcSY8CW*3-y$fW^fv~C5zr>d)zKv1BUqE3CIzqXOnq)DSl_M8aX77ZH8paml6JqC4F502FweHc1TguW3CouoNr zhK@yOsba3tj-88|g@#c_oyCSh=vu_4Cbw+CW=&lKU}*jQ9i*h3?P#KraG! z>(X*~VjbnL@av9u7_vFgPIp|1JT=F2z|q0_sc&K%kKk!BwtfxS?*Tt^kl9Z-R-KmQcWrl2i&ae7UdmBWi-JE*E5XUcAhj4DFtLM)TOJG4>h6oc!B{*}^vB|} z$1$#cT6lV-zjNVfPhgVTcPpaH!sAr?MtD5oQF1A6s+1Kwp()qk9VzZ6_QE7qfmmfA zgLPX;qk+qlPw^sZxF>ekXPE7A?^)zN84`u1!qb_4xmKEoMEnaqjcmQzcdEM&Lkg}9 z@6)z(wJnTt979-)4nM$RsIm@CIg^U5rakC=W{(^y9YMhS&wSc()x8^DOToJE9$dW8 zq|3l7Gyt5{x>QZ-PIRe29!+XTlmtX$yAKb;d0iRnFx7ZSlN!M%G#UCYuiT;@?58>Q zAwaE5(4>uw*>;Rd^gg^prGMk;?}q73cRvrWv0*)UU52Eh6?FG=49xurfp!E&*F--9 zo`WW$-X7|9JSyQ8?qLMKw#AdnM07H|QiO^}GH*SV0 ztzk$CV~Sye5oU53P1*VqiWD~z_5l0L- z7TH&aOM|7ano2VeWimV8<<;O;hoy<0F`zf=<}y5u4Cn$G>6mX< zeSayuc)JRV-zKRe!mWO+{0|}2DwlkLR87+F0CE0g-BAyZF1>}P7P4+~RmjhN2Lhax zGsK9OR={;NFSwyes(=r#+H6O3nJ8<{4mPL#d4wbXg>=mP$J1-(uiIw<;I1&9LbYK_ zUxRT_OId;t^(qb6jiQ+|VLjV2nay_ui4=vJF04!VzE-4VbOL#At!3=>Sm7Ia5NcK6nf#EJmd-r?zP zv4yTeq*@;5s$8%iA*rb%s_4(o$m#I+ddA(%;ss_=h&`ZMN$rJ#g2bma=#H+4yvmV@ zCtl?B3LQcv=Wkf0?#RVRbF|ePqL7UPdkeXvi{B3NKC|b*X%FV<^un!U@rHi z=6s1m_VZcw2CuTan5l!AzC8}L`szh6-z=%<(P_t&qM8P;7oAW?pz*J@P$it9cEOP% ziqX#;ceoc|T+VvUG|qe}2EQ`t8&7oLaAJtmYT`47gMpSYOz=j2Wn^m}B0H``U|OZ- z=nWU|)JZ4|u1H|_6}S0Ukj^Ad{|XZBi(N9reIe=zYG1(y{Fn4A!8e?R;Egwv0*+7% ze8#g#qi3oSn_NQ7n?HrZebLDjOBM6Dl=nfg0UKykG`wWq=03{>@l}CF-TqV$D2?|f z+)d7|sAz6yo^)>63t2n7xK8ly(0v`Ov*6=auQ-ivb1;p=ZsQ5m77e_6&@FZQ;0w7a zueS2L+~gLRtlQ=PPSky!Tzn-s86z;P<024`@=JOk0M`Xrz}Oagm2vMEX3JdO+A`1V zzTa-8%bSN%k)@l&UOv_0-G1__}G?N((K?|dyyG7Yu z-^y+%*co49hJYC1Z^i2xd{69mTtn#4Bz4ATA(8?~O_Bspg1;NCP6YlGzDN|;c>fGq z_7_}~;GxMSC<(=`f?%cCv4H`Io4KnfebuO-uP>VSCmfq@!%II(8g6GCF+#``@BJug zMt0gdKW^Ua#Nn$wP^JVYTg8cb*%(M^RDvwL)q6YNUCGo=tJ{t-T#o+V;{qlkUS- zx@DjX|A-SqoK&`zm;-1!goeE3{}=LJyybt&`|p28g~Yc^7Uw2OG7 z?1CW>ga5W;m^FEmj#r`Pf`Kkom_exw>ZgjJ56z%-24ya0Hhs@Quft$)Yd4r*(oQ`2 zhXH*IyfwBY^L??-Zo=|nuI`4e7~&pWjdE+%xE}lo z;IPSo%e>kV_3Yq+8~Jv@9;5+~7jA<`GqB62?Dtp}IELLVFYW^ohdn44+u?>*BYs$} zT%1Vokw`U7F1{w-VhLtF&%K?6FEbD?43`AyIrNBm_1&h=vn2_Ej8CsM7X>#Xk&<^v z(G1h17;j51{TB5Hi5DM@=}4RI52eTLcc880;;RK&$|{g4+9Y0{+->xv>Z29Z zhBIgwTT^8`I%}m;VZ=d~JXYiLA2~u)7Y;@G239<7r$EUPj1#ahG0&o(t;V-X(0&YT zFazb4g$(m*>wI#lP|5_OIu`Z8dGb#X<}Hjw{L@hn5r3XXF8u*K<&}#>@``gd9L1;& z@m^fB#Ov@K8W+WT3eV!>23mbZGrsa5W7@`5fDZMGMpz3^Mf}5XMjNjg6%Fk=)GfkW zjF?dqsE;Mt=pH!v!bii)tf7#^%6?n#0*dZ=UR$82L%sD_~rT|4!>4(NVc(poTq z85S`HoCc4-GpTrYLMRZ1Mq^ew)yF^Zd@@Cc>R^Aobh)WLxsb>lu5P9r_kf3ygV8;7 zhmZ)3eGH;PqOCFv0v-dzdP*q*3@^qEScDjj{aspx{TZ!~T>X%M!W~9kV6rQ|iX6{Q z3$&wpE_fJeRzF4|#4s+_nx0xf-Tmd^m`tCdBZj_(0HSWTz;l5JLn%V(=RyS2QhHVl zQ$j>$-Mt0rgGgpvdNmqErSB2;lE>l05!_hhWWE%;8eBqoX6P`4qR#)0AYPVGh7PRk zj8%a8Axe*t>hA~j{qddO97dKP^0Nyg#XG-)5E;6k1G8>_;lgcmoD#E1 za9dEcRv9FY7*zv>s+5dIDx(?-8QyZiM8mhy|AnrB70748aj$@5)I>+gVq~m4UPBPS zPw_nR*QIelSQ}yKFnW{bm=Awg>Kgz2-w*0;pTRyh9z3TT7ec(2zX33!ikK*d~ zCV0^SO$8kw`mC^`M5Tqb|M`M4M-<#g_FoHS`+$jqwpZ0mi~m5>PSvHS7^)B3=F>vZ zE>^Vh%{4glt41~FBM#R+UJ;J;EF3*!0qOI8JTEa`QBP8IC1G?PuI!Yv7^C8CTO%_P zBI$f3YwZAzD}8TeV#$lACXK|?SaKcL;+f>8uETYhlYtICmwN!PzZ2?>Bqu2_AL)w{J$)gO+J*>Mo|DmN{4=kPsRjMw{7E&p}L8 zUoyM!6>u^3g)fDR$uisnE_PwUo#A4Y9&QI0l6g;l@9nehi)9|{*`=W}eE;pOb?8T` zQgqsbMUmg_dN4U~*f;b+Tfp$?*Wr>7EkVvqm@c{ks=uCN$PFUIzfK80nG_k9AgUlN z2^EZWMRD%~3iL3>H>2Ob&{DP1Wcc}q;4$#>?RB^DJC3n%dYy#;q0OalVqgKqeN4Ff z6GqmERS&GVsZ6E2vl&&qBHH?7bDwF0!(2Fx5BGrNFo%avK4$8az3}H~N0B}i3B_ay z3LBZ+1SYY7<4m&gQzRKGorFJNzQhg_+Q_WJ;Bhosq>R0ZkU~3FHVqSuIaWQ-gKTOd z?0C$FN3*qh*raF`R60C3w5QVOrcqu^6^p(zFV{voXlhgQ?=&*AG|s$ zd9`gtXsKxFomL<0?y+vxn$zn4n+W)>240^l_B=9n-c;H6rJZH!77viE)bJ|g_wmz-Ji;qy~ZZGP!N!r}WYDx9?#T|lQ zp?bWQY2B(Fg*_?W%Xyf&B>ywoez=PjB?@o9V6K>`AeVkxz3wgqJA4-bPp&&W5Q_H5 z;_2v%^78N0ia5N06*(^VKXqL0f-fC;<>mEiB?H$Za6>c_1c*%MyIKg9RzQg3^27>+ z`kcM^1(a9CWMIYW1DD;94`)v71o6pi6OyKREih*yWR&9^9HJgJ`J7#0{a?Y}e<7u? zGNYM!5|ES6IXIdZ?N3FYk?PZqp~jAML~Zo{NbDyRV=2H3;oYertTx;b>&FXdaFHZC zRnajSW|V}>Ru;2mbL5FVT8aE;JExJ zR2!-+y{W<`7xPrBsltl=$}(^@>uELWDRdSQ<}RA3yF!MAL>LBAlTKmS4-6Bdtm>Z8 z0o5okF^xFZkvg{@))$?TLVTo4(MRH?GAMwe7o-~}-xHe@#;qSfk+fmcEZ+jW2fmjDBv~mCK_cucN+2-@tZ-x&M`kfs3?-9Kf)J;K{-5?VKEKk4R|J z{Ek#u_2Bz;3E0b&D)5wpuR>_-7Jm+4S`*U8^lG>f&ik`TD3tLRD!Ybzs(I40=jlr5 zgXcIFJ{g$I+xr_$d4Mzaty8 zbEfeCBn{8ai67VpQCY6}3I0*p*c`#vC=wB{U{tpD{b+4J?z-~3WM|v+<+8#3%uMbE zf$=&r!3v-hBgAgR-t3y!KOyiHK7XcFhr4IF0*QeYCTOo!X9aOMi?5-53PO4Da5Si? z!L_0~ObxCZop*@2L82uf%ho5LhLW^tROLxRmAB+;xm4xxROMW%@+6_kh;6IN;(Nzz zZhc0&Xdy9AwP~Z=e~7B?7g~>vB3wY%q#npHwu_AR05XP)+c%O8e(^zRqd!t%KP+D8 zQcc;Sy4W;x70%j1_n=I%0Rf%2JLZguu}K&9Z<|r8=(j~wZ}hw*T<_=Y+bnFi!%e_P zEmN_N#uut9!l@@~Xi#|Nm#VYmm52D=obGtN9n7??7`&Q;1##eD^U2HYMF`B&HhR^X zIKUA8AJmSW+}uYjua!)liO+S{}aXEUnXVFnFG5c);pKOhvYqi{yN z5!zE(aWLUiT;d(?%+xAP&yb1+4DCMWFa@od%h@3_8+#SF8v_9_K~xCGJsgfP8I=|8 z173-pSXkBFjk)<>*6~RKajiOj;1Ja8$tvb5m+)RXUprLdQ!Ym^&LpsQXI_`m+75KW z73ReD^3(f4)+D_uVdjvFeJWbi+6)kUX_(_vYuQr;q7R8 zhrpX?dWXT=nb9}0Kr`ADXd!Yyff|M5ehiNBESp_-l!)BE-rXj*D!Et{xs8bCmM*Uv z5zXydc%!)uhF9cvy%L<{oXXtNv$2<{J1#bJd&jbS3ZFdMJpsUnkLh99 zEGfrTT{)|1EqF(dCdcuD3N9qy930k9mr~4xURUkB48gCL;MXz6PabV=$B;I>bVp-6 zn=WD`fE9#Kz^L^-S{);9e8$g+!@V^;DNl_Y{DCp}9u`8fG=tZ^F(hEurp0`Ffbts` z{SDOsCF*BdYW=y^djSd9XjkLp5&uZ5PJI zWQNcBD`u0XbqR9G1CRi|Lb>EU_+ncg$HCuN%u%vv9Xo29P8?I`(DfKMT0t5Bg}ffU zX4?uO+qK%Z3M0Yvr&sJlubJc=%x3qrY4*#I5tK|9jysXrUJ02)5$utpUK4}UFd$&? zjUSiHr*CBQ~R36Qa6sWViojr5?11;4>!> z+#SI&li@BD6#n(cy>kDJrgb$@I(9d|5ImsHy9O;meGso-h(^m}zMbC%ms~QJEv?n@(|H$cLa$5HeIPru3;@m?04c%&(lkcJ@Xj`;zah$_XNF{l-I&G7f zF*s*xD1%nv)j~K0z_+`cRH+0;T!F184D_kg5wQ$0PT=5JEIDFd1VIIQQ612}{&0-G zCggGmm{c}W$-+3;>N(;P!%y1WWYP9ts^nCR|qWc+fJVO8t5|6na)ZH77Lq_LinTD9m);!RN; z;Fya)zTii`#`Wlsu$uIY(WbPwGlQcn6D$W<$WgMJ-V#O-4woT&#Z&nTOA3%}cw3=JNO^~$)O);cZTtm8Yg&*QtS334z27Iy6jv5|o-y63l}6?%fRxu0B$S};1JP@z50;{U*u z)nrWmBh%u|pspR!og#v%n@lfhW!k_rYuL#?0-4ZVD8|`iwBi#AZyG2UPeDRsp=dn3 zWQtRj`w(!9i;RSPd646NygeX4{bz)gCT;YfqIVlRQ1ntq6nqN+YH+(C9zm0w3CJd; z_$-@eR~c7O#uSV&P=Jyp;K!bh9HcxD$O~VLhH!aiNAqIPDAyzvanTV$I({w|Ivt@x zrzlMh1Xz0?3rsCz$U&$PbL6_i_zi=@jSA7-OsxUSc!0>Nh1bE;G}kA5oMyPMd=@vbm3{bM+qi8S&aw z2`@B}VkRg;$>e!K$*aFWR>kvN$!-+XOd;W}T!Tpg&45((Krixso$Cw55g68sg_$}h=BmJr#z!(WJbG>Vr z7v8sRY{5q|Z-9So0=73weehwl>lu^K$bE%UOI;8E-AaOBfy*FRHU+z?Rj@`1R*kI9 zVe3WH337rlYEW}XWxo6Rzt1t_AdFG^D$M=OJa5m163jh?jKwR@(#PM!3v11T;OEW< zzZ?x#*lRh+B=XUV544ue$y#93&^7jmd8rCFG$sP;_8|CZN_Rb@ke(Sycn6Z6+yT1u z1U)1A)sEmXrlod-hYmhau8r%3BT8*Vvg&>r^5QC?QX4-P+_YJ#+N@;l7N>SguO{hf z#Krr4N>L{(>>FKr6j*%Z?E&FQ)E)Q3qwY;#&7BOjCCTqt-Cp=o{he@m=i+j;0T(7a zXd@IcPovosER>*qx#MEB2=X>FH75ScATye;6C5KS^@t`*730%O!k=q?L1flzX9|KRcb+cY5;4oksf}P6JH<2z1zTkweLgC7JSO+XVsnuPn^&>bR!e+#4U#YVH*nIvOjw zeqo7DCXYw^f5mh;yiE{FM+HOgHes4y+l$=ToMq#|(ir`QC|uh&Ke-#$Pw9k>wdn9z z)b|Ty3n3$GrlQxZ<$gNq>lnT-f%^Ihj?n|E5$eNiitLL)Y_Xs#w|^a5}vOF=UQvPke8XiCNkTL*gft zqjaBu&U**-rJvB6Oz|8np8awkXf^gkMQEWM?I6ms4|$w#!RCMuD?}ldQw=Eki4AzS+F; z=&wVsAk;PRW$=)Tu__0rOTp=6O;RC4enTv_SCY7w@;GP^v>i{ZX&(X-qp7$u+UAm3 z8!wJ4wbNVn`AWVHB;Z<$+MI~*;|}bPl$s-zhJkW03LuU81ZCKX<_A|9JV9^7|N z86F&5f)EQYJJ`HxLxQ(Mx%z98*V^c_R`A7Gmdsmd4owYe`NOzm&!H*T7aV2vVP|k{ z0a{aBzh6y0#*5FPl551Yfjgbr0gkcGO$6 zM@Y0?Jz`TM*4dl1fs;X}ImYoBEsS=`EBXrF*M;VU=7EJ-EUze6=s^S&o!tdF3Z5%! z5w50IU^F}car;2rK5K&)io1U)j7+L)Xf!yEQ}o`{l!6x^E^cO{&4iUTaXY`oWFAIM zgHqBS=*_t3ZZQ@GBh$t1Fg%&j`RWdZUnk2|)B=wsfbTzJXhk{&$;?Gj=_?QgJ#Q=u zXnczyl$T-j5t+DN=pU-Mq?TitXXaoa?(KgJi<5r%G;^|8psW!rD4Q`GT?L)Bm}f|+ z^J#bCSPO%D8In>8EEmRkVrd_+nlnjkB1uJ>HJsm!70g^8;bD@AY`XB03%cD3ursa_ zMB~y$U%MUME^sGux*t*IH9)!e4%8sG#=Xyz-7T4IGtb32sF2&c8l$kqaQ`vO(8WxR zc?k@|AD5=C7j^UFH&Ls}_y!50H1=|Yu62TqIUy*ZHHI<%6xtt0wz%>AEMlpr>P;Q| z15n9XtPxmVl5rdg3Of_0AX=rm_pIi423~BB1#U*kanz@~FK(s1662w2p9X^H)R&Lt zd6P5|0jYlT;H#7~dD+`&O5@tC(CD>h&56*zGPD;%|8{=CoB;jNPWbdc=X#5N;oAkR z-)vaDM;J}FFhEq&`v~A=fv?!sUUesGceimJ0&%^G-gtO%AvuJ@WTY{Ay^r*V0_NAS z#TOB(%%tI3MZYW4cu}0X!Wi9UyuBAfZDj?Wl%8(Hua1^O*V;9W%(eREQe=kvJ!}qE zi1A4aXw3nYvf~Ilm<6#q&XKhWzAY5lQr<^ea|8pMH1s^H5nz=f+J}-hHJ~JCtpR+0 z19yVKmOBy%pFrM6TFJJ89WFvaEK^xvtNOrFZ0ca6(?BLBt?urHaO29wQ6ZV+0+l@r zQe&>!jazv&Ban-7-SHNEx_c|B_6DO&q94Y>X5SwQ$?D(I&_VkCFbjD*5=HBah3qZr zi-qZeLel6(L17=F8(~`q-4QX5oI<${VROiQ2cYp;s=5vPKwQzI*I(txpg_|%8_*k? zw8s>=KTa|?Am(z!AZR^lK5%s^E9MLPWr9Iw> z-Ip9^FOG22)_6dVV>9DrbXHs)gMHHrb5T7)`HhunU!>lI`w!XNyueb*?@TqXPOmkS z9Wa*bB3XFz|ML4S=KTm>q`=X=>%oM;Auqm@k*g0i__;dh=}sm1WO8I&Qg~7pKJ15a z{A?tUrb~C>DidGmx8a3;t+Hq@ub|+|;eENAgz+=+3rkkVTp89aLi6Uf*&;4{GTE|V5WdA5y}!YN`qwH$uL>N-m-*StpWi8@ z+s%vnn@B$Qdwx;09lUY=^{A;ti$6nNIpX4SqkVCCU=_MPR&dyGd5k?8&G#;~ug&E4 zBwlR^1rS;Io{K^Ks9WDNhLp-7Q&bv zm=|xY*M87`M43Nc2|e8=FPB~er6v{NDK8(mh+yDsJZT3D$Gr}YQMQEQxmPnNPkILu z<7}wC62H(ufqc3rQ@1OK(;YX8L>X^0QBRl>+^E!~S4F5h#L)XNMCtaP@am2vJjG~X zuA}N8lU% z4QDd>|I>-rUo2s4!P?u2JRAo?D9HPm-N0!J{Kk8@&jBUCn*qBGHwJN>qj-ZOZ-VxF zbdxJM47B5<7*B}=CraMk>6uccF?yg|S$b{Hpza^cX9KG*# zbH$RL>p<*hydjG76@k~)O~^TeOSNiOXgJjTPgf#y!l)`L-~M;*l3Imvp>}_YTYq;QU!o?4`@kJ zFNEiG!04Yb#Ba-yhgXLNf~WB%I}W~qaQ-cgUwFcZ@_EcjMt_=Xt6DDdE+4;cY9PU^ zkg(nGeuLTQsP{`WDjRS!l-f9po`*4Sh+TXw-WB42q>#jHz=+kRjX-IHOl8~sqjJ{%qO3cY|uE7jyJq=IY} zvpW{q5C00u67b898NWn*rmO>L#>FZVE01VpW$+nY z4U*py^f|*YV!r{SOPF?59F}!yGDz@4kAuXQK5_w~*OcO=kpw#OaDNP+u}!7ve6=7i z*ko?CUj}AnVrx2?KCv0znLe?_ZG(@6KQ7-H4=xAxgUhZ1umy$J61b$za43P+IDzUh zeh+gRD4Vg~6Wz7$fb*>I_pG3ozyS=`vy$fE#}jce@0ffi_+#e}p9$of0NhkS@sdSk zH1qao7U>?O{UdP;H7-h=U=GVYVX%wpVT}p-j2W>Y^HffCSO0>x@#o>n`{$yHq<_;9 z_XoBquBusB18#?zS%4fdwHU&2PlsdNw~*XLwrnC%Bd}4|LUd6Gy0+B8DL_ya~q-%P|bECatlLhikkRJT&DB#Ll{byHUL1 z74PT_EQ-R;jKaW7#Xi>HoxFp{%ZsbZmZ7qZcfcY1CBFN+5BpDG$!dV5F7_huBa^p? zn~TzVlp`nTG53A5UClGE0e*@TKk(JXmMk(4f6(?q4R-kXW@ZyEEM;roW@+EyS`F`7a)`nT znwKqboyMna^dSH@3~G%(W1Cg0)V8UOsklU*wM}026XZ!{Uz}=w@QC}4N}qbA0}gyA z!AM2QseJQg8aL0T@`n(o`Lt6!r8YjpDo&Sd5cq>ax#TcxCV3SOwepMh1>^9osN$E< z5bD>d_@?)(nD1a|<5=+5SHy7b(WQ9^6N(ovrHP{FLS&ff*rDKzmz;yiP^^s5D}+RL zp8Vp5S!gL67{ASkj1_%pGIUGP3zO)yCe|G(U{4le?QXKDQ##ZmeR!NH%3VSfx%dsR z(;X+i{BQKQsfUdDISzJ{cX4H z`1J6Vp=n6KjlGh);mOe&{!oSSSFj4#Wmg=4S{OMUno%E79T3EW7nS!zPcQG(V@XE^lOr3<(=#L=BEd+V_MkYEaFLyjk29>?N8v zV9YevFwM_}Oq1U>O>hFL+i{0TguTBD6H#{KeI!ET42D12IDO&8?^{LN$CS>@RJjDds zy6X=D)rf%qF3@Z;+=Kl7hd>YgpZ4AbKC0^4AKys`BEk?KAOh+r4}(Gsc@aQ#Lf$rk z2mzvKhsn%IGBTMN=aB?oL{SPwEh<%fw56@}qJ4;0i)dS=R*}{|u(e!jZOipq8mr!N zYs;mrxAXh1z1N=X9J96m-h2E1{QiFqOupxQ*V=2Zz4qGs>~rSKjH+=tDfBCcZX$~P zZC2t65`SBY>D__Tr9;R8RJ;CA&d^B=ORB%%=Tz{FG{*fDd=VWU2`ogLPa#u?Vr4_{ z-|1ejPB-g0zF=yT5>B{R`fs`76MB@r!-=BcB3A^ZDk}aFQ>-SDIMZPleV{A*>&8 zdH(t?rS&#WhWXzh!x#>W$*GQUk1ZStV*N|G9h(;JpuN30Aj{wVIqiZh|1?N>_JIeV z!v3@0r|Zs5FR3-pi`Q~3^6@F$Gblq9`=HjaexDD^q;FAa^9{W}z%T~IF8~7*F)vCN zQD&8!)6QBRj1Ly3;dmy#H-&F2^nsa1_7e9bWeh)Oi%thsbWPOId z8EIh+l+@zpXnra7bLr>3p{*{+&VP-I@Uv4ly+gWHxDmGLy_fQ9P(N)i~7CJljSI$=7lQeEuOU^C$*!q)1{&{w4 zLIa+L9=B^z&NpbL#BHGpTqM7}roS&&q~E0PW+qc_ZIJa}{B-~}DniT2al1xLx&;-` zClJ>Uv|coI+$X+-UG+^D@!$I3O3kDkeAqR8a>M3YawauXdIqy__UAu?sZIEeOZa*_ z9!cEnlm6aHx0l{NePA6fMhBkj#Uym=n>XSczoCV4bZ|n$rrGyUVf@}5K#aSE-i?2j zK9w%#mw~l6;xdB{2Ohft194$DT6?QnJC3Wt&%0lR==H|{V*uHB#~doynf*uI27Y<{??Kp*_Y|(^#pa%gGY{Z@{#+658%y8L~YzH|Av+DGt?)~(w~99AkgxAD-hs) z5H)R{y#*yVm**-oCy|-G*JUzYG~Bx;6=G~Hzd8qRdt<3}Ka65w@+rXPu@pzlbNGvA ze&E0>U11b{K9jzpNfp#j=%IJ7@HS3_>%^ZVwgM^^paxrvhK00<~1&Rw}T$ zdOt7O^oFHqSt*UD-?OM?7&z9!4iRoOP1Q@WOnY{lta0IYr9ST1(lP77nJaB65atJ?x~EY=lCb8$N48In4(H~pP8ykMO3M)s8pL#3ey!8K3~Bs z)Qh^j>T*C{$z4DtW+e0@HWx^93&#_?MRnX9?5hd0R0)s$7{a6yPRR;ulrF4h{q#r~^KwVoT1=%V|6a=f&sQM9dujvse;(uaExIDuTz;`qJCD?Ik>T=`03Rk0 zQH8X4b~oM`QtPa{u+)BbU-oc44sjE0r!))4U&{X~@2XhEl;7V6IZ#i2BQr1sJaaZV zNMA^@DL)PwU0oOB-1sE+pSccYo~mh789)VX^ahi=VqJ{HJhlaya_|OS@jO)cb7xb(_f#l#BGwLy>%9U7F+mRE&g~c_UTr}aB)~{u`|t5Z<~cb zXo=gOE&N7{Kg|~V^nIAa`5&;@r;o4=r$5(X|N9uB!|Bt=9gg2);s3)@?-LgN9E(3c zu<*aO@J$x`trmYCvgnVoWvBdLni~U9X(-TI_$>61QCzKF<=LcP;(WWvREy5{G9jcHXkYVX7t0r55`$E$yP`(}o-G zr&{d%(&8t5srGPo3N8L0v-H=`EOu_P_>W)W7`EPVmUhjw__VhjIgoBtO5Ut0Wp%#sgJTH5I#rk2jKO8zukZkKfW>1zEo|G6HUpIS({6GqfSD!)kV_DE=bl!gQ9JD6lTN8l#^&|Zj8d6 zXet~?In{wkq&*N^Bifn+@s;6dDAo%f6AnZz;lA!bT(s32{#ADcqMeS?t4gI3;r4XO zf#0e`T`ZcaPA8HvRNmd*6pO7%$Cp7V+9~~OtX>g_qzBb?b#)2H5p6AjMAE5_MW}F% z(-BBVQp z<1b@f@5H*DR03%vqtzIUMODPepPFzo9tl9UG!{$gjzaE6Rn{&}cehiDEp_671oBU7 ztO%$~SnMFvLKGgI8}-#l$Z!OG!6C*$1hGOQc1F-mXlXd==%^_xjVQA`nuO=NFDMsf zl4j#w7i>6rbvn^Z zLphmB#MXj9C_2KO%hK)1R5*n(KvY*Cfk@V%=}LzkgBOE_XoxfiQo*i4qmXBv;{G0D zw=o(<6gyH>l)8q^5=B)uMng^?9el7XX;3c8X+-s{GE*IiB@uI!Nd$V~b9E#f&-Mi} za)>}5)<@RHyU2-UaR(DhhfKEGz7)DYxf}yR>#4q@qqay#NJW(`QXM21^h#!AmQpi; zs$Gp$gh^KEWTVDmM$~9;Fz#G!wy#(Lt)k^6-;7UXvb`@Mxeh#>A2{2y1QdN4jrbDbjh_C z0^I_oxd-+`>d^n?*d)ErW! z-k=y}^@D1!zNv9hwZEijPMOfVrN(ZSoHIAOTT)bx6E*$18Gi5%|1_F=xgw7>26YDY zNK!Q=RH{Yj1<=}_oO_|7uWOgsp0Tge4I*rf{*W&@;q@TAEzgUPo&%2 z58{}%gJ-Mt6O4*eC8PcvGWc5!Uaz;XT$A_7vm&puLuRq`%Wk6#UbRSx3Je}g$m}-J z;IS0VZeD|@y7g9Q@YDvq6&pM^Rf;MNKD%aw#AombPq zwZWr(MO+f107c&EUry{C0z%VDLK( z{&a)iY48&bez(D&Veoqlev-lOGx*5{zu(}^H);+V{F#RSA%j25;0Fwz9*NT15rg+? z5_rtu&oOv$&S3u2wUXXO8T?dD0t*a&n!!&r_~{1kHTW3@Uuf{>8GNz97aDw}!Ot{! zpTXlHz3kRt@E2rRgAW+Im^zsM z?FK)};DZKVVDKS>pJ?!o!Fvt9!{7@IzSH1~4Zh3ZD-Awu@IHgT+Ta@uevQGm7<|Ow z+YG+j;8z=b)Zn`eK4$Q7gO3}0pTS>a@cjm#F!+rIpEUR_2A?wcZ3dq<`0WPYWAHl+ zzSrP)8hoF@?>6|g2EWJPuQm9622bxj>21HkU$05vL4&`+;13!6jRrqp@aqizh{5+8 z{4s;S$>7Db!Ti73;71w!dV?=8_*)EqqQP%4c(1{K!r%)Hextz`8~i4NuQd2i8obZo zHyeC|!QW=^Ee3zP!M7Rw9R|PJ;6G*XT?W6!;OTl0t9qQ0eLjO0xTbfK!NStYne)Ak z(O?XR!IU>DZx4E79bViX#H}^lpG$fRr-x>a)@4<>`QG+G$eZYF_sV;wP)W~{|Fepw z{mza)FYfgvywgM8baYKL)*JnRJ?6c*QPG2YfnMCnxDXyXDQ|Zyu^yy?O7bO9YV6$5gw z!@=E5#Dcq!U6swC5P5qMA&2?{Z#bYqV9>aD47a<;=H#oUC#Q!NAO+^5E~>BZ!Vm16 zAh_N0z2Rt2AcB5S`EvEb>7iNP_(JLlwWX7RPDBy4tePDN_gn=CdV`TbGD*7~=*MV^ z_By?teEXY@qTVR7l=jdbxO>%TZ+2TXyVZ+(ydWV?4es`Od*z+za0r4`vmp+~y1Nl@ z@2WteGwCfVD!M9b=O66P?1&eS89Lroly?5CI|vePzS8|v<32K{Ge(NDMrJy{BP2tm zk{qF>_Xza+2lKtvu{5$i8cWgm^0qDJd*1rZq@vOKu76{-sT{=pOpM8J6!%&YE!J&i z@;Z2z0(R&<3fY`wkvA9!cEN?D64R|*xdpQ=k~db#O_gHjO;1wtH-+(jhrDm;ZH`4_ z^W)(@Cqg53;hd8B^1ZZ3IFMYJN~E3nfpjX?g>;uk$~Qd)q1+E%5y_3ouXJ$&I*j}Rig09 z=uG@c&>shXXe`a-yTz(A(Fb%-SsmcsEbcd}eKnQQEoQ{5H;m~C?-HicNjZsyvvd2< znG6F@CIf{Nca|e4gOJWwXyUiqs6j@+NLH^y`*V*^WtTqsc~`bGSOPQw7I&fMKmp` zUtHB(E0!;-T`C%uV2>n&jiLls108cZP0-Ykp;@Tj1;C7lNq4?JGkJr_uvbi8RUE~i z>EQ*_!%KxsGKv)X03Dl%b$gRtP9(y8A=7AJ|)DWUW8Vujzuh!!7}=TGe&_UU%bl7hYS$8~1F)>wfXZw)NtT zdpBSo{FY64-Go&Ylaw{~pZpG`H;*C3=!Rs5??+4QH zx3K>pUMREmNxZ&-*UjRMO*r24CGjJuZQ3rL|Dt&QDN)_j*s^Fz)zTWA&gF?xB$3Mj zdY>>E!Qe&|qnPSw*BgpCN!i|RdKYiB=yk%KU96$@KcJ%KBANWs5M@wy{tzMcexaT_ z1 z{zpelUe!E3NdH7@)zbRf)@5KC@meA-Y!62-#6ttt>5yijINo{*g;U1+|9G1hxlmj* zuc#DDB02TS=7!X}$l$WmYZ&il%DI@|&lxSIwg=in?eoOc5~sARv_kmXYMU3;)`*qO z{+imR+SXeC%ErYtOI8a1;@Va}SUFzxL1Xoj#Y35v#u~A_wXV{?taWMQ;(9SXk`~GW z1PIHe_%mlgNdtgh6rCk~*r@{2D}nt*KzgOHk3vi6)r0+NAYQA=7nD#?%jr)oUdm>9 z9dDY6b0=sQt1C_M7Cfv{2SX`=ro}SLp&tmXM)RRiK4-AzyMeUTg4z z-jZ&-Fu5#=;T6a08oZKtrSa;;Yb^@F5i|)a}`u7C_%fafPyjwAl{f}en##M9Xs=umt=geiTxeJumT-K_TT9vF-$y$}HRmocOq}Dvvn#Wr6 zSZkirD&e_6X_XW!EuhNFV%CyVMF|>6Hi0S==dc#~O!EAfwRFCgvQ{Z;$?PvFW34i_ zRmNInq;&?~ZJZjwiU(_+sb$W*Kyihb8Y~OQ zmY0gDPDjbS5=AG`72SuUn3y>dpa7vbYqrzyEc zE!W2%g<4{*S`H)$cIJN$YRreh`fMxXE>_`M(u||IPB(a`}5*{rx;2 zzsoqm)t*9E9{j!W*YWv#%l~hcoBu!5zK`-x=gBs%uZi)EN;S@Y{t@WbTfzDh8Bb)a zv)~-&?{lH9*KOZ@to7Wum+fi(%Y5AJ;>dzH}b^)iRz&&-?Gi zY0-bPy!KDyITm}m9yi`yqx_#yr||QxW6l47c^hiI|7kp0tNhAm|31q84_W^W#*fn1 z^51a#&a{-9W#M&sjdd2i$1HKx<^G>yAJ=m$+dYlrqw8^_&HjTHe{K41Uf1L1zsY`S ze6(8iW6%FjapVHk{&N^V#<=_<#jj(0gz?9WjSH1L%J>DwXBqFhSjjKHMB$T+?l@23 zx8>aYDAu2ADW~=S-6fxEv8VOjsN132`#!h-WyY0kPsc^K_b(iW>sg*=eYgF)*}u=Q z-WL|Bc)0cSv4`{U)$T6bMSx2-dk+vr2nC~O<_C7$F0ie z|69gwPK)xlkZ}=XO{?M?mnzi9AB9g{ru5zTQRR-yhmZ4>+#kZVjsAhs_b{HqIDv5z z<5`U7GP=zCUvv1!msNXba68UtEMhEStYDnacrjxYV+~^iV-sTwV=H4Djq_`+DnQ6uYFXykc(Uy!i_*T8N_z z8Oq7c8<9Wqgi$AsK55KY&$xnbW(2-;PV$Y!?_XuWWt@21(0V@RI=+*Sn?9xVZeY}Yz2rL9{0T0gGWgz`hTReio8@>6@&adD5r5sX8WaPYH~l55oRyBx*qV~sPozx1)j zdJC`RKVRq5yMGft6d$TznpDRv(V_f1-9wKBZ}fG_v`zYn!gCqVXDnfy&$x(@zPy>X zOZj*y;}wkUj8`)z7_Vi#nNb=!{^xiEjz{2l1dd1Gcm$3|;2#=+Px5*AF2?&Azrg4n z;?0rg)M24d;gL%S`)?_#lE-^)_9;v##D@yxvDl>0$BVpvzUd}5?a7&(w_wZ&*npzf zlgYGT-*p#k+yA;!Nfbz-#B=>f%JK)k+W_;KzV9z>CVD59|bf1-K2k z4>++LznTDS1dhP7&aVTf0C!j5S5bgF=Vvkx0nb{H$?O5n1s()`8TbzHl#5W`NkS|D z&H`2geZb3rZ9qRT4t&m+$$S$is?aXrsla!D=d>bzW56%ZWUdE(0r()Wab+g+Ch!el z!C1uga`*$h-H-MHTLPKP7!Ps+SP0w1x*aG}1Fa$gd>;s~w-o}u z2kZylw+{UVEbd3Y0bc-4I~Dm1^Z{4h1V4ax0B-^Q1o$9uGJa2ZKd|UluO&uDc86fVTpRfnVK<_5dqBjeG>&348(g8t@Qs)IFKZG2mIiG59e5 z0^l?tZE#0!IoB=~Iei5=lgFGeYD&^hao=QaCXhDb_T%*k^nyfSBkoPSj)6Oq2yDb1!z*WeCPViU zXd`Y6+8qJcDH+9i!954=apoF;W#F!ykjdPYi-Sd;f=}fx@=UxVZ;{7)`-nxJ!rStz zJjEMER(UGdpHSmj9USec1X1NFgaQ;%V3B836~-&Yw;#5?fcbSNP*iye*5~GabF>nR ziJ-QFFTp%|Cdq3&1-Z3KT8TsYCG^tCnanlVCqHf{KW@vb@_09lKy~Z$Ydpo5jrJ6R zs50v*U7uIu@#f{{ zNYyG*mHxIscf*uSWz&qHT3>AoJr$1Lq;iUvVk< z*#>*xk$G?$Zn&KT+1Vt+M!7-X?>GzGHpwXNB5*swUC128ss-H3;BFz#B(ySDnfH3i}^u+rJF9jda_f`@qyprk-?%I%`+#ol zVXaH~{uSumfHgr0+NAOa`F=Z%yW1$=H_*6SKk{;w@y1A_{vJTtB-R5bQ5@h8^72H= zONt$>S=zBi*p7Ws?>R&&RAvhJ--4e*JjUK)9+(u@O2{&h(f3W0FCQA$)p)ebbcmt0rr(|;p}+Y4%risxqYCyz&!;{&gp}Bh-pzx zAXFEvCI1W^c^-kiHVz`$r1j+qSO<>6K5bM#tu_AweJpikUNmwZHG%&R7f-no0{YS|s zMeFdtLjF%YuI`|7+6TsXy=P}`-r2)C3>$@b9fX~$F3n`_!#=g!JSSYIW*~VMZiRRR z*6iPd?xJj+e12=m^?BN=JgcibT~(fVm8Wl!r$1-4r={C4kbC*CDowC^CG0-=u}mh6 z92^?^Vb%%#Ie8@`t)UYwFhSQEKe#ND`40Q}q5a^?3*>kj7I|8(_V}(Eep0907r5@Y zXxY%bA)0cZu3cIn!!#{7;)=o5gPU$}HQ?G@TpPGBxRVXN2)GV#=NMc+xFk4qm)xk1 zt>8Mr@jFt2=8_%YBH;Fu7W#AeyvW@%G=J52eG_FxEBb-TV$9@*Jfmh|_kHk}fxnvg zdR~vnbxrP4qlfD~qrUS)8hdn2wi){8l0NKQg)v4ktO5UN@YhklV6Ixn2~D!qko{qK zCUY6~W!*j{Y{235kj-3?$?W=D=7lXed7l|Ed_PjI?SrkmufUoV^ZG~R8s_bsyhm)4 z42n~1CXT`NlArx{jh`BiFBi?kAW=&-icJIbk3!#^6KNc-2LBHDHtf@QA6z@;)|(3f zvT+-9C$8o-1N9R|DE0kA;OoJki8ULwZJ2d#OWsA84O}Z-s{atm()B6*zQmCFeRa4ozqg!859$Ku>)WnV4wRMvTXeV$_K&tmBO6xXi{uuuKj%za&q!zRf7 z09hNAhvD^z((1gEay(sio_L+7uL`qAw`X;etP(0@uODS5cH+7e`(*FqY>$o~glsir zt=#Sp9q;knux0kh8!yU%RKJ)d(cYP`lbtF1#j_ISczL7FS}lpow^;$R=bDQ=H^j=J~ci zC-*^P;9bG~2H|_|G8`;|^B>0niplj4?0h$p$=r)$vDi}(mtN3nSUWeS*oylw^ z`S5vDoa=lbZKVGbabIE^%Fd#)=$F;pFO*YdknMnM4atV)bfXyw%IVe6{S9F7(vDB~Nzd zWZzNSiy=E5va>kH2gh^X_#8c)NDr?op?hKi z^W{g+Z&-Eb$+&Dmng2{@GUubbxB_c#)J%Q7gKYI=GG8NJ`jqw5Au5Sl0O9gG4Qf2n6$c>KL-8<@aB4n`lSFTtmj<(H1LJsp8<{hpcXhL2QnWyC zBko5nCp~ObHU#dw;N-mqd94XYuGA~g+@(0bhz97t&Mt0kSuSk&*v&zij38nGBsWX`}p_2Cf-g;op*9SjRl_ zA^Al)l!CqKH{zGUsRE1%x!m;DdkX74#eEneX1{KS?#<9$MY_13kc-7OwP82-7r{3Y z4;#H)i2CqV$bJu55y?WH0$QI_+ui{`YF#FSTVZk|UHWp=(ct90AzJ9p4^V&2L;&p=MC=Llw|V|exBhQ>ymwYKE;lJwSGi!=pk?q?yaM^UAFbq)^e z=0g7sAqoGrb7oFDY7cUpN{bCPsH2i4G5jpah+@uZPHakR4y3}^kr}DH>dQn&Y zZ*Hr{`lR_{M(yu8xE`TRA79`)p6fcUb{(&99n*K{(uUK#+%C%VNyMpBZt+k1BK1E41nS(DnZ{ zquR4?mru$we60KB2p@0%x{^z!f8d|p+z;+5d$@l$Fy6!XFyk)9y^IGK4>KNR963U@ zYXaj8#tOzd#ubbmj6IAS81G?xm~j{5Ud98AhZ&DDj?Cx!8D}t7FxD}yVC-P*VcfuY z597m(yBPN}9$-Aoc$9JENUoo824e+d9pehd4#pnF4UG3NKFqj_aWCTm#>0$98AqPL z^)t?3tYEBTT*27E*u%Ji@gBy98Fw-6Wjw%mnDHp%$WdHB;|#_M#yZ9oj2(Rs!+{<`?@i602#*w4B ze#RM$6^wO^D;PT%dl)w`-oyAX<1WU%j0YGGGah9ec@o#pID@f*v5s*CV+Ugo;|9ii z7$0Wb#kiO80OMiCql_cRaQ%!k7%Ld-7{xGK@uI==ms>pCgR_Ir;9h)*uWC_sO>JF$ zL*u0%YieG+q~)@u%UYMOSlM>@6|1hi>f`>^f%ag?>FDeVU%e*M9gW4WNhDM0p5DH- z*Isx14L6EUXH< z{&`)V%em!W(sDL%pUeIuF8glzS9N(V=axUN+)RAE&rC5v;A!@ z``>oicgtVY<++?&{#`9+`}eaRABrDnIp>3x>;8GwCI5`ezn{6}53+m&+xrE}nHFyO zzp`BWqviT|z*1hz-?5a}{=dg^c1JwmYX32dT$j(d2ML@{?Gu+ppz1 zJ_R2kKh0ua`#0HAUdz3%@}FZzbp6v^<=yg`mh!s2#g_6~UT!I`{kzCgUdw&1@{h9r zT3*X?z25z@OWx>`-|mt(e}w$vS5*IUG4WMb`O948zvhxJcgY`f$uD=wce>$>`ej>})TjYBDe9|J%=kj-20 zKkD{rx%>Rs>Wa_9F8f-p{9{EN%Jmh1TZ(jwRK`L#u^{N3vdx7@ux(Q+MM_xi&vcdtLRT>Ix< zf4JrD^@qA@Q1>ONP44xFTkc+eXt|EBd;Q^-yVoCDuKjnfKiqQn`ePKwU-w6qtN*lI z_pi?-Z*ldX`})f*cVB;Lx%S_E{pFUsufJAu{ks3%>vOl|g4#ujSgl`}#o3wSD*X!Lw{%=f`C(`&zE; zFSp2b|6FO2`?!LjMXvMjYKz><<*#wcpL6-&XOV0FueZpx|LZMs?f)k&a_#?}7PYEpqMuqZYaL|8a|4`@h>F*Zx1}l7HLf|6Yq+`~N+QT>JluMXvon zXpw9Gf8~-t@AB`kMXvpO$0FDM{lOyF{=ILJYyU?4SZfNoeaGeB7>iu{cZx->{hMTw z>srsX$hCjxyW}sp{424@wSScsx%SUzk!$}NEOPB%i%b5Z%fB{@T>JNNi(LEHVUcV9 zq87RKug4|d>+*s zW07nBo_ERjx%~T{MXvqZZ;@;N4qD{eze5(e_HV!?|E|lwBNn;#@2Ewt{rjs$uKgSN z6TK(ort8=Kd0g`Ex%_jVzh8FA-RH;eyX5Zk&ktO3_x%1tm)t$S{>UYF&u{x(a`*i5 zic9Vu-#>QA-Q(*gF1b6uUv02ByN30a&KWvB^tcMJ z{5qFhk4r6I?~-f#T7IWXuCMF$I{x4Kb^Y|4SL!(yLcI<*Jq5ac{TZ_#_6#q-oJ&36lI!;C>tMHB+h51=+p}V*eTom=>vWgXb_uBu#wS%d z_w^5zH{}yt^B0vTA9Q*5^$nFbG_WqyyyX(JiK!8qPg->)+7709r}Y$;x1kO zd+NBp8oxwTh$o3=dgqiE;pf4b4upS@fI$1Lf(cziUqUFXNqC9E9lFFKA=5! zrl?vmzbrndWZpcnbj5u98jJkNo6ca+U6WX}0zVZLjkSlP-r>acE9V#Y%?(u)msgbF z=c6u6u1$uVc=E!&%8CmE?cv$@y(On8D3-O%FBQwG=g$$#Ja* zrEz|lSXMi~7*D<9&kz^rr#eudtcCZbN!}l#OFtD94$e+E_-P^KW3l}CAcK>3Tzb-; zi}>f>jyhSS-@ zb#?G_V#DdgxV*-{S4aE0@*wk;-+-MQ{0_v%DpfD}M_VgiId~FQKi4Kn{J9P< zs#ia!r}gVJ%|1frj^Z(1>$xc7_pPqMjOFJIn{jCpsgP${|Nj^t-@WDDC z(efEx!o7Hroday=QX;Ya2rp{aYDcvzE?GR|2_${5$0r%h|AP4~%%3Kucm|vKhnSzq z{Cmv*fcYTve__6OoKG_Pv%}|&QS~-6zmE0kGp*$3c8-4~^P|9%pWkQwPUcT#zO=w6 zna7!*%>0|o%g-1?MxVVT`(BPiG3!rd{hm{Ol98Vy#$GY=GsY`ketsA{eJ+>$sh*(t zYrxS~4L%1?2XCBKC6Dzwfu2Jr{U=$!OG@#KF!NJR_en;6HW+(;Y3B^Fr(M};<2U}o ztbhJQr7u4p4E+@Ip)(XOKN}4G25AR*@*Bk;!7**0V7`p=N1u;B#r!-Me=m6Q|Fuaz zS$?zpE5zrScP1--8uRqIGt%FCrs7{={&D8-J&So9)AlU$U0$C&{+0ZT&yg@+e~#ko znWxX;lKnfF{~hzcVE%@4mA?FpD`bb6e{8zq<>yzyzsvmfGZlXUINFXfe~|fp$>Lm( zexUpj+%G$r9}AxR*>t{7GIujSp7q}hR7pnX!x^kU? z<>wGl=6vQuixn?FBL}`r+LQvVEb z$mP!x%~z>*39mA=ob9}Esj@?PKwE(MbDMqgSbjzjdnsuL`T3UO5nQ?50)Du8awFT( z>&26ly4WJ^oFV!7UH{UuOG<*gnliw0({B z_4nqTqiFFo^ZI-9bex}KehJ&x&r`q1d>8Zk+0HBAX+GKRQ|;Q$d>;%_KInCOnEBVG zKITvMQ_r6hPEh)v=XL(otpBfE?@5>YBy%qFzhQnh^YSx!kfleHKOV8?9i{K(hW(!P zcU+(@!HQS=Jod?A|vJ2!2EXBZ)3id`D4u2Gw)~q@`z6|3z&~Ge@Tbp zjN+Qm&fU9*5AnbS9U3V`MEpD?w0yz;CEA1Jat}eXTHV7?_@iLYm^=NnLOCp z&HPsOQ-0PC{Bvw)qRS3_F9P*TgNy$$+tJ_C7=jJje$Kr9K1cc4JMh0|UVs0ij?WS1 ze--ypaUnk^2Yeqq)jNXg)%lQv9hp}yekAi_T)YQ7<%7Q7lb@%9{RzyMCVe;(;^7<# z;vDAn_h#O!XmK9%@w87eTbVCrUVjhgEaoej*Wc5*hWRSy_4REj^OrKOueWQNZ)IM8 z?WEQ`esM_*MUznukWYmI6TXCMzI|n%IziQ3taXu zI8nv9hR=^${}rh}QTSZ^0k%`@vhyqED_#6s;DG z{n2Ntic_&5%)zH)^yg@_aHizP3-{+rzJd07kyrgZk7@l9=$`^lXWi_R)N&SFBzZnC zpw7atwD4UP{$>k*w}t9;WXSImKv?-u;o}M~H8H!Y3)5 zM&X0WTD)no^Ct^GX5{eoo&lcxZ*}?q9Qup;rGJd7cLvux)ncbY@*~6! zUgvLP{Y%(>pDPZH7CWmfe7A*PXW{R%@L#s@yTNOEh4_NsBI$`+u|3&JL z5XUa?$!g_yxL_~WqCZ~pBgE93l>Vu*ln`e!@8k8q<_n3>%@_I}5vz+Li~ac)p1yO1 z^5JuHRK2S)kI>d?(WlP~lD_X!pVa$;{EP3Rv*`C*_`5Cqmo5CWk{==T{d5o4`#t7g z0F5-u=>>&{xZpr z5R*2mb`^2GS6K8rEc`WW=cb#Lopo$yokjmP*3XnHebt-zZb^&&gWylj#X5@P%;x3y z_6#?V?Y7u?)x!VU!XLHpC*cOmaP>~I@Uy|6oa+($c|2{y_Ir68rP%%#!P9s=<{FQWSnPbm!vDy^zhU8zT6p?S zmf`w^KEOX5f2xI_CixMffyc!JZtsQ6zq?7rc@y&sz|%a?;F>3EE%q)oweUU*zu3Zm96XJqHrM&I)1rSZc*^Hvd>@s=FV)*FQ>EFqAzRCRgpBT#Twb*&h!vD^~{}*_gZ}s@q`B^Y- z`0+B;!k1e3Y74)_!mqaQDGUDz3;%$Hf6T(aXyJcq;RnFW^`2|}@H>nCegt;7^TmHz z^iPuOLrww%(iZ(fUQhnhOqC}(-yX%flKOYRbv~WL`dj#Y8GW5w$$X6Olib2}S26$0 z3}t^i^R>+D=ca zp`RPkO?Zm+#d@EliRzKezC|V>@s2`lpjCc%OM6$Nxg+^G_Z=e?%~mno|@L zem|ZR@}~lwM6^4d;V8(ElfYgw(%s!_QN%d%(}O}oRFm{PQ99L8 z*%OF_LxGe-2i@VQgg~E2MZ!@h;zR{p?G~w|lM00sDUs?(M~ zDyA@ionTR^ECYEg5)r9*I+Y5vi=-1tMgwueXe`l5Ldx&Ep*noe*u0TTcq~;VC`->4WYEB@z zhOm7tnwO%|)F`zlePQo{5eHF3zr@fK<#IYMQcm{*+#!`(52fNA^ngc;9 z0jJVVH0TKIvyaJeXRs?067g^dY-+6tc8Ab53P_ODx&qNq#6hEqN<TV2 zr#le+q6*q6Dis#F9b3445Ca#ADGUv}LUPp>St7l`JX~edye1x*Jb`tr5NG zRHp|8@HY|b6@6iRJqW6ftrdN}q9>dTw}&HPxR#D~qi?zch@|L_rISt|n4*w$C%a;a zR4|=Niau&;IvMD6L|-Hp2&sPSlK~W=py=xktZ_OcYvWxVv1lq8cY;YICB#vJAj|_q zUkJ@`X$^A1>5IqECe)QmCpr;OI2!KG`W!}<;yJZwDoj}`-Jr@uAQ=pY{W66D?TBes zl7z=jNIFFMhI9+0BdP2$Y{%kou&)crmlUyh${)ork~_gjAels;IZ4@OnYXEM$666j z#Nv1ekalIL5>6a360tt?U~DbYuN@hWuAz=gI#G1K6G>2dBvYaz7EC8qF$!mUH|!wH z?Ra9(K|hjeJb)=e1S8?N+G~%atCM04gM)~qqtpSi1bGoayS8RyEi9 zYZuq}{g`<0Y^T4r!LP`Mnx(>D-?U^=Rg-^7UEQ+UR)1^NqNZ9X)?B{0s=2XR^w9v9 zGeBE)!a=Wss|!|RJTG%nRiO~=foKV&FgZkPIC=ekJeAqw#|VoCBEI^j#zocs5AqTphZbV{8**@f2B1IWX9pm&-n?*7z z^Q>ctCQ4$C8bY;oq-3QLN@&c0$#@`$34gFHX-r8$%44z}#AGjL3Cz@;NINPu#9TNQ zObu;npdD#9Lb|r>TFce%vvuG;ZJ!o*wT*D9Kq2J%x9gF&9bJ2}$O|jUTbbMI~ zb8_dPCc2|0mZtIL^f|$v_>hW_?3gqMmFt}pE7M@6WS5zy(n_Abpi2R2mXl6fbu8NB zBvQ*{EaYs=ncW6UE5lJt!7UhgP+3a@3%L@4#lX;*j|56N z-9sqZuY6jQbCMiAZOfzjyxrK`I5_=OqptPJjZul zJE_L%6@dsQ2C{+E#1L;NB2nZ8VzD@lL5IYxQPbCoa55c;=nQV}6qtm=@n9^zRNX5;MXBPnCBvYGo8=XOE;*Bgk?nV1$A6r+{RjX{KfrscM}a0JspZ4Z__ zIGZ?w2~9&E0gJD#LPt}eX!VrMmvjn?2b}hBE*j!C@(6QdCraeac^S@m7|fh)uo+CPjXVB!znPPzvQc$0 zXm4{M&Vz{j#HxT+VQTh( zFZ!e)CyM3}15}iH8)OFP~HmYd8kdegPNh{eQh2WHXKJ5*J zhZ(6zx|CC2O~cBG_6)J1;~=Y}MV9O`btdYgReZ8bo?v)sf<;sI41tNS5o_X341I1a ztuy?71Unj|q{74gv7s5=Ru@SpyF_2RgQYjz$LVm0H-3M6GRbK!C!l7YfOJ6Qxsj6% zv8~3$GUyOa0E=Q+Otvjs8x6`d%ud%!dZRQ$sS&{ivq>!ndNic+P*%(7?GHt;kd-ZQ z$SZt!!5us0k^bq#471vVa&R}s9dB6rl3xh&nD#HlBZ zvu!qS)QP7zm~{7XcBal|7EPUv&ANFQV^OSv+gbw1DA})~ZACy$>x;2MaY8hVhUS_CQQPdJ%Ovy|PV{TRFWD1|!qaJEPc14`#SSZ{Prl~JYvyv^hYn*th z3+aX{+@X^IA{mQzggcG!x2fo5rR$y5I-O!h_=!Q(qr@h!u=pUCOnW2qF2^G6X9MTF%7>r_4EJf~GjvkfVupNaO2* zjdT*HPF%^eVV#`fIK^6!DYUF3pM<quO4DNi&Sg8FSsri*lHkSHyHf zfaX?O#j0yTH7@F%ShqtrCPYzkZ8zNkD55!DVwc{NpQY-Irik43YD+;ZR!lpvhEG1YxCTZDd9{3@{^b()~ycdtaZXzbTugK}* z8zq>6MfOM(W!;MF6{n)A7B$YMn>x}-bxTD#srOM&pgSCd6F6YF)qn zzVTyURSNq1#aUh2(EEh#c;O?VS^xF-d2j!^(wfE%*7fRoG{&Gt-xaQp_wDk@g8O!< z@~aid?-w`yXIk8d9qJQZUVlG%;ZsVZffaTA8g=_Oa`{E9r@!w!&gJ#@qf@!l@p6~H z3mo~S>(|fQ?0Eqsw*F#;`ma~-Fy0R|%j@S&eScI6V_XU6I+VX#X_jyKlTQ|G`I9P7 zzb9&L+JDXcGj`1Je}tcpJwiWLiOnbf4*qeMe-R?Hy#8Eb@mN*fJ6Kfix&8kMSb7hF zhkN#OfjhXoyFPuakv@-uw^*{}_2&?G{*C3|Mp>G>bp87I-QC)M?pPW>wCQ^F{PPEJ z)KXpk;MrBACJv5M?bmve#2;N=BXyHmUO!hnz~wi(0;bDpq|aBI<@IyM{imt=g?=D_ z9q=-0pwH`&E$zR4U$LJ*5Ycy@)>P;ndMGGNRFf;sg)V=kOP{@xWk@u^QvP+We~B)@ zcJ!gf>DZzC)7!C)RdRP$snV#?imc!Yr}RUQU2TIfPCvqm?Z`P2#94r?c +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "st.h" +#include "win.h" + +#if defined(__linux) + #include +#elif defined(__OpenBSD__) || defined(__NetBSD__) || defined(__APPLE__) + #include +#elif defined(__FreeBSD__) || defined(__DragonFly__) + #include +#endif + +/* Arbitrary sizes */ +#define UTF_INVALID 0xFFFD +#define UTF_SIZ 4 +#define ESC_BUF_SIZ (128*UTF_SIZ) +#define ESC_ARG_SIZ 16 +#define STR_BUF_SIZ ESC_BUF_SIZ +#define STR_ARG_SIZ ESC_ARG_SIZ + +/* macros */ +#define IS_SET(flag) ((term.mode & (flag)) != 0) +#define ISCONTROLC0(c) (BETWEEN(c, 0, 0x1f) || (c) == 0x7f) +#define ISCONTROLC1(c) (BETWEEN(c, 0x80, 0x9f)) +#define ISCONTROL(c) (ISCONTROLC0(c) || ISCONTROLC1(c)) +#define ISDELIM(u) (u && wcschr(worddelimiters, u)) + +enum term_mode { + MODE_WRAP = 1 << 0, + MODE_INSERT = 1 << 1, + MODE_ALTSCREEN = 1 << 2, + MODE_CRLF = 1 << 3, + MODE_ECHO = 1 << 4, + MODE_PRINT = 1 << 5, + MODE_UTF8 = 1 << 6, +}; + +enum cursor_movement { + CURSOR_SAVE, + CURSOR_LOAD +}; + +enum cursor_state { + CURSOR_DEFAULT = 0, + CURSOR_WRAPNEXT = 1, + CURSOR_ORIGIN = 2 +}; + +enum charset { + CS_GRAPHIC0, + CS_GRAPHIC1, + CS_UK, + CS_USA, + CS_MULTI, + CS_GER, + CS_FIN +}; + +enum escape_state { + ESC_START = 1, + ESC_CSI = 2, + ESC_STR = 4, /* DCS, OSC, PM, APC */ + ESC_ALTCHARSET = 8, + ESC_STR_END = 16, /* a final string was encountered */ + ESC_TEST = 32, /* Enter in test mode */ + ESC_UTF8 = 64, +}; + +typedef struct { + Glyph attr; /* current char attributes */ + int x; + int y; + char state; +} TCursor; + +typedef struct { + int mode; + int type; + int snap; + /* + * Selection variables: + * nb – normalized coordinates of the beginning of the selection + * ne – normalized coordinates of the end of the selection + * ob – original coordinates of the beginning of the selection + * oe – original coordinates of the end of the selection + */ + struct { + int x, y; + } nb, ne, ob, oe; + + int alt; +} Selection; + +/* Internal representation of the screen */ +typedef struct { + int row; /* nb row */ + int col; /* nb col */ + Line *line; /* screen */ + Line *alt; /* alternate screen */ + int *dirty; /* dirtyness of lines */ + TCursor c; /* cursor */ + int ocx; /* old cursor col */ + int ocy; /* old cursor row */ + int top; /* top scroll limit */ + int bot; /* bottom scroll limit */ + int mode; /* terminal mode flags */ + int esc; /* escape state flags */ + char trantbl[4]; /* charset table translation */ + int charset; /* current charset */ + int icharset; /* selected charset for sequence */ + int *tabs; + Rune lastc; /* last printed char outside of sequence, 0 if control */ +} Term; + +/* CSI Escape sequence structs */ +/* ESC '[' [[ [] [;]] []] */ +typedef struct { + char buf[ESC_BUF_SIZ]; /* raw string */ + size_t len; /* raw string length */ + char priv; + int arg[ESC_ARG_SIZ]; + int narg; /* nb of args */ + char mode[2]; +} CSIEscape; + +/* STR Escape sequence structs */ +/* ESC type [[ [] [;]] ] ESC '\' */ +typedef struct { + char type; /* ESC type ... */ + char *buf; /* allocated raw string */ + size_t siz; /* allocation size */ + size_t len; /* raw string length */ + char *args[STR_ARG_SIZ]; + int narg; /* nb of args */ +} STREscape; + +static void execsh(char *, char **); +static void stty(char **); +static void sigchld(int); +static void ttywriteraw(const char *, size_t); + +static void csidump(void); +static void csihandle(void); +static void csiparse(void); +static void csireset(void); +static void osc_color_response(int, int, int); +static int eschandle(uchar); +static void strdump(void); +static void strhandle(void); +static void strparse(void); +static void strreset(void); + +static void tprinter(char *, size_t); +static void tdumpsel(void); +static void tdumpline(int); +static void tdump(void); +static void tclearregion(int, int, int, int); +static void tcursor(int); +static void tdeletechar(int); +static void tdeleteline(int); +static void tinsertblank(int); +static void tinsertblankline(int); +static int tlinelen(int); +static void tmoveto(int, int); +static void tmoveato(int, int); +static void tnewline(int); +static void tputtab(int); +static void tputc(Rune); +static void treset(void); +static void tscrollup(int, int); +static void tscrolldown(int, int); +static void tsetattr(const int *, int); +static void tsetchar(Rune, const Glyph *, int, int); +static void tsetdirt(int, int); +static void tsetscroll(int, int); +static void tswapscreen(void); +static void tsetmode(int, int, const int *, int); +static int twrite(const char *, int, int); +static void tfulldirt(void); +static void tcontrolcode(uchar ); +static void tdectest(char ); +static void tdefutf8(char); +static int32_t tdefcolor(const int *, int *, int); +static void tdeftran(char); +static void tstrsequence(uchar); + +static void drawregion(int, int, int, int); + +static void selnormalize(void); +static void selscroll(int, int); +static void selsnap(int *, int *, int); + +static size_t utf8decode(const char *, Rune *, size_t); +static Rune utf8decodebyte(char, size_t *); +static char utf8encodebyte(Rune, size_t); +static size_t utf8validate(Rune *, size_t); + +static char *base64dec(const char *); +static char base64dec_getc(const char **); + +static ssize_t xwrite(int, const char *, size_t); + +/* Globals */ +static Term term; +static Selection sel; +static CSIEscape csiescseq; +static STREscape strescseq; +static int iofd = 1; +static int cmdfd; +static pid_t pid; + +static const uchar utfbyte[UTF_SIZ + 1] = {0x80, 0, 0xC0, 0xE0, 0xF0}; +static const uchar utfmask[UTF_SIZ + 1] = {0xC0, 0x80, 0xE0, 0xF0, 0xF8}; +static const Rune utfmin[UTF_SIZ + 1] = { 0, 0, 0x80, 0x800, 0x10000}; +static const Rune utfmax[UTF_SIZ + 1] = {0x10FFFF, 0x7F, 0x7FF, 0xFFFF, 0x10FFFF}; + +ssize_t +xwrite(int fd, const char *s, size_t len) +{ + size_t aux = len; + ssize_t r; + + while (len > 0) { + r = write(fd, s, len); + if (r < 0) + return r; + len -= r; + s += r; + } + + return aux; +} + +void * +xmalloc(size_t len) +{ + void *p; + + if (!(p = malloc(len))) + die("malloc: %s\n", strerror(errno)); + + return p; +} + +void * +xrealloc(void *p, size_t len) +{ + if ((p = realloc(p, len)) == NULL) + die("realloc: %s\n", strerror(errno)); + + return p; +} + +char * +xstrdup(const char *s) +{ + char *p; + + if ((p = strdup(s)) == NULL) + die("strdup: %s\n", strerror(errno)); + + return p; +} + +size_t +utf8decode(const char *c, Rune *u, size_t clen) +{ + size_t i, j, len, type; + Rune udecoded; + + *u = UTF_INVALID; + if (!clen) + return 0; + udecoded = utf8decodebyte(c[0], &len); + if (!BETWEEN(len, 1, UTF_SIZ)) + return 1; + for (i = 1, j = 1; i < clen && j < len; ++i, ++j) { + udecoded = (udecoded << 6) | utf8decodebyte(c[i], &type); + if (type != 0) + return j; + } + if (j < len) + return 0; + *u = udecoded; + utf8validate(u, len); + + return len; +} + +Rune +utf8decodebyte(char c, size_t *i) +{ + for (*i = 0; *i < LEN(utfmask); ++(*i)) + if (((uchar)c & utfmask[*i]) == utfbyte[*i]) + return (uchar)c & ~utfmask[*i]; + + return 0; +} + +size_t +utf8encode(Rune u, char *c) +{ + size_t len, i; + + len = utf8validate(&u, 0); + if (len > UTF_SIZ) + return 0; + + for (i = len - 1; i != 0; --i) { + c[i] = utf8encodebyte(u, 0); + u >>= 6; + } + c[0] = utf8encodebyte(u, len); + + return len; +} + +char +utf8encodebyte(Rune u, size_t i) +{ + return utfbyte[i] | (u & ~utfmask[i]); +} + +size_t +utf8validate(Rune *u, size_t i) +{ + if (!BETWEEN(*u, utfmin[i], utfmax[i]) || BETWEEN(*u, 0xD800, 0xDFFF)) + *u = UTF_INVALID; + for (i = 1; *u > utfmax[i]; ++i) + ; + + return i; +} + +char +base64dec_getc(const char **src) +{ + while (**src && !isprint((unsigned char)**src)) + (*src)++; + return **src ? *((*src)++) : '='; /* emulate padding if string ends */ +} + +char * +base64dec(const char *src) +{ + size_t in_len = strlen(src); + char *result, *dst; + static const char base64_digits[256] = { + [43] = 62, 0, 0, 0, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, + 0, 0, 0, -1, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, + 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 0, 0, 0, 0, + 0, 0, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, + 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51 + }; + + if (in_len % 4) + in_len += 4 - (in_len % 4); + result = dst = xmalloc(in_len / 4 * 3 + 1); + while (*src) { + int a = base64_digits[(unsigned char) base64dec_getc(&src)]; + int b = base64_digits[(unsigned char) base64dec_getc(&src)]; + int c = base64_digits[(unsigned char) base64dec_getc(&src)]; + int d = base64_digits[(unsigned char) base64dec_getc(&src)]; + + /* invalid input. 'a' can be -1, e.g. if src is "\n" (c-str) */ + if (a == -1 || b == -1) + break; + + *dst++ = (a << 2) | ((b & 0x30) >> 4); + if (c == -1) + break; + *dst++ = ((b & 0x0f) << 4) | ((c & 0x3c) >> 2); + if (d == -1) + break; + *dst++ = ((c & 0x03) << 6) | d; + } + *dst = '\0'; + return result; +} + +void +selinit(void) +{ + sel.mode = SEL_IDLE; + sel.snap = 0; + sel.ob.x = -1; +} + +int +tlinelen(int y) +{ + int i = term.col; + + if (term.line[y][i - 1].mode & ATTR_WRAP) + return i; + + while (i > 0 && term.line[y][i - 1].u == ' ') + --i; + + return i; +} + +void +selstart(int col, int row, int snap) +{ + selclear(); + sel.mode = SEL_EMPTY; + sel.type = SEL_REGULAR; + sel.alt = IS_SET(MODE_ALTSCREEN); + sel.snap = snap; + sel.oe.x = sel.ob.x = col; + sel.oe.y = sel.ob.y = row; + selnormalize(); + + if (sel.snap != 0) + sel.mode = SEL_READY; + tsetdirt(sel.nb.y, sel.ne.y); +} + +void +selextend(int col, int row, int type, int done) +{ + int oldey, oldex, oldsby, oldsey, oldtype; + + if (sel.mode == SEL_IDLE) + return; + if (done && sel.mode == SEL_EMPTY) { + selclear(); + return; + } + + oldey = sel.oe.y; + oldex = sel.oe.x; + oldsby = sel.nb.y; + oldsey = sel.ne.y; + oldtype = sel.type; + + sel.oe.x = col; + sel.oe.y = row; + selnormalize(); + sel.type = type; + + if (oldey != sel.oe.y || oldex != sel.oe.x || oldtype != sel.type || sel.mode == SEL_EMPTY) + tsetdirt(MIN(sel.nb.y, oldsby), MAX(sel.ne.y, oldsey)); + + sel.mode = done ? SEL_IDLE : SEL_READY; +} + +void +selnormalize(void) +{ + int i; + + if (sel.type == SEL_REGULAR && sel.ob.y != sel.oe.y) { + sel.nb.x = sel.ob.y < sel.oe.y ? sel.ob.x : sel.oe.x; + sel.ne.x = sel.ob.y < sel.oe.y ? sel.oe.x : sel.ob.x; + } else { + sel.nb.x = MIN(sel.ob.x, sel.oe.x); + sel.ne.x = MAX(sel.ob.x, sel.oe.x); + } + sel.nb.y = MIN(sel.ob.y, sel.oe.y); + sel.ne.y = MAX(sel.ob.y, sel.oe.y); + + selsnap(&sel.nb.x, &sel.nb.y, -1); + selsnap(&sel.ne.x, &sel.ne.y, +1); + + /* expand selection over line breaks */ + if (sel.type == SEL_RECTANGULAR) + return; + i = tlinelen(sel.nb.y); + if (i < sel.nb.x) + sel.nb.x = i; + if (tlinelen(sel.ne.y) <= sel.ne.x) + sel.ne.x = term.col - 1; +} + +int +selected(int x, int y) +{ + if (sel.mode == SEL_EMPTY || sel.ob.x == -1 || + sel.alt != IS_SET(MODE_ALTSCREEN)) + return 0; + + if (sel.type == SEL_RECTANGULAR) + return BETWEEN(y, sel.nb.y, sel.ne.y) + && BETWEEN(x, sel.nb.x, sel.ne.x); + + return BETWEEN(y, sel.nb.y, sel.ne.y) + && (y != sel.nb.y || x >= sel.nb.x) + && (y != sel.ne.y || x <= sel.ne.x); +} + +void +selsnap(int *x, int *y, int direction) +{ + int newx, newy, xt, yt; + int delim, prevdelim; + const Glyph *gp, *prevgp; + + switch (sel.snap) { + case SNAP_WORD: + /* + * Snap around if the word wraps around at the end or + * beginning of a line. + */ + prevgp = &term.line[*y][*x]; + prevdelim = ISDELIM(prevgp->u); + for (;;) { + newx = *x + direction; + newy = *y; + if (!BETWEEN(newx, 0, term.col - 1)) { + newy += direction; + newx = (newx + term.col) % term.col; + if (!BETWEEN(newy, 0, term.row - 1)) + break; + + if (direction > 0) + yt = *y, xt = *x; + else + yt = newy, xt = newx; + if (!(term.line[yt][xt].mode & ATTR_WRAP)) + break; + } + + if (newx >= tlinelen(newy)) + break; + + gp = &term.line[newy][newx]; + delim = ISDELIM(gp->u); + if (!(gp->mode & ATTR_WDUMMY) && (delim != prevdelim + || (delim && gp->u != prevgp->u))) + break; + + *x = newx; + *y = newy; + prevgp = gp; + prevdelim = delim; + } + break; + case SNAP_LINE: + /* + * Snap around if the the previous line or the current one + * has set ATTR_WRAP at its end. Then the whole next or + * previous line will be selected. + */ + *x = (direction < 0) ? 0 : term.col - 1; + if (direction < 0) { + for (; *y > 0; *y += direction) { + if (!(term.line[*y-1][term.col-1].mode + & ATTR_WRAP)) { + break; + } + } + } else if (direction > 0) { + for (; *y < term.row-1; *y += direction) { + if (!(term.line[*y][term.col-1].mode + & ATTR_WRAP)) { + break; + } + } + } + break; + } +} + +char * +getsel(void) +{ + char *str, *ptr; + int y, bufsize, lastx, linelen; + const Glyph *gp, *last; + + if (sel.ob.x == -1) + return NULL; + + bufsize = (term.col+1) * (sel.ne.y-sel.nb.y+1) * UTF_SIZ; + ptr = str = xmalloc(bufsize); + + /* append every set & selected glyph to the selection */ + for (y = sel.nb.y; y <= sel.ne.y; y++) { + if ((linelen = tlinelen(y)) == 0) { + *ptr++ = '\n'; + continue; + } + + if (sel.type == SEL_RECTANGULAR) { + gp = &term.line[y][sel.nb.x]; + lastx = sel.ne.x; + } else { + gp = &term.line[y][sel.nb.y == y ? sel.nb.x : 0]; + lastx = (sel.ne.y == y) ? sel.ne.x : term.col-1; + } + last = &term.line[y][MIN(lastx, linelen-1)]; + while (last >= gp && last->u == ' ') + --last; + + for ( ; gp <= last; ++gp) { + if (gp->mode & ATTR_WDUMMY) + continue; + + ptr += utf8encode(gp->u, ptr); + } + + /* + * Copy and pasting of line endings is inconsistent + * in the inconsistent terminal and GUI world. + * The best solution seems like to produce '\n' when + * something is copied from st and convert '\n' to + * '\r', when something to be pasted is received by + * st. + * FIXME: Fix the computer world. + */ + if ((y < sel.ne.y || lastx >= linelen) && + (!(last->mode & ATTR_WRAP) || sel.type == SEL_RECTANGULAR)) + *ptr++ = '\n'; + } + *ptr = 0; + return str; +} + +void +selclear(void) +{ + if (sel.ob.x == -1) + return; + sel.mode = SEL_IDLE; + sel.ob.x = -1; + tsetdirt(sel.nb.y, sel.ne.y); +} + +void +die(const char *errstr, ...) +{ + va_list ap; + + va_start(ap, errstr); + vfprintf(stderr, errstr, ap); + va_end(ap); + exit(1); +} + +void +execsh(char *cmd, char **args) +{ + char *sh, *prog, *arg; + const struct passwd *pw; + + errno = 0; + if ((pw = getpwuid(getuid())) == NULL) { + if (errno) + die("getpwuid: %s\n", strerror(errno)); + else + die("who are you?\n"); + } + + if ((sh = getenv("SHELL")) == NULL) + sh = (pw->pw_shell[0]) ? pw->pw_shell : cmd; + + if (args) { + prog = args[0]; + arg = NULL; + } else if (scroll) { + prog = scroll; + arg = utmp ? utmp : sh; + } else if (utmp) { + prog = utmp; + arg = NULL; + } else { + prog = sh; + arg = NULL; + } + DEFAULT(args, ((char *[]) {prog, arg, NULL})); + + unsetenv("COLUMNS"); + unsetenv("LINES"); + unsetenv("TERMCAP"); + setenv("LOGNAME", pw->pw_name, 1); + setenv("USER", pw->pw_name, 1); + setenv("SHELL", sh, 1); + setenv("HOME", pw->pw_dir, 1); + setenv("TERM", termname, 1); + + signal(SIGCHLD, SIG_DFL); + signal(SIGHUP, SIG_DFL); + signal(SIGINT, SIG_DFL); + signal(SIGQUIT, SIG_DFL); + signal(SIGTERM, SIG_DFL); + signal(SIGALRM, SIG_DFL); + + execvp(prog, args); + _exit(1); +} + +void +sigchld(int a) +{ + int stat; + pid_t p; + + if ((p = waitpid(pid, &stat, WNOHANG)) < 0) + die("waiting for pid %hd failed: %s\n", pid, strerror(errno)); + + if (pid != p) + return; + + if (WIFEXITED(stat) && WEXITSTATUS(stat)) + die("child exited with status %d\n", WEXITSTATUS(stat)); + else if (WIFSIGNALED(stat)) + die("child terminated due to signal %d\n", WTERMSIG(stat)); + _exit(0); +} + +void +stty(char **args) +{ + char cmd[_POSIX_ARG_MAX], **p, *q, *s; + size_t n, siz; + + if ((n = strlen(stty_args)) > sizeof(cmd)-1) + die("incorrect stty parameters\n"); + memcpy(cmd, stty_args, n); + q = cmd + n; + siz = sizeof(cmd) - n; + for (p = args; p && (s = *p); ++p) { + if ((n = strlen(s)) > siz-1) + die("stty parameter length too long\n"); + *q++ = ' '; + memcpy(q, s, n); + q += n; + siz -= n + 1; + } + *q = '\0'; + if (system(cmd) != 0) + perror("Couldn't call stty"); +} + +int +ttynew(const char *line, char *cmd, const char *out, char **args) +{ + int m, s; + + if (out) { + term.mode |= MODE_PRINT; + iofd = (!strcmp(out, "-")) ? + 1 : open(out, O_WRONLY | O_CREAT, 0666); + if (iofd < 0) { + fprintf(stderr, "Error opening %s:%s\n", + out, strerror(errno)); + } + } + + if (line) { + if ((cmdfd = open(line, O_RDWR)) < 0) + die("open line '%s' failed: %s\n", + line, strerror(errno)); + dup2(cmdfd, 0); + stty(args); + return cmdfd; + } + + /* seems to work fine on linux, openbsd and freebsd */ + if (openpty(&m, &s, NULL, NULL, NULL) < 0) + die("openpty failed: %s\n", strerror(errno)); + + switch (pid = fork()) { + case -1: + die("fork failed: %s\n", strerror(errno)); + break; + case 0: + close(iofd); + close(m); + setsid(); /* create a new process group */ + dup2(s, 0); + dup2(s, 1); + dup2(s, 2); + if (ioctl(s, TIOCSCTTY, NULL) < 0) + die("ioctl TIOCSCTTY failed: %s\n", strerror(errno)); + if (s > 2) + close(s); +#ifdef __OpenBSD__ + if (pledge("stdio getpw proc exec", NULL) == -1) + die("pledge\n"); +#endif + execsh(cmd, args); + break; + default: +#ifdef __OpenBSD__ + if (pledge("stdio rpath tty proc", NULL) == -1) + die("pledge\n"); +#endif + close(s); + cmdfd = m; + signal(SIGCHLD, sigchld); + break; + } + return cmdfd; +} + +size_t +ttyread(void) +{ + static char buf[BUFSIZ]; + static int buflen = 0; + int ret, written; + + /* append read bytes to unprocessed bytes */ + ret = read(cmdfd, buf+buflen, LEN(buf)-buflen); + + switch (ret) { + case 0: + exit(0); + case -1: + die("couldn't read from shell: %s\n", strerror(errno)); + default: + buflen += ret; + written = twrite(buf, buflen, 0); + buflen -= written; + /* keep any incomplete UTF-8 byte sequence for the next call */ + if (buflen > 0) + memmove(buf, buf + written, buflen); + return ret; + } +} + +void +ttywrite(const char *s, size_t n, int may_echo) +{ + const char *next; + + if (may_echo && IS_SET(MODE_ECHO)) + twrite(s, n, 1); + + if (!IS_SET(MODE_CRLF)) { + ttywriteraw(s, n); + return; + } + + /* This is similar to how the kernel handles ONLCR for ttys */ + while (n > 0) { + if (*s == '\r') { + next = s + 1; + ttywriteraw("\r\n", 2); + } else { + next = memchr(s, '\r', n); + DEFAULT(next, s + n); + ttywriteraw(s, next - s); + } + n -= next - s; + s = next; + } +} + +void +ttywriteraw(const char *s, size_t n) +{ + fd_set wfd, rfd; + ssize_t r; + size_t lim = 256; + + /* + * Remember that we are using a pty, which might be a modem line. + * Writing too much will clog the line. That's why we are doing this + * dance. + * FIXME: Migrate the world to Plan 9. + */ + while (n > 0) { + FD_ZERO(&wfd); + FD_ZERO(&rfd); + FD_SET(cmdfd, &wfd); + FD_SET(cmdfd, &rfd); + + /* Check if we can write. */ + if (pselect(cmdfd+1, &rfd, &wfd, NULL, NULL, NULL) < 0) { + if (errno == EINTR) + continue; + die("select failed: %s\n", strerror(errno)); + } + if (FD_ISSET(cmdfd, &wfd)) { + /* + * Only write the bytes written by ttywrite() or the + * default of 256. This seems to be a reasonable value + * for a serial line. Bigger values might clog the I/O. + */ + if ((r = write(cmdfd, s, (n < lim)? n : lim)) < 0) + goto write_error; + if (r < n) { + /* + * We weren't able to write out everything. + * This means the buffer is getting full + * again. Empty it. + */ + if (n < lim) + lim = ttyread(); + n -= r; + s += r; + } else { + /* All bytes have been written. */ + break; + } + } + if (FD_ISSET(cmdfd, &rfd)) + lim = ttyread(); + } + return; + +write_error: + die("write error on tty: %s\n", strerror(errno)); +} + +void +ttyresize(int tw, int th) +{ + struct winsize w; + + w.ws_row = term.row; + w.ws_col = term.col; + w.ws_xpixel = tw; + w.ws_ypixel = th; + if (ioctl(cmdfd, TIOCSWINSZ, &w) < 0) + fprintf(stderr, "Couldn't set window size: %s\n", strerror(errno)); +} + +void +ttyhangup(void) +{ + /* Send SIGHUP to shell */ + kill(pid, SIGHUP); +} + +int +tattrset(int attr) +{ + int i, j; + + for (i = 0; i < term.row-1; i++) { + for (j = 0; j < term.col-1; j++) { + if (term.line[i][j].mode & attr) + return 1; + } + } + + return 0; +} + +void +tsetdirt(int top, int bot) +{ + int i; + + if (term.row <= 0) + return; + + LIMIT(top, 0, term.row-1); + LIMIT(bot, 0, term.row-1); + + for (i = top; i <= bot; i++) + term.dirty[i] = 1; +} + +void +tsetdirtattr(int attr) +{ + int i, j; + + for (i = 0; i < term.row-1; i++) { + for (j = 0; j < term.col-1; j++) { + if (term.line[i][j].mode & attr) { + tsetdirt(i, i); + break; + } + } + } +} + +void +tfulldirt(void) +{ + tsetdirt(0, term.row-1); +} + +void +tcursor(int mode) +{ + static TCursor c[2]; + int alt = IS_SET(MODE_ALTSCREEN); + + if (mode == CURSOR_SAVE) { + c[alt] = term.c; + } else if (mode == CURSOR_LOAD) { + term.c = c[alt]; + tmoveto(c[alt].x, c[alt].y); + } +} + +void +treset(void) +{ + uint i; + + term.c = (TCursor){{ + .mode = ATTR_NULL, + .fg = defaultfg, + .bg = defaultbg + }, .x = 0, .y = 0, .state = CURSOR_DEFAULT}; + + memset(term.tabs, 0, term.col * sizeof(*term.tabs)); + for (i = tabspaces; i < term.col; i += tabspaces) + term.tabs[i] = 1; + term.top = 0; + term.bot = term.row - 1; + term.mode = MODE_WRAP|MODE_UTF8; + memset(term.trantbl, CS_USA, sizeof(term.trantbl)); + term.charset = 0; + + for (i = 0; i < 2; i++) { + tmoveto(0, 0); + tcursor(CURSOR_SAVE); + tclearregion(0, 0, term.col-1, term.row-1); + tswapscreen(); + } +} + +void +tnew(int col, int row) +{ + term = (Term){ .c = { .attr = { .fg = defaultfg, .bg = defaultbg } } }; + tresize(col, row); + treset(); +} + +void +tswapscreen(void) +{ + Line *tmp = term.line; + + term.line = term.alt; + term.alt = tmp; + term.mode ^= MODE_ALTSCREEN; + tfulldirt(); +} + +void +tscrolldown(int orig, int n) +{ + int i; + Line temp; + + LIMIT(n, 0, term.bot-orig+1); + + tsetdirt(orig, term.bot-n); + tclearregion(0, term.bot-n+1, term.col-1, term.bot); + + for (i = term.bot; i >= orig+n; i--) { + temp = term.line[i]; + term.line[i] = term.line[i-n]; + term.line[i-n] = temp; + } + + selscroll(orig, n); +} + +void +tscrollup(int orig, int n) +{ + int i; + Line temp; + + LIMIT(n, 0, term.bot-orig+1); + + tclearregion(0, orig, term.col-1, orig+n-1); + tsetdirt(orig+n, term.bot); + + for (i = orig; i <= term.bot-n; i++) { + temp = term.line[i]; + term.line[i] = term.line[i+n]; + term.line[i+n] = temp; + } + + selscroll(orig, -n); +} + +void +selscroll(int orig, int n) +{ + if (sel.ob.x == -1 || sel.alt != IS_SET(MODE_ALTSCREEN)) + return; + + if (BETWEEN(sel.nb.y, orig, term.bot) != BETWEEN(sel.ne.y, orig, term.bot)) { + selclear(); + } else if (BETWEEN(sel.nb.y, orig, term.bot)) { + sel.ob.y += n; + sel.oe.y += n; + if (sel.ob.y < term.top || sel.ob.y > term.bot || + sel.oe.y < term.top || sel.oe.y > term.bot) { + selclear(); + } else { + selnormalize(); + } + } +} + +void +tnewline(int first_col) +{ + int y = term.c.y; + + if (y == term.bot) { + tscrollup(term.top, 1); + } else { + y++; + } + tmoveto(first_col ? 0 : term.c.x, y); +} + +void +csiparse(void) +{ + char *p = csiescseq.buf, *np; + long int v; + int sep = ';'; /* colon or semi-colon, but not both */ + + csiescseq.narg = 0; + if (*p == '?') { + csiescseq.priv = 1; + p++; + } + + csiescseq.buf[csiescseq.len] = '\0'; + while (p < csiescseq.buf+csiescseq.len) { + np = NULL; + v = strtol(p, &np, 10); + if (np == p) + v = 0; + if (v == LONG_MAX || v == LONG_MIN) + v = -1; + csiescseq.arg[csiescseq.narg++] = v; + p = np; + if (sep == ';' && *p == ':') + sep = ':'; /* allow override to colon once */ + if (*p != sep || csiescseq.narg == ESC_ARG_SIZ) + break; + p++; + } + csiescseq.mode[0] = *p++; + csiescseq.mode[1] = (p < csiescseq.buf+csiescseq.len) ? *p : '\0'; +} + +/* for absolute user moves, when decom is set */ +void +tmoveato(int x, int y) +{ + tmoveto(x, y + ((term.c.state & CURSOR_ORIGIN) ? term.top: 0)); +} + +void +tmoveto(int x, int y) +{ + int miny, maxy; + + if (term.c.state & CURSOR_ORIGIN) { + miny = term.top; + maxy = term.bot; + } else { + miny = 0; + maxy = term.row - 1; + } + term.c.state &= ~CURSOR_WRAPNEXT; + term.c.x = LIMIT(x, 0, term.col-1); + term.c.y = LIMIT(y, miny, maxy); +} + +void +tsetchar(Rune u, const Glyph *attr, int x, int y) +{ + static const char *vt100_0[62] = { /* 0x41 - 0x7e */ + "↑", "↓", "→", "←", "█", "▚", "☃", /* A - G */ + 0, 0, 0, 0, 0, 0, 0, 0, /* H - O */ + 0, 0, 0, 0, 0, 0, 0, 0, /* P - W */ + 0, 0, 0, 0, 0, 0, 0, " ", /* X - _ */ + "◆", "▒", "␉", "␌", "␍", "␊", "°", "±", /* ` - g */ + "␤", "␋", "┘", "┐", "┌", "└", "┼", "⎺", /* h - o */ + "⎻", "─", "⎼", "⎽", "├", "┤", "┴", "┬", /* p - w */ + "│", "≤", "≥", "π", "≠", "£", "·", /* x - ~ */ + }; + + /* + * The table is proudly stolen from rxvt. + */ + if (term.trantbl[term.charset] == CS_GRAPHIC0 && + BETWEEN(u, 0x41, 0x7e) && vt100_0[u - 0x41]) + utf8decode(vt100_0[u - 0x41], &u, UTF_SIZ); + + if (term.line[y][x].mode & ATTR_WIDE) { + if (x+1 < term.col) { + term.line[y][x+1].u = ' '; + term.line[y][x+1].mode &= ~ATTR_WDUMMY; + } + } else if (term.line[y][x].mode & ATTR_WDUMMY) { + term.line[y][x-1].u = ' '; + term.line[y][x-1].mode &= ~ATTR_WIDE; + } + + term.dirty[y] = 1; + term.line[y][x] = *attr; + term.line[y][x].u = u; +} + +void +tclearregion(int x1, int y1, int x2, int y2) +{ + int x, y, temp; + Glyph *gp; + + if (x1 > x2) + temp = x1, x1 = x2, x2 = temp; + if (y1 > y2) + temp = y1, y1 = y2, y2 = temp; + + LIMIT(x1, 0, term.col-1); + LIMIT(x2, 0, term.col-1); + LIMIT(y1, 0, term.row-1); + LIMIT(y2, 0, term.row-1); + + for (y = y1; y <= y2; y++) { + term.dirty[y] = 1; + for (x = x1; x <= x2; x++) { + gp = &term.line[y][x]; + if (selected(x, y)) + selclear(); + gp->fg = term.c.attr.fg; + gp->bg = term.c.attr.bg; + gp->mode = 0; + gp->u = ' '; + } + } +} + +void +tdeletechar(int n) +{ + int dst, src, size; + Glyph *line; + + LIMIT(n, 0, term.col - term.c.x); + + dst = term.c.x; + src = term.c.x + n; + size = term.col - src; + line = term.line[term.c.y]; + + memmove(&line[dst], &line[src], size * sizeof(Glyph)); + tclearregion(term.col-n, term.c.y, term.col-1, term.c.y); +} + +void +tinsertblank(int n) +{ + int dst, src, size; + Glyph *line; + + LIMIT(n, 0, term.col - term.c.x); + + dst = term.c.x + n; + src = term.c.x; + size = term.col - dst; + line = term.line[term.c.y]; + + memmove(&line[dst], &line[src], size * sizeof(Glyph)); + tclearregion(src, term.c.y, dst - 1, term.c.y); +} + +void +tinsertblankline(int n) +{ + if (BETWEEN(term.c.y, term.top, term.bot)) + tscrolldown(term.c.y, n); +} + +void +tdeleteline(int n) +{ + if (BETWEEN(term.c.y, term.top, term.bot)) + tscrollup(term.c.y, n); +} + +int32_t +tdefcolor(const int *attr, int *npar, int l) +{ + int32_t idx = -1; + uint r, g, b; + + switch (attr[*npar + 1]) { + case 2: /* direct color in RGB space */ + if (*npar + 4 >= l) { + fprintf(stderr, + "erresc(38): Incorrect number of parameters (%d)\n", + *npar); + break; + } + r = attr[*npar + 2]; + g = attr[*npar + 3]; + b = attr[*npar + 4]; + *npar += 4; + if (!BETWEEN(r, 0, 255) || !BETWEEN(g, 0, 255) || !BETWEEN(b, 0, 255)) + fprintf(stderr, "erresc: bad rgb color (%u,%u,%u)\n", + r, g, b); + else + idx = TRUECOLOR(r, g, b); + break; + case 5: /* indexed color */ + if (*npar + 2 >= l) { + fprintf(stderr, + "erresc(38): Incorrect number of parameters (%d)\n", + *npar); + break; + } + *npar += 2; + if (!BETWEEN(attr[*npar], 0, 255)) + fprintf(stderr, "erresc: bad fgcolor %d\n", attr[*npar]); + else + idx = attr[*npar]; + break; + case 0: /* implemented defined (only foreground) */ + case 1: /* transparent */ + case 3: /* direct color in CMY space */ + case 4: /* direct color in CMYK space */ + default: + fprintf(stderr, + "erresc(38): gfx attr %d unknown\n", attr[*npar]); + break; + } + + return idx; +} + +void +tsetattr(const int *attr, int l) +{ + int i; + int32_t idx; + + for (i = 0; i < l; i++) { + switch (attr[i]) { + case 0: + term.c.attr.mode &= ~( + ATTR_BOLD | + ATTR_FAINT | + ATTR_ITALIC | + ATTR_UNDERLINE | + ATTR_BLINK | + ATTR_REVERSE | + ATTR_INVISIBLE | + ATTR_STRUCK ); + term.c.attr.fg = defaultfg; + term.c.attr.bg = defaultbg; + break; + case 1: + term.c.attr.mode |= ATTR_BOLD; + break; + case 2: + term.c.attr.mode |= ATTR_FAINT; + break; + case 3: + term.c.attr.mode |= ATTR_ITALIC; + break; + case 4: + term.c.attr.mode |= ATTR_UNDERLINE; + break; + case 5: /* slow blink */ + /* FALLTHROUGH */ + case 6: /* rapid blink */ + term.c.attr.mode |= ATTR_BLINK; + break; + case 7: + term.c.attr.mode |= ATTR_REVERSE; + break; + case 8: + term.c.attr.mode |= ATTR_INVISIBLE; + break; + case 9: + term.c.attr.mode |= ATTR_STRUCK; + break; + case 22: + term.c.attr.mode &= ~(ATTR_BOLD | ATTR_FAINT); + break; + case 23: + term.c.attr.mode &= ~ATTR_ITALIC; + break; + case 24: + term.c.attr.mode &= ~ATTR_UNDERLINE; + break; + case 25: + term.c.attr.mode &= ~ATTR_BLINK; + break; + case 27: + term.c.attr.mode &= ~ATTR_REVERSE; + break; + case 28: + term.c.attr.mode &= ~ATTR_INVISIBLE; + break; + case 29: + term.c.attr.mode &= ~ATTR_STRUCK; + break; + case 38: + if ((idx = tdefcolor(attr, &i, l)) >= 0) + term.c.attr.fg = idx; + break; + case 39: /* set foreground color to default */ + term.c.attr.fg = defaultfg; + break; + case 48: + if ((idx = tdefcolor(attr, &i, l)) >= 0) + term.c.attr.bg = idx; + break; + case 49: /* set background color to default */ + term.c.attr.bg = defaultbg; + break; + case 58: + /* This starts a sequence to change the color of + * "underline" pixels. We don't support that and + * instead eat up a following "5;n" or "2;r;g;b". */ + tdefcolor(attr, &i, l); + break; + default: + if (BETWEEN(attr[i], 30, 37)) { + term.c.attr.fg = attr[i] - 30; + } else if (BETWEEN(attr[i], 40, 47)) { + term.c.attr.bg = attr[i] - 40; + } else if (BETWEEN(attr[i], 90, 97)) { + term.c.attr.fg = attr[i] - 90 + 8; + } else if (BETWEEN(attr[i], 100, 107)) { + term.c.attr.bg = attr[i] - 100 + 8; + } else { + fprintf(stderr, + "erresc(default): gfx attr %d unknown\n", + attr[i]); + csidump(); + } + break; + } + } +} + +void +tsetscroll(int t, int b) +{ + int temp; + + LIMIT(t, 0, term.row-1); + LIMIT(b, 0, term.row-1); + if (t > b) { + temp = t; + t = b; + b = temp; + } + term.top = t; + term.bot = b; +} + +void +tsetmode(int priv, int set, const int *args, int narg) +{ + int alt; const int *lim; + + for (lim = args + narg; args < lim; ++args) { + if (priv) { + switch (*args) { + case 1: /* DECCKM -- Cursor key */ + xsetmode(set, MODE_APPCURSOR); + break; + case 5: /* DECSCNM -- Reverse video */ + xsetmode(set, MODE_REVERSE); + break; + case 6: /* DECOM -- Origin */ + MODBIT(term.c.state, set, CURSOR_ORIGIN); + tmoveato(0, 0); + break; + case 7: /* DECAWM -- Auto wrap */ + MODBIT(term.mode, set, MODE_WRAP); + break; + case 0: /* Error (IGNORED) */ + case 2: /* DECANM -- ANSI/VT52 (IGNORED) */ + case 3: /* DECCOLM -- Column (IGNORED) */ + case 4: /* DECSCLM -- Scroll (IGNORED) */ + case 8: /* DECARM -- Auto repeat (IGNORED) */ + case 18: /* DECPFF -- Printer feed (IGNORED) */ + case 19: /* DECPEX -- Printer extent (IGNORED) */ + case 42: /* DECNRCM -- National characters (IGNORED) */ + case 12: /* att610 -- Start blinking cursor (IGNORED) */ + break; + case 25: /* DECTCEM -- Text Cursor Enable Mode */ + xsetmode(!set, MODE_HIDE); + break; + case 9: /* X10 mouse compatibility mode */ + xsetpointermotion(0); + xsetmode(0, MODE_MOUSE); + xsetmode(set, MODE_MOUSEX10); + break; + case 1000: /* 1000: report button press */ + xsetpointermotion(0); + xsetmode(0, MODE_MOUSE); + xsetmode(set, MODE_MOUSEBTN); + break; + case 1002: /* 1002: report motion on button press */ + xsetpointermotion(0); + xsetmode(0, MODE_MOUSE); + xsetmode(set, MODE_MOUSEMOTION); + break; + case 1003: /* 1003: enable all mouse motions */ + xsetpointermotion(set); + xsetmode(0, MODE_MOUSE); + xsetmode(set, MODE_MOUSEMANY); + break; + case 1004: /* 1004: send focus events to tty */ + xsetmode(set, MODE_FOCUS); + break; + case 1006: /* 1006: extended reporting mode */ + xsetmode(set, MODE_MOUSESGR); + break; + case 1034: /* 1034: enable 8-bit mode for keyboard input */ + xsetmode(set, MODE_8BIT); + break; + case 1049: /* swap screen & set/restore cursor as xterm */ + if (!allowaltscreen) + break; + tcursor((set) ? CURSOR_SAVE : CURSOR_LOAD); + /* FALLTHROUGH */ + case 47: /* swap screen buffer */ + case 1047: /* swap screen buffer */ + if (!allowaltscreen) + break; + alt = IS_SET(MODE_ALTSCREEN); + if (alt) { + tclearregion(0, 0, term.col-1, + term.row-1); + } + if (set ^ alt) /* set is always 1 or 0 */ + tswapscreen(); + if (*args != 1049) + break; + /* FALLTHROUGH */ + case 1048: /* save/restore cursor (like DECSC/DECRC) */ + tcursor((set) ? CURSOR_SAVE : CURSOR_LOAD); + break; + case 2004: /* 2004: bracketed paste mode */ + xsetmode(set, MODE_BRCKTPASTE); + break; + /* Not implemented mouse modes. See comments there. */ + case 1001: /* mouse highlight mode; can hang the + terminal by design when implemented. */ + case 1005: /* UTF-8 mouse mode; will confuse + applications not supporting UTF-8 + and luit. */ + case 1015: /* urxvt mangled mouse mode; incompatible + and can be mistaken for other control + codes. */ + break; + default: + fprintf(stderr, + "erresc: unknown private set/reset mode %d\n", + *args); + break; + } + } else { + switch (*args) { + case 0: /* Error (IGNORED) */ + break; + case 2: + xsetmode(set, MODE_KBDLOCK); + break; + case 4: /* IRM -- Insertion-replacement */ + MODBIT(term.mode, set, MODE_INSERT); + break; + case 12: /* SRM -- Send/Receive */ + MODBIT(term.mode, !set, MODE_ECHO); + break; + case 20: /* LNM -- Linefeed/new line */ + MODBIT(term.mode, set, MODE_CRLF); + break; + default: + fprintf(stderr, + "erresc: unknown set/reset mode %d\n", + *args); + break; + } + } + } +} + +void +csihandle(void) +{ + char buf[40]; + int len; + + switch (csiescseq.mode[0]) { + default: + unknown: + fprintf(stderr, "erresc: unknown csi "); + csidump(); + /* die(""); */ + break; + case '@': /* ICH -- Insert blank char */ + DEFAULT(csiescseq.arg[0], 1); + tinsertblank(csiescseq.arg[0]); + break; + case 'A': /* CUU -- Cursor Up */ + DEFAULT(csiescseq.arg[0], 1); + tmoveto(term.c.x, term.c.y-csiescseq.arg[0]); + break; + case 'B': /* CUD -- Cursor Down */ + case 'e': /* VPR --Cursor Down */ + DEFAULT(csiescseq.arg[0], 1); + tmoveto(term.c.x, term.c.y+csiescseq.arg[0]); + break; + case 'i': /* MC -- Media Copy */ + switch (csiescseq.arg[0]) { + case 0: + tdump(); + break; + case 1: + tdumpline(term.c.y); + break; + case 2: + tdumpsel(); + break; + case 4: + term.mode &= ~MODE_PRINT; + break; + case 5: + term.mode |= MODE_PRINT; + break; + } + break; + case 'c': /* DA -- Device Attributes */ + if (csiescseq.arg[0] == 0) + ttywrite(vtiden, strlen(vtiden), 0); + break; + case 'b': /* REP -- if last char is printable print it more times */ + LIMIT(csiescseq.arg[0], 1, 65535); + if (term.lastc) + while (csiescseq.arg[0]-- > 0) + tputc(term.lastc); + break; + case 'C': /* CUF -- Cursor Forward */ + case 'a': /* HPR -- Cursor Forward */ + DEFAULT(csiescseq.arg[0], 1); + tmoveto(term.c.x+csiescseq.arg[0], term.c.y); + break; + case 'D': /* CUB -- Cursor Backward */ + DEFAULT(csiescseq.arg[0], 1); + tmoveto(term.c.x-csiescseq.arg[0], term.c.y); + break; + case 'E': /* CNL -- Cursor Down and first col */ + DEFAULT(csiescseq.arg[0], 1); + tmoveto(0, term.c.y+csiescseq.arg[0]); + break; + case 'F': /* CPL -- Cursor Up and first col */ + DEFAULT(csiescseq.arg[0], 1); + tmoveto(0, term.c.y-csiescseq.arg[0]); + break; + case 'g': /* TBC -- Tabulation clear */ + switch (csiescseq.arg[0]) { + case 0: /* clear current tab stop */ + term.tabs[term.c.x] = 0; + break; + case 3: /* clear all the tabs */ + memset(term.tabs, 0, term.col * sizeof(*term.tabs)); + break; + default: + goto unknown; + } + break; + case 'G': /* CHA -- Move to */ + case '`': /* HPA */ + DEFAULT(csiescseq.arg[0], 1); + tmoveto(csiescseq.arg[0]-1, term.c.y); + break; + case 'H': /* CUP -- Move to */ + case 'f': /* HVP */ + DEFAULT(csiescseq.arg[0], 1); + DEFAULT(csiescseq.arg[1], 1); + tmoveato(csiescseq.arg[1]-1, csiescseq.arg[0]-1); + break; + case 'I': /* CHT -- Cursor Forward Tabulation tab stops */ + DEFAULT(csiescseq.arg[0], 1); + tputtab(csiescseq.arg[0]); + break; + case 'J': /* ED -- Clear screen */ + switch (csiescseq.arg[0]) { + case 0: /* below */ + tclearregion(term.c.x, term.c.y, term.col-1, term.c.y); + if (term.c.y < term.row-1) { + tclearregion(0, term.c.y+1, term.col-1, + term.row-1); + } + break; + case 1: /* above */ + if (term.c.y > 0) + tclearregion(0, 0, term.col-1, term.c.y-1); + tclearregion(0, term.c.y, term.c.x, term.c.y); + break; + case 2: /* all */ + tclearregion(0, 0, term.col-1, term.row-1); + break; + default: + goto unknown; + } + break; + case 'K': /* EL -- Clear line */ + switch (csiescseq.arg[0]) { + case 0: /* right */ + tclearregion(term.c.x, term.c.y, term.col-1, + term.c.y); + break; + case 1: /* left */ + tclearregion(0, term.c.y, term.c.x, term.c.y); + break; + case 2: /* all */ + tclearregion(0, term.c.y, term.col-1, term.c.y); + break; + } + break; + case 'S': /* SU -- Scroll line up */ + if (csiescseq.priv) break; + DEFAULT(csiescseq.arg[0], 1); + tscrollup(term.top, csiescseq.arg[0]); + break; + case 'T': /* SD -- Scroll line down */ + DEFAULT(csiescseq.arg[0], 1); + tscrolldown(term.top, csiescseq.arg[0]); + break; + case 'L': /* IL -- Insert blank lines */ + DEFAULT(csiescseq.arg[0], 1); + tinsertblankline(csiescseq.arg[0]); + break; + case 'l': /* RM -- Reset Mode */ + tsetmode(csiescseq.priv, 0, csiescseq.arg, csiescseq.narg); + break; + case 'M': /* DL -- Delete lines */ + DEFAULT(csiescseq.arg[0], 1); + tdeleteline(csiescseq.arg[0]); + break; + case 'X': /* ECH -- Erase char */ + DEFAULT(csiescseq.arg[0], 1); + tclearregion(term.c.x, term.c.y, + term.c.x + csiescseq.arg[0] - 1, term.c.y); + break; + case 'P': /* DCH -- Delete char */ + DEFAULT(csiescseq.arg[0], 1); + tdeletechar(csiescseq.arg[0]); + break; + case 'Z': /* CBT -- Cursor Backward Tabulation tab stops */ + DEFAULT(csiescseq.arg[0], 1); + tputtab(-csiescseq.arg[0]); + break; + case 'd': /* VPA -- Move to */ + DEFAULT(csiescseq.arg[0], 1); + tmoveato(term.c.x, csiescseq.arg[0]-1); + break; + case 'h': /* SM -- Set terminal mode */ + tsetmode(csiescseq.priv, 1, csiescseq.arg, csiescseq.narg); + break; + case 'm': /* SGR -- Terminal attribute (color) */ + tsetattr(csiescseq.arg, csiescseq.narg); + break; + case 'n': /* DSR -- Device Status Report */ + switch (csiescseq.arg[0]) { + case 5: /* Status Report "OK" `0n` */ + ttywrite("\033[0n", sizeof("\033[0n") - 1, 0); + break; + case 6: /* Report Cursor Position (CPR) ";R" */ + len = snprintf(buf, sizeof(buf), "\033[%i;%iR", + term.c.y+1, term.c.x+1); + ttywrite(buf, len, 0); + break; + default: + goto unknown; + } + break; + case 'r': /* DECSTBM -- Set Scrolling Region */ + if (csiescseq.priv) { + goto unknown; + } else { + DEFAULT(csiescseq.arg[0], 1); + DEFAULT(csiescseq.arg[1], term.row); + tsetscroll(csiescseq.arg[0]-1, csiescseq.arg[1]-1); + tmoveato(0, 0); + } + break; + case 's': /* DECSC -- Save cursor position (ANSI.SYS) */ + tcursor(CURSOR_SAVE); + break; + case 'u': /* DECRC -- Restore cursor position (ANSI.SYS) */ + if (csiescseq.priv) { + goto unknown; + } else { + tcursor(CURSOR_LOAD); + } + break; + case ' ': + switch (csiescseq.mode[1]) { + case 'q': /* DECSCUSR -- Set Cursor Style */ + if (xsetcursor(csiescseq.arg[0])) + goto unknown; + break; + default: + goto unknown; + } + break; + } +} + +void +csidump(void) +{ + size_t i; + uint c; + + fprintf(stderr, "ESC["); + for (i = 0; i < csiescseq.len; i++) { + c = csiescseq.buf[i] & 0xff; + if (isprint(c)) { + putc(c, stderr); + } else if (c == '\n') { + fprintf(stderr, "(\\n)"); + } else if (c == '\r') { + fprintf(stderr, "(\\r)"); + } else if (c == 0x1b) { + fprintf(stderr, "(\\e)"); + } else { + fprintf(stderr, "(%02x)", c); + } + } + putc('\n', stderr); +} + +void +csireset(void) +{ + memset(&csiescseq, 0, sizeof(csiescseq)); +} + +void +osc_color_response(int num, int index, int is_osc4) +{ + int n; + char buf[32]; + unsigned char r, g, b; + + if (xgetcolor(is_osc4 ? num : index, &r, &g, &b)) { + fprintf(stderr, "erresc: failed to fetch %s color %d\n", + is_osc4 ? "osc4" : "osc", + is_osc4 ? num : index); + return; + } + + n = snprintf(buf, sizeof buf, "\033]%s%d;rgb:%02x%02x/%02x%02x/%02x%02x\007", + is_osc4 ? "4;" : "", num, r, r, g, g, b, b); + if (n < 0 || n >= sizeof(buf)) { + fprintf(stderr, "error: %s while printing %s response\n", + n < 0 ? "snprintf failed" : "truncation occurred", + is_osc4 ? "osc4" : "osc"); + } else { + ttywrite(buf, n, 1); + } +} + +void +strhandle(void) +{ + char *p = NULL, *dec; + int j, narg, par; + const struct { int idx; char *str; } osc_table[] = { + { defaultfg, "foreground" }, + { defaultbg, "background" }, + { defaultcs, "cursor" } + }; + + term.esc &= ~(ESC_STR_END|ESC_STR); + strparse(); + par = (narg = strescseq.narg) ? atoi(strescseq.args[0]) : 0; + + switch (strescseq.type) { + case ']': /* OSC -- Operating System Command */ + switch (par) { + case 0: + if (narg > 1) { + xsettitle(strescseq.args[1]); + xseticontitle(strescseq.args[1]); + } + return; + case 1: + if (narg > 1) + xseticontitle(strescseq.args[1]); + return; + case 2: + if (narg > 1) + xsettitle(strescseq.args[1]); + return; + case 52: /* manipulate selection data */ + if (narg > 2 && allowwindowops) { + dec = base64dec(strescseq.args[2]); + if (dec) { + xsetsel(dec); + xclipcopy(); + } else { + fprintf(stderr, "erresc: invalid base64\n"); + } + } + return; + case 10: /* set dynamic VT100 text foreground color */ + case 11: /* set dynamic VT100 text background color */ + case 12: /* set dynamic text cursor color */ + if (narg < 2) + break; + p = strescseq.args[1]; + if ((j = par - 10) < 0 || j >= LEN(osc_table)) + break; /* shouldn't be possible */ + + if (!strcmp(p, "?")) { + osc_color_response(par, osc_table[j].idx, 0); + } else if (xsetcolorname(osc_table[j].idx, p)) { + fprintf(stderr, "erresc: invalid %s color: %s\n", + osc_table[j].str, p); + } else { + tfulldirt(); + } + return; + case 4: /* color set */ + if (narg < 3) + break; + p = strescseq.args[2]; + /* FALLTHROUGH */ + case 104: /* color reset */ + j = (narg > 1) ? atoi(strescseq.args[1]) : -1; + + if (p && !strcmp(p, "?")) { + osc_color_response(j, 0, 1); + } else if (xsetcolorname(j, p)) { + if (par == 104 && narg <= 1) { + xloadcols(); + return; /* color reset without parameter */ + } + fprintf(stderr, "erresc: invalid color j=%d, p=%s\n", + j, p ? p : "(null)"); + } else { + /* + * TODO if defaultbg color is changed, borders + * are dirty + */ + tfulldirt(); + } + return; + case 110: /* reset dynamic VT100 text foreground color */ + case 111: /* reset dynamic VT100 text background color */ + case 112: /* reset dynamic text cursor color */ + if (narg != 1) + break; + if ((j = par - 110) < 0 || j >= LEN(osc_table)) + break; /* shouldn't be possible */ + if (xsetcolorname(osc_table[j].idx, NULL)) { + fprintf(stderr, "erresc: %s color not found\n", osc_table[j].str); + } else { + tfulldirt(); + } + return; + } + break; + case 'k': /* old title set compatibility */ + xsettitle(strescseq.args[0]); + return; + case 'P': /* DCS -- Device Control String */ + case '_': /* APC -- Application Program Command */ + case '^': /* PM -- Privacy Message */ + return; + } + + fprintf(stderr, "erresc: unknown str "); + strdump(); +} + +void +strparse(void) +{ + int c; + char *p = strescseq.buf; + + strescseq.narg = 0; + strescseq.buf[strescseq.len] = '\0'; + + if (*p == '\0') + return; + + while (strescseq.narg < STR_ARG_SIZ) { + strescseq.args[strescseq.narg++] = p; + while ((c = *p) != ';' && c != '\0') + ++p; + if (c == '\0') + return; + *p++ = '\0'; + } +} + +void +strdump(void) +{ + size_t i; + uint c; + + fprintf(stderr, "ESC%c", strescseq.type); + for (i = 0; i < strescseq.len; i++) { + c = strescseq.buf[i] & 0xff; + if (c == '\0') { + putc('\n', stderr); + return; + } else if (isprint(c)) { + putc(c, stderr); + } else if (c == '\n') { + fprintf(stderr, "(\\n)"); + } else if (c == '\r') { + fprintf(stderr, "(\\r)"); + } else if (c == 0x1b) { + fprintf(stderr, "(\\e)"); + } else { + fprintf(stderr, "(%02x)", c); + } + } + fprintf(stderr, "ESC\\\n"); +} + +void +strreset(void) +{ + strescseq = (STREscape){ + .buf = xrealloc(strescseq.buf, STR_BUF_SIZ), + .siz = STR_BUF_SIZ, + }; +} + +void +sendbreak(const Arg *arg) +{ + if (tcsendbreak(cmdfd, 0)) + perror("Error sending break"); +} + +void +tprinter(char *s, size_t len) +{ + if (iofd != -1 && xwrite(iofd, s, len) < 0) { + perror("Error writing to output file"); + close(iofd); + iofd = -1; + } +} + +void +toggleprinter(const Arg *arg) +{ + term.mode ^= MODE_PRINT; +} + +void +printscreen(const Arg *arg) +{ + tdump(); +} + +void +printsel(const Arg *arg) +{ + tdumpsel(); +} + +void +tdumpsel(void) +{ + char *ptr; + + if ((ptr = getsel())) { + tprinter(ptr, strlen(ptr)); + free(ptr); + } +} + +void +tdumpline(int n) +{ + char buf[UTF_SIZ]; + const Glyph *bp, *end; + + bp = &term.line[n][0]; + end = &bp[MIN(tlinelen(n), term.col) - 1]; + if (bp != end || bp->u != ' ') { + for ( ; bp <= end; ++bp) + tprinter(buf, utf8encode(bp->u, buf)); + } + tprinter("\n", 1); +} + +void +tdump(void) +{ + int i; + + for (i = 0; i < term.row; ++i) + tdumpline(i); +} + +void +tputtab(int n) +{ + uint x = term.c.x; + + if (n > 0) { + while (x < term.col && n--) + for (++x; x < term.col && !term.tabs[x]; ++x) + /* nothing */ ; + } else if (n < 0) { + while (x > 0 && n++) + for (--x; x > 0 && !term.tabs[x]; --x) + /* nothing */ ; + } + term.c.x = LIMIT(x, 0, term.col-1); +} + +void +tdefutf8(char ascii) +{ + if (ascii == 'G') + term.mode |= MODE_UTF8; + else if (ascii == '@') + term.mode &= ~MODE_UTF8; +} + +void +tdeftran(char ascii) +{ + static char cs[] = "0B"; + static int vcs[] = {CS_GRAPHIC0, CS_USA}; + char *p; + + if ((p = strchr(cs, ascii)) == NULL) { + fprintf(stderr, "esc unhandled charset: ESC ( %c\n", ascii); + } else { + term.trantbl[term.icharset] = vcs[p - cs]; + } +} + +void +tdectest(char c) +{ + int x, y; + + if (c == '8') { /* DEC screen alignment test. */ + for (x = 0; x < term.col; ++x) { + for (y = 0; y < term.row; ++y) + tsetchar('E', &term.c.attr, x, y); + } + } +} + +void +tstrsequence(uchar c) +{ + switch (c) { + case 0x90: /* DCS -- Device Control String */ + c = 'P'; + break; + case 0x9f: /* APC -- Application Program Command */ + c = '_'; + break; + case 0x9e: /* PM -- Privacy Message */ + c = '^'; + break; + case 0x9d: /* OSC -- Operating System Command */ + c = ']'; + break; + } + strreset(); + strescseq.type = c; + term.esc |= ESC_STR; +} + +void +tcontrolcode(uchar ascii) +{ + switch (ascii) { + case '\t': /* HT */ + tputtab(1); + return; + case '\b': /* BS */ + tmoveto(term.c.x-1, term.c.y); + return; + case '\r': /* CR */ + tmoveto(0, term.c.y); + return; + case '\f': /* LF */ + case '\v': /* VT */ + case '\n': /* LF */ + /* go to first col if the mode is set */ + tnewline(IS_SET(MODE_CRLF)); + return; + case '\a': /* BEL */ + if (term.esc & ESC_STR_END) { + /* backwards compatibility to xterm */ + strhandle(); + } else { + xbell(); + } + break; + case '\033': /* ESC */ + csireset(); + term.esc &= ~(ESC_CSI|ESC_ALTCHARSET|ESC_TEST); + term.esc |= ESC_START; + return; + case '\016': /* SO (LS1 -- Locking shift 1) */ + case '\017': /* SI (LS0 -- Locking shift 0) */ + term.charset = 1 - (ascii - '\016'); + return; + case '\032': /* SUB */ + tsetchar('?', &term.c.attr, term.c.x, term.c.y); + /* FALLTHROUGH */ + case '\030': /* CAN */ + csireset(); + break; + case '\005': /* ENQ (IGNORED) */ + case '\000': /* NUL (IGNORED) */ + case '\021': /* XON (IGNORED) */ + case '\023': /* XOFF (IGNORED) */ + case 0177: /* DEL (IGNORED) */ + return; + case 0x80: /* TODO: PAD */ + case 0x81: /* TODO: HOP */ + case 0x82: /* TODO: BPH */ + case 0x83: /* TODO: NBH */ + case 0x84: /* TODO: IND */ + break; + case 0x85: /* NEL -- Next line */ + tnewline(1); /* always go to first col */ + break; + case 0x86: /* TODO: SSA */ + case 0x87: /* TODO: ESA */ + break; + case 0x88: /* HTS -- Horizontal tab stop */ + term.tabs[term.c.x] = 1; + break; + case 0x89: /* TODO: HTJ */ + case 0x8a: /* TODO: VTS */ + case 0x8b: /* TODO: PLD */ + case 0x8c: /* TODO: PLU */ + case 0x8d: /* TODO: RI */ + case 0x8e: /* TODO: SS2 */ + case 0x8f: /* TODO: SS3 */ + case 0x91: /* TODO: PU1 */ + case 0x92: /* TODO: PU2 */ + case 0x93: /* TODO: STS */ + case 0x94: /* TODO: CCH */ + case 0x95: /* TODO: MW */ + case 0x96: /* TODO: SPA */ + case 0x97: /* TODO: EPA */ + case 0x98: /* TODO: SOS */ + case 0x99: /* TODO: SGCI */ + break; + case 0x9a: /* DECID -- Identify Terminal */ + ttywrite(vtiden, strlen(vtiden), 0); + break; + case 0x9b: /* TODO: CSI */ + case 0x9c: /* TODO: ST */ + break; + case 0x90: /* DCS -- Device Control String */ + case 0x9d: /* OSC -- Operating System Command */ + case 0x9e: /* PM -- Privacy Message */ + case 0x9f: /* APC -- Application Program Command */ + tstrsequence(ascii); + return; + } + /* only CAN, SUB, \a and C1 chars interrupt a sequence */ + term.esc &= ~(ESC_STR_END|ESC_STR); +} + +/* + * returns 1 when the sequence is finished and it hasn't to read + * more characters for this sequence, otherwise 0 + */ +int +eschandle(uchar ascii) +{ + switch (ascii) { + case '[': + term.esc |= ESC_CSI; + return 0; + case '#': + term.esc |= ESC_TEST; + return 0; + case '%': + term.esc |= ESC_UTF8; + return 0; + case 'P': /* DCS -- Device Control String */ + case '_': /* APC -- Application Program Command */ + case '^': /* PM -- Privacy Message */ + case ']': /* OSC -- Operating System Command */ + case 'k': /* old title set compatibility */ + tstrsequence(ascii); + return 0; + case 'n': /* LS2 -- Locking shift 2 */ + case 'o': /* LS3 -- Locking shift 3 */ + term.charset = 2 + (ascii - 'n'); + break; + case '(': /* GZD4 -- set primary charset G0 */ + case ')': /* G1D4 -- set secondary charset G1 */ + case '*': /* G2D4 -- set tertiary charset G2 */ + case '+': /* G3D4 -- set quaternary charset G3 */ + term.icharset = ascii - '('; + term.esc |= ESC_ALTCHARSET; + return 0; + case 'D': /* IND -- Linefeed */ + if (term.c.y == term.bot) { + tscrollup(term.top, 1); + } else { + tmoveto(term.c.x, term.c.y+1); + } + break; + case 'E': /* NEL -- Next line */ + tnewline(1); /* always go to first col */ + break; + case 'H': /* HTS -- Horizontal tab stop */ + term.tabs[term.c.x] = 1; + break; + case 'M': /* RI -- Reverse index */ + if (term.c.y == term.top) { + tscrolldown(term.top, 1); + } else { + tmoveto(term.c.x, term.c.y-1); + } + break; + case 'Z': /* DECID -- Identify Terminal */ + ttywrite(vtiden, strlen(vtiden), 0); + break; + case 'c': /* RIS -- Reset to initial state */ + treset(); + resettitle(); + xloadcols(); + xsetmode(0, MODE_HIDE); + xsetmode(0, MODE_BRCKTPASTE); + break; + case '=': /* DECPAM -- Application keypad */ + xsetmode(1, MODE_APPKEYPAD); + break; + case '>': /* DECPNM -- Normal keypad */ + xsetmode(0, MODE_APPKEYPAD); + break; + case '7': /* DECSC -- Save Cursor */ + tcursor(CURSOR_SAVE); + break; + case '8': /* DECRC -- Restore Cursor */ + tcursor(CURSOR_LOAD); + break; + case '\\': /* ST -- String Terminator */ + if (term.esc & ESC_STR_END) + strhandle(); + break; + default: + fprintf(stderr, "erresc: unknown sequence ESC 0x%02X '%c'\n", + (uchar) ascii, isprint(ascii)? ascii:'.'); + break; + } + return 1; +} + +void +tputc(Rune u) +{ + char c[UTF_SIZ]; + int control; + int width, len; + Glyph *gp; + + control = ISCONTROL(u); + if (u < 127 || !IS_SET(MODE_UTF8)) { + c[0] = u; + width = len = 1; + } else { + len = utf8encode(u, c); + if (!control && (width = wcwidth(u)) == -1) + width = 1; + } + + if (IS_SET(MODE_PRINT)) + tprinter(c, len); + + /* + * STR sequence must be checked before anything else + * because it uses all following characters until it + * receives a ESC, a SUB, a ST or any other C1 control + * character. + */ + if (term.esc & ESC_STR) { + if (u == '\a' || u == 030 || u == 032 || u == 033 || + ISCONTROLC1(u)) { + term.esc &= ~(ESC_START|ESC_STR); + term.esc |= ESC_STR_END; + goto check_control_code; + } + + if (strescseq.len+len >= strescseq.siz) { + /* + * Here is a bug in terminals. If the user never sends + * some code to stop the str or esc command, then st + * will stop responding. But this is better than + * silently failing with unknown characters. At least + * then users will report back. + * + * In the case users ever get fixed, here is the code: + */ + /* + * term.esc = 0; + * strhandle(); + */ + if (strescseq.siz > (SIZE_MAX - UTF_SIZ) / 2) + return; + strescseq.siz *= 2; + strescseq.buf = xrealloc(strescseq.buf, strescseq.siz); + } + + memmove(&strescseq.buf[strescseq.len], c, len); + strescseq.len += len; + return; + } + +check_control_code: + /* + * Actions of control codes must be performed as soon they arrive + * because they can be embedded inside a control sequence, and + * they must not cause conflicts with sequences. + */ + if (control) { + /* in UTF-8 mode ignore handling C1 control characters */ + if (IS_SET(MODE_UTF8) && ISCONTROLC1(u)) + return; + tcontrolcode(u); + /* + * control codes are not shown ever + */ + if (!term.esc) + term.lastc = 0; + return; + } else if (term.esc & ESC_START) { + if (term.esc & ESC_CSI) { + csiescseq.buf[csiescseq.len++] = u; + if (BETWEEN(u, 0x40, 0x7E) + || csiescseq.len >= \ + sizeof(csiescseq.buf)-1) { + term.esc = 0; + csiparse(); + csihandle(); + } + return; + } else if (term.esc & ESC_UTF8) { + tdefutf8(u); + } else if (term.esc & ESC_ALTCHARSET) { + tdeftran(u); + } else if (term.esc & ESC_TEST) { + tdectest(u); + } else { + if (!eschandle(u)) + return; + /* sequence already finished */ + } + term.esc = 0; + /* + * All characters which form part of a sequence are not + * printed + */ + return; + } + if (selected(term.c.x, term.c.y)) + selclear(); + + gp = &term.line[term.c.y][term.c.x]; + if (IS_SET(MODE_WRAP) && (term.c.state & CURSOR_WRAPNEXT)) { + gp->mode |= ATTR_WRAP; + tnewline(1); + gp = &term.line[term.c.y][term.c.x]; + } + + if (IS_SET(MODE_INSERT) && term.c.x+width < term.col) { + memmove(gp+width, gp, (term.col - term.c.x - width) * sizeof(Glyph)); + gp->mode &= ~ATTR_WIDE; + } + + if (term.c.x+width > term.col) { + if (IS_SET(MODE_WRAP)) + tnewline(1); + else + tmoveto(term.col - width, term.c.y); + gp = &term.line[term.c.y][term.c.x]; + } + + tsetchar(u, &term.c.attr, term.c.x, term.c.y); + term.lastc = u; + + if (width == 2) { + gp->mode |= ATTR_WIDE; + if (term.c.x+1 < term.col) { + if (gp[1].mode == ATTR_WIDE && term.c.x+2 < term.col) { + gp[2].u = ' '; + gp[2].mode &= ~ATTR_WDUMMY; + } + gp[1].u = '\0'; + gp[1].mode = ATTR_WDUMMY; + } + } + if (term.c.x+width < term.col) { + tmoveto(term.c.x+width, term.c.y); + } else { + term.c.state |= CURSOR_WRAPNEXT; + } +} + +int +twrite(const char *buf, int buflen, int show_ctrl) +{ + int charsize; + Rune u; + int n; + + for (n = 0; n < buflen; n += charsize) { + if (IS_SET(MODE_UTF8)) { + /* process a complete utf8 char */ + charsize = utf8decode(buf + n, &u, buflen - n); + if (charsize == 0) + break; + } else { + u = buf[n] & 0xFF; + charsize = 1; + } + if (show_ctrl && ISCONTROL(u)) { + if (u & 0x80) { + u &= 0x7f; + tputc('^'); + tputc('['); + } else if (u != '\n' && u != '\r' && u != '\t') { + u ^= 0x40; + tputc('^'); + } + } + tputc(u); + } + return n; +} + +void +tresize(int col, int row) +{ + int i; + int minrow = MIN(row, term.row); + int mincol = MIN(col, term.col); + int *bp; + TCursor c; + + if (col < 1 || row < 1) { + fprintf(stderr, + "tresize: error resizing to %dx%d\n", col, row); + return; + } + + /* + * slide screen to keep cursor where we expect it - + * tscrollup would work here, but we can optimize to + * memmove because we're freeing the earlier lines + */ + for (i = 0; i <= term.c.y - row; i++) { + free(term.line[i]); + free(term.alt[i]); + } + /* ensure that both src and dst are not NULL */ + if (i > 0) { + memmove(term.line, term.line + i, row * sizeof(Line)); + memmove(term.alt, term.alt + i, row * sizeof(Line)); + } + for (i += row; i < term.row; i++) { + free(term.line[i]); + free(term.alt[i]); + } + + /* resize to new height */ + term.line = xrealloc(term.line, row * sizeof(Line)); + term.alt = xrealloc(term.alt, row * sizeof(Line)); + term.dirty = xrealloc(term.dirty, row * sizeof(*term.dirty)); + term.tabs = xrealloc(term.tabs, col * sizeof(*term.tabs)); + + /* resize each row to new width, zero-pad if needed */ + for (i = 0; i < minrow; i++) { + term.line[i] = xrealloc(term.line[i], col * sizeof(Glyph)); + term.alt[i] = xrealloc(term.alt[i], col * sizeof(Glyph)); + } + + /* allocate any new rows */ + for (/* i = minrow */; i < row; i++) { + term.line[i] = xmalloc(col * sizeof(Glyph)); + term.alt[i] = xmalloc(col * sizeof(Glyph)); + } + if (col > term.col) { + bp = term.tabs + term.col; + + memset(bp, 0, sizeof(*term.tabs) * (col - term.col)); + while (--bp > term.tabs && !*bp) + /* nothing */ ; + for (bp += tabspaces; bp < term.tabs + col; bp += tabspaces) + *bp = 1; + } + /* update terminal size */ + term.col = col; + term.row = row; + /* reset scrolling region */ + tsetscroll(0, row-1); + /* make use of the LIMIT in tmoveto */ + tmoveto(term.c.x, term.c.y); + /* Clearing both screens (it makes dirty all lines) */ + c = term.c; + for (i = 0; i < 2; i++) { + if (mincol < col && 0 < minrow) { + tclearregion(mincol, 0, col - 1, minrow - 1); + } + if (0 < col && minrow < row) { + tclearregion(0, minrow, col - 1, row - 1); + } + tswapscreen(); + tcursor(CURSOR_LOAD); + } + term.c = c; +} + +void +resettitle(void) +{ + xsettitle(NULL); +} + +void +drawregion(int x1, int y1, int x2, int y2) +{ + int y; + + for (y = y1; y < y2; y++) { + if (!term.dirty[y]) + continue; + + term.dirty[y] = 0; + xdrawline(term.line[y], x1, y, x2); + } +} + +void +draw(void) +{ + int cx = term.c.x, ocx = term.ocx, ocy = term.ocy; + + if (!xstartdraw()) + return; + + /* adjust cursor position */ + LIMIT(term.ocx, 0, term.col-1); + LIMIT(term.ocy, 0, term.row-1); + if (term.line[term.ocy][term.ocx].mode & ATTR_WDUMMY) + term.ocx--; + if (term.line[term.c.y][cx].mode & ATTR_WDUMMY) + cx--; + + drawregion(0, 0, term.col, term.row); + xdrawcursor(cx, term.c.y, term.line[term.c.y][cx], + term.ocx, term.ocy, term.line[term.ocy][term.ocx]); + term.ocx = cx; + term.ocy = term.c.y; + xfinishdraw(); + if (ocx != term.ocx || ocy != term.ocy) + xximspot(term.ocx, term.ocy); +} + +void +redraw(void) +{ + tfulldirt(); + draw(); +} diff --git a/st.h b/st.h index fd3b0d8..808f5f7 100644 --- a/st.h +++ b/st.h @@ -33,6 +33,7 @@ enum glyph_attribute { ATTR_WRAP = 1 << 8, ATTR_WIDE = 1 << 9, ATTR_WDUMMY = 1 << 10, + ATTR_BOXDRAW = 1 << 11, ATTR_BOLD_FAINT = ATTR_BOLD | ATTR_FAINT, }; @@ -111,6 +112,14 @@ void *xmalloc(size_t); void *xrealloc(void *, size_t); char *xstrdup(const char *); +int isboxdraw(Rune); +ushort boxdrawindex(const Glyph *); +#ifdef XFT_VERSION +/* only exposed to x.c, otherwise we'll need Xft.h for the types */ +void boxdraw_xinit(Display *, Colormap, XftDraw *, Visual *); +void drawboxes(int, int, int, int, XftColor *, XftColor *, const XftGlyphFontSpec *, int); +#endif + /* config.h globals */ extern char *utmp; extern char *scroll; @@ -124,3 +133,4 @@ extern unsigned int tabspaces; extern unsigned int defaultfg; extern unsigned int defaultbg; extern unsigned int defaultcs; +extern const int boxdraw, boxdraw_bold, boxdraw_braille; diff --git a/st.h.orig b/st.h.orig new file mode 100644 index 0000000..fd3b0d8 --- /dev/null +++ b/st.h.orig @@ -0,0 +1,126 @@ +/* See LICENSE for license details. */ + +#include +#include + +/* macros */ +#define MIN(a, b) ((a) < (b) ? (a) : (b)) +#define MAX(a, b) ((a) < (b) ? (b) : (a)) +#define LEN(a) (sizeof(a) / sizeof(a)[0]) +#define BETWEEN(x, a, b) ((a) <= (x) && (x) <= (b)) +#define DIVCEIL(n, d) (((n) + ((d) - 1)) / (d)) +#define DEFAULT(a, b) (a) = (a) ? (a) : (b) +#define LIMIT(x, a, b) (x) = (x) < (a) ? (a) : (x) > (b) ? (b) : (x) +#define ATTRCMP(a, b) ((a).mode != (b).mode || (a).fg != (b).fg || \ + (a).bg != (b).bg) +#define TIMEDIFF(t1, t2) ((t1.tv_sec-t2.tv_sec)*1000 + \ + (t1.tv_nsec-t2.tv_nsec)/1E6) +#define MODBIT(x, set, bit) ((set) ? ((x) |= (bit)) : ((x) &= ~(bit))) + +#define TRUECOLOR(r,g,b) (1 << 24 | (r) << 16 | (g) << 8 | (b)) +#define IS_TRUECOL(x) (1 << 24 & (x)) + +enum glyph_attribute { + ATTR_NULL = 0, + ATTR_BOLD = 1 << 0, + ATTR_FAINT = 1 << 1, + ATTR_ITALIC = 1 << 2, + ATTR_UNDERLINE = 1 << 3, + ATTR_BLINK = 1 << 4, + ATTR_REVERSE = 1 << 5, + ATTR_INVISIBLE = 1 << 6, + ATTR_STRUCK = 1 << 7, + ATTR_WRAP = 1 << 8, + ATTR_WIDE = 1 << 9, + ATTR_WDUMMY = 1 << 10, + ATTR_BOLD_FAINT = ATTR_BOLD | ATTR_FAINT, +}; + +enum selection_mode { + SEL_IDLE = 0, + SEL_EMPTY = 1, + SEL_READY = 2 +}; + +enum selection_type { + SEL_REGULAR = 1, + SEL_RECTANGULAR = 2 +}; + +enum selection_snap { + SNAP_WORD = 1, + SNAP_LINE = 2 +}; + +typedef unsigned char uchar; +typedef unsigned int uint; +typedef unsigned long ulong; +typedef unsigned short ushort; + +typedef uint_least32_t Rune; + +#define Glyph Glyph_ +typedef struct { + Rune u; /* character code */ + ushort mode; /* attribute flags */ + uint32_t fg; /* foreground */ + uint32_t bg; /* background */ +} Glyph; + +typedef Glyph *Line; + +typedef union { + int i; + uint ui; + float f; + const void *v; + const char *s; +} Arg; + +void die(const char *, ...); +void redraw(void); +void draw(void); + +void printscreen(const Arg *); +void printsel(const Arg *); +void sendbreak(const Arg *); +void toggleprinter(const Arg *); + +int tattrset(int); +void tnew(int, int); +void tresize(int, int); +void tsetdirtattr(int); +void ttyhangup(void); +int ttynew(const char *, char *, const char *, char **); +size_t ttyread(void); +void ttyresize(int, int); +void ttywrite(const char *, size_t, int); + +void resettitle(void); + +void selclear(void); +void selinit(void); +void selstart(int, int, int); +void selextend(int, int, int, int); +int selected(int, int); +char *getsel(void); + +size_t utf8encode(Rune, char *); + +void *xmalloc(size_t); +void *xrealloc(void *, size_t); +char *xstrdup(const char *); + +/* config.h globals */ +extern char *utmp; +extern char *scroll; +extern char *stty_args; +extern char *vtiden; +extern wchar_t *worddelimiters; +extern int allowaltscreen; +extern int allowwindowops; +extern char *termname; +extern unsigned int tabspaces; +extern unsigned int defaultfg; +extern unsigned int defaultbg; +extern unsigned int defaultcs; diff --git a/st.o b/st.o index 54d67b71a1f413d8fc0904b0ef511eaa1e7d1d76..be5de31f6fe466b8d70b125a1f30969987caf3dd 100644 GIT binary patch delta 6517 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 delta 6597 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 WR;f0JJNL5Y`ANXrPG{Z}2>Tb<^dT7l diff --git a/x.c b/x.c index 2f2180c..54d40a3 100644 --- a/x.c +++ b/x.c @@ -1341,6 +1341,8 @@ xinit(int cols, int rows) xsel.xtarget = XInternAtom(xw.dpy, "UTF8_STRING", 0); if (xsel.xtarget == None) xsel.xtarget = XA_STRING; + + boxdraw_xinit(xw.dpy, xw.cmap, xw.draw, xw.vis); } int @@ -1387,8 +1389,13 @@ xmakeglyphfontspecs(XftGlyphFontSpec *specs, const Glyph *glyphs, int len, int x yp = winy + font->ascent; } - /* Lookup character index with default font. */ - glyphidx = XftCharIndex(xw.dpy, font->match, rune); + if (mode & ATTR_BOXDRAW) { + /* minor shoehorning: boxdraw uses only this ushort */ + glyphidx = boxdrawindex(&glyphs[i]); + } else { + /* Lookup character index with default font. */ + glyphidx = XftCharIndex(xw.dpy, font->match, rune); + } if (glyphidx) { specs[numspecs].font = font->match; specs[numspecs].glyph = glyphidx; @@ -1592,8 +1599,12 @@ xdrawglyphfontspecs(const XftGlyphFontSpec *specs, Glyph base, int len, int x, i r.width = width; XftDrawSetClipRectangles(xw.draw, winx, winy, &r, 1); - /* Render the glyphs. */ - XftDrawGlyphFontSpec(xw.draw, fg, specs, len); + if (base.mode & ATTR_BOXDRAW) { + drawboxes(winx, winy, width / len, win.ch, fg, bg, specs, len); + } else { + /* Render the glyphs. */ + XftDrawGlyphFontSpec(xw.draw, fg, specs, len); + } /* Render underline and strikethrough. */ if (base.mode & ATTR_UNDERLINE) { @@ -1636,7 +1647,7 @@ xdrawcursor(int cx, int cy, Glyph g, int ox, int oy, Glyph og) /* * Select the right color for the right mode. */ - g.mode &= ATTR_BOLD|ATTR_ITALIC|ATTR_UNDERLINE|ATTR_STRUCK|ATTR_WIDE; + g.mode &= ATTR_BOLD|ATTR_ITALIC|ATTR_UNDERLINE|ATTR_STRUCK|ATTR_WIDE|ATTR_BOXDRAW; if (IS_SET(MODE_REVERSE)) { g.mode |= ATTR_REVERSE; diff --git a/x.c.orig b/x.c.orig index d73152b..2f2180c 100644 --- a/x.c.orig +++ b/x.c.orig @@ -157,6 +157,8 @@ static void xhints(void); static int xloadcolor(int, const char *, Color *); static int xloadfont(Font *, FcPattern *); static void xloadfonts(const char *, double); +static int xloadsparefont(FcPattern *, int); +static void xloadsparefonts(void); static void xunloadfont(Font *); static void xunloadfonts(void); static void xsetenv(void); @@ -306,6 +308,7 @@ zoomabs(const Arg *arg) { xunloadfonts(); xloadfonts(usedfont, arg->f); + xloadsparefonts(); cresize(0, 0); redraw(); xhints(); @@ -1050,6 +1053,101 @@ xloadfonts(const char *fontstr, double fontsize) FcPatternDestroy(pattern); } +int +xloadsparefont(FcPattern *pattern, int flags) +{ + FcPattern *match; + FcResult result; + + match = FcFontMatch(NULL, pattern, &result); + if (!match) { + return 1; + } + + if (!(frc[frclen].font = XftFontOpenPattern(xw.dpy, match))) { + FcPatternDestroy(match); + return 1; + } + + frc[frclen].flags = flags; + /* Believe U+0000 glyph will present in each default font */ + frc[frclen].unicodep = 0; + frclen++; + + return 0; +} + +void +xloadsparefonts(void) +{ + FcPattern *pattern; + double sizeshift, fontval; + int fc; + char **fp; + + if (frclen != 0) + die("can't embed spare fonts. cache isn't empty"); + + /* Calculate count of spare fonts */ + fc = sizeof(font2) / sizeof(*font2); + if (fc == 0) + return; + + /* Allocate memory for cache entries. */ + if (frccap < 4 * fc) { + frccap += 4 * fc - frccap; + frc = xrealloc(frc, frccap * sizeof(Fontcache)); + } + + for (fp = font2; fp - font2 < fc; ++fp) { + + if (**fp == '-') + pattern = XftXlfdParse(*fp, False, False); + else + pattern = FcNameParse((FcChar8 *)*fp); + + if (!pattern) + die("can't open spare font %s\n", *fp); + + if (defaultfontsize > 0) { + sizeshift = usedfontsize - defaultfontsize; + if (sizeshift != 0 && + FcPatternGetDouble(pattern, FC_PIXEL_SIZE, 0, &fontval) == + FcResultMatch) { + fontval += sizeshift; + FcPatternDel(pattern, FC_PIXEL_SIZE); + FcPatternDel(pattern, FC_SIZE); + FcPatternAddDouble(pattern, FC_PIXEL_SIZE, fontval); + } + } + + FcPatternAddBool(pattern, FC_SCALABLE, 1); + + FcConfigSubstitute(NULL, pattern, FcMatchPattern); + XftDefaultSubstitute(xw.dpy, xw.scr, pattern); + + if (xloadsparefont(pattern, FRC_NORMAL)) + die("can't open spare font %s\n", *fp); + + FcPatternDel(pattern, FC_SLANT); + FcPatternAddInteger(pattern, FC_SLANT, FC_SLANT_ITALIC); + if (xloadsparefont(pattern, FRC_ITALIC)) + die("can't open spare font %s\n", *fp); + + FcPatternDel(pattern, FC_WEIGHT); + FcPatternAddInteger(pattern, FC_WEIGHT, FC_WEIGHT_BOLD); + if (xloadsparefont(pattern, FRC_ITALICBOLD)) + die("can't open spare font %s\n", *fp); + + FcPatternDel(pattern, FC_SLANT); + FcPatternAddInteger(pattern, FC_SLANT, FC_SLANT_ROMAN); + if (xloadsparefont(pattern, FRC_BOLD)) + die("can't open spare font %s\n", *fp); + + FcPatternDestroy(pattern); + } +} + void xunloadfont(Font *f) { @@ -1147,6 +1245,9 @@ xinit(int cols, int rows) usedfont = (opt_font == NULL)? font : opt_font; xloadfonts(usedfont, 0); + /* spare fonts */ + xloadsparefonts(); + /* colors */ xw.cmap = XDefaultColormap(xw.dpy, xw.scr); xloadcols(); diff --git a/x.o b/x.o index ea8664c7c50ea2071d3551883e317812efd897df..17bf96a1b7ec4e2a4566ce351a8ca9501a8678a3 100644 GIT binary patch delta 21460 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 delta 20839 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